mysql - 具有复杂 'if/else' 要求的 SQL SELECT 语句

标签 mysql sql lookup-tables

我正在使用 MySQL 数据库,这是我的情况:

我需要一个选择查询来获取可以使用 N 个耗材完成的项目列表,其中 N 是一个耗材数组。此项目列表必须包括可以使用任何或所有 N 用品完成的所有项目,但不能包括任何需要未在 N 中列出的用品的项目。(例如,在下表中的草图项目中,纸有没有替代品;但是,铅笔可以用钢笔代替。如果查询搜索可以使用铅笔、钢笔和卷笔刀完成的项目,则“制作草图”不应作为可以完成的项目返回,即使它使用列出的一些用品)

此外,某些项目所需的部分物资可以用其他物资替代;但是,仅仅因为一个项目可以使用替代供应品并不意味着另一个项目可以使用相同的替代品。 (例如,在下面的削铅笔项目中,钢笔不能代替铅笔,但是,对于绘图它可以)

这些是我的表格:

Projects
+----+---------------------+
| id |        name         |
+----+---------------------+
|  1 | make sketch         |
|  2 | sharpen pencil      |
|  3 | make paper airplane |
+----+---------------------+

Supplies
+----+------------------+
| id |       name       |
+----+------------------+
|  1 | paper            |
|  2 | pencil           |
|  3 | pen              |
|  4 | pencil sharpener |
+----+------------------+

ProjectSupplies
+----+-----------+------------+
| id | projectid |  supplyid  |
+----+-----------+------------+
|  1 |         1 |          1 |
|  2 |         1 |          2 |
|  3 |         2 |          2 |
|  4 |         2 |          4 |
|  5 |         3 |          1 |
+----+-----------+------------+

SubstituteSupplies
+-------------------+------------+
| projectsuppliesid |  supplyid  |
+-------------------+------------+
|                 2 |          3 |
+-------------------+------------+

数据无论如何都不是详尽无遗的,但您应该明白这一点。

这是我在更新数据库之前提出的查询(请参阅下面的更新),但是,它违反了规则,因为查询结果包括需要纸张的项目,因为它 COUNT 的 supplyid 和 substitute 作为两个独立的需求,而不是简单地满足相同的供应需求。

SELECT projects.name FROM supplies
INNER JOIN projectsupplies ON supplyid = supplies.id OR substitute = supplies.id
INNER JOIN projects ON projects.id = projectid
WHERE supplies.id IN (2,3,4)
GROUP BY projects.name
HAVING COUNT(*) <= 3
ORDER BY projects.id

有没有办法改变这个:

INNER JOIN projectsupplies ON supplyid = supplies.id OR substitute = supplies.id

本质上是这样的:

INNER JOIN projectsupplies ON (supplies.id = supplyid) ? (supplies.id = supplyid) : (supplies.id = substitute)

或类似于使用 if 语句或其他任何东西以使查询结果正确的东西?

我遇到的一个问题是上述查询将返回“make sketch”作为有效项目,即使在查询中指定没有论文。

最终目标是能够通过许多项目和大量供应大规模地实现这一目标。

更新:我在我的数据库设计中发现了一个问题,该问题导致无法允许供应有多个替代品。我更正了问题以允许多个替代品,并根据需要更新了上面的表格,所以现在上面的 SELECT 查询不再适用。但是,我仍然需要完成本文顶部提到的相同目标

最佳答案

查询级别的“OR”倾向于转换为 UNION。


架构发生重大变化后

(SELECT projectid, supplyid FROM ProjectSupplies
 UNION
 SELECT ps.Projectid, ss.supplyid
   FROM SubstituteSupplies AS ss
   JOIN ProjectSupplies    AS ps
     ON ss.ProjectSuppliesID = ps.ID
)

并将其插入到更大的查询中:

SELECT p.id, p.name
  FROM supplies AS s
  JOIN (SELECT projectid, supplyid FROM ProjectSupplies
        UNION
        SELECT ps.Projectid, ss.supplyid
          FROM SubstituteSupplies AS ss
          JOIN ProjectSupplies    AS ps
            ON ss.ProjectSuppliesID = ps.ID
       )        AS ps ON s.id = ps.supplyid
  JOIN projects AS p  ON p.id = ps.projectid
 WHERE s.id IN (2,3,4)
 GROUP BY p.id, p.name
HAVING COUNT(*) <= 3
 ORDER BY p.id;

(请注意,在这个阶段,我还没有验证查询的其余部分是否合理;我只解决了如何将补给品和替代补给品纳入连接操作。)

在 Mac OS X 10.7.5 上针对 IBM Informix Dynamic Server 11.70.FC2 运行时,示例数据和上述查询的输出为:

1   make sketch
2   sharpen pencil

显然,这是不正确的;项目 1 需要纸张才能完成,但这不是可用的供应品之一,也没有可用的替代品。所以,外部查询也是无效的。


修复主查询

可以使用给定供应 list (此处为供应品 2、3、4)完成的项目是指所有必要供应品或替代供应品都在可用供应品 list 中的项目。一个陷阱是确保如果有可用的替代供应但缺少一个不可替代的供应,则该项目无法完成。

因此,例如,项目 1 需要供应 SupplyID 1 和 SupplyID 2 或备选的 SupplyID 3; 2 和 3 都可用的事实是不够的。在此示例中,只有一个替代品,但一般来说,可能需要多个 SupplyID,其中许多都有替代品。因此,需要格外小心。

应用测试驱动查询设计 (TDQD)

当面对一个复杂的查询时,我会一步一步地构建它。发现原始主查询未命中标记后,我将不得不逐步构建它,结果是适度复杂,但易于理解,因为对步骤进行了解释。还有一个关键的设计步骤 - 算法的巧妙部分 - 需要提出,但这需要经验。

一个标准是每个项目都需要有它使用的所有供应品。因此,我们需要了解每个项目需要多少不同的供应品。这很简单:

SELECT ProjectID, COUNT(*) AS ItemCount
  FROM ProjectSupplies
 GROUP BY ProjectID;

结果

1   2
2   2
3   1

神奇的成分来了:“SupplyGroup”。之前生成的 UNION 查询需要扩展以包含一个 SupplyGroup。 SupplyGroup 对应于 ProjectSupplies 表中的“所需”SupplyID; SupplyID 是满足项目等效标准的 SupplyID,并且与来自 ProjectSupplies 的 SupplyID 相同,或者是来自 SubstituteSupplies 的 SupplyID:

SELECT ps.ProjectID, ps.SupplyID AS SupplyGroup, ps.SupplyID AS SupplyID
  FROM ProjectSupplies AS ps
UNION
SELECT ps.ProjectID, ps.SupplyID AS SupplyGroup, ss.SupplyID AS SupplyID
  FROM SubstituteSupplies AS ss
  JOIN ProjectSupplies    AS ps
    ON ss.ProjectSuppliesID = ps.ID;

结果

1   1   1
1   2   2
1   2   3
2   2   2
2   4   4
3   1   1

现在我们需要从列表 (2, 3, 4) of available SupplyIDs 中生成可以满足的 ProjectIDs 和 SupplyGroups 的列表:

SELECT DISTINCT ProjectID, SupplyGroup
  FROM (SELECT ps.ProjectID, ps.SupplyID AS SupplyGroup, ps.SupplyID AS SupplyID
          FROM ProjectSupplies AS ps
        UNION
        SELECT ps.ProjectID, ps.SupplyID AS SupplyGroup, ss.SupplyID AS SupplyID
          FROM SubstituteSupplies AS ss
          JOIN ProjectSupplies    AS ps
            ON ss.ProjectSuppliesID = ps.ID
       ) AS i
 WHERE i.SupplyID IN (2, 3, 4);

结果

1   2
2   2
2   4

事实上,我们需要计算可用于该列表中每个项目的不同供应组的数量:

SELECT ProjectID, COUNT(DISTINCT SupplyGroup) AS ItemCount
  FROM (SELECT ps.ProjectID, ps.SupplyID AS SupplyGroup, ps.SupplyID AS SupplyID
          FROM ProjectSupplies AS ps
        UNION
        SELECT ps.ProjectID, ps.SupplyID AS SupplyGroup, ss.SupplyID AS SupplyID
          FROM SubstituteSupplies AS ss
          JOIN ProjectSupplies    AS ps
            ON ss.ProjectSuppliesID = ps.ID
       ) AS i
 WHERE i.SupplyID IN (2, 3, 4)
 GROUP BY ProjectID;

结果

2   2
1   1

现在我们需要将关于项目 ID 和项目计数的第一个查询与第二个查询结合起来,并将其与项目表结合以列出项目名称:

SELECT p.ID, p.Name
  FROM (SELECT ProjectID, COUNT(DISTINCT SupplyGroup) AS ItemCount
          FROM (SELECT ps.ProjectID, ps.SupplyID AS SupplyGroup, ps.SupplyID AS SupplyID
                  FROM ProjectSupplies AS ps
                UNION
                SELECT ps.ProjectID, ps.SupplyID AS SupplyGroup, ss.SupplyID AS SupplyID
                  FROM SubstituteSupplies AS ss
                  JOIN ProjectSupplies    AS ps
                    ON ss.ProjectSuppliesID = ps.ID
               ) AS i
         WHERE i.SupplyID IN (2, 3, 4)
         GROUP BY ProjectID
       ) AS z
  JOIN (SELECT ProjectID, COUNT(*) AS ItemCount
          FROM ProjectSupplies
         GROUP BY ProjectID
       ) AS y
    ON z.ProjectID = y.ProjectID AND z.ItemCount = y.ItemCount
  JOIN Projects AS p ON p.ID = z.ProjectID
 ORDER BY p.ID, p.Name;

结果

2   sharpen pencil

而且,根据数据,我相信这是正确的结果。


架构发生重大变化之前

查询的原始版本针对不同的表结构,其中没有 SubstituteSupplies 表,而 ProjectSupplies 表有一个额外的列 Substitute,通常包含 null 但当它不为 null 时,确定了可以替代的供应。该问题还在 IN 列表中列出了 (2,3,4,5),并且聚合与 4 而不是 3 进行了比较。

您可以在子选择中使用两个内部联接的 UNION 来完成此操作:

(SELECT projectid, supplyid FROM ProjectSupplies
 UNION
 SELECT projectid, substitute FROM ProjectSupplies WHERE substitute IS NOT NULL
)

这需要插入到您的主查询中:

SELECT p.name
  FROM supplies AS s
  JOIN (SELECT projectid, supplyid FROM ProjectSupplies
         UNION
        SELECT projectid, substitute AS supplyid
          FROM ProjectSupplies WHERE substitute IS NOT NULL
       )        AS ps ON s.id = ps.supplyid
  JOIN projects AS p  ON p.id = ps.projectid
 WHERE s.id IN (2,3,4,5)
 GROUP BY p.name
HAVING COUNT(*) <= 4
 ORDER BY p.id;

关于mysql - 具有复杂 'if/else' 要求的 SQL SELECT 语句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12648821/

相关文章:

MySQL:NOT BETWEEN 未按预期运行

php - 如何显示内部连接表中的必填字段

sql - 定制化场景的数据库表设计

c++ - C++ 中不同 channel 的 LUT,opencv2

mysql - 从多个表中选择但按日期时间字段排序

php - Laravel 5 使用关系查询导致 "Call to a member function addEagerConstraints() on null"错误

android - 如何在另一个 Activity 上显示 ListView 所选项目的详细信息?

php - CURDATE() 12 月不工作

Sql 查询辅助 - 从 CASE WHEN 语句中删除重复项

javascript - 如何使用查表和异或来计算二进制中的 1?