SQL Присоединение к серверу по результату агрегатной функции - PullRequest
0 голосов
/ 07 февраля 2020

SQL Server 2017. У меня есть 2 таблицы с именем и идентификатором хакера и еще одной проблемой кодирования, представленной каждой (ниже). Мне нужно вывести идентификатор, имя и количество вызовов, отфильтровывая тех хакеров, которые представили одинаковое количество вызовов, за исключением случаев, когда это число является максимальным.
Вот пример данных и окончательный результат, который мне нужен

Хакеры:

hacker_id name
1 john
2 tom
3 anna
4 mary
5 steve

Задачи:

challenge_id    hacker_id
1   1
2   1
3   1
4   2
5   2
6   2
7   2
8   3
9   3
10  3
11  4
12  4
13  4
14  4
15  5
16  5

это количество вызовов на человека (отсюда мы видим, что максимальное количество составляет 4 на человека):

hacker_id   name    count of challenges
1   john    3
2   tom     4
3   anna    3
4   mary    4
5   steve   2

Окончательный результат будет следующим:

hacker_id   name    count of challenges
2   tom     4
4   mary    4
5   steve   2

т.е. Том и Мэри оба подали 4 вызова. Они включены, потому что, хотя число 4 повторяется, это максимум, что Джон и Анна представили по 3. Они исключены, потому что 3 не максимум на человека. Стив отправил 2, и этот номер уникален, поэтому он тоже включен.

Вот мой код:

SELECT h.hacker_id, 
h.name, 
COUNT(c.challenge_id) AS ChalCountPerHead
FROM    hackers h 
        JOIN challenges c ON h.hacker_id = c.hacker_id
        LEFT JOIN ( 
            SELECT d.FreqHacker, COUNT(d.FreqHacker) as FreqOfFreq FROM 
                (SELECT hacker_id, COUNT(challenge_id) AS FreqHacker 
                 FROM Challenges GROUP BY hacker_id) d
            GROUP BY d.FreqHacker
        ) dd
        ON FreqHacker = COUNT(c.challenge_id)
GROUP BY h.hacker_id, h.name
HAVING 
COUNT(c.challenge_id) = (SELECT MAX(d.FreqHacker) from d) 
OR
dd.FreqOfFreq = 1

Не работает, с сообщением об ошибке в этой строке

ON FreqHacker = COUNT(c.challenge_id)

Агрегат не может появиться в предложении ON, если он не входит в подзапрос, содержащийся в предложении HAVING или списке выбора, а агрегируемый столбец является внешней ссылкой.

1 Ответ

1 голос
/ 07 февраля 2020

Вот один из способов сделать это.

Наличие примеров данных в вопросе облегчает проверку решения. Пожалуйста, включите его в следующий раз.

CTE - это простая агрегация для получения количества вызовов, поданных каждым хакером.

In CTE2 MAX дает глобальную максимальную частоту. HackerCountOfSameFreq - это число хакеров с одинаковой частотой.

Final WHERE удаляет группы хакеров, состоящие из более чем одного хакера, но покидает группу с максимальной частотой.

Пример данных

DECLARE @Hackers TABLE (hacker_id int, name varchar(50));
INSERT INTO @Hackers VALUES
(1, 'john'),
(2, 'tom'),
(3, 'anna'),
(4, 'mary'),
(5, 'steve');

DECLARE @Challenges TABLE (challenge_id int, hacker_id int);
INSERT INTO @Challenges VALUES
(1 ,  1),
(2 ,  1),
(3 ,  1),
(4 ,  2),
(5 ,  2),
(6 ,  2),
(7 ,  2),
(8 ,  3),
(9 ,  3),
(10,  3),
(11,  4),
(12,  4),
(13,  4),
(14,  4),
(15,  5),
(16,  5);

Запрос

WITH
CTE
AS
(
    SELECT hacker_id, COUNT(*) AS FreqHacker
    FROM @Challenges
    GROUP BY hacker_id
)
,CTE2
AS
(
    SELECT
        hacker_id
        ,FreqHacker
        ,COUNT(*) OVER (PARTITION BY FreqHacker) AS HackerCountOfSameFreq
        ,MAX(FreqHacker) OVER () AS GlobalMaxFreq
    FROM CTE
)

SELECT
    CTE2.hacker_id
    ,CTE2.FreqHacker
    ,H.Name
FROM
    CTE2
    INNER JOIN @Hackers AS H ON H.hacker_id = CTE2.hacker_id
WHERE
    HackerCountOfSameFreq = 1
    OR FreqHacker = GlobalMaxFreq
ORDER BY
    CTE2.hacker_id
;

Результат

+-----------+------------+-------+
| hacker_id | FreqHacker | Name  |
+-----------+------------+-------+
| 2         | 4          | tom   |
+-----------+------------+-------+
| 4         | 4          | mary  |
+-----------+------------+-------+
| 5         | 2          | steve |
+-----------+------------+-------+

Ваш запрос также дает правильный результат (по крайней мере, с этими примерами данных), как только синтаксис исправлен.

Я разделил его на CTE, оставив большую часть ваших логик c как:

WITH
d
AS
(
    SELECT hacker_id, COUNT(challenge_id) AS FreqHacker 
    FROM @Challenges 
    GROUP BY hacker_id
)
,dd
AS
(
    SELECT d.FreqHacker, COUNT(d.FreqHacker) as FreqOfFreq 
    FROM d
    GROUP BY d.FreqHacker
)
,d3
AS
(
    SELECT 
        h.hacker_id, 
        h.name, 
        COUNT(c.challenge_id) AS ChalCountPerHead
    FROM    
        @hackers h 
        JOIN @challenges c ON h.hacker_id = c.hacker_id
    GROUP BY h.hacker_id, h.name
)
,d4
AS
(
    SELECT *
    FROM 
        d3
        LEFT JOIN dd ON dd.FreqHacker = ChalCountPerHead
)
SELECT *
FROM d4
WHERE
    ChalCountPerHead = (SELECT MAX(d.FreqHacker) from d)
    OR FreqOfFreq = 1
ORDER BY hacker_id
;
...