简体   繁体   English

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

[英]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_namelast_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_namelast_name可以为null (两者都为null或都不为null )。 For example, I would want to following list to be sorted thusly: 例如,我希望对下面的列表进行排序:

  1. Volunteer [first_name: 'Alex', last_name: 'Diego', email: 'a.diego@person.com'] 志愿者[名字:'Alex',姓氏:'Diego',电子邮件:'a.diego@person.com']
  2. Volunteer [first_name: null , last_name: null , email: 'cxxr@person.com'] 志愿者[FIRST_NAME: null ,姓氏: null ,电子邮件: 'cxxr@person.com']
  3. Volunteer [first_name: 'Josh', last_name: 'Broger', email: 'broger@person.com'] 志愿者[名字:'Josh',姓氏:'Broger',电子邮件:'broger@person.com']
  4. Volunteer [first_name: 'Josh', last_name: 'Broger', email: 'jcool@person.com'] 志愿者[名字:'Josh',姓氏:'Broger',电子邮件:'jcool@person.com']
  5. Volunteer [first_name: 'Josh', last_name: 'Kenton', email: 'aj@person.com'] 志愿者[名字:'Josh',姓氏:'Kenton',电子邮件:'aj@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_namelast_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!! 任何帮助是极大的赞赏!!

UPDATE & ANSWER 更新和答案

After getting invaluable help from Kristján and his answer below, I solved what turned out to be multiple problems: 在从克里斯蒂安(Kristján)及其以下答案中获得了宝贵的帮助后,我解决了原来是多个问题:

  1. When you add .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). 正如克里斯蒂安(Kristján)指出并在该SO答案中更详细地描述的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部分。
  2. 1 above just removes the error I was getting. 上面的1只是删除了我遇到的错误。 At that point, my query was running but the results were being sorted the same as they were before using 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. Kristján在下面的回答中提供了适当的描述,但事实证明我的查询运行正确,只是COALESCE()大写的内容排在小写的前面。 so "Z" will be sorted in front of "a". 因此“ Z”将排在“ a”之前。 This problem can be solved by adding a function to convert the 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.comJosh之后排序。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM