【问题标题】:FIND_IN_SET() vs IN()FIND_IN_SET() 与 IN()
【发布时间】:2011-05-08 12:56:00
【问题描述】:

我的数据库中有 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

这是为什么,为什么第一个查询返回所有公司,而第二个查询只返回第一个?

【问题讨论】:

标签: mysql


【解决方案1】:
SELECT  name
FROM    orders,company
WHERE   orderID = 1
        AND companyID IN (attachedCompanyIDs)

attachedCompanyIDs 是一个标量值,它被转换为INTcompanyID 的类型)。

强制转换仅返回第一个非数字(在您的情况下为逗号)之前的数字。

因此,

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)

【讨论】:

  • 感谢您的解释。我没有意识到 attachCompanyIDs 字段被转换为 INT。在 MySQL 中有没有办法解决这个问题? FIND_IN_SET 有效,但不使用索引,而且由于 Company 表中有很多信息,可能会很慢。
  • 你能解释一下这个更新吗?这到底是做什么的,因为它似乎有效。
  • @Rocket:它从CVS 的开头剥离pos 项目并将其余部分转换为整数。
  • 10 things in MySQL (that won’t work as expected)点赞(y)
  • @Quassnoi,你为什么写CROSS JOIN?他们不是在 MySQL 中的 all the same 吗?
【解决方案2】:

attachedCompanyIDs 是一个大字符串,所以 mysql 尝试在它的整数转换中找到公司

当你使用 where in 时

所以如果comapnyid = 1:

companyID IN ('1,2,3')

这是返回真

但如果数字1不在第一位

 companyID IN ('2,3,1')

返回错误

【讨论】:

    【解决方案3】:

    获取所有相关的公司名称,而不是基于特定的 Id。

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

    【讨论】:

      【解决方案4】:

      因为第二个查询要查找 id 为 1 OR 2 OR 3 的行, 第一个查询是在 companyID 中寻找一个逗号分隔的值,

      这里的另一个问题是您没有在 where 的公共键上加入表,因此您将获得 = count(table1) * count(table2); 的行突变;

      我的回答的第 2 部分确实存在您的问题。 (与您的第二个查询)

      【讨论】:

      • 两个表中的行数比我显示的要多。在这两个表中,都有您登录的用户的 ID,是否会加入该帮助?
      • 如果您的第一个查询没有返回预期结果,您只需要更改任何内容。如果第一个查询返回您想要的结果,那么确实没有问题。我以为你只是好奇为什么 2 没有显示相同的结果。
      • @superfro,我很好奇为什么 2 没有显示相同的结果。
      • 您的第二个查询使用 where IN (values),其中“values”部分来自表,它是一个字符串。该字符串被评估为 bool true which = 1 这就是为什么它只显示第一行。
      • 如果您担心性能,您可能应该考虑更改您的数据库结构。您可以添加一个包含 2 个值(order_ID 和 company_ID)的联合表,而不是在 order 表中使用逗号分隔的列表。这将允许您从公司中选择名称 left join order_companies on company.company_ID = order_companies.company_ID left join orders on order_companies.order_ID = order.order_ID where orders.order_ID = 1;这将使用索引。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-16
      • 1970-01-01
      相关资源
      最近更新 更多