我在Postgres有一张名为tasks
的桌子。它记录了机械土耳其式的任务。它包含以下列:
entity_name, text (the thing being reviewed)
reviewer_email, text (the email address of the person doing the reviewing)
result, boolean (the entry provided by the reviewer)
需要审核的每个实体都会生成两个任务行,每个行分配给不同的审阅者。当两个评论者都不同意时(例如他们的result
的值不相等),应用程序将启动第三个任务,分配给主持人。主持人始终拥有相同的电子邮件域。
我试图获取每次审核人员的计数,审核人员已经被主持人推翻,或者得到主持人的肯定。我认为我相当接近,但最后一点证明是棘手的:
SELECT
reviewer_email,
COUNT(*) FILTER(
WHERE entity_name IN (
SELECT entity_name
FROM tasks
GROUP BY entity_name
HAVING
COUNT(*) FILTER (WHERE result IS NOT NULL) = 3 -- find the entities that have exactly three reviews
AND
-- this is the tricky part:
-- need something like:
-- WHERE current_review.result = moderator_review.result
)
) AS overruled_count
FROM
tasks
WHERE
result IS NOT NULL
GROUP BY
reviewer_email
HAVING
reviewer_email NOT LIKE '%@moderators-domain.net'
示例数据:
id | entity_name | reviewer_email | result
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
期望的结果:
reviewer_email | overruled_count | affirmed_count
bob@email.net | 1 | 1
alice@email.net | 1 | 1
Bob和Alice各自做了三次评论。在一次审查中,他们同意,因此没有适度。他们对其他两次评论持不同意见并被推翻一次,并由主持人确认一次。
我相信上面的代码让我走在正确的轨道上,但我对其他方法肯定感兴趣。
答案 0 :(得分:5)
我认为这是一个比你意识到的更难的问题。以下内容将主持人审核附加到每个非主持人审核:
select t.*, tm.result as moderator_result
from tasks t join
tasks tm
on t.entity_name = tm.entity_name
where t.reviewer_email NOT LIKE '%@moderators-domain.net' and
tm.reviewer_email LIKE '%@moderators-domain.net';
由此,我们可以汇总您想要的结果:
select reviewer_email,
sum( (result = moderator_result)::int ) as moderator_agrees,
sum( (result <> moderator_result)::int ) as moderator_disagrees
from (select t.*, tm.result as moderator_result
from tasks t join
tasks tm
on t.entity_name = tm.entity_name
where t.reviewer_email NOT LIKE '%@moderators-domain.net' and
tm.reviewer_email LIKE '%@moderators-domain.net'
) t
group by reviewer_email;
使用filter
甚至窗口函数可能有办法实现此目的。这种方法对我来说似乎最自然。
我应该注意,子查询当然不是必需的:
select t.reviewer_email,
sum( (t.result = tm.result)::int ) as moderator_agrees,
sum( (t.result <> tm.result)::int ) as moderator_disagrees
from tasks t join
tasks tm
on t.entity_name = tm.entity_name
where t.reviewer_email NOT LIKE '%@moderators-domain.net' and
tm.reviewer_email LIKE '%@moderators-domain.net'
group by t.reviewer_email;
答案 1 :(得分:2)
在我看来,只需添加一些更改就可以让查询更容易理解。 我猜我们还需要考虑我们的用户从未被肯定或否决的情况(因此他们的计数将为0)
SELECT
tasks.reviewer_email,
COUNT(*) FILTER (WHERE tasks.result = modtasks.result) AS affirmed_count,
COUNT(*) FILTER (WHERE tasks.result <> modtasks.result) AS overruled_count
FROM tasks
LEFT JOIN tasks modtasks
ON modtasks.entity_name = tasks.entity_name
AND modtasks.reviewer_email LIKE '%@moderators-domain.net'
WHERE tasks.reviewer_email NOT LIKE '%@moderators-domain.net'
GROUP BY tasks.reviewer_email
答案 2 :(得分:2)
示例数据
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')
;
<强> Query 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
都会产生错误,因此这些行不会导致任何计数。
最终HAVING reviewer_email NOT LIKE '%@moderators-domain.net'
会自行删除主持人结果的计数。
同样,你不必在这里使用CTE,我主要用它来提高可读性。我建议先运行CTE并检查中间结果,以了解查询的工作原理。
<强> Query 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'