A resposta atualmente aceita não responde à pergunta. E é errado em princípio.
a BETWEEN x AND y
traduz para:a >= x AND a <= y
Incluindo o limite superior, enquanto as pessoas normalmente precisam excluir isto:
a >= x AND a < y
Com datas você pode ajustar facilmente. Para o ano de 2009, use '2009-12-31' como limite superior.
Mas não é tão simples com timestamps que permitem dígitos fracionários. As versões modernas do Postgres usam um inteiro de 8 bytes internamente para armazenar até 6 segundos fracionários (resolução µs). Sabendo disso, poderíamos ainda faz funcionar, mas isso não é intuitivo e depende de um detalhe de implementação. Péssima ideia.
Além disso,
a BETWEEN x AND y
não encontra intervalos sobrepostos. Nós precisamos:b >= x AND a < y
E jogadores que nunca saíram não são considerados, ainda.
Resposta adequada
Assumindo o ano
2009
, reformularei a pergunta sem alterar seu significado:"Encontre todos os jogadores de um determinado time que entraram antes de 2010 e não saíram 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';
Mas há mais:
Se a integridade referencial for aplicada com restrições FK, a tabela
team
em si é apenas ruído na consulta e pode ser removido. Embora o mesmo jogador possa sair e voltar ao mesmo time, também precisamos dobrar possíveis duplicatas, por exemplo, com
DISTINCT
. E nós podemos precisa prever um caso especial:jogadores que nunca saíram. Supondo que esses jogadores tenham NULL em
date_leave
. "Assume-se que um jogador que não se sabe que saiu está jogando pelo time até hoje."
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);
A precedência do operador funciona contra nós,
AND
vincula antes de OR
. Precisamos de parênteses. Resposta relacionada com
DISTINCT
otimizado (se duplicatas são comuns):- Tabela de muitos para muitos - o desempenho é ruim
Normalmente, nomes das pessoas físicas não são únicas e uma chave primária substituta é usada. Mas, obviamente,
name_player
é a chave primária do player
. Se tudo o que você precisa são nomes de jogadores, não precisamos da mesa player
na consulta, ou: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
O manual:
OVERLAPS
automaticamente toma o valor anterior do par como o início. Cada período de tempo é considerado para representar o intervalo de meio-abertostart <= time < end
, a menos questart
eend
são iguais, caso em que representa aquele único instante de tempo.
Para cuidar de possíveis
NULL
valores, COALESCE
parece mais 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 intervalo com suporte a índice
No Postgres 9.2 ou posterior você também pode operar com tipos de intervalo reais :
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
Os tipos de intervalo adicionam alguma sobrecarga e ocupam mais espaço. 2 x
date
=8 bytes; 1 x daterange
=14 bytes no disco ou 17 bytes na RAM. Mas em combinação com o operador de sobreposição &&
a consulta pode ser suportada com um índice GiST. Além disso, não há necessidade de valores NULL de maiúsculas e minúsculas. NULL significa "range aberto" em um tipo de intervalo - exatamente o que precisamos. A definição da tabela nem precisa ser alterada:podemos criar o tipo de intervalo em tempo real - e oferecer suporte à consulta com um índice de expressão correspondente:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Relacionado:
- Tabela de histórico de estoque médio