【问题标题】:SQL Server: A Grouping question that's annoying meSQL Server:一个让我烦恼的分组问题
【发布时间】:2010-06-14 22:22:05
【问题描述】:

十年来,我一直在使用 SQL Server,但这种分组(或分区,或排名......我不确定答案是什么!)让我感到困惑。感觉它也应该是一件容易的事。我将概括我的问题:

假设我有 3 名员工(不用担心他们会辞职或其他任何事情......总是有 3 名),并且我按月分配他们的薪水。

Month   Employee  PercentOfTotal
--------------------------------
1       Alice     25%
1       Barbara   65%
1       Claire    10%

2       Alice     25%
2       Barbara   50%
2       Claire    25%

3       Alice     25%
3       Barbara   65%
3       Claire    10%

如您所见,我在第 1 个月和第 3 个月支付了相同的百分比,但在第 2 个月,我给了 Alice 相同的 25%,但 Barbara 得到了 50%,而 Claire 得到了 25%。

我想知道的是我曾经给出的所有不同的分布。在这种情况下,将有两个 - 一个用于第 1 个月和第 3 个月,一个用于第 2 个月。

我希望结果看起来像这样(注意:ID、排序器或其他任何东西都无关紧要)

ID      Employee  PercentOfTotal
--------------------------------
X       Alice     25%
X       Barbara   65%
X       Claire    10%

Y       Alice     25%
Y       Barbara   50%
Y       Claire    25%

看起来很简单,对吧?我难住了!任何人都有一个优雅的解决方案?我只是在写这个问题时把这个解决方案放在一起,这似乎可行,但我想知道是否有更好的方法。或者也许是我从中学到一些东西的不同方式。

WITH temp_ids (Month)
AS
(
  SELECT DISTINCT MIN(Month)
    FROM employees_paid
  GROUP BY PercentOfTotal
)
SELECT EMP.Month, EMP.Employee, EMP.PercentOfTotal
  FROM employees_paid EMP
         JOIN temp_ids IDS ON EMP.Month = IDS.Month
GROUP BY EMP.Month, EMP.Employee, EMP.PercentOfTotal

谢谢大家! -瑞奇

【问题讨论】:

    标签: sql sql-server database-partitioning


    【解决方案1】:

    这会给你一个与你要求的格式略有不同的答案:

    SELECT DISTINCT
        T1.PercentOfTotal AS Alice,
        T2.PercentOfTotal AS Barbara,
        T3.PercentOfTotal AS Claire
    FROM employees_paid T1
    JOIN employees_paid T2
      ON T1.Month = T2.Month AND T1.Employee = 'Alice' AND T2.Employee = 'Barbara'
    JOIN employees_paid T3
      ON T2.Month = T3.Month AND T3.Employee = 'Claire'
    

    结果:

    Alice   Barbara  Claire
    25%     50%      25%
    25%     65%      10%
    

    如果您愿意,可以使用UNPIVOT 将此结果集转换为您要求的形式。

    SELECT rn AS ID, Employee, PercentOfTotal
    FROM (
        SELECT *, ROW_NUMBER() OVER (ORDER BY Alice) AS rn
        FROM (
            SELECT DISTINCT
                T1.PercentOfTotal AS Alice,
                T2.PercentOfTotal AS Barbara,
                T3.PercentOfTotal AS Claire
            FROM employees_paid T1
            JOIN employees_paid T2 ON T1.Month = T2.Month AND T1.Employee = 'Alice'
                                                          AND T2.Employee = 'Barbara'
            JOIN employees_paid T3 ON T2.Month = T3.Month AND T3.Employee = 'Claire'
        ) T1
    ) p UNPIVOT (PercentOfTotal FOR Employee IN (Alice, Barbara, Claire)) AS unpvt
    

    结果:

    ID  Employee  PercentOfTotal  
    1   Alice     25%
    1   Barbara   50%      
    1   Claire    25%             
    2   Alice     25%             
    2   Barbara   65%              
    2   Claire    10%               
    

    【讨论】:

    • 感谢 UNPIVOT 的建议——我以前没有使用过。
    【解决方案2】:

    您希望每个月的分布充当您希望在其他月份找到的值的签名或模式。尚不清楚的是,获得价值的员工是否与百分比分解一样重要。例如,Alice=65%、Barbara=25%、Claire=10% 是否与您示例中的第 3 个月相同?在我的示例中,我假设它不会相同。与 Martin Smith 的解决方案类似,我通过将每个百分比乘以 10 来找到签名。这假定所有百分比值都小于 1。例如,如果某人可能有 110% 的百分比,这会给这个解决方案带来问题。

    With Employees As
        (
        Select 1 As Month, 'Alice' As Employee, .25 As PercentOfTotal
        Union All Select 1, 'Barbara', .65
        Union All Select 1, 'Claire', .10
        Union All Select 2, 'Alice', .25
        Union All Select 2, 'Barbara', .50
        Union All Select 2, 'Claire', .25
        Union All Select 3, 'Alice', .25
        Union All Select 3, 'Barbara', .65
        Union All Select 3, 'Claire', .10
        )
        , EmployeeRanks As
        (
        Select Month, Employee, PercentOfTotal
            , Row_Number() Over ( Partition By Month Order By Employee, PercentOfTotal ) As ItemRank
        From Employees
        )
        , Signatures As
        (
        Select Month
            , Sum( PercentOfTotal * Cast( Power( 10, ItemRank ) As bigint) ) As SignatureValue
        From EmployeeRanks
        Group By Month
        )
        , DistinctSignatures As
        (
        Select Min(Month) As MinMonth, SignatureValue
        From Signatures
        Group By SignatureValue
        )
    Select E.Month, E.Employee, E.PercentOfTotal
    From Employees As E
        Join DistinctSignatures As D
            On D.MinMonth = E.Month
    

    【讨论】:

    • 非常感谢 - 我认为这个在所有答案的最一般意义上都有效。就我而言,第 1 个月和第 3 个月是相同的。最后,我不需要知道每个分布来自哪个月份,只需知道有 2 个不同的分布,以及这些分布是什么。
    【解决方案3】:

    我假设性能不会很好(子查询的原因)

    SELECT * FROM employees_paid where Month not in (
         SELECT
              a.Month
         FROM
              employees_paid a
              INNER JOIN employees_paid b ON 
                   (a.employee = B.employee AND 
                   a.PercentOfTotal = b.PercentOfTotal AND 
                   a.Month > b.Month)
         GROUP BY
              a.Month,
              b.Month
         HAVING
              Count(*) = (SELECT COUNT(*) FROM employees_paid c 
                   where c.Month = a.Month)
         )
    
    1. 内部 SELECT 执行自联接以识别匹配的员工和百分比组合(同月除外)。 JOIN 中的 > 确保只获取一组匹配项,即如果 Month1 条目 = Month3 条目,我们只会得到 Month3-Month1 条目组合,而不是 Month1-Month3、Month3-Month1 和 Month3-Month3。
    2. 然后我们对每个月-月组合的匹配条目 COUNT 进行 GROUP
    3. 然后 HAVING 会排除匹配项少于月份条目数的月份
    4. 外部 SELECT 获取除内部查询返回的条目(具有完整匹配的条目)之外的所有条目

    【讨论】:

    • 嘿,谢谢——优雅,一般意义上的作品,很好的解释。性能对我来说不是那么重要,因为它是一次性数据转换脚本,而不是生产级代码。
    【解决方案4】:

    如果我对您的理解正确,那么对于一般解决方案,我认为您需要将整个组连接在一起 - 例如产生Alice:0.25, Barbara:0.50, Claire:0.25。然后选择不同的组,这样就可以执行以下操作(相当笨拙)。

    WITH EmpSalaries
    AS
    (
    
    SELECT 1 AS Month, 'Alice' AS Employee, 0.25 AS PercentOfTotal UNION ALL
    SELECT 1 AS Month, 'Barbara' AS Employee, 0.65 UNION ALL
    SELECT 1 AS Month, 'Claire' AS Employee, 0.10 UNION ALL
    
    SELECT 2 AS Month, 'Alice' AS Employee, 0.25 UNION ALL
    SELECT 2 AS Month, 'Barbara' AS Employee, 0.50 UNION ALL
    SELECT 2 AS Month, 'Claire' AS Employee, 0.25 UNION ALL
    
    SELECT 3 AS Month,  'Alice' AS Employee, 0.25 UNION ALL
    SELECT 3 AS Month,  'Barbara' AS Employee, 0.65 UNION ALL
    SELECT 3 AS Month,  'Claire' AS Employee, 0.10 
    ),
    Months AS 
    (
    SELECT DISTINCT Month FROM EmpSalaries
    ),
    MonthlySummary AS
    (
    SELECT Month,
    Stuff(
                (
                Select ', ' + S1.Employee + ':' + cast(PercentOfTotal as varchar(20))
                From EmpSalaries As S1
                Where S1.Month = Months.Month
                Order By S1.Employee
                For Xml Path('')
                ), 1, 2, '') As Summary
    FROM Months
    )
    SELECT * FROM EmpSalaries
    WHERE Month IN (SELECT MIN(Month)
                    FROM MonthlySummary
                    GROUP BY Summary)
    

    【讨论】:

    • 正确——这类似于我的客户目前如何在他们的系统中提取这些数字,然后解析字符串。我正在将他们的旧数据转移到我们的新系统中,这会使其正常化,从而消除需要。我认为可能有一个返回表值的“简单”解决方案——看起来它不像我想象的那么常见!
    【解决方案5】:

    我只是把这个解决方案放在一起 在写这个问题时, 似乎有效

    我认为它不起作用。在这里,我添加了另外两组(分别为月 = 4 和 5),我认为它们是不同的,但结果是相同的,即仅月 = 1 和 2:

    WITH employees_paid (Month, Employee, PercentOfTotal)
    AS 
    (
     SELECT 1, 'Alice', 0.25
     UNION ALL
     SELECT 1, 'Barbara', 0.65
     UNION ALL
     SELECT 1, 'Claire', 0.1
     UNION ALL
     SELECT 2, 'Alice', 0.25
     UNION ALL
     SELECT 2, 'Barbara', 0.5
     UNION ALL
     SELECT 2, 'Claire', 0.25
     UNION ALL
     SELECT 3, 'Alice', 0.25
     UNION ALL
     SELECT 3, 'Barbara', 0.65
     UNION ALL
     SELECT 3, 'Claire', 0.1
     UNION ALL
     SELECT 4, 'Barbara', 0.25
     UNION ALL
     SELECT 4, 'Claire', 0.65
     UNION ALL
     SELECT 4, 'Alice', 0.1
     UNION ALL
     SELECT 5, 'Diana', 0.25
     UNION ALL
     SELECT 5, 'Emma', 0.65
     UNION ALL
     SELECT 5, 'Fiona', 0.1
    ), 
    temp_ids (Month)
    AS
    (
     SELECT DISTINCT MIN(Month)
       FROM employees_paid
      GROUP 
         BY PercentOfTotal
    )
    SELECT EMP.Month, EMP.Employee, EMP.PercentOfTotal
      FROM employees_paid AS EMP
           INNER JOIN temp_ids AS IDS 
              ON EMP.Month = IDS.Month
     GROUP 
        BY EMP.Month, EMP.Employee, EMP.PercentOfTotal;
    

    【讨论】:

    • 好点——但是,在我的例子中,总是有固定数量的员工。每个分布将有相同的 3 名员工,不多,不少,也没有不同。基于这个假设,我可以走捷径,但你在一般意义上是正确的——当引入新员工时,它就行不通了。
    • 好吧,如果您的解决方案适合您,那么它在我看来就是最好的解决方案;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-20
    • 1970-01-01
    • 2011-11-09
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    相关资源
    最近更新 更多