【问题标题】:SQL SELECT TOP <dynamic> BASED ON CATEGORY/PERCENTAGESQL SELECT TOP <dynamic> 基于类别/百分比
【发布时间】:2016-07-07 13:10:32
【问题描述】:

这是我拥有的示例数据:

在 11 行中,我需要从 CAT-1 中选择前 60% 的行,从 CAT-2 中选择 30% 和从 CAT-3 中选择 10%。有人可以帮我建立一个 SQL 吗?目标是 SQL 2014 数据库。

【问题讨论】:

  • 工会怎么样?
  • 请不要将您的数据发布为图片。这使得设置测试场景变得非常困难!
  • 如果您发布的数据是您的真实表格,则存在巨大的设计缺陷:您必须创建一个“类别”表格并通过外键与您的客户一起使用它...

标签: sql-server tsql select sql-server-2014 dynamic-sql


【解决方案1】:

我还没有测试过查询,但你应该可以使用UNION ALL

  SELECT TOP(60) PERCENT *
  FROM Table1 
  WHERE Category = 'CAT-1'
UNION ALL
  SELECT TOP(30) PERCENT *
  FROM Table1 
  WHERE Category = 'CAT-2'
UNION ALL
  SELECT TOP(10) PERCENT *
  FROM Table1 
  WHERE Category = 'CAT-3'

显然,您必须定义一些 ORDER BY 标准,否则前 60% 将是任意结果。

【讨论】:

  • 类别将再次动态化。所以我担心如果我可以硬编码类别及其 %
  • @user2980765,如果一切都是动态的,您将需要全新的 SQL Server 2022 魔术版 :-) 老实说:一个可怜的小查询应该如何知道您需要什么?
  • @Shnugo,开个玩笑,明天我会抽出时间来开怀大笑。我是一个非常基本的 sql 开发人员,我想知道是否有我遗漏的方法。目前,我正在使用这些动态类别进行 CURSOR,然后选择数据。我希望检查出来没有任何问题。
  • @user2980765,稍等片刻,我将提供动态 SQL 的建议
【解决方案2】:

这是一种使用动态 SQL 的方法。首先,我为客户和类别创建单独的表。然后生成一条 SQL 命令。看看吧:

CREATE TABLE #Cat(CatID INT IDENTITY PRIMARY KEY,Category VARCHAR(100),Percentage INT);
INSERT INTO #Cat(Category,Percentage) VALUES('CAT-1',60),('CAT-2',30),('CAT-3',10);

CREATE TABLE #Cust(CustID INT IDENTITY PRIMARY KEY
                  ,Customer VARCHAR(100)
                  ,CatID INT FOREIGN KEY REFERENCES #Cat(CatID));
INSERT INTO #Cust(Customer,CatID) VALUES
 ('A',1),('B',1),('C',1),('D',1),('E',1),('F',2),('G',2),('H',2),('I',3),('J',3),('K',1);

DECLARE @cmd VARCHAR(MAX)=
(
    SELECT STUFF
    (
        (
            SELECT 'UNION ALL SELECT TOP(' +  CAST(c.Percentage AS VARCHAR(10)) + ') PERCENT * FROM #Cust WHERE CatID=' + CAST(c.CatID AS  VARCHAR(10)) + ' '
            FROM #Cat AS c
            FOR XML PATH('')
        ),1,10,''
    )
);


SET @cmd='SELECT tbl.CustID,tbl.Customer,tbl.CatID,c.Category,c.Percentage FROM(' +  @cmd + ') AS tbl INNER JOIN #Cat AS c ON c.CatID=tbl.CatID';

--This is the generated query
SELECT @cmd;

--And this is its execution
EXEC (@cmd);

DROP TABLE #Cust;
DROP TABLE #Cat;

结果:

1   A   1   CAT-1   60
2   B   1   CAT-1   60
3   C   1   CAT-1   60
4   D   1   CAT-1   60
6   F   2   CAT-2   30
9   I   3   CAT-3   10

【讨论】:

    【解决方案3】:

    虽然你可以做到

    DECLARE @N INT = 20
    SELECT TOP (@n) PERCENT * FROM BLAH
    

    我无法为您数据中的每个组设置@NCROSS APPLY 任何人?)。

    所以这是一个使用两个 CTE 的解决方案。这可能远非最佳:)

    测试数据

    SELECT *
    INTO #Test
    FROM (VALUES
    (1, 'A', 'CAT-1', 60),
    (2, 'B', 'CAT-1', 60),
    (3, 'C', 'CAT-1', 60),
    (4, 'D', 'CAT-1', 60),
    (5, 'E', 'CAT-1', 60),
    (6, 'F', 'CAT-2', 30),
    (7, 'G', 'CAT-2', 30),
    (8, 'H', 'CAT-2', 30),
    (9, 'I', 'CAT-3', 10),
    (10, 'J', 'CAT-3', 10),
    (11, 'K', 'CAT-1', 60)
    ) A (RowID, Customer, Category, Percentage)
    

    解决方案

    在这里,我对第一个 CTE 中的每个组进行排名和计数,然后在第二个 CTE 中设置“百分比括号范围”(这是为了捕获例如前 10% 的查询,其中只有两行括号是50% 和 100%)。

    ;WITH Ranked AS (
        SELECT *,
            RANK() OVER (PARTITION BY Category ORDER BY RowId)  * 100 RANK,
            COUNT(*) OVER (PARTITION BY Category ) COUNT
        FROM #Test),
    Grouped AS (
        SELECT *, 
        COALESCE(LAG(RANK) OVER (PARTITION BY Category order BY Rank) / COUNT, 0) BracketStart,
        RANK / COUNT BracketEnd
        FROM Ranked
    )
    SELECT 
        G.RowID
        ,G.Customer
        ,G.Category
    FROM Grouped G
    WHERE G.BracketEnd <= G.Percentage OR G.Percentage BETWEEN G.BracketStart AND G.BracketEnd
    ORDER BY G.Category
    
    RowID       Customer Category
    ----------- -------- --------
    1           A        CAT-1
    2           B        CAT-1
    3           C        CAT-1
    4           D        CAT-1
    6           F        CAT-2
    9           I        CAT-3
    

    【讨论】:

    • 您好,您的回答让我想到了一种新方法。从我这边为您的“交叉申请任何人”投票。我复制了您的 TestData 部分,希望您不要介意...
    【解决方案4】:

    我将此添加为新答案,因为我的第一个答案是完全不同的。用户“Les H”把我带到了这里:

    --Credits to @Les H
    SELECT *
    INTO #Test
    FROM (VALUES
    (1, 'A', 'CAT-1', 60),
    (2, 'B', 'CAT-1', 60),
    (3, 'C', 'CAT-1', 60),
    (4, 'D', 'CAT-1', 60),
    (5, 'E', 'CAT-1', 60),
    (6, 'F', 'CAT-2', 30),
    (7, 'G', 'CAT-2', 30),
    (8, 'H', 'CAT-2', 30),
    (9, 'I', 'CAT-3', 10),
    (10, 'J', 'CAT-3', 10),
    (11, 'K', 'CAT-1', 60)
    ) A (RowID, Customer, Category, Percentage)
    
    
     SELECT Percentages.*
     FROM (SELECT DISTINCT Category,Percentage FROM #Test) AS c
     CROSS APPLY(SELECT TOP (c.Percentage) PERCENT * FROM #Test WHERE #Test.Category=c.Category ORDER BY #Test.RowID) AS Percentages;
    
     DROP TABLE #Test;
    

    结果:

    1   A   CAT-1   60
    2   B   CAT-1   60
    3   C   CAT-1   60
    4   D   CAT-1   60
    6   F   CAT-2   30
    9   I   CAT-3   10
    

    【讨论】:

    • 不错!我今天早些时候在兔子洞里追了这个,最后很简单
    • @Shnugo,这正是我需要的。谢谢你的时间。看我们不需要 SQL 2022 魔术版 :)
    • @user2980765,我很高兴读到这篇文章!您是否理解我关于区分类别和客户的提示?您的数据 - 按照设计 - 在非规范化结构中......还有一个提示:我的回答似乎解决了您的问题。在这种情况下,您应该勾选接受检查以关闭此答案(并给我声誉积分(-:)。顺便说一句:我访问了您的个人资料并发现,在两年多的时间里,您提出的投票不超过 3 票。但这是 SO 运行的燃料...Please read this: someone-answers。谢谢
    • @Shnugo,标记为答案。我在这里不那么频繁,所以不知道那部分。我将负责标记答案。祝你有个好的一天!此外,这不是确切的表/数据。我有不同的表格/数据,这些表格/数据已正确规范化。
    猜你喜欢
    • 1970-01-01
    • 2014-02-17
    • 2022-11-04
    • 2021-03-22
    • 2019-06-03
    • 2020-05-21
    • 1970-01-01
    • 2013-12-24
    • 2020-12-09
    相关资源
    最近更新 更多