【问题标题】:SELECT DISTINCT + ORDER BY additional expressionSELECT DISTINCT + ORDER BY 附加表达式
【发布时间】:2021-08-09 20:46:31
【问题描述】:

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

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

查询:

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 中运行查询,返回错误:

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

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

【问题讨论】:

  • 在名为users 的表中,我希望所有行都有不同的ID。为什么需要 DISTINCT?
  • 根据 JOIN 可能有重复的用户

标签: sql postgresql ruby-on-rails-5 distinct


【解决方案1】:

就像错误消息说:

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 半联接(表达式/子查询)而不是联接。这避免了重复。假设表 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 *,但这是一个非常糟糕的主意。如果基础表中列的顺序发生变化,您的查询将被静默更改。拼出这个用例中的列!

(通常,您首先不需要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 查询要便宜很多。

见:

【讨论】:

  • 您写道:a larger query with some JOINs and does a different ordering with some CASEs, but to simplify the query: 很有可能,您通过过度简化删除了DISTINCT 的原因。或者你不需要DISTINCT 开始。在问题中提供原始查询、CREATE TABLE users ... 的(核心)定义和您的 Postgres 版本。如果DISTINCT 确实有意义,您需要定义从每组重复项中选择哪一行。那么这应该很快解决。
  • 使用我在问题中评论的查询,问题已经发生,取决于 JOIN 它可以带来重复的用户,所以我使用 DISTINCT。原来的查询是: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
  • 我使用这个 id = 3 和 id = 5 因为我需要这些记录在前
  • @Marcus:请edit您的问题并将此查询添加到其中。我为我的答案添加了一个解决方案。
  • 谢谢!负数的好提示,我不知道它更快!出于好奇,在 PostgreSQL 中不可能像 MySQL 一样在这个查询中使用 DISTINCT?我问是因为我的 API 默认使用 Rails .distinct
猜你喜欢
  • 2018-01-16
  • 1970-01-01
  • 1970-01-01
  • 2012-09-23
  • 2011-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-06
相关资源
最近更新 更多