sql >> Databáze >  >> RDS >> PostgreSQL

Najděte překrývající se období v PostgreSQL

Aktuálně přijímaná odpověď na otázku neodpovídá. A v principu je to špatně. a BETWEEN x AND y překládá do:

a >= x AND a <= y

Včetně horní mez, zatímco lidé obvykle potřebují vyloučit to:

a >= x AND a < y

S daty můžete snadno upravit. Pro rok 2009 použijte jako horní hranici '2009-12-31'.
Ale s časovými razítky to není tak jednoduché které umožňují zlomkové číslice. Moderní verze Postgresu používají interně 8bajtové celé číslo k uložení až 6 zlomkových sekund (rozlišení µs). Když to víme, mohli bychom stále to funguje, ale to není intuitivní a závisí na detailu implementace. Špatný nápad.

Navíc a BETWEEN x AND y nenajde překrývající se rozsahy. Potřebujeme:

b >= x AND a < y

A hráči, kteří nikdy neodešli zatím nejsou zvažovány.

Správná odpověď

Za předpokladu roku 2009 , přeformuluji otázku, aniž bych změnil její význam:

"Najděte všechny hráče daného týmu, kteří se připojili před rokem 2010 a neodešli před rokem 2009."

Základní dotaz:

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

Ale je toho víc:

Pokud je referenční integrita vynucena s omezeními FK, tabulka team sám o sobě je pouze šum v dotazu a lze jej odstranit.

I když stejný hráč může odejít a znovu se připojit ke stejnému týmu, musíme také poskládat možné duplikáty, například pomocí DISTINCT .

A my můžeme potřeba zajistit speciální případ:hráče, kteří nikdy neodešli. Za předpokladu, že tito hráči mají v date_leave hodnotu NULL .

"Předpokládá se, že hráč, o kterém není známo, že odešel, hraje za tým dodnes."

Upřesněný dotaz:

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

Přednost operátora jde proti nám, AND se váže před OR . Potřebujeme závorky.

Související odpověď s optimalizovaným DISTINCT (pokud jsou duplikáty běžné):

  • Tabulka mnoho k mnoha – výkon je špatný

Obvykle jména fyzických osob nejsou jedinečné a používá se náhradní primární klíč. Ale samozřejmě name_player je primární klíč player . Pokud potřebujete pouze jména hráčů, nepotřebujeme tabulku player v dotazu buď:

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 operátor

Manuál:

OVERLAPS automaticky vezme dřívější hodnotu páru jako start. Každé časové období je považováno za polovinu otevřeného intervalu start <= time < end , pokud start a end jsou stejné, v takovém případě představuje tento jediný časový okamžik.

Postarat se o potenciální NULL hodnoty, COALESCE zdá se nejjednodušší:

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

Typ rozsahu s podporou indexu

V Postgres 9.2 nebo novější můžete také pracovat se skutečnými typy rozsahů :

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

Typy rozsahů zvyšují režii a zabírají více místa. 2 x date =8 bajtů; 1 x daterange =14 bajtů na disku nebo 17 bajtů v RAM. Ale v kombinaci s operátorem překrytí && dotaz může být podporován indexem GiST.

Také není třeba zadávat hodnoty NULL se speciálními případy. NULL znamená "otevřený rozsah" v typu rozsahu - přesně to, co potřebujeme. Definice tabulky se ani nemusí měnit:můžeme vytvořit typ rozsahu za běhu – a podpořit dotaz s odpovídajícím indexem výrazu:

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

Související:

  • Tabulka průměrné historie akcií


  1. při vložení perského znaku do Oracle db vidím otazník

  2. 3 způsoby, jak naformátovat číslo jako procento v PostgreSQL

  3. Proč bych v PHP neměl používat funkce mysql_*?

  4. Jak změnit vlastnictví všech objektů v konkrétním schématu v PostgreSQL?