J'ai une table (dans PostgreSQL 9.4) qui ressemble à ceci:
CREATE TABLE dates_ranges (kind int, start_date date, end_date date);
INSERT INTO dates_ranges VALUES
(1, '2018-01-01', '2018-01-31'),
(1, '2018-01-01', '2018-01-05'),
(1, '2018-01-03', '2018-01-06'),
(2, '2018-01-01', '2018-01-01'),
(2, '2018-01-01', '2018-01-02'),
(3, '2018-01-02', '2018-01-08'),
(3, '2018-01-05', '2018-01-10');
Maintenant, je veux calculer pour les dates données et pour chaque type, dans combien de lignes de dates_ranges
chaque date tombe. Des zéros pourraient éventuellement être omis.
Résultat désiré:
+-------+------------+----+
| kind | as_of_date | n |
+-------+------------+----+
| 1 | 2018-01-01 | 2 |
| 1 | 2018-01-02 | 2 |
| 1 | 2018-01-03 | 3 |
| 2 | 2018-01-01 | 2 |
| 2 | 2018-01-02 | 1 |
| 3 | 2018-01-02 | 1 |
| 3 | 2018-01-03 | 1 |
+-------+------------+----+
J'ai trouvé deux solutions, une avec LEFT JOIN
etGROUP BY
SELECT
kind, as_of_date, COUNT(*) n
FROM
(SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates
LEFT JOIN
dates_ranges ON dates.as_of_date BETWEEN start_date AND end_date
GROUP BY 1,2 ORDER BY 1,2
et un avec LATERAL
, qui est légèrement plus rapide:
SELECT
kind, as_of_date, n
FROM
(SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates,
LATERAL
(SELECT kind, COUNT(*) AS n FROM dates_ranges WHERE dates.as_of_date BETWEEN start_date AND end_date GROUP BY kind) ss
ORDER BY kind, as_of_date
Je me demande si c'est une meilleure façon d'écrire cette requête? Et comment inclure des paires date-kind avec 0 compte?
En réalité, il existe plusieurs types distincts, une période pouvant aller jusqu'à cinq ans (1800 dates) et environ 30 000 lignes dans le dates_ranges
tableau (mais cela pourrait augmenter considérablement).
Il n'y a pas d'index. Pour être précis dans mon cas, c'est le résultat d'une sous-requête, mais j'ai voulu limiter la question à un seul problème, c'est donc plus général.
la source
(1,2018-01-01,2018-01-15)
et(1,2018-01-20,2018-01-25)
voulez-vous en tenir compte lorsque vous déterminez le nombre de dates qui se chevauchent?2018-01-31
ou2018-01-30
ou2018-01-29
en quand la première gamme a tous?generate_series
sont des paramètres externes - elles ne couvrent pas nécessairement toutes les plages dudates_ranges
tableau. Quant à la première question, je suppose que je ne la comprends pas - les lignesdates_ranges
sont indépendantes, je ne veux pas déterminer les chevauchements.Réponses:
La requête suivante fonctionne également si les "zéros manquants" sont OK:
mais ce n'est pas plus rapide que la
lateral
version avec le petit ensemble de données. Cependant, il peut être plus évolutif, car aucune jointure n'est requise, mais la version ci-dessus est agrégée sur toutes les lignes, elle peut donc y perdre à nouveau.La requête suivante tente d'éviter un travail inutile en supprimant toutes les séries qui ne se chevauchent pas de toute façon:
- et je dois utiliser l'
overlaps
opérateur! Notez que vous devez ajouterinterval '1 day'
à droite car l'opérateur de chevauchements considère que les périodes sont ouvertes à droite (ce qui est assez logique car une date est souvent considérée comme un horodatage avec une composante horaire de minuit).la source
generate_series
qu'on pouvait l'utiliser comme ça. Après quelques tests, j'ai les observations suivantes. Votre requête évolue en effet très bien avec la longueur de plage sélectionnée - il n'y a donc pratiquement aucune différence entre une période de 3 ans et 10 ans. Cependant, pour des périodes plus courtes (1 an), mes solutions sont plus rapides - je suppose que la raison en est qu'il existe de très longues plagesdates_ranges
(comme 2010-2100), qui ralentissent votre requête. Limiterstart_date
etend_date
à l'intérieur de la requête interne devrait cependant aider. J'ai besoin de faire quelques tests supplémentaires.Construisez une grille de toutes les combinaisons puis
LATERAL
joignez-vous à votre table, comme ceci:Devrait également être aussi rapide que possible.
J'ai eu
LEFT JOIN LATERAL ... on true
au début, mais il y a un agrégat dans la sous-requêtec
, donc nous obtenons toujours une ligne et pouvons également l'utiliserCROSS JOIN
. Aucune différence de performance.Si vous avez un tableau contenant tous les types pertinents , utilisez-le au lieu de générer la liste avec la sous-requête
k
.La conversion en
integer
est facultative. Sinon, vous obtenezbigint
.Les index seraient utiles, en particulier un index multicolonne sur
(kind, start_date, end_date)
. Étant donné que vous construisez sur une sous-requête, cela peut être possible ou non.L'utilisation de fonctions de retour de set comme
generate_series()
dans laSELECT
liste n'est généralement pas recommandée dans les versions Postgres antérieures à 10 (sauf si vous savez exactement ce que vous faites). Voir:Si vous avez beaucoup de combinaisons avec peu ou pas de lignes, ce formulaire équivalent peut être plus rapide:
la source
SELECT
liste - j'ai lu que ce n'est pas conseillé, mais il semble que cela fonctionne très bien, s'il n'y a qu'une seule de ces fonctions. Si je suis sûr qu'il n'y en aura qu'un, quelque chose pourrait-il mal tourner?SELECT
liste fonctionne comme prévu. Ajoutez peut-être un commentaire pour déconseiller d'en ajouter un autre. Ou déplacez-le dans laFROM
liste pour commencer dans les anciennes versions de Postgres. Pourquoi risquer des complications? (C'est aussi du SQL standard et ne confondra pas les personnes venant d'autres SGBDR.)Utilisation du
daterange
typePostgreSQL a un
daterange
. Son utilisation est assez simple. À partir de vos exemples de données, nous passons à l'utilisation du type sur la table.Maintenant, pour l'interroger, nous inversons la procédure et générons une série de dates, mais voici le problème que la requête elle-même peut utiliser l'
@>
opérateur containment ( ) pour vérifier que les dates sont dans la plage, à l' aide d'un index.Remarque que nous utilisons
timestamp without time zone
(pour arrêter les dangers DST)Quels sont les jours-chevauchements détaillés sur l'indice.
En bonus, avec le type daterange, vous pouvez arrêter les insertions de plages qui se chevauchent avec d'autres en utilisant un
EXCLUDE CONSTRAINT
la source
JOIN
trop je suppose.count(DISTINCT kind)
1
date type se2018-01-01
trouve dans les deux premières lignes dedates_ranges
, mais votre requête donne8
.count(DISTINCT kind)
avez-vous ajouté leDISTINCT
mot clé à cet endroit?DISTINCT
mot clé, cela ne fonctionne toujours pas comme prévu. Il compte des types distincts pour chaque date, mais je veux compter toutes les lignes de chaque type pour chaque date.