0

EAV テーブルで次のような「類似」製品を返すクエリが必要です。

1) 少なくとも 1 つの類似した属性を共有する

2) 製品と異なる属性がないこと

例えば

ProductID Attribute Value
1         Prop1      1
1         Prop2      2
2         Prop1      1
3         Prop1      1
3         Prop2      3

この例では、製品 ID 1 (Prop1:1 および Prop2:2) に類似する製品を検索するとします。Prop1 が 1 であるため、製品 2 は返品されますが、Prop2 が異なるため、製品 3 は問題ありません。

製品ごとに可変数の属性があるため、属性ごとにテーブルを結合することはできません。現時点では、props のリストを連結して動的 SQL "where" を構築していますが、これを実行する適切な (高速な?) SQL ステートメントが見つかりません。

この問題に集中しすぎたのかもしれませんが、これを行うための明らかな方法が欠けているという感覚を揺るがすことはできません...

4

3 に答える 3

3

この種の問題に直面したとき、私は TDQD (テスト駆動型クエリ設計) を使用します。

テーブルに名前を付けると、誰にでも役立つことに注意してください。

パス 1

商品 1 と同じ属性を 1 つ以上持つ商品のリスト

SELECT a.ProductID, COUNT(*) AS matches
  FROM EAV_Table AS a
  JOIN EAV_Table AS b
    ON a.Attribute = b.Attribute AND a.value = b.value
 WHERE a.ProductID != 1
   AND b.ProductID  = 1
 GROUP BY a.ProductID

これにより、カウントが 0 の製品は明らかにリストされません。これは問題ありません。

1 つ以上の属性が商品 1 と一致しない商品のリスト

SELECT c.ProductID, COUNT(*) AS matches
  FROM EAV_Table AS c
  JOIN EAV_Table AS d
    ON c.Attribute = d.Attribute AND c.value != d.value
 WHERE c.ProductID != 1
   AND d.ProductID  = 1
 GROUP BY c.ProductID

これはまた、カウントが 0 の製品をリストしないため、より厄介です。

結果 — パス 1

製品が 2 番目のクエリにリストされていない最初のクエリからのすべての製品が必要です。これは、NOT EXISTS と相関サブクエリで表現できます。

SELECT a.ProductID, COUNT(*) AS matches
  FROM EAV_Table AS a
  JOIN EAV_Table AS b
    ON a.Attribute = b.Attribute AND a.value = b.value
 WHERE a.ProductID != 1
   AND b.ProductID  = 1
   AND NOT EXISTS
       (SELECT c.ProductID
          FROM EAV_Table AS c
          JOIN EAV_Table AS d
            ON c.Attribute = d.Attribute AND c.value != d.value
         WHERE c.ProductID != 1
           AND d.ProductID  = 1
           AND c.ProductID = a.ProductID
       )
 GROUP BY a.ProductID

それはかなり醜いです。動作しますが、醜いです。

テストデータ

CREATE TABLE eav_table
(
    productid INTEGER NOT NULL,
    attribute CHAR(5) NOT NULL,
    value INTEGER NOT NULL,
    PRIMARY KEY(productid, attribute, value)
);

INSERT INTO eav_table VALUES(1, "Prop1", 1);
INSERT INTO eav_table VALUES(1, "Prop2", 2);
INSERT INTO eav_table VALUES(2, "Prop1", 1);
INSERT INTO eav_table VALUES(3, "Prop1", 1);
INSERT INTO eav_table VALUES(3, "Prop2", 3);
INSERT INTO eav_table VALUES(4, "Prop1", 1);
INSERT INTO eav_table VALUES(4, "Prop3", 1);

Q1結果

2    1
3    1
4    1

第2四半期の結果

3    1

第3四半期の結果

2    1
4    1

それらは私が生成したカウントです。より洗練されたレンディションでは、それらが削除されます。


パス 2

それが管理できる場合、より適切な最終クエリは、製品 ID 1 と共通する一致する属性/値のペアが少なくとも 1 つあるすべての製品 ID をリストするテーブルと、一致しないすべての製品 ID をリストするテーブルを結合します。製品 ID 1。

商品 1 と同じ属性を 1 つ以上持つ商品のリスト

最初のクエリは、パス 1 の最初のクエリと同じですが、結果セットのカウントを削除する点が異なります。

SELECT a.ProductID
  FROM EAV_Table AS a
  JOIN EAV_Table AS b
    ON a.Attribute = b.Attribute AND a.value = b.value
 WHERE a.ProductID != 1
   AND b.ProductID  = 1
 GROUP BY a.ProductID

通常、選択リスト内の GROUP BY 句または DISTINCT のいずれかが必要です (ただし、サンプル データでは正式には必要ありません)。

商品 1 と一致しない属性がゼロの商品のリスト

COUNT(column)null 以外の値のみをカウントするという事実を利用して、LEFT OUTER JOIN を使用します。

SELECT c.ProductID
  FROM      EAV_Table AS c
  LEFT JOIN EAV_Table AS d
    ON c.Attribute = d.Attribute
   AND c.Value != d.Value
   AND c.ProductID != 1
   AND d.ProductID  = 1
 GROUP BY c.ProductID
HAVING COUNT(d.Value) == 0;

WHERE 句が ON 句にマージされていることに注意してください。これは実際にはかなり重要です。

結果 — パス 2

上記の 2 つのクエリを、結合して最終結果を生成するサブクエリとして作成します。

SELECT f.ProductID
  FROM (SELECT a.ProductID
          FROM EAV_Table AS a
          JOIN EAV_Table AS b
            ON a.Attribute = b.Attribute AND a.value = b.value
         WHERE a.ProductID != 1
           AND b.ProductID  = 1
         GROUP BY a.ProductID
       ) AS e
  JOIN (SELECT c.ProductID
          FROM      EAV_Table AS c
          LEFT JOIN EAV_Table AS d
            ON c.Attribute = d.Attribute
           AND c.Value != d.Value
           AND c.ProductID != 1
           AND d.ProductID  = 1
         GROUP BY c.ProductID
        HAVING COUNT(D.Value) = 0
       ) AS f
    ON e.ProductID = f.ProductID

これにより、サンプル データの答え 2 と 4 が生成されます。

この演習の一部は、最初に作成した答えに満足しないことを学習していることに注意してください。テーブルに 7 行しかないテスト データ セットではなく、フル サイズのデータ​​ セットでソリューションのベンチマークを行うことをお勧めします。

于 2012-12-16T20:47:23.853 に答える
1

あなたの質問を正しく理解していれば、これでうまくいくはずです。これがフィドルです。

DECLARE @pId INT = 1

SELECT A.pid
FROM (
        SELECT pid, count(*) total
        FROM t
        WHERE pid <> @pId
        GROUP BY pid 
     ) A JOIN 
     (
        SELECT pid, count(*) matches
        FROM t 
        WHERE pid<>@pId and att + ':' + convert(varchar(12), val) in (
             SELECT att + ':' + convert(varchar(12), val) FROM t
             WHERE pid=@pId)
        GROUP BY pid
     ) B ON A.pid = B.pid

WHERE total = matches

注:追加データを使用してコメントに従って編集

于 2012-12-16T18:37:45.670 に答える
0

完全を期すために、CTEを使用します。(注:これにより、productId = 1の双子だけでなく、すべての双子が検出されます)

DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;

CREATE TABLE eav
        ( zentity INTEGER NOT NULL
        , zattribute varchar NOT NULL
        , zvalue INTEGER
        , PRIMARY KEY (zentity,zattribute)
        );
INSERT INTO eav(zentity, zattribute, zvalue) VALUES
 (1, 'Prop1',1) ,(1, 'Prop2',2)
,(2, 'Prop1',1)
,(3, 'Prop1',1) ,(3, 'Prop2',3)
,(4, 'Prop1',1) ,(4, 'Prop3',3) -- added by Jonathan L.
        ;

        -- CTE: pair of entities that have an 
        -- {attribute,value} in common
WITH pair AS (
        SELECT a.zentity AS one
                , b.zentity AS two
                , a. zattribute AS att
        FROM eav a
        JOIN eav b ON a.zentity <> b.zentity  -- tie-breaker
                AND a.zattribute = b.zattribute
                AND a.zvalue = b.zvalue
        )
SELECT pp.one, pp.two, pp.att
FROM pair pp
        -- The Other entity (two) may not have extra attributes
        -- NOTE: this NOT EXISTS could be repeated for pair.one, to also
        -- suppress the one.* products that have an extra attribute
WHERE NOT EXISTS (
        SELECT * FROM eav nx
        WHERE nx.zentity = pp.two
        AND nx.zattribute <> pp.att
        )
ORDER BY pp.one, pp.two, pp.att
        ;

ところで:本当の根本的な問題は「関係の分割」です。たぶん、新しいSQL標準はそのための演算子を導入する必要がありますか?

于 2012-12-16T21:55:53.083 に答える