1

私は MySQL データベースを使用しています。これが私の状況です。

N 個の消耗品 (N は消耗品の配列) を使用して完了することができるプロジェクトのリストを取得できるようにするには、選択クエリが必要です。このプロジェクトのリストには、N の供給品の一部またはすべてを使用して完了することができるすべてのプロジェクトを含める必要がありますが、N にリストされていない供給品を必要とするプロジェクトを含めることはできません(たとえば、下の表のスケッチ プロジェクトでは、紙には代替物がありません。ただし、鉛筆はペンで置き換えることができます. クエリが、鉛筆、ペン、および鉛筆削りを使用して完成できるプロジェクトを検索する場合、'make sketch' は、完成できるプロジェクトとして返されるべきではありません。消耗品が記載されています)

さらに、特定のプロジェクトで必要なサプライ品の一部は、他のサプライ品に置き換えることができます。ただし、あるプロジェクトが代替供給品目を使用できるからといって、別のプロジェクトが同じ代替品で機能するとは限りません。(たとえば、以下のシャープペンシル プロジェクトでは、ペンを鉛筆の代わりにすることはできませんが、make drawing では可能です)

これらは私のテーブルです:

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。同じ供給要件を単に満たすだけではありません。

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 ステートメントなどを使用するのと同様のものですか?

私が経験していた問題の 1 つは、クエリで指定されているように用紙がないにもかかわらず、上記のクエリが有効なプロジェクトとして「スケッチを作成」を返すことです。

最終的な目標は、多くのプロジェクトと多くの物資を使って大規模にこれを達成できるようにすることです。

更新:データベースの設計に問題があり、供給品に複数の代替品を持たせることができませんでした。多くの代用を可能にするために問題を修正し、必要に応じて上記の表を更新したため、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 を完了するには紙が必要ですが、それは利用可能な供給品の 1 つではなく、利用可能な代替品もありません。したがって、外側のクエリも無効です。


メインクエリの修正

特定の供給リスト (ここでは供給 2、3、4) で完了することができるプロジェクトは、必要なすべての供給または代替供給が利用可能な供給のリストにあるプロジェクトです。落とし穴の 1 つは、利用可能な代替供給があるが、代替不可能な供給が 1 つ不足している場合、プロジェクトは完了できないことを確認することです。

したがって、たとえば、プロジェクト 1 には SupplyID 1 のサプライと、SupplyID 2 または別の SupplyID 3 のいずれかが必要です。2 と 3 の両方が利用可能であるという事実は十分ではありません。この例では、代替は 1 つしかありませんが、一般に、必要な SupplyID が多数あり、それらの多くが代替を持つ可能性があります。そのため、かなりの注意が必要です。

テスト駆動クエリ設計 (TDQD) の適用

複雑なクエリに直面したとき、私はそれを段階的に構築します。元のメイン クエリが的を射ていないことがわかったので、それを段階的に構築していく必要があります。結果は適度に複雑ですが、手順が説明されているので理解できます。また、重要な設計ステップ (アルゴリズムの巧妙な部分) を考え出す必要がありますが、それには経験が必要です。

基準の 1 つは、各プロジェクトで使用するすべての物資を利用できるようにする必要があるということです。そのため、プロジェクトごとに必要なサプライ品の数を知る必要があります。かんたんだよ:

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 のリストから満たすことができる ProjectID と SupplyGroup のリストを生成する必要が(2, 3, 4)あります。

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 と項目数について最初のクエリを 2 番目のクエリと結合し、それを projects テーブルと結合してプロジェクト名を一覧表示する必要があります。

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 でない場合は、代わりの供給を識別した、別のテーブル構造に対するものでした。質問(2,3,4,5)は IN リストにも記載されており、集計は 3 ではなく 4 と比較されました。

サブセレクト内の 2 つの内部結合の 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 に答える