[英]ORDER BY columns that are sometimes empty using Active Record & Rails
In my rails app (using postgresql), I'm trying to compose an Active Record query to find a group of volunteer records and then order them by first_name
, then last_name
, then email
. 在我的Rails应用程序中(使用postgresql),我试图组成一个Active Record查询来查找一组志愿者记录,然后按
first_name
, last_name
,然后按email
进行last_name
。 Additionally, first_name
and last_name
may be null
(either both will be null
or both will be not null
). 此外,
first_name
和last_name
可以为null
(两者都为null
或都不为null
)。 For example, I would want to following list to be sorted thusly: 例如,我希望对下面的列表进行排序:
null
, last_name: null
, email: 'cxxr@person.com'] null
,姓氏: null
,电子邮件: 'cxxr@person.com'] Originally, I had the following code: 最初,我有以下代码:
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)
This code works except the results are grouped by volunteers with first_name
& last_name
first, other volunteers with only email
last (so in the example list above, volunteer #2 would be last). 该代码有效,除了将结果按
first_name
和last_name
首先的志愿者分组,将其他结果仅按email
志愿者分组(因此,在上面的示例列表中,#2自愿者是最后一名)。 The answer to this helpful post indicates that I should use a COALESCE()
function in the ORDER BY
part of the statement to get the results I want. 这篇有用的帖子的答案表明,我应该在语句的
ORDER BY
部分中使用COALESCE()
函数以获得所需的结果。 Awesome! 真棒! So I updated my code to the following:
因此,我将代码更新为以下内容:
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')
The problem is that this code now returns 问题在于此代码现在返回
PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
Using to_sql
on both versions of the code, I find they are exactly the same except for the addition of the COALESCE()
function. 在两个版本的代码上使用
to_sql
,我发现它们完全相同,只是增加了COALESCE()
函数。
to_sql
of original, working, code: 原始的,有效的代码的
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
of updated code (the only difference is after ORDER BY
): 更新代码的
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"
I tested trying my new code without .uniq
(to remove the DISTINCT
part of the sql) and when I do this the new code runs without error, however the results are NOT sorted properly: they are sorted the same way as my original code is (the code without COALESCE()
). 我测试了不带
.uniq
新代码(以删除sql的DISTINCT
部分),当我这样做时,新代码运行没有错误,但是结果未正确排序:它们的排序方式与我的原始代码相同(不带COALESCE()
的代码)。
I imagine that there is a syntax error that I've committed, but I can't figure out what it is (or perhaps I'm wrong and COALESCE()
is not the proper solution to my problem). 我想我已经犯了一个语法错误,但是我无法弄清楚它是什么(或者我错了,而
COALESCE()
不是解决我问题的正确方法)。
Any help is GREATLY appreciated!! 任何帮助是极大的赞赏!!
After getting invaluable help from Kristján and his answer below, I solved what turned out to be multiple problems: 在从克里斯蒂安(Kristján)及其以下答案中获得了宝贵的帮助后,我解决了原来是多个问题:
.uniq
to an ActiveRecord query, it adds DISTINCT
to the sql that gets sent to the database. .uniq
添加到ActiveRecord查询时,它将DISTINCT
添加到要发送到数据库的sql中。 SELECT DISTINCT
has some stricter requirements than simply SELECT
. SELECT DISTINCT
比简单的SELECT
有一些更严格的要求。 As pointed out by Kristján and described in more detail in this SO answer , the DISTINCT
expression(s) must match the leftmost ORDER BY
expression(s). DISTINCT
表达式必须与最左边的ORDER BY
表达式匹配。 When I updated .order()
with my sql fragment including COALESCE()
, I also needed to add a matching sql fragment to the SELECT
part of the statement with .select()
. COALESCE()
sql片段更新.order()
时,我还需要使用.select()
将匹配的sql片段添加到语句的SELECT
部分。 COALESCE()
. COALESCE()
之前的排序相同。 Kristján provides a proper description in his answer below, but turns out my query was running correctly, its just that COALESCE()
sorts anything uppercase before anything lowercase. COALESCE()
大写的内容排在小写的前面。 so "Z" will be sorted in front of "a". COALESCE()
fields to lowercase using LOWER()
. LOWER()
将COALESCE()
字段转换为小写字母的函数可以解决此问题。 Here is my answer: 这是我的答案:
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')
Note: 注意:
My answer above actually created another problem when I later call .count
on the query. 当我稍后在查询中调用
.count
时,上述答案实际上创建了另一个问题。 .count
breaks because of the custom .select()
fragment I've added. .count
因为自定义的中断.select()
片段,我补充道。 To solve this, I needed to add a custom volunteers_count
method to the User
model that didn't make use of the .select()
fragment. 为了解决这个问题,我需要向
User
模型中添加一个不使用.select()
片段的自定义volunteers_count
.select()
。
You're running in to a letter case problem: Your names are all capitalized, but the emails are lowercase, and with most collations, uppercase letters come before lowercase. 您遇到了字母大小写的问题:您的姓名全部大写,但电子邮件为小写,并且在大多数归类中,大写字母排在小写字母之前。 Check out this trivial example:
看看这个简单的例子:
#= select * from (values ('b'), ('B'), ('a'), ('A')) t (letter);
letter
--------
b
B
a
A
#= select * from (values ('b'), ('B'), ('a'), ('A')) t (letter) order by letter;
letter
--------
A
B
a
b
So your query is actually working perfectly, it's just that cxxr@person.com
sorts after Josh
. 因此您的查询实际上运行良好,只是
cxxr@person.com
在Josh
之后排序。 To avoid this, you can sort by the lowercase value. 为避免这种情况,可以按小写值排序。 Here's a simple version of the data you have:
这是您拥有的数据的简单版本:
#= select * from volunteers;
first_name | last_name | email
------------+-----------+--------------------
Josh | Broger | jcool@person.com
Josh | Kenton | aj@person.com
∅ | ∅ | cxxr@person.com
Josh | Broger | broger@person.com
Alex | Diego | a.diego@person.com
Then to sort using the coalesce
you're after: 然后使用
coalesce
后的内容进行排序:
#= select * from volunteers order by lower(coalesce(first_name, email));
first_name | last_name | email
------------+-----------+--------------------
Alex | Diego | a.diego@person.com
∅ | ∅ | cxxr@person.com
Josh | Broger | broger@person.com
Josh | Broger | jcool@person.com
Josh | Kenton | aj@person.com
Or for your full version using ActiveRecord
: 或使用
ActiveRecord
的完整版本:
Volunteer
.joins(:volunteer_lists)
.where(
"(volunteer_lists.organizer_id = ? AND organizer_type = 'Organization') OR (volunteer_lists.organizer_id IN (?) AND organizer_type = 'Collaborative')",
organization.id, collaboratives
)
.order('LOWER(COALESCE("volunteers"."first_name", "volunteers"."last_name", "volunteers"."email"))')
Just throwing it out there but have you tried (mysql) 只是将它扔在那里,但您尝试过(mysql)
ORDER BY volunteers ASC THEN last_name ASC THEN email ASC ORDER BY志愿者ASC THEN姓氏ASC THEN电子邮件ASC
(wanted to make this a comment but not enough rep :((( ) if this isnt good please just comment so I can take it down so i dont lose rep :) (想对此发表评论,但代表不足:((()如果这不好,请发表评论,这样我就可以把它记下来,这样我就不会失去代表:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.