SELECT DISTINCT + ORDER BY 附加表达式

SELECT DISTINCT + ORDER BY additional expression

我没有使用 PostgreSQL 的经验,我正在将 Rails5+MySQL 应用程序迁移到 Rails5+PostgreSQL,但我遇到了查询问题。

我已经看了一些 questions/answers,但仍然无法解决我的问题。我的问题似乎很荒谬,但我需要在这里寻求帮助!

查询:

SELECT DISTINCT users.* FROM users 
INNER JOIN areas_users ON areas_users.user_id = users.id 
INNER JOIN areas ON areas.deleted_at IS NULL AND areas.id = areas_users.area_id 
WHERE users.deleted_at IS NULL AND users.company_id = 2 AND areas.id IN (2, 4, 5) 
ORDER BY CASE WHEN users.id=3 THEN 0 WHEN users.id=5 THEN 1 END, users.id, 1 ASC

运行DBeaver中的查询,returns错误:

SQL Error [42P10]: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list

我需要做什么才能将此 SELECT DISTINCT 与此 ORDER BY CASE 一起使用?

好像是错误提示:

for SELECT DISTINCT, ORDER BY expressions must appear in select list

这是一个表达式:

CASE WHEN users.id=3 THEN 0 WHEN users.id=5 THEN 1 END

您不能在执行 SELECT DISTINCT users.* FROM ... 时按它排序,因为这只允许出现在 SELECT 列表中的 ORDER BY 表达式。

通常,DISTINCT 的最佳解决方案是一开始就不要使用它。如果您不复制行,则以后不必对它们进行重复数据删除。参见:

在您的情况下,使用 EXISTS 半连接(表达式/子查询)而不是连接。这避免了重复。假设 table users 中的不同行,DISTINCT 失业。

SELECT u.*
FROM   users u
WHERE  u.deleted_at IS NULL
AND    u.company_id = 2
AND    EXISTS (
   SELECT FROM areas_users au JOIN areas a ON a.id = au.area_id
   WHERE  au.user_id = u.id
   AND    a.id IN (2, 4, 5)
   AND    a.deleted_at IS NULL
   )
ORDER BY CASE u.id WHEN 3 THEN 0
                   WHEN 5 THEN 1 END, u.id, 1;  -- ①

满足您的要求,而且通常速度也快得多。

使用 simple ("switched") CASE 语法。

①还有一点丑。在 ORDER BY 中使用位置引用可以是方便的短语法。但是,虽然您有 SELECT *,但这是一个非常糟糕的主意。如果底层 table 中的列顺序发生变化,您的查询也会悄无声息地发生变化。拼出此用例中的列!

(通常,您一开始不需要 SELECT *,只需要选择列。)

如果您的 ID 列保证有正数,这会更快一些:

...
ORDER BY CASE u.id WHEN 3 THEN -2
                   WHEN 5 THEN -1
                   ELSE u.id END, <name_of_first_column>

我必须使用 DISTINCT

(真的吗?)如果你坚持:

SELECT DISTINCT CASE u.id WHEN 3 THEN -2 WHEN 5 THEN -1 ELSE u.id END AS order_column, u.*
FROM   users u
JOIN   areas_users au ON au.user_id = u.id
JOIN   areas a ON a.id = au.area_id
WHERE  u.deleted_at IS NULL
AND    u.company_id = 2
AND    a.id IN (2, 4, 5)
AND    a.deleted_at IS NULL
ORDER  BY 1, <name_of_previously_first_column>;  -- now, "ORDER BY 1" is ok

您在结果中获得了额外的列 order_column。您可以将其包装在具有不同 SELECT ...

的子查询中

只是概念验证。不要使用这个。

DISTINCT ON?

SELECT DISTINCT ON (CASE u.id WHEN 3 THEN -2 WHEN 5 THEN -1 ELSE u.id END, <name_of_first_column>)
       u.*
FROM   users u
JOIN   areas_users au ON au.user_id = u.id
JOIN   areas a ON a.id = au.area_id
WHERE  u.deleted_at IS NULL
AND    u.company_id = 2
AND    a.id IN (2, 4, 5)
AND    a.deleted_at IS NULL
ORDER  BY CASE u.id WHEN 3 THEN -2 WHEN 5 THEN -1 ELSE u.id END, <name_of_first_column>;

这可以在不返回附加列的情况下工作。仍然只是概念证明。别用了,EXISTS查询便宜多了。

参见:

  • Select first row in each GROUP BY group?