我有以下表格:
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%的相似性数字只是对客户实际需求的最佳猜测,未来可能会发生变化。适用于任何相似性的解决方案将是更可取的。
答案 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系数的公式:
答案 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)
-- 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%。许多工具都可以免费使用该算法。