FIND_IN_SET () vs IN ()

125

Eu tenho 2 tabelas no meu banco de dados. Um é para pedidos, e um é para empresas.

Orders tem esta estrutura:

OrderID     |     attachedCompanyIDs
------------------------------------
   1                     1,2,3
   2                     2,4

E a empresa tem essa estrutura:

CompanyID      |        name
--------------------------------------
    1                 Company 1
    2                 Another Company
    3                 StackOverflow
    4                 Nothing

Para obter os nomes das empresas de um pedido, posso fazer uma consulta como:

SELECT name FROM orders,company
WHERE orderID = 1 AND FIND_IN_SET(companyID, attachedCompanyIDs)

Essa consulta funciona bem, mas a consulta a seguir não.

SELECT name FROM orders,company
WHERE orderID = 1 AND companyID IN (attachedCompanyIDs)

Por que a primeira consulta funciona, mas não a segunda?

A primeira consulta retorna:

name
---------------
Company 1
Another Company
StackOverflow

A segunda consulta retorna apenas:

name
---------------
Company 1

Por que isso ocorre, por que a primeira consulta retorna todas as empresas, mas a segunda consulta retorna apenas a primeira?

Foguete Hazmat
fonte
3
attachedCompanyIDs é uma grande string, então o MySQL tentar encontrar empresa neste seu elenco para inteiro
Haim Evgi
Acho que este é o melhor exemplo mysqltutorial.org/mysql-find_in_set
Shurvir Mori

Respostas:

100
SELECT  name
FROM    orders,company
WHERE   orderID = 1
        AND companyID IN (attachedCompanyIDs)

attachedCompanyIDsé um valor escalar que é convertido em INT(tipo de companyID).

O elenco retorna apenas números até o primeiro não dígito (uma vírgula no seu caso).

Portanto,

companyID IN ('1,2,3')  companyID IN (CAST('1,2,3' AS INT))  companyID IN (1)

Em PostgreSQL, você pode converter a string na matriz (ou armazená-la como uma matriz em primeiro lugar):

SELECT  name
FROM    orders
JOIN    company
ON      companyID = ANY (('{' | attachedCompanyIDs | '}')::INT[])
WHERE   orderID = 1

e isso usaria mesmo um índice companyID.

Infelizmente, isso não funciona, MySQLpois o último não suporta matrizes.

Você pode achar este artigo interessante (consulte #2):

Atualizar:

Se houver algum limite razoável no número de valores nas listas separadas por vírgula (digamos, não mais que 5), você poderá tentar usar esta consulta:

SELECT  name
FROM    orders
CROSS JOIN
        (
        SELECT  1 AS pos
        UNION ALL
        SELECT  2 AS pos
        UNION ALL
        SELECT  3 AS pos
        UNION ALL
        SELECT  4 AS pos
        UNION ALL
        SELECT  5 AS pos
        ) q
JOIN    company
ON      companyID = CAST(NULLIF(SUBSTRING_INDEX(attachedCompanyIDs, ',', -pos), SUBSTRING_INDEX(attachedCompanyIDs, ',', 1 - pos)) AS UNSIGNED)
Quassnoi
fonte
3
Obrigada pelo esclarecimento. Não percebi que o campo attachedCompanyIDs foi convertido para um INT. Existe alguma maneira de contornar isso no MySQL? FIND_IN_SETfunciona, mas não usa índices e pode ser lento com muitas informações na tabela Empresa.
Foguete Hazmat
1
Você pode explicar essa atualização? O que exatamente isso faz, porque parece funcionar.
Rocket Hazmat
1
@Rocket: retira positens do início do CVSe lança o restante em número inteiro.
Quassnoi 11/11/2010
9
Polegares para cima (y) para10 things in MySQL (that won’t work as expected)
NullPointer 28/02
@Quassnoi, por que você escreve CROSS JOIN? Eles não são todos iguais no MySQL?
Pacerier 18/04/2015
13

attachedCompanyIDs é uma string grande, então o mysql tenta encontrar a empresa neste seu elenco para inteiro

quando você usa onde

então se comapnyid = 1:

companyID IN ('1,2,3')

isso é retorno verdadeiro

mas se o número 1 não estiver em primeiro lugar

 companyID IN ('2,3,1')

seu retorno falso

Haim Evgi
fonte
3

Para obter o nome de todas as empresas relacionadas, não com base em um ID específico.

SELECT 
    (SELECT GROUP_CONCAT(cmp.cmpny_name) 
    FROM company cmp 
    WHERE FIND_IN_SET(cmp.CompanyID, odr.attachedCompanyIDs)
    ) AS COMPANIES
FROM orders odr
Anupriya Pundir
fonte
1

como a segunda consulta procura linhas com o ID 1 OU 2 OU 3, a primeira consulta procura um dos valores delimitados por vírgula no ID da empresa,

e outro problema aqui é que você não está juntando as tabelas em uma chave comum no seu local, para obter uma mutação de linhas que = count (tabela1) * contagem (tabela2);

Seu problema realmente existe com a parte 2 da minha resposta. (com sua segunda consulta)

superfro
fonte
Há mais linhas nas duas tabelas do que estou mostrando. Nas duas tabelas, existe o ID do usuário no qual você está conectado, entrando nessa ajuda?
Rocket Hazmat
Bem, você só precisa alterar qualquer coisa se sua primeira consulta não retornar os resultados esperados. Se a primeira consulta estiver retornando os resultados desejados, não haverá realmente nenhum problema. Eu pensei que você estava curioso para saber por que os 2 não mostram o mesmo resultado.
superfro
@ superfro, estou curioso para saber por que os 2 não mostram o mesmo resultado.
Foguete Hazmat
sua segunda consulta está usando um where IN (values), onde a parte 'values' vem da tabela e é uma string. A cadeia está sendo avaliada como um valor booleano, que = 1 e é por isso que mostra apenas a primeira linha.
superfro
1
Se você está preocupado com o desempenho, provavelmente deve pensar em alterar sua estrutura de banco de dados. Você pode adicionar uma tabela conjunta que contém 2 valores, order_ID e company_ID, em vez de usar a lista delimitada por vírgulas na tabela de pedidos. Isso permitiria que você selecionasse o nome da empresa que ingressou à esquerda em order_companies em company.company_ID = order_companies.company_ID deixou em pedidos de ingresso em order_companies.order_ID = order.order_ID em que orders.order_ID = 1; Isso usaria índices.
superfro
-1

Deixe-me explicar quando usar FIND_IN_SET e quando usar IN.

Vamos pegar a tabela A que possui colunas denominadas "aid", "aname". Vamos pegar a tabela B, que possui colunas denominadas "bid", "bname", "aids".

Agora, existem valores simulados na Tabela A e na Tabela B, como abaixo.

Quadro A

aid aname

1 Apple

2 Banana

3 Manga

Quadro B

lances de bname

1 Apple 1,2

2 Banana 2,1

3 Manga 3,1,2

enter code here

Caso1: se você deseja obter esses registros da tabela b que possui 1 valor presente nas colunas de aids, use FIND_IN_SET.

Consulta: selecione * em A JOIN B ON FIND_IN_SET (A.aid, b.aids) em que A.aid = 1;

Caso2: se você deseja obter esses registros da tabela a com o valor 1 OU 2 OU 3 presente nas colunas de ajuda, é necessário usar IN.

Consulta: selecione * em A JOIN B ON A.aid IN (b.aids);

Agora, aqui você decide o que precisa através da consulta mysql.

prashant
fonte
Esta questão já foi resolvida. Também não acho que seu segundo exemplo, com IN, funcione ... esse foi basicamente o problema que eu estava tentando resolver no começo.
Rocket Hazmat
-2
SELECT o.*, GROUP_CONCAT(c.name) FROM Orders AS o , Company.c
    WHERE FIND_IN_SET(c.CompanyID , o.attachedCompanyIDs) GROUP BY o.attachedCompanyIDs
amit gangrade
fonte
6
Bem-vindo ao SO! Código sem explicações raramente é útil. Nesse caso, ele nem tenta responder à pergunta "Por que ...?". Observe também que esta pergunta em particular já possui uma resposta aceita, que fornece uma resposta bem recebida (> 80 votos!). Como novo usuário, talvez seja melhor se concentrar em perguntas não respondidas e / ou fazer boas perguntas.
Cfi 21/10