PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Trouver des plages de dates qui se chevauchent dans PostgreSQL

La réponse actuellement acceptée ne répond pas à la question. Et c'est faux dans le principe. a BETWEEN x AND y se traduit par :

a >= x AND a <= y

Y compris la limite supérieure, alors que les gens doivent généralement exclure il :

a >= x AND a < y

Avec dates vous pouvez facilement régler. Pour l'année 2009, utilisez '2009-12-31' comme limite supérieure.
Mais ce n'est pas aussi simple avec les horodatages qui autorisent les chiffres fractionnaires. Les versions modernes de Postgres utilisent un entier de 8 octets en interne pour stocker jusqu'à 6 secondes fractionnaires (résolution en µs). Sachant cela, nous pourrions le faire fonctionner, mais ce n'est pas intuitif et dépend d'un détail d'implémentation. Mauvaise idée.

De plus, a BETWEEN x AND y ne trouve pas les plages qui se chevauchent. Nous avons besoin :

b >= x AND a < y

Et des joueurs qui ne sont jamais partis ne sont pas encore pris en compte.

Bonne réponse

En supposant l'année 2009 , je reformule la question sans en changer le sens :

"Rechercher tous les joueurs d'une équipe donnée qui ont rejoint avant 2010 et n'ont pas quitté avant 2009."

Requête de base :

SELECT p.*
FROM   team     t
JOIN   contract c USING (name_team) 
JOIN   player   p USING (name_player) 
WHERE  t.name_team = ? 
AND    c.date_join  <  date '2010-01-01'
AND    c.date_leave >= date '2009-01-01';

Mais il y a plus :

Si l'intégrité référentielle est appliquée avec des contraintes FK, la table team lui-même n'est que du bruit dans la requête et peut être supprimé.

Bien qu'un même joueur puisse quitter et rejoindre la même équipe, nous devons également plier d'éventuels doublons, par exemple avec DISTINCT .

Et nous pourrons faut prévoir un cas particulier :les joueurs qui ne sont jamais partis. En supposant que ces joueurs ont NULL dans date_leave .

"Un joueur dont on ne sait pas qu'il est parti est supposé jouer pour l'équipe à ce jour."

Requête affinée :

SELECT DISTINCT p.* 
FROM   contract c
JOIN   player   p USING (name_player) 
WHERE  c.name_team = ? 
AND    c.date_join  <  date '2010-01-01'
AND   (c.date_leave >= date '2009-01-01' OR c.date_leave IS NULL);

La priorité des opérateurs joue contre nous, AND se lie avant OR . Nous avons besoin de parenthèses.

Réponse associée avec DISTINCT optimisé (si les doublons sont fréquents) :

  • Tableau plusieurs à plusieurs – Les performances sont mauvaises

Généralement, les noms des personnes physiques ne sont pas uniques et une clé primaire de substitution est utilisée. Mais, évidemment, name_player est la clé primaire du player . Si vous n'avez besoin que des noms de joueurs, nous n'avons pas besoin de la table player dans la requête, soit :

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    date_join  <  date '2010-01-01'
AND   (date_leave >= date '2009-01-01' OR date_leave IS NULL);

SQL OVERLAPS opérateur

Le manuel :

OVERLAPS prend automatiquement la valeur la plus ancienne de la paire comme début. Chaque période est considérée comme représentant l'intervalle de demi-ouverture start <= time < end , sauf si start et end sont égaux auquel cas il représente cet instant unique.

Pour prendre soin des potentiels NULL valeurs, COALESCE semble le plus simple :

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    (date_join, COALESCE(date_leave, CURRENT_DATE)) OVERLAPS
       (date '2009-01-01', date '2010-01-01');  -- upper bound excluded

Type de plage avec prise en charge d'index

Dans Postgres 9.2 ou version ultérieure vous pouvez également opérer avec des types de plage réels :

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    daterange(date_join, date_leave) &&
       daterange '[2009-01-01,2010-01-01)';  -- upper bound excluded

Les types de plage ajoutent des frais généraux et occupent plus d'espace. 2 x date =8 octets ; 1 x daterange =14 octets sur disque ou 17 octets en RAM. Mais en combinaison avec l'opérateur de chevauchement && la requête peut être prise en charge avec un index GiST.

En outre, pas besoin de valeurs NULL spéciales. NULL signifie "plage ouverte" dans un type de plage - exactement ce dont nous avons besoin. La définition de la table n'a même pas besoin de changer :nous pouvons créer le type de plage à la volée - et prendre en charge la requête avec un index d'expression correspondant :

CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));

Connexe :

  • Tableau d'historique des stocks moyens