0

我需要一个在我的 EAV 表中返回“相似”产品的查询:

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)的产品。产品 2 会被退回,因为 Prop1 是 1,但是产品 3 不行,因为 Prop2 不同。等等。

每个产品都有可变数量的属性,因此不能为每个属性加入表格。目前我正在连接道具列表以构建动态 SQL “where”,但我找不到一个好的(快速?)SQL 语句来执行此操作。

也许我花了太多时间专注于这个问题,但我无法摆脱我错过了一个明显的方法来做到这一点的感觉......

4

3 回答 3

3

遇到此类问题时,我使用 TDQD — 测试驱动的查询设计。

请注意,如果你给你的桌子起个名字,它会对每个人都有帮助!

通过 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 不匹配的产品列表

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

我们需要第一个查询中未在第二个查询中列出的所有产品。这可以通过 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);

第一季度结果

2    1
3    1
4    1

第二季度结果

3    1

第三季度结果

2    1
4    1

这些是我生成的计数;更优美的演绎将删除它们。


通过 2

如果可以管理,一个更好的最终查询将连接一个表,该表列出了与产品 ID 1 具有至少一个匹配属性/值对的所有产品 ID产品编号 1。

一个或多个属性与产品 1 相同的产品列表

第一个查询与 Pass 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)我们将利用仅计算非空值的事实,并使用 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

我们将上面的两个查询构建为连接以生成最终结果的子查询:

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 回答