Qual è il risultato corretto per questa query?


20

Mi sono imbattuto in questo puzzle nei commenti qui

CREATE TABLE r (b INT);

SELECT 1 FROM r HAVING 1=1;

SQL Server e PostgreSQL restituiscono 1 riga.

MySQL e Oracle restituiscono zero righe.

Che è corretto? O sono entrambi ugualmente validi?


Bel puzzle. Penso che sia corretto restituire 1 riga. SQL-Server si sta contraddicendo perché SELECT COUNT(*) FROM r;restituisce 1 riga (con 0), mentre SELECT COUNT(*) FROM r GROUP BY ();non restituisce righe.
ypercubeᵀᴹ

1
Voglio di più? SELECT 1 WHERE 1=0 HAVING 1=1;. SQL Server e PostgreSQL restituiscono comunque una riga. Oracle vuole FROM DUAL e non restituisce righe. MySQL non viene compilato né con FROM DUALsenza di esso .
Andriy M,

1
@AndriyM Per qualche ragione sconosciuta "dual" e "HAVING" non giocano bene in MySQL. (Bella scoperta). Ma l'equivalente funziona: SELECT 1 AS t FROM (SELECT 1) tmp WHERE 1=0 HAVING 1=1; 1 riga-no-dual e restituisce 0 righe.
ypercubeᵀᴹ

1
@SQLKiwi - Che dire di questo passaggio dalle specifiche. "Se TE non contiene immediatamente a <group by clause>, allora “GROUP BY ()”è implicito.". Allora entrambe le query non dovrebbero restituire gli stessi risultati?
Martin Smith,

1
Ma non sono d'accordo (Oracle esegue le query in modo HAVINGdiverso): SQl-fiddle 2: HAVING rende le cose diverse
ypercubeᵀᴹ

Risposte:


17

Secondo lo standard:

SELECT 1 FROM r HAVING 1=1

si intende

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

Visto ISO / IEC 9075-2: 2011 7.10 Sintassi Regola 1 (Parte della definizione della clausola HAVING):

Lascia che HCsia il <having clause>. Lascia che TEsia quello <table expression>che contiene immediatamente HC. Se TEnon contiene immediatamente a <group by clause>, allora " GROUP BY ()" è implicito. Lascia che Tsia il descrittore della tabella definita dal <group by clause> GBCcontenuto immediatamente contenuto TEe Rsia il risultato di GBC.

Ok, è molto chiaro.


Asserzione: 1=1è la vera condizione di ricerca. Non fornirò alcuna citazione per questo.


Adesso

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

è equivalente a

SELECT 1 FROM r GROUP BY ()

Visto ISO / IEC 9075-2: 2011 7.10 Regola generale 1:

La <search condition>viene valutata per ciascun gruppo di R. Il risultato di <having clause>è una tabella raggruppata di quei gruppi di R per i quali il risultato di <search condition>è Vero.

Logica: poiché la condizione di ricerca è sempre vera, il risultato è R, che è il risultato del gruppo in base all'espressione.


Quello che segue è un estratto delle Regole generali di 7.9 (la definizione del GRUPPO PER CLAUSOLA)

1) Se non <where clause>viene specificato no , allora Tsia il risultato del precedente <from clause>; altrimenti, Tsia il risultato del precedente <where clause>.

2) Caso:

a) Se non ci sono colonne di raggruppamento, il risultato di <group by clause>è la tabella raggruppata costituita dal Tsuo unico gruppo.

Quindi possiamo concludere che

FROM r GROUP BY ()

risulta in una tabella raggruppata, composta da un gruppo, con zero righe (poiché R è vuota).


Un estratto dalle Regole generali di 7.12, che definisce una specifica di query (aka un'istruzione SELECT):

1) Caso:

a) Se Tnon è una tabella raggruppata, [...]

b) Se Tè una tabella raggruppata, allora

Astuccio:

i) Se Tha 0 (zero) gruppi, allora lascia che TEMP sia una tabella vuota.

ii) Se Tha uno o più gruppi, ognuno <value expression>viene applicato a ciascun gruppo di Trestituire una tabella TEMPdi Mrighe, dove Mè il numero di gruppi in T. La icolonna -th di TEMP contiene i valori derivati ​​dalla valutazione del i-th <value expression>. [...]

2) Caso:

a) Se <set quantifier> DISTINCTnon è specificato, il risultato di <query specification>è TEMP.

Pertanto, poiché la tabella ha un gruppo, deve avere una riga di risultati.

così

SELECT 1 FROM r HAVING 1=1

dovrebbe restituire un set di risultati di 1 riga.

QED


+1 Grazie per andare a tutti quei problemi! Come dice @ypercube, SQL Server sembra contraddirsi qui come SELECT 1 FROM r GROUP BY (); restituisce zero righe ma il passaggio che hai citato sembra abbastanza chiaro su questo punto.
Martin Smith,

Posso chiederti dove hai trovato lo standard? Se dici "sulla mia libreria" rimarrò deluso :)
dezso

Tecnicamente ho usato il Final Draft International Standard, piuttosto che lo standard stesso. Secondo le norme ISO / IEC sono consentite solo modifiche editoriali (non tecniche) tra FDIS e lo standard finale. Lo standard è suddiviso in più parti. Parte 1 , Parte 2 , Parte 4 ...
Kevin Cathcart

Parte 11 e Parte 14 . Le parti 3,9,10 e 13 non sono state aggiornate nel 2011 e pertanto si applicano le loro versioni precedenti. Non c'è parte 12. Allo stesso modo non ci sono parti 5-8. Vedi la pagina Wikipedia per Sql: 2011 o Parte 1 stessa per una spiegazione di ciò che contiene ciascuna parte.
Kevin Cathcart,

7

Quando c'è una HAVINGclausola, senza una WHEREclausola:

SELECT 1 FROM r HAVING 1=1;

... allora GROUP BY ()è implicito. Pertanto, la query dovrebbe essere equivalente a:

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

... che dovrebbe raggruppare tutte le righe della tabella in un gruppo (anche se la tabella non ha alcuna riga - è ancora un gruppo di 0 righe) e restituire 1 riga. Il HAVINGcon la Truecondizione dovrebbe avere alcun effetto a tutti dopo.


Da un'angolazione diversa, quante righe dovrebbe restituire una query come questa?

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

Uno, zero o "zero o uno, a seconda che la tabella sia vuota o meno"?

Penso che una riga, indipendentemente da quante righe rabbia.


Bene, la questione chiave è se sia effettivamente vero che "anche se la tabella non ha alcuna riga, è ancora un gruppo di 0 righe". E lo standard risulta esplicito al riguardo: "Se non ci sono colonne di raggruppamento, allora ... è la tabella raggruppata composta da T come unico gruppo". (e ciò vale anche se T è vuoto - quindi esiste davvero un gruppo.) Più avanti, la clausola having specifica che la condizione viene applicata a ciascun gruppo (nell'esempio quindi una volta). Probabilmente lo hanno definito in questo modo per fare in modo che SUM e COUNT restituiscano una riga anche per T vuote.
Erwin Smout,

+1 (prima!) Anche se la tua logica è la stessa di Kevin, ho accettato la sua risposta a causa delle citazioni dalle specifiche. Grazie!
Martin Smith,

@MartinSmith. Thnx. Che ottengo dall'essere pigro :)
ypercubeᵀᴹ

@ypercube: +1 anche da me. Ho deciso di dedicare del tempo extra per estrarre dalle specifiche per dimostrare che non c'erano parole da donnola nascoste da qualche parte che avrebbero sbagliato la tua risposta. Ma una volta fatto ciò, potrei anche pubblicarlo come una risposta completa. Così ho fatto.
Kevin Cathcart,

3
@ErwinSmout: Certo che no. Tuttavia, ciò rientra nel fair use ai sensi della legge sul copyright degli Stati Uniti. Porzioni relativamente piccole, citate nel contesto dell'analisi (es. Critica) dell'opera, a fini didattici, con un impatto trascurabile sulla capacità dell'opera di essere venduta.
Kevin Cathcart,

3

Da quello che vedo, sembra che SQL Server e PostgerSQL non si preoccupino affatto di guardare la tabella:

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

restituisce anche solo una riga. Anche se i documenti di SQLServer dicono

Quando GROUP BY non viene utilizzato, HAVING si comporta come una clausola WHERE.

ciò non è vero in questo caso, WHERE 1=1invece di HAVINGrestituire il numero corretto di righe. Direi che è un bug di ottimizzazione (o almeno un bug di documentazione) ... Il piano SQL Server mostra "Scansione costante" in caso di HAVING"Scansione tabella" per WHERE...

Il comportamento di Oracle e Mysql mi sembra più logico e corretto ...


1
Hai ragione che SQL Server non guarda la tabella. Il piano di esecuzione ha solo una scansione costante e non fa nemmeno riferimento alla tabella. Se fosse stato solo SQL Server, lo avrei semplicemente ridotto a un bug, ma poiché non è solo SQL Server, mi chiedo se qui ci sia qualche vera ambiguità.
Martin Smith,

PostgreSQL mostra gli stessi risultati di SQLServer e, per quanto ne so, dall'output di explain"Risultato (righe = 1) ..." per avere e "Scansione Seq" per "DOVE" non viene visualizzato nella tabella. .. Immagino sia in qualche modo correlato al fatto che "FROM" non è obbligatorio in TSQL e PostgreSQL. So che anche Mysql non lo richiede, ma poiché supportano dual, probabilmente analizzano la query in modo leggermente diverso. Sono d'accordo, sembra una speculazione, ma spero che abbia un senso.
a1ex07,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.