Jaki jest poprawny wynik dla tego zapytania?

20

Tę zagadkę natrafiłem w komentarzach tutaj

CREATE TABLE r (b INT);

SELECT 1 FROM r HAVING 1=1;

SQL Server i PostgreSQL zwracają 1 wiersz.

MySQL i Oracle zwracają zero wierszy.

Który jest poprawny? Czy oba są równie ważne?

Martin Smith
źródło
Niezła łamigłówka. Myślę, że poprawne jest zwrócenie 1 wiersza. SQL Server jest jednak sprzeczny, ponieważ SELECT COUNT(*) FROM r;zwraca 1 wiersz (z 0), a SELECT COUNT(*) FROM r GROUP BY ();nie zwraca wierszy.
ypercubeᵀᴹ
1
Chcieć więcej? SELECT 1 WHERE 1=0 HAVING 1=1;. SQL Server i PostgreSQL nadal zwracają jeden wiersz. Oracle chce OD DUALU i nie zwraca wierszy. MySQL nie kompiluje się ani z FROM DUAL, ani bez niego .
Andriy M
1
@AndriyM Z nieznanego powodu „dual” i „HAVING” nie działają dobrze w MySQL. (Niezłe znalezisko). Ale odpowiednik działa: SELECT 1 AS t FROM (SELECT 1) tmp WHERE 1=0 HAVING 1=1; 1-wiersz-bez-podwójnego i zwraca 0 wierszy.
ypercubeᵀᴹ
1
@SQLKiwi - Co z tym fragmentem ze specyfikacji. „Jeśli TE nie zawiera natychmiast a <group by clause>, “GROUP BY ()”oznacza to domniemaną.”. Czy oba zapytania nie powinny zwracać tych samych wyników?
Martin Smith
1
Ale nie HAVING
zgadzaj się

Odpowiedzi:

17

Zgodnie ze standardem:

SELECT 1 FROM r HAVING 1=1

znaczy

SELECT 1 FROM r GROUP BY () HAVING 1=1

Odniesienie ISO / IEC 9075-2: 2011 7.10 Reguła składniowa 1 (Część definicji klauzuli HAVING):

Niech HCbędzie <having clause>. Niech TEbędzie to, <table expression>co natychmiast zawiera HC. Jeśli TEnie zawiera od razu znaku „, oznacza <group by clause>to„ GROUP BY ()”. Niech Tbędzie deskryptorem tabeli zdefiniowanej przez <group by clause> GBCbezpośrednio zawartą TEi niech Rbędzie wynikiem GBC.

Ok, więc wszystko jest całkiem jasne.


Twierdzenie: 1=1jest prawdziwym warunkiem wyszukiwania. Nie podam za to żadnego cytatu.


Teraz

SELECT 1 FROM r GROUP BY () HAVING 1=1

jest równoważne z

SELECT 1 FROM r GROUP BY ()

Odniesienie ISO / IEC 9075-2: 2011 7.10 Zasada ogólna 1:

<search condition>Oceniano dla każdej grupy R. Wynikiem <having clause>jest pogrupowana tabela tych grup R, dla których wynikiem <search condition>jest Prawda.

Logika: Ponieważ warunek wyszukiwania jest zawsze prawdziwy, wynikiem jest wynik R, który jest wynikiem grupy według wyrażenia.


Poniżej znajduje się fragment Ogólnych zasad 7.9 (definicja GRUPY WEDŁUG KLAUZULI)

1) Jeśli nie <where clause>podano no, niech Tbędzie wynikiem poprzedniego <from clause>; w przeciwnym razie niech Tbędzie wynikiem poprzedniego <where clause>.

2) Obudowa:

a) Jeśli nie ma kolumn grupujących, wynikiem <group by clause>jest zgrupowana tabela składająca się Tjako jedyna grupa.

Możemy zatem dojść do wniosku

FROM r GROUP BY ()

daje zgrupowaną tabelę, składającą się z jednej grupy, z zerowymi wierszami (ponieważ R jest puste).


Fragment Zasad ogólnych z 7.12, który definiuje specyfikację zapytania (zwaną także instrukcją SELECT):

1) Obudowa:

a) Jeśli Tnie jest zgrupowana tabela, to [...]

b) Jeśli Ttabela jest zgrupowana, to

Walizka:

i) Jeśli Tma 0 (zero) grup, niech TEMP będzie pustą tabelą.

ii) w przypadku Tposiada jedną lub więcej grup, a każdy <value expression>jest stosowany do każdej grupy Totrzymując tabelę TEMPz Mrzędów, gdzie Mjest liczba grup T. i-Tej kolumny temp zawiera wartości uzyskane przez ocenę i-tym <value expression>. [...]

2) Obudowa:

a) Jeśli <set quantifier> DISTINCTnie określono, wynik <query specification>jest TEMP.

Dlatego, ponieważ tabela ma jedną grupę, musi mieć jeden wiersz wyników.

A zatem

SELECT 1 FROM r HAVING 1=1

powinien zwrócić zestaw wyników z 1 wierszem.

CO BYŁO DO OKAZANIA

Kevin Cathcart
źródło
+1 Dzięki za poświęcenie się tym wszystkim kłopotom! Jak twierdzi @ypercube, SQL Server wydaje się przeczyć tutaj jako WYBIERZ 1 Z R GROUP BY (); zwraca zero wierszy, ale cytowany fragment wydaje się całkiem jasny w tym punkcie.
Martin Smith
Czy mogę zapytać, gdzie znalazłeś standard? Jeśli powiesz „na mojej półce”, będę rozczarowany :)
dezso,
Technicznie użyłem ostatecznego standardu międzynarodowego, a nie samego standardu. Zgodnie z zasadami ISO / IEC dozwolone są tylko redakcyjne (nietechniczne) zmiany między FDIS a ostateczną normą. Standard jest podzielony na wiele części. Część 1 , Część 2 , Część 4 ...
Kevin Cathcart
Część 11 i część 14 . Części 3,9,10 i 13 nie zostały zaktualizowane w 2011 r., A zatem obowiązują ich poprzednie wersje. Nie ma części 12. Podobnie nie ma części 5-8. Zobacz stronę Wikipedii dla Sql: 2011 lub samej części 1, aby uzyskać wyjaśnienie, co zawiera każda część.
Kevin Cathcart
7

Gdy istnieje HAVINGklauzula bez WHEREklauzuli:

SELECT 1 FROM r HAVING 1=1;

... to GROUP BY ()jest dorozumiane. Tak więc zapytanie powinno być równoważne z:

SELECT 1 FROM r GROUP BY () HAVING 1=1;

... która powinna zgrupować wszystkie wiersze tabeli w jedną grupę (nawet jeśli tabela w ogóle nie ma wierszy - nadal jest to jedna grupa 0 wierszy) i zwrócić 1 wiersz. Po HAVINGtym Truewarunek nie powinien już mieć żadnego efektu.


Pod innym kątem, ile wierszy powinno zwrócić takie zapytanie?

SELECT COUNT(*), MAX(b) FROM r;

Jeden, zero lub „zero lub jeden, w zależności od tego, czy tabela jest pusta czy nie”?

Myślę, że jeden wiersz, bez względu na liczbę wierszy r.

ypercubeᵀᴹ
źródło
Cóż, kluczową kwestią jest to, czy rzeczywiście jest prawdą, że „nawet jeśli tabela w ogóle nie ma wierszy, nadal jest to jedna grupa 0 wierszy”. A standard okazuje się jednoznacznie na ten temat: „Jeśli nie ma kolumn grupujących, to… jest zgrupowana tabela składająca się z T jako jedynej grupy”. (i tak jest nawet wtedy, gdy T jest puste - więc rzeczywiście istnieje grupa). Dalej, klauzula have określa, że ​​warunek jest stosowany do każdej grupy (w tym przykładzie w ten sposób jeden raz). Prawdopodobnie zdefiniowali to w ten sposób, aby SUMA i LICZBA zwracały jeden wiersz nawet dla pustych T.
Erwin Smout
+1 (wcześniej!) Mimo że twoja logika jest taka sama, jak Kevina, zaakceptowałem jego odpowiedź ze względu na cytaty ze specyfikacji. Dzięki!
Martin Smith
@MartinSmith. Dziękuję Że
czerpię
@ypercube: +1 ode mnie też. Postanowiłem poświęcić dodatkowy czas, aby oderwać się od specyfikacji, aby udowodnić, że w żadnym miejscu nie było ukrytych słów łasicy, które mogłyby źle podać odpowiedź. Ale kiedy to zrobię, równie dobrze mogę opublikować to jako pełną odpowiedź. Więc zrobiłem.
Kevin Cathcart
3
@ErwinSmout: Oczywiście, że nie. Jest to jednak dozwolone w ramach prawa autorskiego Stanów Zjednoczonych. Stosunkowo małe fragmenty, cytowane w kontekście analizy (tj. Krytyki) dzieła, do celów edukacyjnych, z nieistotnym wpływem na zdolność sprzedaży dzieła.
Kevin Cathcart
3

Z tego, co widzę, wygląda na to, że SQLServer i PostgerSQL wcale nie przejmują się przeglądaniem tabeli:

CREATE TABLE r (b INT);
insert into r(b) values (1);
insert into r(b) values (2);
SELECT 1 FROM r HAVING 1=1;

zwraca również tylko jeden wiersz. Nawet jeśli dokumentacja SQLServer mówi

Gdy nie jest używane GROUP BY, HAVING zachowuje się jak klauzula WHERE.

to nie jest prawda w tym przypadku - WHERE 1=1zamiast HAVINGzwraca odpowiednią liczbę wierszy. Powiedziałbym, że to błąd optymalizatora (a przynajmniej błąd dokumentacji) ... Plan SQLServer pokazuje „Ciągłe skanowanie” w przypadku HAVINGi „skanowanie tabeli” dla WHERE...

Zachowanie Oracle i Mysql wydaje mi się bardziej logiczne i poprawne ...

a1ex07
źródło
1
Masz rację, że SQL Server nie patrzy na tabelę. Plan wykonania ma po prostu ciągłe skanowanie i nawet nie odwołuje się do tabeli. Gdyby to był tylko SQL Server, po prostu sprowadziłbym go na błąd, ale ponieważ nie jest to tylko SQL Server, zastanawiam się, czy jest w tym jakaś prawdziwa dwuznaczność.
Martin Smith
PostgreSQL pokazuje te same wyniki co SQLServer i, o ile mogę powiedzieć z danych wyjściowych explain„Wynik (wiersze = 1) ...” dla posiadania i „Skanowanie sekwencji” dla „GDZIE”, również nie zagląda do tabeli. .. Myślę, że jest to w jakiś sposób związane z faktem, że „FROM” nie jest obowiązkowy w TSQL i PostgreSQL. Wiem, że Mysql również tego nie wymaga, ale ponieważ obsługują dual, prawdopodobnie analizują zapytanie nieco inaczej. Zgadzam się, to brzmi jak spekulacja, ale mam nadzieję, że ma to jakiś sens.
a1ex07