SQL Server: вопрос группировки, который меня раздражает - PullRequest
4 голосов
/ 15 июня 2010

Я работаю с 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 месяце я дал Алисе те же 25%, но Барбара получила 50%, а Клэр получила 25%.

То, что я хочу знать, это все отличные дистрибутивы, которые я когда-либо давал. В этом случае их будет два - один для месяцев 1 и 3 и один для месяца 2.

Я ожидаю, что результаты будут выглядеть примерно так (ПРИМЕЧАНИЕ: идентификатор, или секвенсор, или что-то еще, не имеет значения)

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

Спасибо вам всем! -Ricky

Ответы [ 5 ]

4 голосов
/ 15 июня 2010

Это дает вам ответ в несколько ином формате, чем вы запрашивали:

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%               
3 голосов
/ 15 июня 2010

То, что вы хотите, чтобы распределение каждого месяца действовало как подпись или шаблон значений, которые вы затем хотели бы найти в другие месяцы.Что не ясно, так это то, важен ли сотрудник, к которому относится стоимость, как разбивка процентов.Например, будет ли Алиса = 65%, Барбара = 25%, Клэр = 10% такой же, как месяц 3 в вашем примере?В моем примере я предположил, что это не будет то же самое.Подобно решению Мартина Смита, я нахожу подписи, умножая каждый процент на 10. Это предполагает, что все процентные значения меньше единицы.Например, если у кого-то может быть процент 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
2 голосов
/ 15 июня 2010

Я просто собрал это решение во время написания этого вопроса, который похоже на работу

Я не думаю, что это работает. Здесь я добавил еще две группы (месяц = ​​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;
2 голосов
/ 15 июня 2010

Я предполагаю, что производительность не будет большой (причина подзапроса)

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 выполняет самостоятельное объединение для определения совпадающих комбинаций сотрудников и процентов (кроме тех, что указаны в одном месяце). Символ> в СОЕДИНЕНИИ гарантирует, что будет взят только один набор совпадений, т. Е. Если запись Месяц1 = запись Месяц3, мы получим только комбинацию записей Месяц3-Месяц1 вместо месяца1-Месяц3, Месяца3-Месяц1 и Месяц3-Месяц3.
  2. Затем мы сгруппируем по COUNT соответствующих записей для каждой комбинации месяц-месяц
  3. Тогда HAVING исключает месяцы, в которых совпадений не так много, как записей за месяц
  4. Внешний SELECT получает все записи, кроме возвращаемых внутренним запросом (с полным набором совпадений)
2 голосов
/ 15 июня 2010

Если я вас правильно понял, то для общего решения, я думаю, вам нужно объединить всю группу вместе - например, произвести 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)
...