SQL Server - 按固定值

时间:2016-09-19 21:39:17

标签: sql sql-server group-by cube rollup

enter image description here

我必须制作最便宜的篮子,其中包含固定物品。

例如,对于具有(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倍。

同时连接ItemPackageId非常昂贵。在选择加入源表的最便宜的包后,我可以找到所需的包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可以一次生成场景。这种方式的优点是,场景只生成一次。

Resuls如下所示。

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

SUM

在我的测试中,这种方式比递归CTE快10倍,特别是当商店和请求项目数量增加时。它也获得100%正确的结果。因为当商店数量和要求的商品数量增加时,递归CTE尝试了数百万个不需要的JOIN。

5 个答案:

答案 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)

  • (1,5,2,60)
  • (1,6,1,1),(1,7,1,1)
  • (1,8,50,1)

已编辑。以上内容将为您提供组合成本最低的商店的每个组合。您注意到的错误归因于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可以一次生成场景。这种方式的优点是,场景只生成一次。

Resuls如下所示。

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

SUM

在我的测试中,这种方式比递归CTE快10倍,特别是当商店和请求项目数量增加时。它也获得100%正确的结果。因为当商店数量和要求的商品数量增加时,递归CTE尝试了数百万个不需要的JOIN。