ORDER BY使用Active Record&Rails有时为空的列

在我的rails应用程序中(使用postgresql),我正在尝试编写一个Active Record查询来查找一组志愿者记录,然后通过first_namelast_name ,然后email进行last_name 。 此外, first_namelast_name可能为null (两者都将为null或两者都不为null )。 例如,我想跟踪列表进行如此排序:

  1. 志愿者[first_name:’Alex’,last_name:’Diego’,电子邮件:’a.diego@person.com’]
  2. 志愿者[first_name: null ,last_name: null ,电子邮件:’cxxr@person.com’]
  3. 志愿者[first_name:’Josh’,last_name:’Broger’,电子邮件:’broger@person.com’]
  4. 志愿者[first_name:’Josh’,last_name:’Broger’,电子邮件:’jcool@person.com’]
  5. 志愿者[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_namelast_name进行分组,其他志愿者仅使用最后email (因此在上面的示例列表中,志愿者#2将是最后一个)。 这个有用的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的宝贵帮助以及下面的回答后,我解决了原因是多方面的问题:

  1. .uniq添加到ActiveRecord查询时,会将DISTINCT添加到发送到数据库的sql中。 SELECT DISTINCT有一些比简单SELECT更严格的要求。 正如Kristján所指出并在本SO答案中更详细地描述的那样 , DISTINCT表达式必须与最左边的ORDER BY表达式匹配。 当我使用包含COALESCE() sql片段更新.order()时,我还需要使用.select()将匹配的sql片段添加到语句的SELECT部分。
  2. 上面的图1只是删除了我得到的错误。 此时,我的查询正在运行,但结果与使用COALESCE()之前的结果相同。 Kristján在下面的答案中提供了正确的描述,但事实certificate我的查询运行正常,只是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时,上面的答案实际上创建了另一个问题。 .select()由于我添加的自定义.select()片段而中断。 为了解决这个问题,我需要在没有使用.select()片段的User模型中添加自定义的volunteers_count方法。

你遇到了一个字母大小写的问题:你的名字都是大写的,但是电子邮件是小写的,而且对于大多数排序规则,大写字母都是小写的。 看看这个简单的例子:

 #= 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 

所以你的查询实际上工作得很好,只是cxxr@person.comJosh之后排序。 为避免这种情况,您可以按小写值排序。 这是您拥有的简单数据版本:

 #= 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 

然后使用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 

或者使用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"))') 

把它扔出去但你试过(mysql)

订购志愿者ASC那么last_name ASC然后发送电子邮件ASC

(想让这个评论但不足够的代表:((()如果这不好请注释,所以我可以把它拿下来所以我不会失去代表:)