【问题标题】:ORDER BY columns that are sometimes empty using Active Record & Rails使用 Active Record 和 Rails 有时为空的 ORDER BY 列
【发布时间】:2016-04-12 17:32:20
【问题描述】:

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

  1. 志愿者 [first_name:'Alex',last_name:'Diego',电子邮件:'a.diego@person.com']
  2. 志愿者 [first_name: null, last_name: null, email: 'cxxr@person.com']
  3. 志愿者 [名字:'Josh',姓氏:'Broger',电子邮件:'broger@person.com']
  4. 志愿者 [first_name: 'Josh', last_name: 'Broger', email: '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 将在最后)。 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 的宝贵帮助和他在下面的回答后,我解决了原来是多个问题:

  1. 当您将 .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 部分。
  2. 上面的
  3. 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


【解决方案1】:

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

#= 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"))')

【讨论】:

  • 天哪,你是一个了不起的人!谢谢你!!谢谢你!!!!谢谢你!!!!!它分类正确!!!!太感谢了。我自己永远也想不通。这正是正在发生的事情。不幸的是,当查询中包含.uniq (DISTINCT) 时,它仍然返回相同的PG::InvalidColumnReference 错误。你知道这是为什么吗?错误消息表明ORDER BY 表达式必须出现在选择列表中,但是,正如我的to_sql 测试所证明的那样,带有LOWER(COALESCE()) 的ORDER BY 表达式与不带LOWER(COALESCE()) 的查询出现在同一位置
  • 如果您不知道DISTINCT 发生了什么,我将更新我的问题以专门解决您已回答的问题,然后将您的答案标记为正确。您似乎已经表明我的查询实际上有两个错误。我知道的一个(我的问题的主题)和一个我不知道的(你的答案)。
  • 无论如何,我还是非常感谢您的回答。我不认为自己能自己解决这个问题。我真的(!!!!)感谢您抽出时间回答我的问题!
  • 所有distinct 错误的意思是,您还必须在select 子句中包含order by 中的值,例如select distinct lower(coalesce(first_name, last_name, email)), * from ... order by lower(...)。但是由于您目前拥有distinct *,除非您的表中有大量完全重复的行,否则这不会有太大的作用——您要区分什么?
  • 谢谢!这就是我需要的信息。我做了更多的研究,让一切正常。最后,我用我的具体问题的完整答案更新了我的问题,因为事实证明,有几个问题。我非常感谢你在这里帮助我。没有你就无法做到。
【解决方案2】:

只是把它扔在那里,但你试过吗(mysql)

由志愿者订购 ASC THEN last_name ASC THEN 电子邮件 ASC

(想发表评论但没有足够的代表:((() 如果这不好,请发表评论,这样我就可以删除它,这样我就不会失去代表:)

【讨论】:

  • 我只是查看了THEN,我没有看到它在ORDER BY 语句中使用,只是案例语句,所以我认为这对我没有帮助。但说真的,感谢您的输入!它让我尝试了更多可能的解决方案。不过还是没搞清楚:(
  • 也试试 OR,我有一些随机查询,最终得到了奇怪的输入以使其正确。祝你好运!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-16
  • 1970-01-01
  • 2013-01-25
  • 2011-10-15
  • 1970-01-01
  • 2023-03-04
  • 2019-05-17
相关资源
最近更新 更多