【问题标题】:PostgreSQL query returning multiple rows instead of onePostgreSQL 查询返回多行而不是一行
【发布时间】:2012-12-12 05:34:19
【问题描述】:

我有两个表:userprojects,两者之间存在一对多关系。
projects 表的字段 status 包含用户的项目状态。

status 可以是以下之一:

launched, confirm, staffed, overdue, complete, failed, ended

我想将用户分为两类:

  1. 项目处于launched 阶段的用户
  2. 具有launched 状态以外的项目的用户。

我正在使用以下查询:

SELECT DISTINCT(u.*), CASE 
    WHEN p.status = 'LAUNCHED' THEN 1
    ELSE 2
    END as user_category
FROM users u
LEFT JOIN projects p ON p.user_id = u.id
WHERE (LOWER(u.username) like '%%%'
    OR LOWER(u.personal_intro) like '%%%'
    OR LOWER(u.location) like '%%%'
    OR u.account_status != 'DELETED'
AND system_role=10 AND u.account_status ='ACTIVE')
ORDER BY set_order, u.page_hits DESC
LIMIT 10
OFFSET 0

我正面临以下情况的重复记录

如果用户有状态为launched 以及overduecompletefailed 的项目,则该用户被记录两次,因为CASE 中的两个条件都满足该用户。

请提出一个查询,其中任何项目处于launched 状态的用户将其user_category 设置为1user_category 2 不应重复使用同一用户。

【问题讨论】:

  • distinct 不是函数。

标签: sql postgresql greatest-n-per-group operator-precedence rails-postgresql


【解决方案1】:

由于多种原因,查询可能没有按照您的想法执行

  • DISTINCT,也有DISTINCTON(col1, col2)
    DISTINCT (u.*)DISTINCT u.*没有区别。括号只是噪音。

  • AND 根据operator precedenceOR 之前绑定。我怀疑你想在条件OR'ed 周围使用括号?还是您需要它原来的样子?但是在任何情况下,整个WHERE 子句都不需要括号。

  • 你的表达LOWER(u.username) LIKE '%%%' 没有任何意义。每个非空字符串都符合条件。可以替换为u.username IS NOT NULL。我怀疑你想要一些不同的东西?

  • Postgres 在字符串处理中区分大小写。您写到status 正在“启动”等,但在查询中使用“启动”。是哪个?

  • 问题中缺少几个表格限定条件,从而使读者感到模棱两可。我按照我认为合适的方式填写。

所有东西放在一起,它可能像这样工作:

SELECT DISTINCT ON (u.set_order, u.page_hits, u.id)
       u.*
     , CASE WHEN p.status = 'LAUNCHED' THEN 1 ELSE 2 END AS user_category
FROM   users         u
LEFT   JOIN projects p ON p.user_id = u.id
WHERE  LOWER(u.username)       LIKE '%%%' -- ???
    OR LOWER(u.personal_intro) LIKE '%%%'
    OR LOWER(u.location)       LIKE '%%%'

    OR u.account_status != 'DELETED'      -- with original logic
   AND u.system_role = 10
   AND u.account_status = 'ACTIVE'
ORDER  BY u.set_order, u.page_hits DESC, u.id, user_category
LIMIT  10

这个相关问题的详细解释:

两个EXISTS semi-joins 而不是DISTINCT ONCASE 可能会更快:

SELECT u.*
     , CASE WHEN EXISTS (
          SELECT FROM projects p
          WHERE p.user_id = u.id AND p.status = 'LAUNCHED')
       THEN 1 ELSE 2 END AS user_category
FROM   users u
WHERE
    (  LOWER(u.username)       LIKE '%%%' -- ???
    OR LOWER(u.personal_intro) LIKE '%%%'
    OR LOWER(u.location)       LIKE '%%%'
    OR u.account_status != 'DELETED'      -- with alternative logic?
    )
AND    u.system_role = 10  -- assuming it comes from users ???
AND    u.account_status = 'ACTIVE'
AND    EXISTS (SELECT 1 FROM projects p WHERE p.user_id = u.id)
ORDER  BY u.set_order, u.page_hits DESC
LIMIT  10;

【讨论】:

    【解决方案2】:

    您可以在您的 CASE 结果上使用 MIN(),似乎放弃 DISTINCT 将是一个明智的选择:

    SELECT u.*, MIN(CASE 
    WHEN p.status = 'LAUNCHED' THEN 1
    ELSE 2
    END) as user_category
    ...
    GROUP BY <list all columns in the users table>
    ...
    

    由于 "launched" 给出 1,使用 MIN() 不仅会强制获得单个结果,而且还会优先选择“launched”而不是其他状态。

    【讨论】:

    • 我认为这不足以解决问题。
    • @ErwinBrandstetter 你为什么这么认为?对我来说这似乎很简单。
    • 如果你放弃DISTINCT(并修复其他问题)GROUP BY 将是一个明智的选择。
    • @ErwinBrandstetter 同意 distinct 似乎是错误的 - 我已经编辑了我的答案以放弃它。老实说,我浏览了查询并想到了这种方法。正如你所指出的,还有更多的问题我的答案
    • 是的,校长现在很健全。我喜欢基本的方法。 +1(使“u中的”并实际声明别名?)
    猜你喜欢
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多