Пример данных
CREATE TABLE tasks
("id" int, "entity_name" text, "reviewer_email" text, "result" boolean)
;
INSERT INTO tasks
("id", "entity_name", "reviewer_email", "result")
VALUES
(1, 'apple', 'bob@email.net', 'true'),
(2, 'apple', 'alice@email.net', 'false'),
(3, 'apple', 'mod@@moderators-domain.net', 'true'),
(4, 'pair', 'bob@email.net', 'true'),
(5, 'pair', 'alice@email.net', 'false'),
(6, 'pair', 'mod@@moderators-domain.net', 'false'),
(7, 'kiwi', 'bob@email.net', 'true'),
(8, 'kiwi', 'alice@email.net', 'true')
;
Запрос 1
WITH
CTE_moderated_tasks
AS
(
SELECT
id AS mod_id
,entity_name AS mod_entity_name
,result AS mod_result
FROM tasks
WHERE reviewer_email LIKE '%@moderators-domain.net'
)
SELECT
tasks.reviewer_email
,SUM(CASE WHEN tasks.result <> mod_result THEN 1 ELSE 0 END) AS overruled_count
,SUM(CASE WHEN tasks.result = mod_result THEN 1 ELSE 0 END) AS affirmed_count
FROM
CTE_moderated_tasks
INNER JOIN tasks ON
tasks.entity_name = CTE_moderated_tasks.mod_entity_name
AND tasks.id <> CTE_moderated_tasks.mod_id
GROUP BY
tasks.reviewer_email
Я разделил запрос на две части.
Сначала я хочу найти все задачи, в которых участвовал модератор (CTE_moderated_tasks
). Предполагается, что модератор не может участвовать более одного раза в одной задаче.
Этот результат внутренне объединен с исходной таблицей tasks
, таким образом, естественным образом отфильтровывая все задачи, в которых не участвовал модератор. Это также дает нам мнение модератора рядом с мнением рецензента. Это предполагает, что для выполнения одной и той же задачи есть только два рецензента.
Теперь осталось только сгруппировать рецензентов и посчитать, сколько раз совпадало мнение рецензента и модератора. Я использовал классический SUM(CASE ...)
для этого условного агрегата.
Вам не нужно использовать CTE, я использовал его в первую очередь для удобства чтения.
Я также хотел бы подчеркнуть, что этот запрос использует LIKE
только во время одного сканирования таблицы. Если на entity_name
есть индекс, объединение может быть довольно эффективным.
Результат
| reviewer_email | overruled_count | affirmed_count |
|-----------------|-----------------|----------------|
| alice@email.net | 1 | 1 |
| bob@email.net | 1 | 1 |
.
Вариант без самостоятельного соединения
Вот еще один вариант без самостоятельного объединения, который может быть более эффективным. Вам нужно проверить свои реальные данные, индексы и оборудование.
В этом запросе используется оконная функция с разбиением по entity_name
, чтобы получить результат модератора для каждой строки без явного самосоединения. Здесь вы можете использовать любую статистическую функцию (SUM
или MIN
или MAX
), потому что для каждого entity_name
.
будет не более одной строки от модератора.
Тогда простая группировка с условным агрегатом дает нам счет.
Здесь условный агрегат использует тот факт, что NULL
по сравнению с любым значением никогда не возвращает true. mod_result
для сущностей, у которых нет модератора, будут нулевые значения, а result <> mod_result
и result = mod_result
приведут к ложному значению, поэтому такие строки не влияют ни на один из них.
Final HAVING reviewer_email NOT LIKE '%@moderators-domain.net'
удаляет счетчик результатов модератора.
Опять же, вам не нужно использовать CTE здесь, и я использовал его в первую очередь для удобства чтения. Я бы рекомендовал сначала запустить только CTE и изучить промежуточные результаты, чтобы понять, как работает запрос.
Запрос 2
WITH
CTE
AS
(
SELECT
id
,entity_name
,reviewer_email
,result::int
,SUM(result::int)
FILTER (WHERE reviewer_email LIKE '%@moderators-domain.net')
OVER (PARTITION BY entity_name) AS mod_result
FROM tasks
)
SELECT
reviewer_email
,SUM(CASE WHEN result <> mod_result THEN 1 ELSE 0 END) AS overruled_count
,SUM(CASE WHEN result = mod_result THEN 1 ELSE 0 END) AS affirmed_count
FROM CTE
GROUP BY reviewer_email
HAVING reviewer_email NOT LIKE '%@moderators-domain.net'