【发布时间】:2016-04-12 17:32:20
【问题描述】:
在我的 rails 应用程序(使用 postgresql)中,我试图编写一个 Active Record 查询来查找一组志愿者记录,然后按first_name、last_name、email 对它们进行排序。此外,first_name 和last_name 可能是null(两者都将是null 或两者都不是null)。例如,我想对以下列表进行排序:
- 志愿者 [first_name:'Alex',last_name:'Diego',电子邮件:'a.diego@person.com']
- 志愿者 [first_name:
null, last_name:null, email: 'cxxr@person.com'] - 志愿者 [名字:'Josh',姓氏:'Broger',电子邮件:'broger@person.com']
- 志愿者 [first_name: 'Josh', last_name: 'Broger', email: 'jcool@person.com']
- 志愿者 [first_name:'Josh',last_name:'Kenton',电子邮件:'aj@person.com']
最初,我有以下代码:
Volunteer.joins(:volunteer_lists).
where("(volunteer_lists.organizer_id = ? AND organizer_type = 'Organization') OR
(volunteer_lists.organizer_id IN (?) AND organizer_type = 'Collaborative')",
self.organization.id, collaboratives).uniq.
order(:first_name, :last_name, :email)
此代码有效,除了结果由具有first_name 和last_name 的志愿者分组,其他志愿者只有email 最后(因此在上面的示例列表中,志愿者#2 将在最后)。 this helpful post 的答案表明我应该在语句的ORDER BY 部分中使用COALESCE() 函数来获得我想要的结果。惊人的!所以我将我的代码更新为以下内容:
Volunteer.joins(:volunteer_lists).
where("(volunteer_lists.organizer_id = ? AND organizer_type = 'Organization') OR
(volunteer_lists.organizer_id IN (?) AND organizer_type = 'Collaborative')",
self.organization.id, collaboratives).uniq.
.order('COALESCE("volunteers"."first_name", "volunteers"."email") ASC, COALESCE("volunteers"."last_name", "volunteers"."email") ASC, "volunteers"."email" ASC')
问题是这段代码现在返回
PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
在两个版本的代码上使用to_sql,我发现它们完全一样,除了添加了COALESCE()函数。
to_sql 的原始,工作,代码:
SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT 1 [["id", 1]]
=> "SELECT DISTINCT \"volunteers\".* FROM \"volunteers\" INNER JOIN \"volunteer_list_connectors\" ON \"volunteer_list_connectors\".\"volunteer_id\" = \"volunteers\".\"id\" INNER JOIN \"volunteer_lists\" ON \"volunteer_lists\".\"id\" = \"volunteer_list_connectors\".\"volunteer_list_id\" WHERE ((volunteer_lists.organizer_id = 1 AND organizer_type = 'Organization') OR\n (volunteer_lists.organizer_id IN (1) AND organizer_type = 'Collaborative')) ORDER BY \"volunteers\".\"first_name\" ASC, \"volunteers\".\"last_name\" ASC, \"volunteers\".\"email\" ASC"
更新代码的to_sql(唯一不同的是ORDER BY之后):
SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT 1 [["id", 1]]
=> "SELECT DISTINCT \"volunteers\".* FROM \"volunteers\" INNER JOIN \"volunteer_list_connectors\" ON \"volunteer_list_connectors\".\"volunteer_id\" = \"volunteers\".\"id\" INNER JOIN \"volunteer_lists\" ON \"volunteer_lists\".\"id\" = \"volunteer_list_connectors\".\"volunteer_list_id\" WHERE ((volunteer_lists.organizer_id = 1 AND organizer_type = 'Organization') OR\n (volunteer_lists.organizer_id IN (1) AND organizer_type = 'Collaborative')) ORDER BY COALESCE(\"volunteers\".\"first_name\", \"volunteers\".\"email\") ASC, COALESCE(\"volunteers\".\"last_name\", \"volunteers\".\"email\") ASC, \"volunteers\".\"email\" ASC"
我测试了在没有 .uniq 的情况下尝试我的新代码(以删除 sql 的 DISTINCT 部分),当我这样做时,新代码运行时没有错误,但是结果没有正确排序:它们排序相同就像我的原始代码一样(没有COALESCE()的代码)。
我想我犯了一个语法错误,但我不知道它是什么(或者我错了,COALESCE() 不是解决我问题的正确方法)。
非常感谢任何帮助!
更新和回答
在得到Kristján 的宝贵帮助和他在下面的回答后,我解决了原来是多个问题:
- 当您将
.uniq添加到 ActiveRecord 查询时,它会将DISTINCT添加到发送到数据库的 sql 中。SELECT DISTINCT比简单的SELECT有一些更严格的要求。正如 Kristján 和 described in more detail in this S.O. answer 所指出的,DISTINCT表达式必须匹配最左边的ORDER BY表达式。当我使用包含COALESCE()的sql 片段更新.order()时,我还需要使用.select()将匹配的sql 片段添加到语句的SELECT部分。
上面的 - 1 只是删除了我得到的错误。那时,我的查询正在运行,但结果的排序与使用
COALESCE()之前相同。 Kristján 在下面的回答中提供了正确的描述,但事实证明我的查询运行正确,只是COALESCE()将任何大写字母排在小写字母之前。所以“Z”将排在“a”前面。这个问题可以通过添加一个函数来解决,使用LOWER()将COALESCE()字段转换为小写。
这是我的答案:
Volunteer.select('LOWER(COALESCE("volunteers"."first_name", "volunteers"."email")), LOWER(COALESCE("volunteers"."last_name", "volunteers"."email")), LOWER("volunteers"."email"), "volunteers".*').
joins(:volunteer_lists).
where("(volunteer_lists.organizer_id = ? AND organizer_type = 'Organization') OR
(volunteer_lists.organizer_id IN (?) AND organizer_type = 'Collaborative')",
self.organization.id, collaboratives).uniq.
order('LOWER(COALESCE("volunteers"."first_name", "volunteers"."email")) ASC, LOWER(COALESCE("volunteers"."last_name", "volunteers"."email")) ASC, LOWER("volunteers"."email") ASC')
注意:
当我稍后在查询中调用.count 时,我上面的回答实际上产生了另一个问题。 .count 因我添加的自定义 .select() 片段而中断。为了解决这个问题,我需要在 User 模型中添加一个自定义的 volunteers_count 方法,该模型没有使用 .select() 片段。
【问题讨论】:
-
DISTINCT是我的氪石,COALESCE在这里听起来不错,但是查看您的代码我不知道如何修复它。如果您没有大量用户,我认为这在 Ruby 中会容易得多;但它可能可以用一些神秘的 SQL 来完成 -
不幸的是,我有成千上万的志愿者,而且这个数字还在不断增加。我最初是用 ruby 做的,但我正在努力提高性能。
标签: sql ruby-on-rails postgresql activerecord