SQL WHERE ID IN (id1, id2,…, idn)

170

Preciso escrever uma consulta para recuperar uma grande lista de IDs.

Nós suportamos muitos back-ends (MySQL, Firebird, SQLServer, Oracle, PostgreSQL ...), então eu preciso escrever um SQL padrão.

O tamanho do conjunto de IDs pode ser grande, a consulta será gerada programaticamente. Então, qual é a melhor abordagem?

1) Escrevendo uma consulta usando IN

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

Minha pergunta aqui é. O que acontece se n for muito grande? Além disso, e o desempenho?

2) Escrevendo uma consulta usando OR

SELECT * FROM TABLE WHERE ID = id1 OR ID = id2 OR ... OR ID = idn

Eu acho que essa abordagem não tem n limite, mas e quanto ao desempenho se n for muito grande?

3) Escrevendo uma solução programática:

  foreach (var id in myIdList)
  {
      var item = GetItemByQuery("SELECT * FROM TABLE WHERE ID = " + id);
      myObjectList.Add(item);
  }

Tivemos alguns problemas com essa abordagem quando o servidor de banco de dados é consultado pela rede. Normalmente, é melhor fazer uma consulta que recupere todos os resultados do que fazer muitas consultas pequenas. Talvez eu esteja errado.

Qual seria a solução correta para esse problema?

Daniel Peñalba
fonte
1
A opção 1 reduz significativamente o tempo de resposta do servidor SQL, selecionando 7k IDs, dos quais alguns não existem. Normalmente, a consulta demorava cerca de 1300ms, e reduz para 80ms usando IN! Fiz o meu como sua solução 1 + 3. Apenas a consulta final foi uma, longa cadeia de caracteres de consulta enviada ao SQL para execução.
Piotr Kula

Respostas:

108

A opção 1 é a única boa solução.

Por quê?

  • A opção 2 faz o mesmo, mas você repete o nome da coluna várias vezes; Além disso, o mecanismo SQL não sabe imediatamente que você deseja verificar se o valor é um dos valores em uma lista fixa. No entanto, um bom mecanismo SQL poderia otimizá-lo para ter um desempenho igual ao anterior IN. Ainda existe o problema de legibilidade ...

  • A opção 3 é simplesmente horrível em termos de desempenho. Ele envia uma consulta a cada loop e martela o banco de dados com pequenas consultas. Também impede o uso de otimizações para "o valor é um daqueles em uma determinada lista"

ThiefMaster
fonte
2
Concordo, mas observe que a lista de entrada é limitada em muitos RDMS e, portanto, você precisaria usar a solução do @Ed Guiness, mas aqui as tabelas temporárias diferem entre o RDBMS. (Efetivamente para problemas complexos você não pode usar SQL padrão apenas pura)
mmmmmm
28

Uma abordagem alternativa pode ser usar outra tabela para conter valores de ID. Essa outra tabela pode ser unida internamente em sua TABLE para restringir as linhas retornadas. Isso terá a grande vantagem de que você não precisará de SQL dinâmico (problemático na melhor das hipóteses) e não terá uma cláusula IN infinitamente longa.

Você truncaria essa outra tabela, insira seu grande número de linhas e, em seguida, talvez crie um índice para ajudar no desempenho da junção. Também permitiria desanexar o acúmulo dessas linhas da recuperação de dados, talvez oferecendo mais opções para ajustar o desempenho.

Atualização : Embora você possa usar uma tabela temporária, não pretendi sugerir que você deva ou deva. Uma tabela permanente usada para dados temporários é uma solução comum com méritos além do descrito aqui.

Ed Guiness
fonte
1
Mas como você passaria a lista de identificações necessárias? (Como você não pode selecionar um intervalo ou algo parecido).
raam86
1
@ raam86: a lista de IDs pode ter sido obtida usando uma selectinstrução em outra tabela. A lista é passada como a outra tabela em que você se encontra inner join.
Bdforbes 26/07/19
19

O que Ed Guiness sugeriu é realmente um impulsionador de desempenho, eu tive uma consulta como esta

select * from table where id in (id1,id2.........long list)

o que eu fiz :

DECLARE @temp table(
            ID  int
            )
insert into @temp 
select * from dbo.fnSplitter('#idlist#')

Então o interior juntou-se ao temp com a tabela principal:

select * from table inner join temp on temp.id = table.id

E o desempenho melhorou drasticamente.

Ritu
fonte
1
Oi, o fnSplitter é uma função do MSSQL? Porque eu não fui capaz de encontrá-lo.
WiiMaxx
Não é uma coisa padrão. Eles devem significar que eles escreveram essa função para esse fim ou, por exemplo, tinham um aplicativo que já a forneceu.
underscore_d
fnSplitter é uma função criada pelo Ritu, você pode encontrar na internet / google semelhante dele
Bashar Abu Shamaa
9

A primeira opção é definitivamente a melhor opção.

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

No entanto, considerando que a lista de IDs é muito grande , digamos milhões, considere os tamanhos dos blocos, como abaixo:

  • Divida sua lista de IDs em pedaços de número fixo, digamos 100
  • O tamanho do pedaço deve ser decidido com base no tamanho da memória do seu servidor
  • Suponha que você tenha 10000 IDs, 10000/100 = 100 pedaços
  • Processe um pedaço por vez, resultando em 100 chamadas de banco de dados para seleção

Por que você deve se dividir em pedaços?

Você nunca receberá uma exceção de estouro de memória, o que é muito comum em cenários como o seu. Você terá um número otimizado de chamadas ao banco de dados, resultando em melhor desempenho.

Sempre funcionou como charme para mim. Espero que funcione para os meus colegas desenvolvedores também :)

Adarsh ​​Kumar
fonte
4

A execução do comando SELECT * FROM MyTable where id in () em uma tabela SQL do Azure com 500 milhões de registros resultou em um tempo de espera de> 7min!

Fazer isso retornou resultados imediatamente:

select b.id, a.* from MyTable a
join (values (250000), (2500001), (2600000)) as b(id)
ON a.id = b.id

Use uma associação.

JakeJ
fonte
3

Na maioria dos sistemas de banco de dados, IN (val1, val2, …)e uma série de ORsão otimizados para o mesmo plano.

A terceira maneira seria importar a lista de valores para uma tabela temporária e juntá-la, o que é mais eficiente na maioria dos sistemas, se houver muitos valores.

Você pode ler estes artigos:

Quassnoi
fonte
3

A amostra 3 seria a pior de todas, porque você está acessando o banco de dados inúmeras vezes sem motivo aparente.

Carregar os dados em uma tabela temporária e depois ingressar nela seria de longe o mais rápido. Depois disso, o IN deve funcionar um pouco mais rápido que o grupo de ORs.

judda
fonte
2

Eu acho que você quer dizer SqlServer, mas no Oracle você tem um limite rígido de quantos elementos IN você pode especificar: 1000.

flq
fonte
1
Até o SQL Server para de funcionar após ~ 40k IN elementos. De acordo com o MSDN: A inclusão de um número extremamente grande de valores (muitos milhares) em uma cláusula IN pode consumir recursos e retornar erros 8623 ou 8632. Para contornar esse problema, armazene os itens na lista IN em uma tabela.
jahav