1

我正在使用 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 又是替代品作为两个单独的要求,而不是而不是简单地满足相同的供应要求。

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上面的查询不再适用。但是,我仍然需要完成本文顶部提到的相同目标

4

2 回答 2

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 表中的 'desired' 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,该列通常包含一个空值,但当它不为空值时,确定了一个替代供应。该问题也在(2,3,4,5)IN 列表中列出,并且与 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;
于 2012-09-29T00:33:29.863 回答
0

希望这可以帮助

INNER JOIN projectsupplies ON supplies.id = IF(supplies.id = supplyid, supplyid, substitute)
于 2012-09-29T00:25:44.870 回答