【问题标题】:using order_by expression and distinct true in rails 6 with postgresql在带有postgresql的rails 6中使用order_by表达式和不同的true
【发布时间】:2021-08-24 17:09:33
【问题描述】:

我正在尝试实现一个非常具体的查询,但似乎无法找到方法。

我正在使用 ransack gem 进行过滤,并且我设置了 (distinct: true),因为我想同时过滤和排序,这给我带来了我正在尝试解决的问题。

我有一个名为 A 的模型,A 有一个名为“状态”的列。 'status' 可以是'approved'、'approved by manager'、'pending' 或'rejected'。

我想保持数据库顺序默认,但想按照自定义顺序返回它

STATUS_ORDER = ['approved_by_leader', 'pending', 'approved', 'rejected']

我使用了这段代码:

  def self.order_by_status
    ret = "CASE"
    STATUS_ORDER.each_with_index do |s, i|
      ret << " WHEN state LIKE '#{s}' THEN #{i}"
    end
    ret << " END ASC"
  end

然后,我刚刚定义了范围:

scope :by_status_priority, -> { order(order_by_status) }

这是我在控制器中使用的范围。这会起作用,除非我想将(distinct: true) 保留在洗劫中,以避免在使用查询时出现重复的结果。

我得到的错误是:

"PG::InvalidColumnReference: ERROR:  for SELECT DISTINCT, ORDER BY expressions must appear in select list\nLINE 1:

我可以就如何处理这个问题提出一些建议。我需要保留 (distinct: true) 或有另一种避免重复的方法。

稍后我还将使用嵌套排序,这是 (distinct: true) 无法使用的,但可以通过在我的控制器中使用包含和连接来解决此问题,这样就不会有问题了。更多信息在这里:Ransack, Postgres - sort on column from associated table with distinct: true

【问题讨论】:

  • 感谢您的建议@engineersmnky。是的,查询很好,如果我的,那只是一个错误。我已经使用 (distinct: false) 进行了尝试,并且范围工作正常。我现在遇到的问题是让它工作(不同:真的)。我想我应该告诉查询“状态”也是不同的值,而不仅仅是“id”(默认)?我建议创建一个新专栏来设置优先级,但我的同事认为我们应该找到更好的方法。
  • @engineersmnky 老实说,您的代码现在超出了我的理解范围(笑)。我确实尝试过,但我得到了这个错误:syntax error, unexpected local variable or method, expecting '}'\n...tatus).as(:status_order) )order(Arel.sql(order_by_status).as...\n... ^~~~~\n" 我试图修复它,但修复我不理解的东西应该不是那么容易!我确实在订购前发现了一个空白区域
  • @engineersmnky 我确实尝试用点修复它(而且,由于 status_order 是一个常量,也尝试设置为(STATUS_ORDER)而不是:status_order)但我无法让它工作,这是我添加点后得到的。 (.):"no implicit conversion of Symbol into String"。知道出了什么问题吗?
  • @engineersmnky 你到底是怎么想到这个的?有用。请您在下面发表评论,以便我可以将其标记为已解决?如果您对我可以在哪里阅读有关该主题的更多信息有任何建议,以充分理解范围表达式,那也很棒。非常感谢!

标签: ruby-on-rails postgresql ransack


【解决方案1】:

我最初的建议是创建一个带有statuspriority 列的Status 模型和表,然后与您现有的模型建立正式关系,因为这将允许在将来进行简单的添加和重新排序,而无需更改任何代码.

我代替这个,错误很明显:

对于 SELECT DISTINCT,ORDER BY 表达式必须出现在选择列表中

这意味着当使用 SELECT DISTINCT 时,您只能按出现在 SELECT 子句中的列排序

为了解决这个问题,我建议如下:

  • self.order_by_status 方法中删除“ASC”(以便我们可以重用CASE 语句
  • 然后按如下方式更新范围
scope :by_status_priority, -> { 
  select(arel_table[Arel.star],Arel.sql(order_by_status).as('status_order') )
    .order(Arel.sql(order_by_status).asc) 
}

TL;DR ActiveRecord 和 Arel 的解释

你会在上面看到几次ArelArel 是 rails 的底层查询组装器,并提供了极大的灵活性,允许进行更多可组合的查询。

每个ActiveRecord 对象都通过称为arel_table 的方法公开其Arel::Table。这部分arel_table[Arel.star] 将组装为“table_name”。“*”(全选)

这部分Arel.sql(order_by_status) 将返回一个Arel::Nodes::SqlLiteral,它允许我们链接别名(如as('status_order'))以及排序(如.asc)。

我们甚至可以通过 Arel 构建您的整个 CASE 语句

def self.order_by_status
  STATUS_ORDER.each_with_index.inject(Arel::Nodes::Case.new) do |cassette, (s, i)|
    cassette.when(arel_table[:status].eq(s)).then(i)
  end
end

这将允许您摆脱 by_status_priority 中的 Arel.sql 包装器(用于原始 sql 字符串),因此范围现在可以变为

scope :by_status_priority, -> { 
  select(arel_table[Arel.star],order_by_status.as('status_order'))
    .order(order_by_status.asc) 
}

ActiveRecord 提供了很多方便的方法来公开Arel 查询接口,例如selectwhereorderjoins 等,但不可能以提供简单顶级 DSL 的简单方式公开所有内容;然而,几乎所有这些“顶级”方法都将毫无问题地接受Arel 参数,从而允许您构建您可能想要的任何查询。如果它是有效的 SQL,那么Arel 可以构造它。

【讨论】:

  • 这太棒了!我肯定会更深入地研究 Arel,一旦你掌握了它的要点,它看起来真的很强大。感谢您冗长而简短的解释。为了解决我的问题。
【解决方案2】:

试试这个:

  STATUS_ORDER = ['approved_by_leader', 'pending', 'approved', 'rejected'].freeze

  def self.order_by_status
    STATUS_ORDER.map.with_index do |status, ordering|
      "('#{status}', #{ordering})"
    end.join(",\n")
  end

  scope :by_status_priority, -> do
    select("#{self.table_name}.*, status_ordering.*").
      joins("JOIN (values #{order_by_status}) AS status_ordering (status, ordering) ON #{self.table_name}.status = status_ordering.status").
      order_by("status_ordering.ordering")
  end

这个想法是有一个名为status_ordering的虚拟关系

status ordering
'approved_by_leader' 0
... ...

order_by_status方法产生 并将其加入原始表。

查询应如下所示:

SELECT TABLE_NAME.*, status_ordering.* FROM TABLE_NAME
  JOIN (
    values ('approved_by_leader', 0), ('pending', 1), ('approved', 2), ('rejected', 3)
  ) AS status_ordering (status, ordering) ON TABLE_NAME.status = status_ordering.status
  ORDER BY status_ordering.ordering
;

如果您添加DISTINCT,它应该仍然可以工作,因为status_ordering.ordering 仍然在SELECT 表达式中。

附:上面的代码我没有测试过。

【讨论】:

  • 感谢您的建议。你的解释很清楚,帮助我理解了这种方法,但它并没有立即奏效,它在某个地方有一个小错误,现在我没有时间调试,但我发现你的答案很有用,所以谢谢你。我选择了@engineersmnky 的上述答案,因为它也很有效,帮助我对 Arel 有了更多的了解。
猜你喜欢
  • 2016-08-17
  • 2015-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-31
  • 1970-01-01
相关资源
最近更新 更多