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