【问题标题】:aggregate functions are not allowed in WHERE - when joining PostgreSQL tablesWHERE 中不允许使用聚合函数 - 加入 PostgreSQL 表时
【发布时间】:2015-12-17 17:18:20
【问题描述】:

在使用 PostgreSQL 9.3.10 的游戏中,一些玩家支付了“VIP 身份”的费用,这由包含未来日期的 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 列设置为 truefalse 或将其保留为 null 来评价其他玩家:

 # \d pref_rep

  Column   |            Type             |                         Modifiers                         
-----------+-----------------------------+-----------------------------------------------------------
 id        | character varying(32)       | not null
 author    | character varying(32)       | not null
 nice      | boolean                     | 

我通过发出以下 SQL JOIN 语句来计算 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...
                                                             ^

更新:

我现在正在尝试使用临时表 -

首先我用所有负面评价的 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 返回了太多相同的行,我找不到那里缺少什么条件:

 # 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);

我想知道这里可能缺少什么条件?

【问题讨论】:

  • 聚合函数必须放在HAVING子句中,不能放在WHERE子句中。
  • 尝试select id from pref_rep having (count(nullif(nice, false)) -count(nullif(nice, true))) &lt; 0;不幸的是:ERROR: column "pref_rep.id" must appear in the GROUP BY clause or be used in an aggregate function
  • 使用聚合时,SELECT 子句中的所有内容都必须具有聚合函数或位于 GROUP BY 子句中。您可能想花一些时间在 SQL 教程上。

标签: sql postgresql join postgresql-9.3 nullif


【解决方案1】:

首先,我会把你的第一个查询写成这样:

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)

最终,have 子句可以从内部查询中删除,您只需在外部查询中使用这个 where 子句:

where
  rep<0
  and exists (select * from pref_rep p where p.author = s.id)

【讨论】:

  • 在 Oracle 和可能的 PostgreSQL 中,将聚合值放在 WHERE 中是不合法的,只能放在 HAVING 中。
【解决方案2】:

您忘记提及 pref_users.id 被定义为 PRIMARY KEY - 否则您的第一个查询将不起作用。这也意味着id 已被编入索引。

最佳查询很大程度上取决于典型的数据分布

假设:

  • ...大多数用户没有得到任何负面评价。
  • ...大多数用户根本不投票。
  • ...一些或许多投票者经常这样做。

识别少数可能的候选人并只计算那些最终选择的总评分是值得的 - 而不是计算每个用户的总评分并然后只过滤少数人。 p>

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 &gt; now())。 vip 上的普通索引将有很长的路要走。甚至是包含id 并从索引中截断旧元组的部分多列索引:

CREATE INDEX pref_users_index_name ON pref_users (vip, id)
WHERE vip > '2015-04-21 18:00';

考虑细节:

如果(且仅当)反对票是少数,pref_rep 上的部分索引也可能会支付:

CREATE INDEX pref_rep_downvote_idx ON pref_rep (id)
WHERE NOT nice;

使用EXPLAIN ANALYZE 测试性能,重复几次以排除缓存影响。

【讨论】:

    猜你喜欢
    • 2017-06-16
    • 2021-07-29
    • 2017-07-17
    • 2014-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-21
    • 2015-03-04
    相关资源
    最近更新 更多