Dans une base de données de transactions couvrant des milliers d'entités sur 18 mois, je voudrais exécuter une requête pour regrouper chaque période de 30 jours possible entity_id
avec un SOMME de leurs montants de transaction et COUNT de leurs transactions au cours de cette période de 30 jours, et retourner les données d'une manière que je peux ensuite interroger. Après de nombreux tests, ce code accomplit une grande partie de ce que je veux:
SELECT id, trans_ref_no, amount, trans_date, entity_id,
SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
COUNT(id) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
FROM transactiondb;
Et je vais utiliser dans une requête plus large structurée quelque chose comme:
SELECT * FROM (
SELECT id, trans_ref_no, amount, trans_date, entity_id,
SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
COUNT(id) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
FROM transactiondb ) q
WHERE trans_count >= 4
AND trans_total >= 50000;
Le cas que cette requête ne couvre pas, c'est quand le nombre de transactions s'étendra sur plusieurs mois, mais toujours dans les 30 jours les uns des autres. Ce type de requête est-il possible avec Postgres? Si c'est le cas, je salue toute contribution. La plupart des autres rubriques traitent de «l' exécution » des agrégats, et non du roulement .
Mise à jour
Le CREATE TABLE
script:
CREATE TABLE transactiondb (
id integer NOT NULL,
trans_ref_no character varying(255),
amount numeric(18,2),
trans_date date,
entity_id integer
);
Des exemples de données peuvent être trouvés ici . J'utilise PostgreSQL 9.1.16.
La production idéale comprendrait SUM(amount)
et COUNT()
de toutes les transactions sur une période continue de 30 jours. Voir cette image, par exemple:
Le surlignage de la date verte indique ce qui est inclus par ma requête. La surbrillance de la ligne jaune indique les enregistrements de ce que j'aimerais faire partie de l'ensemble.
Lecture précédente:
la source
every possible 30-day period by entity_id
vous , la période peut commencer une journée, donc 365 périodes possibles en un an (non bissextile)? Ou souhaitez-vous uniquement considérer les jours avec une transaction réelle comme le début d'une période individuellement pour chacunentity_id
? Dans tous les cas, veuillez fournir votre définition de table, la version Postgres, quelques exemples de données et le résultat attendu pour l'échantillon.entity_id
dans une fenêtre de 30 jours à partir de chaque transaction réelle. Peut-il y avoir plusieurs transactions pour la même chose(trans_date, entity_id)
ou cette combinaison est-elle définie comme unique? Votre définition de table n'a pas deUNIQUE
contrainte PK ou PK, mais les contraintes semblent manquer ...id
la clé primaire. Il peut y avoir plusieurs transactions par entité et par jour.Réponses:
La requête que vous avez
Vous pouvez simplifier votre requête à l'aide d'une
WINDOW
clause, mais cela ne fait que raccourcir la syntaxe, pas changer le plan de requête.count(*)
, carid
est certainement définiNOT NULL
?ORDER BY entity_id
puisque vous l'avez déjàPARTITION BY entity_id
Vous pouvez simplifier davantage, cependant:
n'ajoutez rien
ORDER BY
à la définition de la fenêtre, cela n'est pas pertinent pour votre requête. Ensuite, vous n'avez pas besoin de définir un cadre de fenêtre personnalisé, soit:Plus simple, plus rapide, mais toujours juste une meilleure version de ce que vous avez , avec des mois statiques .
La requête que vous voudrez peut-être
... n'est pas clairement défini, je vais donc construire sur ces hypothèses:
Comptez les transactions et le montant pour chaque période de 30 jours au cours de la première et de la dernière transaction
entity_id
. Exclure les périodes de début et de fin sans activité, mais inclure toutes les périodes de 30 jours possibles dans ces limites extérieures.Cela répertorie toutes les périodes de 30 jours pour chacune
entity_id
avec vos agrégats et avectrans_date
le premier jour (incl.) De la période. Pour obtenir des valeurs pour chaque ligne individuelle, joignez à nouveau la table de base ...La difficulté de base est la même que celle discutée ici:
La définition du cadre d'une fenêtre ne peut pas dépendre des valeurs de la ligne actuelle.
Et plutôt appeler
generate_series()
avectimestamp
entrée:La requête que vous voulez réellement
Après la mise à jour de la question et la discussion:
accumulez des lignes de la même
entity_id
dans une fenêtre de 30 jours à partir de chaque transaction réelle.Étant donné que vos données sont réparties de manière clairsemée, il devrait être plus efficace d'exécuter une auto-jointure avec une condition de plage , d'autant plus que Postgres 9.1 n'a pas de
LATERAL
jointures, pourtant:SQL Fiddle.
Une fenêtre mobile ne pouvait avoir de sens (en termes de performances) qu'avec des données pour la plupart des jours.
Cela n'agrège pas les doublons
(trans_date, entity_id)
par jour, mais toutes les lignes du même jour sont toujours incluses dans la fenêtre de 30 jours.Pour une grande table, un indice de couverture comme celui-ci pourrait aider un peu:
La dernière colonne
amount
n'est utile que si vous en obtenez des analyses d'index uniquement. Sinon, laissez tomber.Mais cela ne sera pas utilisé pendant que vous sélectionnez la table entière de toute façon. Il prendrait en charge les requêtes pour un petit sous-ensemble.
la source
column "t0.amount" must appear in the GROUP BY clause...