1

私は現在MySQLに基づいてWebサイトを開発していますが、必要に応じて(CTEなどのように)別のデータベースに切り替えることは問題ではありません。

現在のプロジェクトを処理するのに最適なデータベースデータ構造(および可能であればいくつかのSQLスニペット)を探しています。これは次のとおりです。

  • 製品は他の製品で(再帰的に)作成できるため、製品はツリー指向のアーキテクチャで設計する必要があります。
  • 製品は、多くの製品の製造に使用できます。これは、ノードが多くのノードの子および親になる可能性があるため、ネストされたセットアーキテクチャのようなものが機能しない場合です。これは、ネストされたセットを使用しては不可能と思われます。
  • 製品には毎日利用可能な数量がありますが、それが「葉」である場合に限ります(別の製品から作成されたものではありません)。それ以外の場合、その量は直接の子の量に依存します(再帰が葉に到達するまでなど)。したがって、製品は、「product_id」、「date」、および「quantity」を含むテーブル「availability」にリンクされる場合があります。

製品の「ツリー」構造は非常に安定しているため、更新/挿入/削除クエリの速度は実際には重要ではありません。

最終的な目標は、SELECTクエリで、指定された2つの日付の間に利用可能なすべての製品のリストをできるだけ速く取得できるようにすることです。

この意味は:

  • 1)製品が別の製品で作られていない場合、2つの日付の間の毎日の数量が0より大きい場合に使用できます。
  • 2)製品が他のものでできている場合、(1)がすべての子供に当てはまる場合にのみ利用できます。直接の子が「葉」でない場合、それらには量がないため、(2)の再帰は、最終的な葉に到達するまで発生します。

ちなみに、実際のシナリオでは、私の製品は5レベルを超える深さを持つことはありません。たぶん、すべての親IDを別々の列に格納するのは良い(しかし非常に醜い)アイデアでしょう。

4

1 に答える 1

8

説明するデータ構造はツリーではありません(親がないルートを除いて、すべてのノードに親が1つだけ必要です)。むしろ、より一般的なDAGです。

有向非巡回グラフ

他のRDBMSは、階層データと再帰クエリをネイティブでサポートしていますが、MySQLはサポートしていません。代わりに、そのようなデータをMySQLに格納するための優れた一般的なリレーショナルモデルは、グラフの推移閉包のテーブルを作成することです。//ステートメント区切り文字としての使用:

CREATE TABLE Products (
  SKU         SERIAL                COMMENT 'Stock-Keeping Unit code',
  Name        VARCHAR(15)           COMMENT 'Product name',
  Description VARCHAR(255)          COMMENT 'Descriptive text',
  Price       DECIMAL(6,2)          COMMENT 'Selling price',
  isAtomic    BOOLEAN DEFAULT FALSE COMMENT 'Flag indicating atomicity'
)
  ENGINE  = InnoDB,
  COMMENT = 'Properties relating to each product'
//

CREATE TABLE ProductComponents (
  SKU          BIGINT UNSIGNED NOT NULL COMMENT 'Stock-Keeping Unit Code',
  ComponentSKU BIGINT UNSIGNED NOT NULL COMMENT 'SKU of comprised component',
  PRIMARY KEY (SKU, ComponentSKU),
  INDEX       (ComponentSKU, SKU),
  FOREIGN KEY (         SKU) REFERENCES Products (SKU),
  FOREIGN KEY (ComponentSKU) REFERENCES Products (SKU)
)
  ENGINE  = InnoDB,
  COMMENT = 'Transitive closure of the product DAG'
//

後者の表では、トリガーを使用して原子性を強制できます。

CREATE TRIGGER ins_atomic BEFORE INSERT ON ProductComponents
FOR EACH ROW IF
  NEW.SKU <> NEW.ComponentSKU
  AND (SELECT isAtomic FROM Products WHERE SKU = NEW.SKU)
THEN
  SIGNAL SQLSTATE '45000' SET
    MESSAGE_TEXT = 'Atomic product cannot have a component'
  ;
END IF//

CREATE TRIGGER upd_atomic BEFORE UPDATE ON ProductComponents
FOR EACH ROW IF
  NEW.SKU <> NEW.ComponentSKU
  AND (SELECT isAtomic FROM Products WHERE SKU = NEW.SKU)
THEN
  SIGNAL SQLSTATE '45000' SET
    MESSAGE_TEXT = 'Atomic product cannot have a component'
  ;
END IF//

Products.isAtomic:の誤った更新を防ぐ同様のトリガーが必要な場合もあります。

CREATE TRIGGER upd_prod BEFORE UPDATE ON Products
FOR EACH ROW IF NEW.isAtomic AND EXISTS (
  SELECT * FROM ProductComponents WHERE SKU <> ComponentSKU AND SKU = NEW.SKU
) THEN
  SIGNAL SQLSTATE '45000' SET
    MESSAGE_TEXT = 'Atomic product cannot have a component'
  ;
END IF//

上記のグラフの場合、データは次のようになります。

INSERT INTO Products
  (SKU, isAtomic)
VALUES
  (  2, TRUE    ),
  (  3, FALSE   ),
  (  5, FALSE   ),
  (  7, FALSE   ),
  (  8, FALSE   ),
  (  9, TRUE    ),
  ( 10, TRUE    ),
  ( 11, FALSE   )
//

INSERT INTO ProductComponents
  (SKU, ComponentSKU)
VALUES
  (2,2),
  (3,3),   (3,8),   (3,9),   (3,10),
  (5,5),   (5,11),  (5,2),   (5,9),   (5,10),
  (7,7),   (7,8),   (7,9),   (7,11),  (7,2),   (7,10),
  (8,8),   (8,9),
  (9,9),
  (10,10),
  (11,11), (11,2),  (11,9),  (11,10)
//

次に、次のように可用性を保存できます。

CREATE TABLE ProductAvailability (
  SKU      BIGINT UNSIGNED NOT NULL COMMENT 'Stock-Keeping Unit Code',
  Date     DATE                     COMMENT 'Availability date',
  Quantity INT                      COMMENT 'Available quantity',
  PRIMARY KEY (SKU, Date),
  FOREIGN KEY (SKU) REFERENCES Products (SKU)
)
  ENGINE  = InnoDB,
  COMMENT = 'Available quantities'
//

上記のいくつかのテストデータは次のようになります。

INSERT INTO ProductAvailability
  (SKU, Date        , Quantity)
VALUES
  (  2, '2012-12-13',     NULL),
  (  2, '2012-12-15',       15),
  (  9, '2012-12-13',      234),
  (  9, '2012-12-14',       46),
  (  9, '2012-12-15',        0),
  ( 10, '2012-12-13',        4),
  ( 10, '2012-12-14',        7),
  ( 10, '2012-12-15',        5)
//

そして、あなたのクエリは次のようになります:

SELECT   p.*
FROM     Products p
    JOIN ProductComponents c USING (SKU)
    JOIN (
           SELECT   p.SKU AS ComponentSKU,
                    COUNT(*) = DATEDIFF(@end_date, @start_date) + 1 AS available
           FROM     Products p LEFT JOIN ProductAvailability a
                 ON a.SKU = p.SKU
                AND a.Quantity > 0
                AND a.Date BETWEEN @start_date AND @end_date
           WHERE    p.isAtomic
           GROUP BY p.SKU
         ) q USING (ComponentSKU)
GROUP BY p.SKU
HAVING   NOT SUM(q.available = 0)

sqlfiddleでそれを参照してください。

于 2012-12-13T18:35:33.147 に答える