我必须制作最便宜的篮子,其中包含固定物品。
例如,对于具有(5)项
的购物篮1和4 =(1 * 50)+(1 * 100)= 150
2和3 =(1 * 60)+(1 * 80)= 140 - 这是我的家伙
2和2和1 =(1 * 60)+(1 * 60)+(1 * 50)= 170
3和3 =(1 * 80)+(1 * 80)= 160 ****这6项,但总项目可以超过最小项目。重要的是总成本...... ....
这也适用于篮子可能有的任何数量的物品。还有很多商店,每个商店有不同的包可能包括几个项目。
如何使用SQL处理此问题?
这是示例数据生成代码。递归CTE解决方案更昂贵。我应该每次在600-700家商店完成500-600毫秒以下的工作。这是一个包搜索引擎。使用'#temp'表或'UNUION'创建手动场景比递归CTE便宜15-20倍。
同时连接Item
或PackageId
非常昂贵。在选择加入源表的最便宜的包后,我可以找到所需的包ID或项目。
我期待一种可以超快速获得正确选择的手术解决方案。 每家商店只需要最便宜的篮子。手动场景创建速度非常快,但有时候无法获得正确的最便宜的篮子。
CREATE TABLE #storePackages(
StoreId int not null,
PackageId int not null,
ItemType int not null, -- there are tree item type 0 is normal item, 1 is item has discount 2 is free item
ItemCount int not null,
ItemPrice decimal(18,8) not null,
MaxItemQouta int not null, -- in generaly a package can have between 1 and 6 qouata but in rare can up to 20-25
MaxFullQouta int not null -- sometimes a package can have additional free or discount item qouta. MaxFullQouta will always greater then MaxItemQouta
)
declare @totalStores int
set @totalStores = (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN 200 AND 400 ORDER BY NEWID())
declare @storeId int;
declare @packageId int;
declare @maxPackageForStore int;
declare @itemMinPrice decimal(18,8);
set @storeId = 1;
set @packageId = 1
while(@storeId <= @totalStores)
BEGIN
set @maxPackageForStore = (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN 2 AND 6 ORDER BY NEWID())
set @itemMinPrice = (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN 40 AND 100 ORDER BY NEWID())
BEGIN
INSERT INTO #storePackages
SELECT DISTINCT
StoreId = @storeId
,PackageId = CAST(@packageId + number AS int)
,ItemType = 0
,ItemCount = number
,ItemPrice = @itemMinPrice + (10 * (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN pkgNo.number AND pkgNo.number + 2 ORDER BY NEWID()))
,MaxItemQouta = @maxPackageForStore
,MaxFullQouta = @maxPackageForStore + (CASE WHEN number > 1 AND number < 4 THEN 1 ELSE 0 END)
FROM master..[spt_values] pkgNo
WHERE number BETWEEN 1 AND @maxPackageForStore
UNION ALL
SELECT DISTINCT
StoreId = @storeId
,PackageId = CAST(@packageId + number AS int)
,ItemType = 1
,ItemCount = 1
,ItemPrice = (@itemMinPrice / 2) + (10 * (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN pkgNo.number AND pkgNo.number + 2 ORDER BY NEWID()))
,MaxItemQouta = @maxPackageForStore
,MaxFullQouta = @maxPackageForStore + (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN 0 AND 2 ORDER BY NEWID())
FROM master..[spt_values] pkgNo
WHERE number BETWEEN 2 AND (CASE WHEN @maxPackageForStore > 4 THEN 4 ELSE @maxPackageForStore END)
set @packageId = @packageId + @maxPackageForStore;
END
set @storeId =@storeId + 1;
END
SELECT * FROM #storePackages
drop table #storePackages
首先,我感谢所有试图帮助我的人。但是,所有建议的解决方案都基于CTE。正如我之前所说的那样,递归CTE会在考虑商店的情况时导致性能问题。一次还请求多个包。这意味着,我要求可以包括多个篮子。一个是5个项目,另外一个是3个项目,另一个是7个项目......
首先,我按项目大小在表格中生成所有可能的场景......通过这种方式,我可以选择消除不需要的场景。
CREATE TABLE ItemScenarios(
Item int,
ScenarioId int,
CalculatedItem int --this will be joined with Store Item
)
然后我生成了从2项到25项的所有可能场景并插入ItemScenarios
表。通过使用WHILE或递归CTE可以一次生成场景。这种方式的优点是,场景只生成一次。
Item | ScenarioId | CalculatedItem
--------------------------------------------------------
2 1 2
2 2 3
2 3 1
2 3 1
3 4 5
3 5 4
3 6 3
3 7 2
3 7 2
3 8 2
3 8 1
3 9 1
3 9 1
3 9 1
....
.....
......
25 993 10
通过这种方式,我可以限制场景大小,Max不同的商店,最大不同的包等。
此外,我可以摒弃一些与其他人相比最不可能的情况。例如,对于4个项目请求,某些情况
情景1:2 + 2
场景2:2 + 1 + 1
场景3:1 + 1 + 1 + 1
在这些情景中;场景2不可能是最便宜的篮子。因为,
如果场景2 &lt; 场景3 - &gt; 场景1 会低于场景 2.因为降低成本的物品是2件物品价格而**场景1 *有两件物品
如果场景2 &lt; 场景1 - &gt; 场景3 会低于场景 2
现在,如果我删除场景2 等场景,我会获得一些性能优势。
现在我可以在商店中选择最便宜的商品价格
DECLARE @requestedItems int;
SET @requestedItems = 5;
CREATE TABLE #JoinedPackageItemWithScenarios(
StoreId int not null,
PackageId int not null,
ItemCount int not null,
ItemPrice decimal(18,8)
ScenarioId int not null,
)
INSERT INTO #JoinedPackageItemWithScenarios
SELECT
SPM.StoreId
,SPM.PackageId
,SPM.ItemCount
,SPM.ItemPrice
,SPM.ScenarioId
FROM (
SELECT
SP.StoreId
,SP.PackageId
,SP.ItemCount
,SP.ItemPrice
,SC.ScenarioId
,RowNumber = ROW_NUMBER() OVER (PARTITION BY SP.StoreId,SC.ScenarioId,SP.ItemCount ORDER BY SP.ItemPrice)
FROM ItemScenarios SC
LEFT JOIN StorePackages AS SP ON SP.ItemCount = SC.CalculatedItem
WHERE SC.Item = @requestedItems
) SPM
WHERE SPM.RowNumber = 1
-- NOW I HAVE CHEAPEST PRICE FOR EACH ITEM, I CAN CREATE BASKET
CREATE TABLE #selectedScenarios(
StoreId int not null,
ScenarioId int not null,
TotalItem int not null,
TotalCost decimal(18,8)
)
INSERT INTO #selectedScenarios
SELECT
StoreId
,ScenarioId
,TotalItem
,TotalCost
FROM (
SELECT
StoreId
,ScenarioId
--,PackageIds = dbo.GROUP_CONCAT(CAST(PackageId AS nvarchar(20))) -- CONCATENING PackageId decreasing performance here. We can joing seleceted scenarios with #JoinedPackageItemWithScenarios after selection complated.
,TotalItem = SUM(ItemCount)
,TotalCost = SUM(ItemPrice)
,RowNumber = ROW_NUMBER() OVER (PARTITION BY StoreId ORDER BY SUM(ItemPrice))
FROM #JoinedPackageItemWithScenarios JPS
GROUP BY StoreId,ScenarioId
HAVING(SUM(ItemCount) >= @requestedItems)
) SLECTED
WHERE RowNumber = 1
-- NOW WE CAN POPULATE PackageIds if needed
SELECT
SS.StoreId
,SS.ScenarioId
,TotalItem = MAX(SS.TotalItem)
,TotalCost = MAX(SS.TotalCost)
,PackageIds = dbo.GROUP_CONCAT(CAST(JPS.PackageId AS nvarchar(20)))
FROM #selectedScenarios SS
JOIN #JoinedPackageItemWithScenarios AS JPS ON JPS.StoreId = SS.StoreId AND JPS.ScenarioId = SS.ScenarioId
GROUP BY SS.StoreId,SS.ScenarioId
在我的测试中,这种方式比递归CTE快10倍,特别是当商店和请求项目数量增加时。它也获得100%正确的结果。因为当商店数量和要求的商品数量增加时,递归CTE尝试了数百万个不需要的JOIN。
答案 0 :(得分:1)
如果你想要组合,你需要一个递归的CTE。防止无限递归是一个挑战。这是一种方法:
with cte as (
select cast(packageid as nvarchar(4000)) as packs, item, cost
from t
union all
select concat(cte.packs, ',', t.packageid), cte.item + t.item, cte.cost + t.cost
from cte join
t
on cte.item + t.item < 10 -- some "reasonable" stop condition
)
select top 1 cte.*
from cte
where cte.item >= 5
order by cost desc;
我并非100%确定SQL Server会接受连接条件,但这应该可以。
答案 1 :(得分:0)
假设您要比较所有可能的项目排列,直到篮子中的总项数超过您的总篮数,以下类似的内容就能满足您的需求。
DECLARE @N INT = 1;
DECLARE @myTable TABLE (storeID INT DEFAULT(1), packageID INT IDENTITY(1, 1), item INT, cost INT);
INSERT @myTable (item, cost) VALUES (1, 50), (2, 60), (3, 80), (4, 100), (5, 169), (5, 165), (4, 101), (2, 61);
WITH CTE1 AS (
SELECT item, cost
FROM (
SELECT item, cost, ROW_NUMBER() OVER (PARTITION BY item ORDER BY cost) RN
FROM @myTable) T
WHERE RN = 1)
, CTE2 AS (
SELECT CAST('items'+CAST(C1.item AS VARCHAR(10)) AS VARCHAR(4000)) items, C1.cost totalCost, C1.item totalItems
FROM CTE1 C1
UNION ALL
SELECT CAST(C2.items + ' + items' + CAST(C1.item AS VARCHAR(10)) AS VARCHAR(4000)), C1.cost + C2.totalCost, C1.item + C2.totalItems
FROM CTE2 C2
CROSS JOIN CTE1 C1
WHERE C2.totalItems < @N)
SELECT TOP 1 *
FROM CTE2
WHERE totalItems >= @N
ORDER BY totalCost, totalItems DESC;
编辑处理@Matt提到的问题。
答案 2 :(得分:0)
IF OBJECT_ID('tempdb..#TestResults') IS NOT NULL
BEGIN
DROP TABLE #TestResults
END
DECLARE @MinItemCount INT = 5
;WITH cteMaxCostToConsider AS (
SELECT
StoreId
,CASE
WHEN (SUM(ItemCount) >= @MinItemCount) AND
SUM(ItemPrice) < MIN(((@MinItemCount / ItemCount) + IIF((@MinItemCount % ItemCount) > 0, 1,0)) * ItemPrice) THEN SUM(ItemPrice)
ELSE MIN(((@MinItemCount / ItemCount) + IIF((@MinItemCount % ItemCount) > 0, 1,0)) * ItemPrice)
END AS MaxCostToConsider
FROM
storePackages
GROUP BY
StoreId
)
, cteRecursive AS (
SELECT
StoreId
,'<PackageId>' + CAST(PackageId AS VARCHAR(MAX)) + '</PackageId>' AS PackageIds
,ItemCount AS CombinedItemCount
,CAST(ItemPrice AS decimal(18,8)) AS CombinedCost
FROM
storePackages
UNION ALL
SELECT
r.StoreId
,r.PackageIds + '<PackageId>' + CAST(t.PackageId AS VARCHAR(MAX)) + '</PackageId>'
,r.CombinedItemCount + t.ItemCount
,CAST(r.CombinedCost + t.ItemPrice AS decimal(18,8))
FROM
cteRecursive r
INNER JOIN storePackages t
ON r.StoreId = t.StoreId
INNER JOIN cteMaxCostToConsider m
ON r.StoreId = m.StoreId
AND r.CombinedCost + t.ItemPrice <= m.MaxCostToConsider
)
, cteCombinedCostRowNum AS (
SELECT
StoreId
,CAST(PackageIds AS XML) AS PackageIds
,CombinedCost
,CombinedItemCount
,DENSE_RANK() OVER (PARTITION BY StoreId ORDER BY CombinedCost) AS CombinedCostRowNum
,ROW_NUMBER() OVER (PARTITION BY StoreId ORDER BY CombinedCost) AS PseudoCartId
FROM
cteRecursive
WHERE
CombinedItemCount >= @MinItemCount
)
SELECT DISTINCT
c.StoreId
,x.PackageIds
,c.CombinedItemCount
,c.CombinedCost
INTO #TestResults
FROM
cteCombinedCostRowNum c
CROSS APPLY (
SELECT( STUFF ( (
SELECT ',' + PackageId
FROM
(SELECT T.N.value('.','VARCHAR(100)') as PackageId FROM c.PackageIds.nodes('PackageId') as T(N)) p
ORDER BY
PackageId
FOR XML PATH(''), TYPE ).value('.','NVARCHAR(MAX)'), 1, 1, '')
) as PackageIds
) x
WHERE
CombinedCostRowNum = 1
SELECT *
FROM
#TestResults
大约1000-2000 MS的变化很大,具体取决于必须在测试数据中考虑的组合(例如,您的脚本会生成更多或更少的数据)。
这个答案无疑看起来比戈登或者ZLK复杂一点,但它处理关系,重复值,1个符合标准的包和其他一些东西。然而,主要区别在于最后一个查询,其中我将在递归查询期间构建的XML拆分,然后按顺序重新组合,以便您可以使用DISTINCT并获得唯一的配对,例如包2 +包3 = 140&amp; package 3 + package 2 = 140将是所有查询中的前2个结果,因此使用XML进行拆分然后重新组合允许它成为单行。但是,让我们说你还有另一行,如(1,5,2,60),有2个项目,成本为60,这个查询也将返回该组合。
你可以在答案之间挑选并使用他们的方法来获得组合和我的方法以获得最终结果等....但是要解释我的查询过程。
cteMaxCostToConsider
- 这只是获取包含递归查询的成本的一种方法,因此必须考虑较少的记录。它的作用是决定所有包装的成本或成本,如果你购买所有相同的包装以满足最低数量。
cteRecursive
- 这类似于ZLK的答案和像戈登一样的小事,但它的作用是熄灭并继续添加项目&amp;项目组合,直到达到MaxCostToConsider。如果我限制查看项目计数,它可能会错过7个项目比5便宜的情况,因此通过约束到确定的组合成本,它限制了递归并且表现更好。
cteCombinedCostRowNum
- 这只是找到最低的合并成本和至少最小的项目数。
最终查询有点棘手但是交叉应用将递归cte中的XML字符串构建拆分到不同的行,重新排序这些行,然后再次连接它们以便反向组合,例如套餐2&amp;套餐3反向套餐3&amp;包2成为相同的记录,然后调用distinct。
这比SELECT top N更灵活。要查看差异,请将以下测试用例一次添加到测试数据1中: (StoreId,PackageId,Item,Cost)
已编辑。以上内容将为您提供组合成本最低的商店的每个组合。您注意到的错误归因于cteMaxCostToConsider
。我使用的是SUM(ItemPrice)
,但有时与SUM(ItemCount)
相关的内容中没有足够的项目可以考虑使用MaxCostToConsider。我修改了case语句来纠正这个问题。
我还修改了您提供的数据示例。请注意,您应该将其中的PackageId更改为IDENTITY列,因为我使用您使用的方法在商店中获取了重复的PackageId。
以下是您的脚本的修改版本,以查看我在说什么:
IF OBJECT_ID('storePackages') IS NOT NULL
BEGIN
DROP TABLE storePackages
END
CREATE TABLE storePackages(
StoreId int not null,
PackageId int not null IDENTITY(1,1),
ItemType int not null, -- there are tree item type 0 is normal item, 1 is item has discount 2 is free item
ItemCount int not null,
ItemPrice decimal(18,8) not null,
MaxItemQouta int not null, -- in generaly a package can have between 1 and 6 qouata but in rare can up to 20-25
MaxFullQouta int not null -- sometimes a package can have additional free or discount item qouta. MaxFullQouta will always greater then MaxItemQouta
)
declare @totalStores int
set @totalStores = (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN 200 AND 400 ORDER BY NEWID())
declare @storeId int;
declare @packageId int;
declare @maxPackageForStore int;
declare @itemMinPrice decimal(18,8);
set @storeId = 1;
set @packageId = 1
while(@storeId <= @totalStores)
BEGIN
set @maxPackageForStore = (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN 2 AND 6 ORDER BY NEWID())
set @itemMinPrice = (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN 40 AND 100 ORDER BY NEWID())
BEGIN
INSERT INTO storePackages (StoreId, ItemType, ItemCount, ItemPrice, MaxFullQouta, MaxItemQouta)
SELECT DISTINCT
StoreId = @storeId
--,PackageId = CAST(@packageId + number AS int)
,ItemType = 0
,ItemCount = number
,ItemPrice = @itemMinPrice + (10 * (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN pkgNo.number AND pkgNo.number + 2 ORDER BY NEWID()))
,MaxItemQouta = @maxPackageForStore
,MaxFullQouta = @maxPackageForStore + (CASE WHEN number > 1 AND number < 4 THEN 1 ELSE 0 END)
FROM master..[spt_values] pkgNo
WHERE number BETWEEN 1 AND @maxPackageForStore
UNION ALL
SELECT DISTINCT
StoreId = @storeId
--,PackageId = CAST(@packageId + number AS int)
,ItemType = 1
,ItemCount = 1
,ItemPrice = (@itemMinPrice / 2) + (10 * (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN pkgNo.number AND pkgNo.number + 2 ORDER BY NEWID()))
,MaxItemQouta = @maxPackageForStore
,MaxFullQouta = @maxPackageForStore + (SELECT TOP 1 n = number FROM master..[spt_values] WHERE number BETWEEN 0 AND 2 ORDER BY NEWID())
FROM master..[spt_values] pkgNo
WHERE number BETWEEN 2 AND (CASE WHEN @maxPackageForStore > 4 THEN 4 ELSE @maxPackageForStore END)
--set @packageId = @packageId + @maxPackageForStore;
END
set @storeId =@storeId + 1;
END
SELECT * FROM storePackages
--drop table #storePackages
没有PackageIds Simply StoreId和最低的CombinedCost - 约200-300MS,具体取决于数据 接下来如果您不关心包含哪些包,并且您只需要每个商店一行,则可以执行以下操作:
IF OBJECT_ID('tempdb..#TestResults') IS NOT NULL
BEGIN
DROP TABLE #TestResults
END
DECLARE @MinItemCount INT = 5
;WITH cteMaxCostToConsider AS (
SELECT
StoreId
,CASE
WHEN (SUM(ItemCount) >= @MinItemCount) AND
SUM(ItemPrice) < MIN(((@MinItemCount / ItemCount) + IIF((@MinItemCount % ItemCount) > 0, 1,0)) * ItemPrice) THEN SUM(ItemPrice)
ELSE MIN(((@MinItemCount / ItemCount) + IIF((@MinItemCount % ItemCount) > 0, 1,0)) * ItemPrice)
END AS MaxCostToConsider
FROM
storePackages
GROUP BY
StoreId
)
, cteRecursive AS (
SELECT
StoreId
,ItemCount AS CombinedItemCount
,CAST(ItemPrice AS decimal(18,8)) AS CombinedCost
FROM
storePackages
UNION ALL
SELECT
r.StoreId
,r.CombinedItemCount + t.ItemCount
,CAST(r.CombinedCost + t.ItemPrice AS decimal(18,8))
FROM
cteRecursive r
INNER JOIN storePackages t
ON r.StoreId = t.StoreId
INNER JOIN cteMaxCostToConsider m
ON r.StoreId = m.StoreId
AND r.CombinedCost + t.ItemPrice <= m.MaxCostToConsider
)
SELECT
StoreId
,MIN(CombinedCost) as CombinedCost
INTO #TestResults
FROM
cteRecursive
WHERE
CombinedItemCount >= @MinItemCount
GROUP BY
StoreId
SELECT *
FROM
#TestResults
WITH PackageIds每个StoreId只有1个记录 - 根据测试数据/组合的不同而变化,考虑~600-1300MS 或者,如果你仍然需要包ID,但你不关心你选择哪种组合而你只需要1条记录,那么你可以这样做:
IF OBJECT_ID('tempdb..#TestResults') IS NOT NULL
BEGIN
DROP TABLE #TestResults
END
DECLARE @MinItemCount INT = 5
;WITH cteMaxCostToConsider AS (
SELECT
StoreId
,CASE
WHEN (SUM(ItemCount) >= @MinItemCount) AND
SUM(ItemPrice) < MIN(((@MinItemCount / ItemCount) + IIF((@MinItemCount % ItemCount) > 0, 1,0)) * ItemPrice) THEN SUM(ItemPrice)
ELSE MIN(((@MinItemCount / ItemCount) + IIF((@MinItemCount % ItemCount) > 0, 1,0)) * ItemPrice)
END AS MaxCostToConsider
FROM
storePackages
GROUP BY
StoreId
)
, cteRecursive AS (
SELECT
StoreId
,CAST(PackageId AS VARCHAR(MAX)) AS PackageIds
,ItemCount AS CombinedItemCount
,CAST(ItemPrice AS decimal(18,8)) AS CombinedCost
FROM
storePackages
UNION ALL
SELECT
r.StoreId
,r.PackageIds + ',' + CAST(t.PackageId AS VARCHAR(MAX))
,r.CombinedItemCount + t.ItemCount
,CAST(r.CombinedCost + t.ItemPrice AS decimal(18,8))
FROM
cteRecursive r
INNER JOIN storePackages t
ON r.StoreId = t.StoreId
INNER JOIN cteMaxCostToConsider m
ON r.StoreId = m.StoreId
AND r.CombinedCost + t.ItemPrice <= m.MaxCostToConsider
)
, cteCombinedCostRowNum AS (
SELECT
StoreId
,PackageIds
,CombinedCost
,CombinedItemCount
,ROW_NUMBER() OVER (PARTITION BY StoreId ORDER BY CombinedCost) AS RowNumber
FROM
cteRecursive
WHERE
CombinedItemCount >= @MinItemCount
)
SELECT DISTINCT
c.StoreId
,c.PackageIds
,c.CombinedItemCount
,c.CombinedCost
INTO #TestResults
FROM
cteCombinedCostRowNum c
WHERE
RowNumber = 1
SELECT *
FROM
#TestResults
请注意,所有基准测试均在4年前的笔记本电脑Intel i7-3520M CPU 2.9 GHz和8 GB RAM以及SAMSUNG 500 GB EVO SSD上完成。因此,如果您在适当资源的服务器上运行它,我会期望指数更快。毫无疑问,在storePackages上添加索引也可以加快答案。
答案 3 :(得分:0)
首先,我们应该找到所有组合,然后选择价格最低的一个组合
DECLARE @Table as TABLE (StoreId INT, PackageId INT, Item INT, Cost INT)
INSERT INTO @Table VALUES (1,1,1,50),(1,2,2,60),(1,3,3,80),(1,4,4,100)
DECLARE @MinItemCount INT = 5;
WITH cteCombinationTable AS (
SELECT cast(PackageId as NVARCHAR(4000)) as Package, Item, Cost
FROM @Table
UNION ALL
SELECT CONCAT(o.Package,',',c.PackageId), c.Item + o.Item, c.Cost + o.Cost FROM @Table as c join cteCombinationTable as o on CONCAT(o.Package,',',c.PackageId) <> Package
where o.Item < @MinItemCount
)
select top 1 *
from cteCombinationTable
where item >= @MinItemCount
order by cast(cost as decimal)/@MinItemCount
答案 4 :(得分:-1)
首先,我感谢所有试图帮助我的人。但是,所有建议的解决方案都基于CTE。正如我之前所说的那样,递归CTE会在考虑商店的情况时导致性能问题。一次还请求多个包。这意味着,一个请求可以包括多个篮子。一个是5个项目,另外一个是3个项目,另一个是7个项目......
首先,我按项目大小在表格中生成所有可能的场景......通过这种方式,我可以选择消除不需要的场景。
CREATE TABLE ItemScenarios(
Item int,
ScenarioId int,
CalculatedItem int --this will be joined with Store Item
)
然后我生成了从2项到25项的所有可能场景并插入ItemScenarios
表。通过使用WHILE或递归CTE可以一次生成场景。这种方式的优点是,场景只生成一次。
Item | ScenarioId | CalculatedItem
--------------------------------------------------------
2 1 2
2 2 3
2 3 1
2 3 1
3 4 5
3 5 4
3 6 3
3 7 2
3 7 2
3 8 2
3 8 1
3 9 1
3 9 1
3 9 1
....
.....
......
25 993 10
通过这种方式,我可以限制场景大小,Max不同的商店,最大不同的包等。
此外,我可以摒弃一些与其他人相比最不可能的情况。例如,对于4个项目请求,某些情况
情景1:2 + 2
场景2:2 + 1 + 1
场景3:1 + 1 + 1 + 1
在这些情景中;场景2不可能是最便宜的篮子。因为,
如果场景2 &lt; 场景3 - &gt; 场景1 会低于场景 2.因为降低成本的物品是2件物品价格而**场景1 *有两件物品
如果场景2 &lt; 场景1 - &gt; 场景3 会低于场景 2
现在,如果我删除场景2 等场景,我会获得一些性能优势。
现在我可以在商店中选择最便宜的商品价格
DECLARE @requestedItems int;
SET @requestedItems = 5;
CREATE TABLE #JoinedPackageItemWithScenarios(
StoreId int not null,
PackageId int not null,
ItemCount int not null,
ItemPrice decimal(18,8)
ScenarioId int not null,
)
INSERT INTO #JoinedPackageItemWithScenarios
SELECT
SPM.StoreId
,SPM.PackageId
,SPM.ItemCount
,SPM.ItemPrice
,SPM.ScenarioId
FROM (
SELECT
SP.StoreId
,SP.PackageId
,SP.ItemCount
,SP.ItemPrice
,SC.ScenarioId
,RowNumber = ROW_NUMBER() OVER (PARTITION BY SP.StoreId,SC.ScenarioId,SP.ItemCount ORDER BY SP.ItemPrice)
FROM ItemScenarios SC
LEFT JOIN StorePackages AS SP ON SP.ItemCount = SC.CalculatedItem
WHERE SC.Item = @requestedItems
) SPM
WHERE SPM.RowNumber = 1
-- NOW I HAVE CHEAPEST PRICE FOR EACH ITEM, I CAN CREATE BASKET
CREATE TABLE #selectedScenarios(
StoreId int not null,
ScenarioId int not null,
TotalItem int not null,
TotalCost decimal(18,8)
)
INSERT INTO #selectedScenarios
SELECT
StoreId
,ScenarioId
,TotalItem
,TotalCost
FROM (
SELECT
StoreId
,ScenarioId
--,PackageIds = dbo.GROUP_CONCAT(CAST(PackageId AS nvarchar(20))) -- CONCATENING PackageId decreasing performance here. We can joing seleceted scenarios with #JoinedPackageItemWithScenarios after selection complated.
,TotalItem = SUM(ItemCount)
,TotalCost = SUM(ItemPrice)
,RowNumber = ROW_NUMBER() OVER (PARTITION BY StoreId ORDER BY SUM(ItemPrice))
FROM #JoinedPackageItemWithScenarios JPS
GROUP BY StoreId,ScenarioId
HAVING(SUM(ItemCount) >= @requestedItems)
) SLECTED
WHERE RowNumber = 1
-- NOW WE CAN POPULATE PackageIds if needed
SELECT
SS.StoreId
,SS.ScenarioId
,TotalItem = MAX(SS.TotalItem)
,TotalCost = MAX(SS.TotalCost)
,PackageIds = dbo.GROUP_CONCAT(CAST(JPS.PackageId AS nvarchar(20)))
FROM #selectedScenarios SS
JOIN #JoinedPackageItemWithScenarios AS JPS ON JPS.StoreId = SS.StoreId AND JPS.ScenarioId = SS.ScenarioId
GROUP BY SS.StoreId,SS.ScenarioId
在我的测试中,这种方式比递归CTE快10倍,特别是当商店和请求项目数量增加时。它也获得100%正确的结果。因为当商店数量和要求的商品数量增加时,递归CTE尝试了数百万个不需要的JOIN。