在 rails 6 中使用 order_by 表达式和不同的 true 与 postgresql
using order_by expression and distinct true in rails 6 with postgresql
我正在尝试实现一个非常具体的查询,但似乎无法找到方法。
我正在使用 ransack gem 进行过滤,并且我设置了 (distinct: true),因为我想同时进行过滤和排序,这给我带来了我正在尝试解决的问题。
我有一个名为 A 的模型,A 有一个名为 'status' 的列。 'status' 可以是 'approved'、'approved by manager'、'pending' 或 'rejected'.
我想保留数据库默认顺序,但想return它遵循自定义顺序
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) }
这是我在控制器中使用的范围。这会起作用,除了我想在 ransack 中保留 (distinct: true)
,以避免在使用查询时出现重复结果。
我得到的错误是:
"PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list\nLINE 1:
我可以使用一些建议来处理这个问题。我需要保留 (distinct: true) 或有避免重复的替代方法。
稍后我也会使用嵌套排序,这是 (distinct: true) 无法使用的,但是可以通过在我的控制器中使用 include 和 join 来解决,所以这不会是一个问题。更多信息请点击此处:Ransack, Postgres - sort on column from associated table with distinct: true
我最初的建议是使用 status
和 priority
列创建一个 Status
模型和 table,然后与现有模型建立正式关系,因为这将允许在未来进行简单的添加和重新排序,而无需更改任何代码。
我代替这个,错误很明显:
for SELECT DISTINCT, ORDER BY expressions must appear in select list
这意味着当使用 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;DRActiveRecord和Arel的解释
你会在上面看到几次Arel
。 Arel
是 rails 的底层查询汇编器,提供了极大的灵活性,允许更多可组合的查询。
每个 ActiveRecord
对象通过名为 arel_table
的方法公开其 Arel::Table
。这部分 arel_table[Arel.star]
将组装为“table_name”。“*”(select 所有内容)
这部分 Arel.sql(order_by_status)
将 return 一个 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
查询接口,例如select
、where
、order
、joins
等,但不可能以提供简单的顶级 DSL 的简单方式公开所有内容;然而,几乎所有这些“顶级”方法都会毫无问题地接受 Arel
参数,从而允许您构建您可能想要的任何查询。如果它是有效的 SQL 那么 Arel
可以构建它。
试试这个:
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
方法产生
并加入原来的 table.
查询应如下所示:
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
表达式中。
P.S。我还没有测试上面的代码。
我正在尝试实现一个非常具体的查询,但似乎无法找到方法。
我正在使用 ransack gem 进行过滤,并且我设置了 (distinct: true),因为我想同时进行过滤和排序,这给我带来了我正在尝试解决的问题。
我有一个名为 A 的模型,A 有一个名为 'status' 的列。 'status' 可以是 'approved'、'approved by manager'、'pending' 或 'rejected'.
我想保留数据库默认顺序,但想return它遵循自定义顺序
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) }
这是我在控制器中使用的范围。这会起作用,除了我想在 ransack 中保留 (distinct: true)
,以避免在使用查询时出现重复结果。
我得到的错误是:
"PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list\nLINE 1:
我可以使用一些建议来处理这个问题。我需要保留 (distinct: true) 或有避免重复的替代方法。
稍后我也会使用嵌套排序,这是 (distinct: true) 无法使用的,但是可以通过在我的控制器中使用 include 和 join 来解决,所以这不会是一个问题。更多信息请点击此处:Ransack, Postgres - sort on column from associated table with distinct: true
我最初的建议是使用 status
和 priority
列创建一个 Status
模型和 table,然后与现有模型建立正式关系,因为这将允许在未来进行简单的添加和重新排序,而无需更改任何代码。
我代替这个,错误很明显:
for SELECT DISTINCT, ORDER BY expressions must appear in select list
这意味着当使用 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;DRActiveRecord和Arel的解释
你会在上面看到几次Arel
。 Arel
是 rails 的底层查询汇编器,提供了极大的灵活性,允许更多可组合的查询。
每个 ActiveRecord
对象通过名为 arel_table
的方法公开其 Arel::Table
。这部分 arel_table[Arel.star]
将组装为“table_name”。“*”(select 所有内容)
这部分 Arel.sql(order_by_status)
将 return 一个 Arel::Nodes::SqlLiteral
允许我们链接别名(如 as('status_order')
)以及排序(如 .asc
).
我们甚至可以使用 Arel
通过
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
查询接口,例如select
、where
、order
、joins
等,但不可能以提供简单的顶级 DSL 的简单方式公开所有内容;然而,几乎所有这些“顶级”方法都会毫无问题地接受 Arel
参数,从而允许您构建您可能想要的任何查询。如果它是有效的 SQL 那么 Arel
可以构建它。
试试这个:
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
方法产生
并加入原来的 table.
查询应如下所示:
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
表达式中。
P.S。我还没有测试上面的代码。