Qual é o resultado correto para esta consulta?

20

Me deparei com este quebra-cabeça nos comentários aqui

CREATE TABLE r (b INT);

SELECT 1 FROM r HAVING 1=1;

SQL Server e PostgreSQL retornam 1 linha.

MySQL e Oracle retornam zero linhas.

Qual é correto? Ou ambos são igualmente válidos?

Martin Smith
fonte
Belo quebra-cabeça. Eu acho que o correto é retornar 1 linha. O SQL-Server está se contradizendo porque SELECT COUNT(*) FROM r;retorna 1 linha (com 0), enquanto SELECT COUNT(*) FROM r GROUP BY ();não retorna nenhuma linha.
usar o seguinte comando
1
Quero mais? SELECT 1 WHERE 1=0 HAVING 1=1;. O SQL Server e o PostgreSQL ainda retornam uma linha. O Oracle quer FROM DUAL e não retorna linhas. O MySQL não compila nem com FROM DUAL nem sem ele .
precisa
1
@AndriyM Por alguma razão desconhecida, "dual" e "HAVING" não funcionam bem no MySQL. (Boa descoberta). Mas o equivalente funciona: SELECT 1 AS t FROM (SELECT 1) tmp WHERE 1=0 HAVING 1=1; 1 linha-no-dual e retorna 0 linhas.
precisa saber é o seguinte
1
@ SQLKiwi - E essa passagem da especificação. "Se o TE não contém imediatamente um <group by clause>, então “GROUP BY ()”está implícito." As duas consultas não deveriam retornar os mesmos resultados?
Martin Smith
1
Mas discordam sobre estes (Oracle executa consultas com HAVINGdiferente): SQL-fiddle 2: TENDO torna as coisas diferentes
ypercubeᵀᴹ

Respostas:

17

De acordo com o padrão:

SELECT 1 FROM r HAVING 1=1

significa

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

Citação ISO / IEC 9075-2: 2011 7.10 Regra de sintaxe 1 (parte da definição da cláusula HAVING):

Let HCSer o <having clause>. Let TESer o <table expression>que contém imediatamente HC. Se TEnão contiver imediatamente um <group by clause>, então " GROUP BY ()" estará implícito. Let TSeja o descritor da tabela definida pelo <group by clause> GBCimediatamente contido TEe Rseja o resultado de GBC.

Ok, então isso é bem claro.


Afirmação: 1=1é verdadeira condição de pesquisa. Não fornecerei nenhuma citação para isso.


Agora

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

é equivalente a

SELECT 1 FROM r GROUP BY ()

Citação ISO / IEC 9075-2: 2011 7.10 Regra Geral 1:

O <search condition>é avaliado para cada grupo de R. O resultado de <having clause>é uma tabela agrupada dos grupos de R para os quais o resultado de <search condition>é True.

Lógica: como a condição de pesquisa é sempre verdadeira, o resultado é R, que é o resultado do grupo por expressão.


A seguir, um trecho das Regras Gerais de 7.9 (a definição do GRUPO POR CLÁUSULA)

1) Se não <where clause>for especificado, Tseja o resultado do anterior <from clause>; caso contrário, Tseja o resultado do anterior <where clause>.

2) Caso:

a) Se não houver colunas de agrupamento, o resultado da <group by clause>é a tabela agrupada que consiste Tem seu único grupo.

Assim, podemos concluir que

FROM r GROUP BY ()

resulta em uma tabela agrupada, composta por um grupo, com zero linhas (já que R está vazio).


Um trecho das Regras Gerais da 7.12, que define uma Especificação de Consulta (também conhecida como instrução SELECT):

1) Caso:

a) Se Tnão for uma tabela agrupada, então [...]

b) Se Tfor uma tabela agrupada, então

Caso:

i) Se Ttiver 0 (zero) grupos, deixe TEMP ser uma tabela vazia.

ii) Se Ttiver um ou mais grupos, cada um <value expression>será aplicado a cada grupo que Tproduz uma tabela TEMPde Mlinhas, onde Mestá o número de grupos em T. A icoluna -th do TEMP contém os valores derivados pela avaliação da i-th <value expression>. [...]

2) Caso:

a) Se <set quantifier> DISTINCTnão for especificado, o resultado <query specification>será TEMP.

Portanto, como a tabela possui um grupo, ela deve ter uma linha de resultado.

portanto

SELECT 1 FROM r HAVING 1=1

deve retornar um conjunto de resultados de 1 linha.

QED

Kevin Cathcart
fonte
+1 Obrigado por dar todo esse problema! Como o @ypercube diz, o SQL Server parece se contradizer aqui como SELECT 1 FROM r GROUP BY (); retorna zero linhas, mas a passagem que você citou parece bastante clara nesse ponto.
Martin Smith
Posso perguntar onde você encontrou o padrão? Se você disser 'na minha estante' Vou ficar desapontado :)
Dezso
Tecnicamente, usei o Esboço Internacional do Padrão Internacional, em vez do próprio padrão. De acordo com as regras da ISO / IEC, somente alterações editoriais (não técnicas) são permitidas entre o FDIS e o padrão final. O padrão é cuspido em várias partes. Parte 1 , Parte 2 , Parte 4 ...
Kevin Cathcart
Parte 11 e Parte 14 . As partes 3,9,10 e 13 não foram atualizadas em 2011 e, portanto, suas versões anteriores se aplicam. Não há parte 12. Da mesma forma, não há partes 5-8. Consulte a página da Wikipedia para Sql: 2011 ou a Parte 1 em si para obter uma explicação do que cada parte contém.
Kevin Cathcart
7

Quando existe uma HAVINGcláusula, sem uma WHEREcláusula:

SELECT 1 FROM r HAVING 1=1;

... então GROUP BY ()está implícito. Portanto, a consulta deve ser equivalente a:

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

... que deve agrupar todas as linhas da tabela em um grupo (mesmo que a tabela não tenha linhas - ainda é um grupo de 0 linhas) e retorna 1 linha. O HAVINGcom a Truecondição não deve ter efeito algum depois disso.


De um ângulo diferente, quantas linhas uma consulta como essa deve retornar?

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

Um, zero ou "zero ou um, dependendo se a tabela está vazia ou não"?

Eu acho que uma linha, não importa quantas linhas rtem.

ypercubeᵀᴹ
fonte
Bem, a questão principal é se é realmente verdade que "mesmo que a tabela não tenha linhas, ainda é um grupo de 0 linhas". E o padrão acaba sendo explícito sobre isso: "Se não houver colunas de agrupamento, então ... é a tabela agrupada que consiste em T como seu único grupo". (e isso é válido mesmo que T esteja vazio - então, de fato, há um grupo.) Mais adiante, a cláusula having especifica que a condição é aplicada a cada grupo (no exemplo assim uma vez). Eles provavelmente o definiram dessa maneira para fazer com que SUM e COUNT retornem uma linha, mesmo para T's vazios.
Erwin Smout
+1 (mais cedo!) Embora sua lógica seja a mesma de Kevin, aceitei a resposta dele por causa das citações das especificações. Obrigado!
Martin Smith
@MartinSmith. Thnx. Que eu recebo de ser preguiçoso :)
ypercubeᵀᴹ
@ypercube: +1 de mim também. Decidi dedicar um tempo extra às especificações para provar que não havia palavras de doninha escondidas em algum lugar que tornariam sua resposta errada. Mas uma vez que eu fiz isso, eu poderia postá-lo como uma resposta completa. Então eu fiz.
Kevin Cathcart
3
@ ErwinSmout: Claro que não. No entanto, isso se enquadra no uso justo sob a lei de direitos autorais dos EUA. Partes relativamente pequenas, citadas no contexto da análise (ou seja, críticas) do trabalho, para fins educacionais, com impacto insignificante na capacidade de venda do trabalho.
23413 Kevin Kevin Cathcart
3

Pelo que vejo, parece que o SQLServer e o PostgerSQL não se incomodam em olhar para a tabela:

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

também retorna apenas uma linha. Embora os documentos do SQLServer digam

Quando GROUP BY não é usado, HAVING se comporta como uma cláusula WHERE.

isso não é verdade neste caso - em WHERE 1=1vez de HAVINGretornar o número adequado de linhas. Eu diria que é um bug do otimizador (ou pelo menos um bug na documentação) ... O plano do SQLServer mostra 'Constant scan' no caso de HAVINGe 'table scan' for WHERE...

O comportamento do Oracle e Mysql parece mais lógico e correto para mim ...

a1ex07
fonte
1
Você está certo que o SQL Server não olha para a tabela. O plano de execução tem apenas uma varredura constante e nem faz referência à tabela. Se fosse apenas o SQL Server, eu simplesmente o atribuiria a um bug, mas como não é apenas o SQL Server, estou me perguntando se há alguma ambiguidade genuína aqui.
Martin Smith
O PostgreSQL mostra os mesmos resultados que o SQLServer, e até onde eu sei pela saída de explain"Resultado (linhas = 1) ..." por ter e "Seq Scan" por "WHERE", também não olha para a tabela. .. Eu acho que está de alguma forma relacionado ao fato de que "FROM" não é obrigatório no TSQL e no PostgreSQL. Eu sei que o Mysql também não exige, mas como eles suportam dual, provavelmente analisam a consulta um pouco diferente. Concordo, parece uma especulação, mas espero que faça algum sentido.
a1ex07