WHERE 中不允许使用聚合函数 - 加入 PostgreSQL 表时
aggregate functions are not allowed in WHERE - when joining PostgreSQL tables
在使用 PostgreSQL 9.3.10 的游戏中,一些玩家支付了 "VIP status",这由包含日期的 vip 列表示未来:
# \d pref_users
Column | Type | Modifiers
------------+-----------------------------+--------------------
id | character varying(32) | not null
first_name | character varying(64) | not null
last_name | character varying(64) |
vip | timestamp without time zone |
玩家也可以通过将 nice 列设置为 true、false 或留在 null:
# \d pref_rep
Column | Type | Modifiers
-----------+-----------------------------+-----------------------------------------------------------
id | character varying(32) | not null
author | character varying(32) | not null
nice | boolean |
我通过发出此 SQL JOIN 语句计算 "reputation" 个 VIP 玩家:
# select u.id, u.first_name, u.last_name,
count(nullif(r.nice, false))-count(nullif(r.nice, true)) as rep
from pref_users u, pref_rep r
where u.vip>now()and u.id=r.id group by u.id order by rep asc;
id | first_name | last_name | rep
-------------------------+--------------------------------+--------------------
OK413274501330 | ali | salimov | -193
OK357353924092 | viktor | litovka | -137
DE20287 | sergej warapow |
我的问题是:
如何找到所有给其他玩家评分的负面评分玩家?
(背景是我已经为所有 VIP 玩家添加了评价他人的可能性。在此之前,只有积极评价的玩家才能评价其他人)。
我尝试了以下方法,但出现以下错误:
# select count(*) from pref_rep r, pref_users u
where r.author = u.id and u.vip > now() and
u.id in (select id from pref_rep
where (count(nullif(nice, false)) -count(nullif(nice, true))) < 0);
ERROR: aggregate functions are not allowed in WHERE
LINE 1: ...now() and u.id in (select id from pref_rep where (count(null...
^
更新:
我现在正在尝试使用临时 table -
首先,我用所有负面评价的 VIP 用户填充它,这很有效:
# create temp table my_temp as select u.id, u.first_name, u.last_name,
count(nullif(r.nice, false))-count(nullif(r.nice, true)) as rep
from pref_users u, pref_rep r
where u.vip>now() and u.id=r.id group by u.id;
SELECT 362
但是我的 SQL JOIN returns 太多相同的行,我找不到那里缺少什么条件:
# select u.id, u.first_name, u.last_name
from pref_rep r, pref_users u, my_temp t
where r.author=u.id and u.vip>now()
and u.id=t.id and t.rep<0;
id | first_name | last_name
-------------------------+--------------------------------+----------------------------
OK400153108439 | Vladimir | Pelix
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
同样的问题(多行具有相同的数据)我得到的声明:
# select u.id, u.first_name, u.last_name
from pref_rep r, pref_users u
where r.author = u.id and u.vip>now()
and u.id in (select id from my_temp where rep < 0);
我想知道这里可能缺少什么条件?
首先,我会这样写你的第一个查询:
select
u.id, u.first_name, u.last_name,
sum(case
when r.nice=true then 1
when r.nice=false then -1
end) as rep
from
pref_users u inner join pref_rep r on u.id=r.id
where
u.vip>now()
group by
u.id, u.first_name, u.last_name;
(和你的一样,但我觉得更清楚)
要查找负面评价的玩家,您可以使用与以前相同的查询,只需添加 HAVING 子句:
having
sum(case
when r.nice=true then 1
when r.nice=false then -1
end)<0
要找到对玩家评分过低的玩家,一种解决方案是:
select
s.id, s.first_name, s.last_name, s.rep
from (
select
u.id, u.first_name, u.last_name,
sum(case
when r.nice=true then 1
when r.nice=false then -1
end) as rep
from
pref_users u inner join pref_rep r on u.id=r.id
where
u.vip>now()
group by
u.id, u.first_name, u.last_name
having
sum(case
when r.nice=true then 1
when r.nice=false then -1
end)<0
) s
where
exists (select * from pref_rep p where p.author = s.id)
最终可以从内部查询中删除 having 子句,您可以在外部查询中使用此 where 子句:
where
rep<0
and exists (select * from pref_rep p where p.author = s.id)
您忘记提及 pref_users.id
被定义为 PRIMARY KEY
- 否则您的第一个查询将无法工作。这也意味着 id
已经编入索引。
最佳查询很大程度上取决于典型的数据分布。
假设:
- ...大多数用户没有得到 任何 负面评价。
- ...大多数用户根本不投票。
- ...一些或许多投票的人经常。
确定少数可能的候选人并只计算最终选择的总评分是值得的 - 而不是计算每个用户的总评分然后 然后 过滤只有少数。
SELECT *
FROM ( -- filter candidates in a subquery
SELECT *
FROM pref_users u
WHERE u.vip > now()
AND EXISTS (
SELECT 1
FROM pref_rep
WHERE author = u.id -- at least one rating given
)
AND EXISTS (
SELECT 1
FROM pref_rep
WHERE id = u.id
AND NOT nice -- at least one neg. rating received
)
) u
JOIN LATERAL ( -- calculate total only for identified candidates
SELECT sum(CASE nice WHEN true THEN 1 WHEN false THEN -1 END) AS rep
FROM pref_rep
WHERE id = u.id
) r ON r.rep < 0;
索引
显然,除了 id
列上的(也假定!)PRIMARY KEY
索引之外,您还需要 pref_rep.author
上的 索引。
如果您的表很大,可以使用一些更高级的索引。
一方面,您似乎只对当前的 VIP 用户感兴趣 (u.vip > now()
)。 vip
上的普通索引会有很长的路要走。或者甚至是包含 id
并从索引中截断旧元组的部分多列索引:
CREATE INDEX pref_users_index_name ON pref_users (vip, id)
WHERE vip > '2015-04-21 18:00';
考虑细节:
- Add datetime constraint to a PostgreSQL multi-column partial index
如果(且仅当)反对票占少数,pref_rep
上的部分索引也可能支付:
CREATE INDEX pref_rep_downvote_idx ON pref_rep (id)
WHERE NOT nice;
使用 EXPLAIN ANALYZE
测试性能,重复几次以排除缓存影响。
在使用 PostgreSQL 9.3.10 的游戏中,一些玩家支付了 "VIP status",这由包含日期的 vip 列表示未来:
# \d pref_users
Column | Type | Modifiers
------------+-----------------------------+--------------------
id | character varying(32) | not null
first_name | character varying(64) | not null
last_name | character varying(64) |
vip | timestamp without time zone |
玩家也可以通过将 nice 列设置为 true、false 或留在 null:
# \d pref_rep
Column | Type | Modifiers
-----------+-----------------------------+-----------------------------------------------------------
id | character varying(32) | not null
author | character varying(32) | not null
nice | boolean |
我通过发出此 SQL JOIN 语句计算 "reputation" 个 VIP 玩家:
# select u.id, u.first_name, u.last_name,
count(nullif(r.nice, false))-count(nullif(r.nice, true)) as rep
from pref_users u, pref_rep r
where u.vip>now()and u.id=r.id group by u.id order by rep asc;
id | first_name | last_name | rep
-------------------------+--------------------------------+--------------------
OK413274501330 | ali | salimov | -193
OK357353924092 | viktor | litovka | -137
DE20287 | sergej warapow |
我的问题是:
如何找到所有给其他玩家评分的负面评分玩家?
(背景是我已经为所有 VIP 玩家添加了评价他人的可能性。在此之前,只有积极评价的玩家才能评价其他人)。
我尝试了以下方法,但出现以下错误:
# select count(*) from pref_rep r, pref_users u
where r.author = u.id and u.vip > now() and
u.id in (select id from pref_rep
where (count(nullif(nice, false)) -count(nullif(nice, true))) < 0);
ERROR: aggregate functions are not allowed in WHERE
LINE 1: ...now() and u.id in (select id from pref_rep where (count(null...
^
更新:
我现在正在尝试使用临时 table -
首先,我用所有负面评价的 VIP 用户填充它,这很有效:
# create temp table my_temp as select u.id, u.first_name, u.last_name,
count(nullif(r.nice, false))-count(nullif(r.nice, true)) as rep
from pref_users u, pref_rep r
where u.vip>now() and u.id=r.id group by u.id;
SELECT 362
但是我的 SQL JOIN returns 太多相同的行,我找不到那里缺少什么条件:
# select u.id, u.first_name, u.last_name
from pref_rep r, pref_users u, my_temp t
where r.author=u.id and u.vip>now()
and u.id=t.id and t.rep<0;
id | first_name | last_name
-------------------------+--------------------------------+----------------------------
OK400153108439 | Vladimir | Pelix
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
OK123283032465 | Edik | Lehtik
同样的问题(多行具有相同的数据)我得到的声明:
# select u.id, u.first_name, u.last_name
from pref_rep r, pref_users u
where r.author = u.id and u.vip>now()
and u.id in (select id from my_temp where rep < 0);
我想知道这里可能缺少什么条件?
首先,我会这样写你的第一个查询:
select
u.id, u.first_name, u.last_name,
sum(case
when r.nice=true then 1
when r.nice=false then -1
end) as rep
from
pref_users u inner join pref_rep r on u.id=r.id
where
u.vip>now()
group by
u.id, u.first_name, u.last_name;
(和你的一样,但我觉得更清楚)
要查找负面评价的玩家,您可以使用与以前相同的查询,只需添加 HAVING 子句:
having
sum(case
when r.nice=true then 1
when r.nice=false then -1
end)<0
要找到对玩家评分过低的玩家,一种解决方案是:
select
s.id, s.first_name, s.last_name, s.rep
from (
select
u.id, u.first_name, u.last_name,
sum(case
when r.nice=true then 1
when r.nice=false then -1
end) as rep
from
pref_users u inner join pref_rep r on u.id=r.id
where
u.vip>now()
group by
u.id, u.first_name, u.last_name
having
sum(case
when r.nice=true then 1
when r.nice=false then -1
end)<0
) s
where
exists (select * from pref_rep p where p.author = s.id)
最终可以从内部查询中删除 having 子句,您可以在外部查询中使用此 where 子句:
where
rep<0
and exists (select * from pref_rep p where p.author = s.id)
您忘记提及 pref_users.id
被定义为 PRIMARY KEY
- 否则您的第一个查询将无法工作。这也意味着 id
已经编入索引。
最佳查询很大程度上取决于典型的数据分布。
假设:
- ...大多数用户没有得到 任何 负面评价。
- ...大多数用户根本不投票。
- ...一些或许多投票的人经常。
确定少数可能的候选人并只计算最终选择的总评分是值得的 - 而不是计算每个用户的总评分然后 然后 过滤只有少数。
SELECT *
FROM ( -- filter candidates in a subquery
SELECT *
FROM pref_users u
WHERE u.vip > now()
AND EXISTS (
SELECT 1
FROM pref_rep
WHERE author = u.id -- at least one rating given
)
AND EXISTS (
SELECT 1
FROM pref_rep
WHERE id = u.id
AND NOT nice -- at least one neg. rating received
)
) u
JOIN LATERAL ( -- calculate total only for identified candidates
SELECT sum(CASE nice WHEN true THEN 1 WHEN false THEN -1 END) AS rep
FROM pref_rep
WHERE id = u.id
) r ON r.rep < 0;
索引
显然,除了 id
列上的(也假定!)PRIMARY KEY
索引之外,您还需要 pref_rep.author
上的 索引。
如果您的表很大,可以使用一些更高级的索引。
一方面,您似乎只对当前的 VIP 用户感兴趣 (u.vip > now()
)。 vip
上的普通索引会有很长的路要走。或者甚至是包含 id
并从索引中截断旧元组的部分多列索引:
CREATE INDEX pref_users_index_name ON pref_users (vip, id)
WHERE vip > '2015-04-21 18:00';
考虑细节:
- Add datetime constraint to a PostgreSQL multi-column partial index
如果(且仅当)反对票占少数,pref_rep
上的部分索引也可能支付:
CREATE INDEX pref_rep_downvote_idx ON pref_rep (id)
WHERE NOT nice;
使用 EXPLAIN ANALYZE
测试性能,重复几次以排除缓存影响。