如何在SQL中选择类似的集合

时间:2012-10-30 03:36:03

标签: sql sql-server algorithm

我有以下表格:

Order
----
ID (pk)

OrderItem
----
OrderID (fk -> Order.ID)
ItemID (fk -> Item.ID)
Quantity

Item
----
ID (pk)

如何撰写可以选择与特定Orders至少85%相似的所有Order的查询?

我考虑使用Jaccard Index统计量来计算两个Orders的相似度。 (通过取每组OrderItems的交集除以每组OrderItems的联合)

但是,如果没有为两个Orders的每个可能组合存储计算的Jaccard索引,我就无法想到这样做的方法。 还有其他办法吗?

另外,有没有办法将每个匹配的Quantity的{​​{1}}中的差异纳入考虑范围?

其他信息:


总计OrderItem:~79k
总计Orders:~1.76万
平均。每OrderItems OrderItems:21.5 总计Order:〜13k

注意


85%的相似性数字只是对客户实际需求的最佳猜测,未来可能会发生变化。适用于任何相似性的解决方案将是更可取的。

7 个答案:

答案 0 :(得分:3)

这真的没有简单的答案。你当然可以存储Jaccard索引(实际上我只是存储那些符合标准的索引,然后扔掉其余的索引),但真正的问题在于计算它(每次新订单时都必须扫描所有现有订单)输入到系统中以计算新索引。)

这可能相当昂贵,具体取决于您维护的订单量。也许你只将它与去年的订单或其他东西进行比较。

如果你在飞行中这样做,它会变得更有趣,但仍然很昂贵。

您可以轻松获得具有相同产品项目的所有订单的列表。每个项目一个列表。事实上,这不一定是很多数据(如果你有一个单个热门项目的大量订单,那么它可能是一个很长的列表)。个别查询也不是特别疯狂(同样取决于您的数据)。如果您拥有大量数据,则可以轻松地映射/缩小查询,甚至可以使用分片数据存储。位图索引(如果你的数据库支持这个)特别适合快速获取这样的列表。

然后,您可以简单地计算订单号在所有列表中出现的次数,然后删除不符合阈值的那些。这是一个直接的合并操作。

但是,每当你想要这些信息时,你就必须进行这种计算,因为你无法真正存储它。

所以,它确实归结为您需要的信息,您需要的频率,您的项目< - >订单分发,您可以等待多长时间等等。

附录:

稍微考虑一下,这是一个简单的查询,但可能需要一些时间才能运行。可能与现代硬件相差无几,你真的没有那么多数据。对于查看订单的单个屏幕,您不会注意到它。如果您在所有订单中运行报表,那么您肯定会注意到它 - 并且需要采用不同的方法。

让我们考虑一个包含20个订单项的订单。

你想要85%的比赛。这意味着共有17个或更多项目的订单。

这是一个查询,可以为您提供您感兴趣的订单:

SELECT orderId, count(*) FROM OrderItem
WHERE itemId in ('list', 'of', 'items', 'in', 'order', 123, 456, 789)
GROUP BY orderId
HAVING count(*) >= 17

因此,这会为您提供包含与订单相同项目的所有订单项的集合。然后你只需按orderId对它们求和,那些等于或大于你的阈值(在这种情况下为17)的是候选订单。

现在,您没有说明目录中有多少件商品。如果你有1000个项目,完美分布,这个查询将咀嚼1600行数据 - 这没什么大不了的。使用适当的索引,这应该很快。但是,如果你有“非常受欢迎”的项目,那么你将会咀嚼更多的数据行。

但是,再次,你没有那么多的数据。大多数此查询可以在适当的数据库上的索引中完成,甚至不会命中实际的表。因此,正如我所说,您可能不会注意到此查询对交互式系统的影响。

所以,试一试,看看它是怎么回事。

答案 1 :(得分:3)

此方法使用扩展Jaccard系数或Tanimoto Similarity考虑​​数量。它通过使用大小数量的常见ItemID的向量来计算所有订单的相似性。它确实需要进行表扫描,但不需要对所有可能的相似性进行N ^ 2计算。

SELECT
    OrderID,
    SUM(v1.Quantity * v2.Quantity) /
    (SUM(v1.Quantity * v1.Quantity) +
     SUM(v2.Quantity * v2.Quantity) -
     SUM(v1.Quantity * v2.Quantity) ) AS coef
FROM
    OrderItem v1 FULL OUTER JOIN OrderItem v2
    ON v1.ItemID = v2.ItemID
    AND v2.OrderID = ?
GROUP BY OrderID
HAVING coef > 0.85;

扩展Jaccard系数的公式:

Tanimoto Similarity

答案 2 :(得分:2)

这不仅仅是一个扩展评论的答案。如果被认为没有意义,我会删除它。

如果您正在尝试动态找到“类似”项目,那么问题是您需要查看很多(~79k)订单。因此,如果您尝试这样做,那么您需要在进行昂贵的集合比较之前减少您正在考虑的订单数量。

@Will指出的一种方法是考虑订单中的项目数。因此,如果您的目标订单有20个项目,那么您只需要考虑具有17-23 OrderItems的订单(或类似的东西,具体取决于'85%相似性'的确切计算)。我假设这些数字可以通过触发器计算,无论何时创建或更改订单,并将其存储在Order表的列中。

但是如果你可以存储集合的大小,那么你也可以存储其他数字。例如,您可以在每个订单中存储奇数 OrderItem主键值的数量。那么您正在考虑的订单必须恰当地接近于具有该数量的奇数订单号(我可能会在某个时刻进行数学计算以填写'恰当接近')。

如果您考虑将值除以“奇数”数字分割为大小为1的条带,则可以使用模数运算符轻松地按不同大小的条带进行分区。例如。 ItemID%4< 2将使尺寸为2的条纹。然后,您可以为每个Order记录这些条带中的OrderItem主键的数量。您的候选订单必须在每个分区值的方式上与您的目标订单适当接近。

所以你最终得到的是一个大的子查询,试图通过查看在该表上存储和索引的一大堆指标来限制Orders表中候选者的大小。

答案 3 :(得分:1)

我会尝试这样的速度,通过与Order @OrderId相似的方式列出订单。连接的INTS应该是交集,相似度值是我计算Jaccard指数的尝试。

我这里根本没有使用数量字段,但我认为如果我们想出一种量化包含数量的相似性的方法,也可以在不减慢查询速度的情况下完成。下面,我将两个订单中的任何相同项目计为相似度。您也可以加入数量,或使用包含数量的匹配计数加倍的度量。我不知道这是否合理。

SELECT 
    OI.OrderId,
    1.0*COUNT(INTS.ItemId) / 
    (COUNT(*)
    + (SELECT COUNT(*) FROM OrderItem WHERE OrderID = @OrderId) 
    - COUNT(INTS.ItemId)) AS Similarity
FROM    
    OrderItem OI        
JOIN
    OrderItem INTS ON INTS.ItemID = OI.ItemID AND INTS.OrderId=@OrderId
GROUP BY 
    OI.OrderId
HAVING  
    1.0*COUNT(INTS.ItemId) / 
    (COUNT(*)
    + (SELECT COUNT(*) FROM OrderItem WHERE OrderID = @OrderId) 
    - COUNT(INTS.ItemId)) > 0.85
ORDER BY
    Similarity DESC

它还预先假定OrderId中的OrderId / ItemId组合是唯一的。我意识到情况可能并非如此,并且可以使用视图进行处理。

我确信有更好的方法,但是衡量量化差异的一种方法是用这样的东西取代提名者COUNT(INTS.ItemId)(假设所有数量都是正数),将命中缓慢地减少到0当数量不同时。

    1/(ABS(LOG(OI.quantity)-LOG(INTS.quantity))+1)  

添加了: 使用JRideout

建议的Tanimoto相似度这个更易读的解决方案
DECLARE 
    @ItemCount INT,
    @OrderId int 
SELECT     
    @OrderId  = 1
SELECT     
    @ItemCount = COUNT(*)
FROM 
    OrderItem
WHERE 
    OrderID = @OrderId 


SELECT 
    OI.OrderId,
    SUM(1.0* OI.Quantity*INTS.Quantity/(OI.Quantity*OI.Quantity+INTS.Quantity*INTS.Quantity-OI.Quantity*INTS.Quantity )) /
    (COUNT(*) + @ItemCount - COUNT(INTS.ItemId)) AS Similarity
FROM    
    OrderItem OI        
LEFT JOIN
    OrderItem INTS ON INTS.ItemID = OI.ItemID AND INTS.OrderId=@OrderId
GROUP BY 
    OI.OrderId
HAVING      
    SUM(1.0* OI.Quantity*INTS.Quantity/(OI.Quantity*OI.Quantity+INTS.Quantity*INTS.Quantity-OI.Quantity*INTS.Quantity )) /
    (COUNT(*) + @ItemCount - COUNT(INTS.ItemId)) > 0.85
ORDER BY
    Similarity DESC

答案 4 :(得分:1)

嗯,好笑,我目前正在做类似的事情。你为什么不加入样品订单(即他们的商品)和所有其他订单(他们的商品),并通过对每个订单的匹配数量进行分组来列出所有至少有85%匹配的订单?

-- let @SampleorderID be the ID of a sample

declare @totalOrders int, @ThresholdOrderCount int
select @totalOrders = count(*) from OrderItems where orderID=@SampleOrderID

set @ThresholdOrderCount = 85*@totalOrders/100 -- 85% of the item amount of the sample

-- Now match the contents of the sample order with the contents of all other orders
-- count the #matches and show only those orders with at least 85% identical items
Select AllOrder.OrderID, count(*)
from   OrderItems sample 
       join OrderItems AllOrder 
         on sample.ItemID = AllOrder.ItemID
where sample.OrderID = @SampleOrderID 
  and sample.OrderID<>AllOrder.OrderID
group by AllOrder.OrderID 
having count(*)>@ThresholdOrderCount

这应该有效。但是,它也会返回包含比样本更多的项目的订单。如果可以,那么上面的查询也应该非常快。

答案 5 :(得分:1)

我将采取的方法是首先找到与所选订单的订购项目85%相似的所有订单项目,然后计算每个订单的这些订单项目的数量,并检查项目数量是否为85 %使用以下查询与所选订单类似:

DECLARE @OrderId int = 2
SET @OrderId = 6

/*
Retrieve orderitems that match 85% with required @orderId
*/
;WITH SelectedOrderItemCount
AS
(
    SELECT COUNT(*) * 0.85 AS LowerBoundary, COUNT(*) * 1.15 AS UpperBoundary
    FROM OrderItem
    WHERE OrderId = @OrderId
)
SELECT OtherOrders.OrderId, COUNT(*) as NumberOfOrderItems
FROM OrderItem SpecificOrder
INNER JOIN OrderItem OtherOrders
    ON OtherOrders.ItemId = SpecificOrder.ItemId
WHERE SpecificOrder.OrderId = @OrderId AND
      OtherOrders.OrderId <> @OrderId AND
    OtherOrders.Quantity BETWEEN SpecificOrder.Quantity * 0.85 AND SpecificOrder.Quantity * 1.15
GROUP BY OtherOrders.OrderId
HAVING COUNT(*) BETWEEN (SELECT LowerBoundary FROM SelectedOrderItemCount) 
                    AND (SELECT UpperBoundary FROM SelectedOrderItemCount)

完整的SQLFiddle演示here

答案 6 :(得分:0)

这是一种数据挖掘问题。因此,不使用sql,您可以使用Apriori算法,支持85%。许多工具都可以免费使用该算法。