sql >> Base de Datos >  >> RDS >> PostgreSQL

Encuentra rangos de fechas superpuestas en PostgreSQL

La respuesta actualmente aceptada no responde a la pregunta. Y está mal en principio. a BETWEEN x AND y se traduce como:

a >= x AND a <= y

Incluyendo el límite superior, mientras que las personas normalmente necesitan excluir es:

a >= x AND a < y

Con fechas usted puede ajustar fácilmente. Para el año 2009, use '2009-12-31' como límite superior.
Pero no es tan simple con marcas de tiempo que permiten dígitos fraccionarios. Las versiones modernas de Postgres utilizan internamente un número entero de 8 bytes para almacenar hasta 6 fracciones de segundo (resolución µs). Sabiendo esto, podríamos Todavía hacer que funcione, pero eso no es intuitivo y depende de un detalle de implementación. Mala idea.

Además, a BETWEEN x AND y no encuentra rangos superpuestos. Necesitamos:

b >= x AND a < y

Y jugadores que nunca se fueron aún no se consideran.

Respuesta adecuada

Asumiendo el año 2009 , reformularé la pregunta sin cambiar su significado:

"Encuentra todos los jugadores de un equipo dado que se unieron antes de 2010 y no se fueron antes de 2009".

Consulta básica:

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';

Pero hay más:

Si la integridad referencial se aplica con restricciones FK, la tabla team en sí mismo es solo ruido en la consulta y se puede eliminar.

Si bien el mismo jugador puede irse y volver a unirse al mismo equipo, también debemos retirar los posibles duplicados, por ejemplo, con DISTINCT .

Y podemos Hay que prever un caso especial:los jugadores que nunca se fueron. Suponiendo que esos jugadores tengan NULL en date_leave .

"Se supone que un jugador que no se sabe que se ha ido sigue jugando para el equipo hasta el día de hoy".

Consulta refinada:

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 precedencia de operadores juega en nuestra contra, AND se une antes de OR . Necesitamos paréntesis.

Respuesta relacionada con DISTINCT optimizado (si los duplicados son comunes):

  • Tabla de muchos a muchos:el rendimiento es malo

Normalmente, nombres de personas físicas no son únicos y se utiliza una clave principal sustituta. Pero, obviamente, name_player es la clave principal de player . Si todo lo que necesita son los nombres de los jugadores, no necesitamos la tabla player en la consulta, ya sea:

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 operador

El manual:

OVERLAPS toma automáticamente el valor anterior del par como el inicio. Se considera que cada período de tiempo representa el intervalo semiabierto start <= time < end , a menos que start y end son iguales, en cuyo caso representa ese único instante de tiempo.

Para cuidar el potencial NULL valores, COALESCE parece más fácil:

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

Tipo de rango con soporte de índice

En Postgres 9.2 o posterior también puede operar con tipos de rango reales :

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

Los tipos de rango agregan algo de sobrecarga y ocupan más espacio. 2 x date =8 bytes; 1 x daterange =14 bytes en disco o 17 bytes en RAM. Pero en combinación con el operador de superposición && la consulta se puede respaldar con un índice GiST.

Además, no es necesario utilizar valores NULL en casos especiales. NULL significa "rango abierto" en un tipo de rango, exactamente lo que necesitamos. La definición de la tabla ni siquiera tiene que cambiar:podemos crear el tipo de rango sobre la marcha y admitir la consulta con un índice de expresión coincidente:

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

Relacionado:

  • Tabla de historial de acciones promedio