具有复杂的“if / else”要求的SQL SELECT语句

时间:2012-09-29 00:15:07

标签: 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 |
+-------------------+------------+

这些数据并非详尽无遗,但你应该明白这一点。

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

COUNT

有没有办法解决这个问题:

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

或类似于使用if语句或类似内容以使查询结果正确的内容?

我遇到的一个问题是,上面的查询会将'make sketch'作为有效项目返回,即使在查询中指定,也没有纸张。

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

更新:我在数据库的设计中发现了一个问题,导致供应不可能有多个替代品。我更正了问题以允许许多替代品,并根据需要更新上面的表格,因此现在上面的INNER JOIN projectsupplies ON (supplies.id = supplyid) ? (supplies.id = supplyid) : (supplies.id = substitute) 查询不再适用。但是,我仍然需要完成本文顶部提到的相同目标

2 个答案:

答案 0 :(得分:3)

查询级别的“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需要完成纸张,但这不是可用的供应品之一,并且没有可用的替代品。因此,外部查询也无效。


修复主要查询

可以使用给定供应清单(此处为供应2,3,4)完成的项目是那些项目,每个必要的供应或替代供应都在可用供应清单中。一个问题是确保如果有替代供应但缺少一个不可替代的供应,该项目是不可完善的。

因此,例如,项目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

现在我们需要从可用的SupplyID的列表(2, 3, 4)生成可以满足的ProjectID和SupplyGroup的列表:

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;

答案 1 :(得分:0)

希望这有帮助

INNER JOIN projectsupplies ON supplies.id = IF(supplies.id = supplyid, supplyid, substitute)