sql - 使用 Active Record 和 Rails 有时为空的 ORDER BY 列

标签 sql ruby-on-rails postgresql activerecord

在我的 rails 应用程序(使用 postgresql)中,我试图编写一个 Active Record 查询来查找一组志愿者记录,然后按 first_name 对它们进行排序。 ,然后 last_name ,然后 email .此外,first_namelast_name可能是 null (两者都将是 null 或两者都不是 null )。例如,我希望对以下列表进行排序:

  • 志愿者 [名字:'Alex',姓氏:'Diego',电子邮件:'a.diego@person.com']
  • 志愿者 [名字:null , 姓氏:null , 电子邮件:'cxxr@person.com']
  • 志愿者 [名字:'Josh',姓氏:'Broger',电子邮件:'broger@person.com']
  • 志愿者 [名字:'Josh',姓氏:'Broger',电子邮件:'jcool@person.com']
  • 志愿者 [名字:'Josh',姓氏:'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的答案表示我应该使用 COALESCE() ORDER BY 中的函数语句的一部分以获得我想要的结果。惊人的!所以我将我的代码更新为以下内容:
    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表达式。当我更新 .order()我的 sql 片段包括 COALESCE() ,我还需要在SELECT 中添加一个匹配的sql片段声明的一部分 .select() .
  • 上面的 1 只是消除了我遇到的错误。那时,我的查询正在运行,但结果的排序方式与使用 COALESCE() 之前的结果相同。 . Kristján 在下面的回答中提供了正确的描述,但事实证明我的查询运行正确,只是 COALESCE()在任何小写字母之前对任何大写字母进行排序。所以“Z”将排在“a”之前。这个问题可以通过添加一个函数来解决COALESCE()字段使用 LOWER() 小写.

  • 这是我的回答:
        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()我添加的片段。为了解决这个问题,我需要添加一个自定义 volunteers_count方法到 User没有使用 .select() 的模型分段。

    最佳答案

    您遇到了字母大小写问题:您的姓名全部大写,但电子邮件是小写的,并且在大多数排序规则中,大写字母在小写字母之前。看看这个简单的例子:

    #= 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.com排序后 Josh .为避免这种情况,您可以按小写值排序。这是您拥有的数据的简单版本:
    #= 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"))')
    

    关于sql - 使用 Active Record 和 Rails 有时为空的 ORDER BY 列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36580266/

    相关文章:

    ruby-on-rails - 限制 has_many 关联中的对象数量

    mysql - 从一行中选择两个不同的值

    mysql - 存储重复字段: good or bad

    sql - 如何在 SQL Server 中有效地合并两个层次结构?

    mysql高级按查询排序

    sql - 如何查找重叠时间段内可用的最大容量 SQL Server 2008

    ruby-on-rails - 如何设置 dokku-persistent-storage 的音量

    ruby-on-rails - Rails 更改 form_for 中提交的路由

    ruby-on-rails - 将 JSON 导入 Heroku Rails 应用程序然后存储在数据库中

    java - 如何从 ArrayList 收集 SQL 请求