FIND_IN_SET () против IN ()

125

У меня в базе данных 2 таблицы. Один для заказов, а другой для компаний.

Заказы имеют такую ​​структуру:

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

И у Компании такая структура:

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

Чтобы получить названия компаний заказа, я могу сделать запрос как таковой:

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

Этот запрос работает нормально, но следующий запрос - нет.

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

Почему работает первый запрос, а второй нет?

Первый запрос возвращает:

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

Второй запрос возвращает только:

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

Почему это так, почему первый запрос возвращает все компании, а второй запрос возвращает только первую?

Ракетный Хазмат
источник
3
attachCompanyIDs - одна большая строка, поэтому mysql попытается найти компанию в этом
преобразовании
Я думаю, что это лучший пример mysqltutorial.org/mysql-find_in_set
Шурвир Мори

Ответы:

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

attachedCompanyIDs- скалярное значение, которое преобразуется в INT(тип companyID).

Приведение возвращает только числа до первой нецифровой (в вашем случае запятой).

Таким образом,

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

В PostgreSQL, вы можете преобразовать строку в массив (или сохранить ее в первую очередь как массив):

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

и это даже будет использовать индекс на companyID.

К сожалению, это не работает, MySQLпоскольку последний не поддерживает массивы.

Эта статья может вас заинтересовать (см. #2):

Обновить:

Если есть разумное ограничение на количество значений в списках, разделенных запятыми (скажем, не более 5), вы можете попробовать использовать этот запрос:

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
источник
3
Спасибо за объяснение. Я не понимал, что поле connectedCompanyIDs было передано в INT. Есть ли способ обойти это в MySQL? FIND_IN_SETработает, но не использует индексы и может работать медленно при большом количестве информации в таблице компании.
Rocket Hazmat
1
Вы можете объяснить это обновление? Что именно это делает, потому что кажется, что это работает.
Rocket Hazmat
1
@Rocket: он удаляет posэлементы с начала CVSи преобразует остальные в целое число.
Quassnoi
9
Недурно (y) для10 things in MySQL (that won’t work as expected)
NullPointer
@Quassnoi, зачем пишешь CROSS JOIN? Разве они не все одинаковы в MySQL?
Pacerier
13

attachCompanyIDs - одна большая строка, поэтому mysql пытается найти компанию в этом преобразовании в целое число.

когда вы используете где в

поэтому, если comapnyid = 1:

companyID IN ('1,2,3')

это верно

но если цифра 1 не на первом месте

 companyID IN ('2,3,1')

его возврат ложный

Хаим Евги
источник
3

Чтобы получить названия всех связанных компаний, а не на основе конкретного идентификатора.

SELECT 
    (SELECT GROUP_CONCAT(cmp.cmpny_name) 
    FROM company cmp 
    WHERE FIND_IN_SET(cmp.CompanyID, odr.attachedCompanyIDs)
    ) AS COMPANIES
FROM orders odr
Ануприя Пундир
источник
1

поскольку второй запрос ищет строки с идентификатором 1 ИЛИ 2 ИЛИ 3, первый запрос ищет одно из значений, разделенных запятыми, которые существуют в companyID,

и еще одна проблема здесь в том, что вы не присоединяетесь к таблицам с общим ключом в вашем where, поэтому вы получите мутацию строк, которая = count (table1) * count (table2);

Ваша проблема действительно существует с частью 2 моего ответа. (с вашим вторым запросом)

superfro
источник
В обеих таблицах больше строк, чем я показываю. В обеих таблицах указан идентификатор пользователя, под которым вы вошли в систему. Будет ли это помогать?
Rocket Hazmat
Что ж, вам нужно только что-то изменить, если ваш первый запрос не возвращает ожидаемых результатов. Если первый запрос возвращает те результаты, которые вам нужны, проблем действительно нет. Я думал, вам просто интересно, почему 2 не показывают одинаковый результат.
superfro
@superfro, мне любопытно, почему 2 не показывают одинаковый результат.
Rocket Hazmat
ваш второй запрос использует where IN (values), где часть 'values' берется из таблицы, а ее строка. Строка оценивается как логическое значение true, которое = 1, поэтому отображается только первая строка.
superfro
1
Если вас беспокоит производительность, вам, вероятно, следует подумать об изменении структуры базы данных. Вы можете добавить объединенную таблицу, содержащую 2 значения, order_ID и company_ID, вместо использования списка, разделенного запятыми, в таблице заказов. Это позволит вам выбрать имя из компании, оставившей присоединение к заказу_компании на company.company_ID = order_companies.company_ID оставшихся заказов на присоединение к order_companies.order_ID = order.order_ID, где orders.order_ID = 1; Это будет использовать индексы.
superfro
-1

Позвольте мне объяснить, когда использовать FIND_IN_SET и когда использовать IN.

Возьмем таблицу A, в которой есть столбцы с именами «помощь» и «аноним». Возьмем таблицу B, в которой есть столбцы с именами «bid», «bname», «aids».

Теперь в таблицах A и B есть фиктивные значения, как показано ниже.

Таблица А

помощь анаме

1 яблоко

2 банана

3 Манго

Таблица B

ставить bname помощники

1 яблоко 1,2

2 банана 2,1

3 Манго 3,1,2

enter code here

Случай 1: если вы хотите получить эти записи из таблицы b, которая имеет 1 значение в столбцах вспомогательных средств, вам необходимо использовать FIND_IN_SET.

Запрос: выберите * из A JOIN B ON FIND_IN_SET (A.aid, b.aids), где A.aid = 1;

Случай 2: если вы хотите получить эти записи из таблицы a, которая имеет значение 1 ИЛИ 2 ИЛИ 3, присутствующее в вспомогательных столбцах, вы должны использовать IN.

Запрос: выберите * из A JOIN B ON A.aid IN (b.aids);

Теперь вот вам, что вам нужно с помощью запроса mysql.

Prashant
источник
Этот вопрос уже решен. Также я не думаю, что ваш второй пример, с IN, работает ... это была в основном проблема, которую я пытался решить в начале.
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
источник
6
Добро пожаловать в SO! Код без объяснений редко бывает полезным. В этом случае он даже не пытается ответить на вопрос «Почему ...?». Также обратите внимание, что на этот конкретный вопрос уже есть принятый ответ, который дает хорошо принятый (> 80 голосов!) Ответ. Новому пользователю, возможно, лучше сосредоточиться на вопросах, на которые нет ответов, и / или самому задавать хорошие вопросы.
cfi