SQL IN () versus OR

23

Eu estava trabalhando com uma consulta que escrevi hoje e tive que alterar o código da WHEREcláusula para usar um filtro IN (lista de coisas) em vez de usar algo como

item_desc = 'item 1'
OR item_desc = 'item 2'
OR item_desc = 'item 3'
OR item_desc = 'item 4'

O procedimento acima foi executado por 15 minutos e não retornou nada, mas o seguinte me deu meu conjunto de resultados em 1,5 minutos

item_desc IN (
'item 1'
,'item 2'
,'item 3'
,'item 4'
)

Eu fiz isso no SQL e estou me perguntando por que a IN (lista de itens) teve um desempenho muito mais rápido que a instrução OR.

- EDIT - SQL Server 2008, peço desculpas por não colocar essas informações em primeiro lugar.

Aqui está a consulta na íntegra usando as ORinstruções:

DECLARE @SD DATETIME
DECLARE @ED DATETIME
SET @SD = '2013-06-01';
SET @ED = '2013-06-15';

-- COLUMN SELECTION
SELECT PV.PtNo_Num AS 'VISIT ID'
, PV.Med_Rec_No AS 'MRN'
, PV.vst_start_dtime AS 'ADMIT'
, PV.vst_end_dtime AS 'DISC'
, PV.Days_Stay AS 'LOS'
, PV.pt_type AS 'PT TYPE'
, PV.hosp_svc AS 'HOSP SVC'
, SO.ord_no AS 'ORDER NUMBER'
--, SO.ent_dtime AS 'ORDER ENTRY TIME'
--, DATEDIFF(HOUR,PV.vst_start_dtime,SO.ent_dtime) AS 'ADM TO ENTRY HOURS'
, SO.svc_desc AS 'ORDER DESCRIPTION'
, OSM.ord_sts AS 'ORDER STATUS'
, SOS.prcs_dtime AS 'ORDER STATUS TIME'
, DATEDIFF(DAY,PV.vst_start_dtime,SOS.prcs_dtime) AS 'ADM TO ORD STS IN DAYS'

-- DB(S) USED
FROM smsdss.BMH_PLM_PtAcct_V PV
JOIN smsmir.sr_ord SO
ON PV.PtNo_Num = SO.episode_no
JOIN smsmir.sr_ord_sts_hist SOS
ON SO.ord_no = SOS.ord_no
JOIN smsmir.ord_sts_modf_mstr OSM
ON SOS.hist_sts = OSM.ord_sts_modf_cd

-- FILTER(S)
WHERE PV.Adm_Date BETWEEN @SD AND @ED
AND SO.svc_cd = 'PCO_REMFOLEY'
OR SO.svc_cd = 'PCO_INSRTFOLEY'
OR SO.svc_cd = 'PCO_INSTFOLEY'
OR SO.svc_cd = 'PCO_URIMETER'

AND SO.ord_no NOT IN (
    SELECT SO.ord_no
    FRROM smsdss.BMH_PLM_PtAcct_V PV
    JOIN smsmir.sr_ord SO
    ON PV.PtNo_Num = SO.episode_no
    JOIN smsmir.sr_ord_sts_hist SOS
    ON SO.ord_no = SOS.ord_no
    JOIN smsmir.ord_sts_modf_mstr OSM
    ON SOS.hist_sts = OSM.ord_sts_modf_cd
    WHERE OSM.ord_sts = 'DISCONTINUE'
    AND SO.svc_cd = 'PCO_REMFOLEY'
    OR SO.svc_cd = 'PCO_INSRTFOLEY'
    OR SO.svc_cd = 'PCO_INSTFOLEY'
    OR SO.svc_cd = 'PCO_URIMETER'
)
ORDER BY PV.PtNo_Num, SO.ord_no, SOS.prcs_dtime

Obrigado,

MCP_infiltrator
fonte
10
Você já olhou o plano de consulta?
1
Isso é MUITO específico da implementação. Qual DBMS você está usando?
James Anderson
Não examinei o plano de consulta, não sabia se isso era específico ou se era uma questão de fato, pois isso sempre funcionaria dessa maneira.
MCP_infiltrator
3
@MCP_infiltrator Portanto, os planos de execução não serão equivalentes porque a lógica não é equivalente. Ao usar ORcomo você faz na consulta real acima, você permite que o mecanismo entre em curto-circuito. WHERE A AND B OR Cserá avaliado como verdadeiro mesmo que A e B sejam falsos, se C for verdadeiro. Se você diz WHERE A and B OR C OR D OR E OR Fcomo faz acima, o AND fator pode ser considerado. A lógica equivalente real iria encapsular as ORséries acima em parêntesis para que eles sejam tratados como um conjunto: WHERE A AND (B OR C OR D OR E). É assim que um INé tratado.
JNK
5
A precedência do operador no SQL Server especificada ANDe tratada anteriormente OR, portanto, sua consulta acima é equivalente a WHERE (OSM.ord_sts = 'DISCONTINUE' AND SO.svc_cd = 'PCO_REMFOLEY') OR SO.svc_cd = 'PCO_INSRTFOLEY' OR SO.svc_cd = 'PCO_INSTFOLEY' OR SO.svc_cd = 'PCO_URIMETER'que significa que, se alguma das 3 últimas condições for verdadeira, poderá causar um curto-circuito no restante da avaliação.
JNK

Respostas:

28

A resposta de Oleski está incorreta. Para o SQL Server 2008, uma INlista é refatorada para uma série de ORinstruções. Pode ser diferente no MySQL, por exemplo.

Estou bastante certo de que se você gerasse planos de execução reais para ambas as suas consultas, eles seriam idênticos.

Com toda a probabilidade, a segunda consulta foi mais rápida porque você a executou em segundo lugar , e a primeira consulta já havia retirado todas as páginas de dados do banco de dados e pagado o custo de IO. A segunda consulta foi capaz de ler todos os dados da memória e executar muito mais rapidamente.

Atualizar

A fonte real da variação é provável que as consultas não sejam equivalentes . Você tem duas ORlistas diferentes abaixo:

WHERE PV.Adm_Date BETWEEN @SD AND @ED
AND SO.svc_cd = 'PCO_REMFOLEY'
OR SO.svc_cd = 'PCO_INSRTFOLEY'
OR SO.svc_cd = 'PCO_INSTFOLEY'
OR SO.svc_cd = 'PCO_URIMETER'

e depois

 WHERE OSM.ord_sts = 'DISCONTINUE'
    AND SO.svc_cd = 'PCO_REMFOLEY'
    OR SO.svc_cd = 'PCO_INSRTFOLEY'
    OR SO.svc_cd = 'PCO_INSTFOLEY'
    OR SO.svc_cd = 'PCO_URIMETER'

Nas duas WHEREcláusulas, a precedência do operador (onde AND é tratado antes do OR) significa que a lógica real executada pelo mecanismo é:

WHERE (ConditionA AND ConditionB)
OR ConditionC
OR ConditionD
OR ConditionE

Se você substituir as ORlistas por uma INexpressão, a lógica será:

WHERE ConditionA
AND (ConditionB OR ConditionC OR ConditionD OR ConditionE)

O que é radicalmente diferente.

JNK
fonte
2
@MCP_infiltrator Bem, esse é o problema de fazer suposições :) Você realmente deve ter planos de execução reais para ambos e ver se há alguma diferença, acho que não haverá.
JNK
4
Bem, se você tiver uma pergunta avançada sobre banco de dados, também poderá perguntar aos administradores de banco de dados - divulgação completa, sou um moderador por lá, mas se for uma pergunta avançada sobre otimização de SQL ou SQL, temos muitos especialistas, especialmente para SQL Server
JNK
1
Eu apenas olhei para os dois planos de execução e eles são muito diferentes. A consulta com as instruções OR ocupa 68% do custo na Verificação de Índice em Cluster, em que a instrução IN é 26%, juntamente com o que parece ser menos etapas de execução.
MCP_infiltrator
3
@MCP_infiltrator Não há necessidade, veja meus comentários na sua postagem original na parte superior. INnão é equivalente aos seus ORs acima, devido às outras condições na sua WHEREcláusula na consulta real. Basicamente, as consultas retornarão resultados diferentes.
JNK
3
@MCP_infiltrator Não há necessidade de postar perguntas idênticas no DBA.SE, o JNK respondeu (e você encontrará respostas semelhantes lá.) Se você deseja movê-lo ("migrar") para lá, pode sempre sinalizá-lo (sua pergunta) mencionando na caixa de comentários o que você deseja. Os mods vão cuidar.
ypercubeᵀᴹ
7

A melhor maneira de saber é examinar o plano de consulta real usando algo parecido EXPLAIN. Isso deve lhe dizer exatamente o que o DBMS está fazendo e, então, você pode ter uma idéia muito melhor por que é mais eficiente.

Com isso dito, os sistemas DBMS são realmente bons em realizar operações entre duas tabelas (como junções). Muito do tempo do otimizador é gasto nessas partes das consultas, porque geralmente são mais caras.

Por exemplo, o DBMS pode classificar essa INlista e, usando um índice item_desc, filtrar os resultados muito rapidamente. Você não pode fazer essa otimização ao listar várias seleções, como no primeiro exemplo.

Ao usar IN, você está criando uma tabela de improviso e filtrando essas técnicas de combinação de tabela mais eficientes.

EDIT : Eu postei esta resposta antes do OP mencionar o DBMS específico. Acontece que NÃO é assim que o SQL Server trata essa consulta, mas pode ser válido para outros sistemas DBMS. Consulte a resposta do JNK para obter uma resposta mais específica e precisa.

Oleksi
fonte
Eu imagino que a cardinalidade tem muito a ver com isso. Isso INnão seria tão rápido se fosse uma subseleção com 100 registros ou mil.
Robert Harvey
@RobertHarvey Sim, isso provavelmente é verdade, mas eu também não esperava que fosse muito pior.
11289 Oleksi
Graças @Oleksi Eu não sabia que os DBMS iria fazer a declaração em uma lista de improviso
MCP_infiltrator
1
-1 - No SQL Server, a INinstrução não é convertida em uma tabela, é tratada de forma idêntica a uma série de ORs.
JNK
2
@ Katana314 Se EXPLAIN fosse uma palavra-chave no SQL Server (que o OP está usando), eu concordaria com você, mas não é assim que não é relevante.
JNK