我有一组记录,其中一些(但不是全部)有一个“路径”字段,并且都有一个“值”字段。我希望只选择那些没有路径,或者具有特定路径的所有记录的最大值。
也就是说,鉴于这些记录:
Name: Path: Value:
A foo 5
B foo 6
C NULL 2
D bar 2
E NULL 4
我想返回B,C,D和E,但不返回A(因为A有一个路径,它的路径与B相同,但A的值较低)。
如何使用ActiveRecord,ARel和Postgres实现这一目标?理想情况下,我想要一个作为范围的解决方案。
答案 0 :(得分:2)
您可以通过使用2个子查询来使用这样的东西(只会执行一个具有子查询的SQL查询)。没有测试,但应该让你朝着正确的方向。这是为Postgres。
scope :null_ids, -> { where(path: nil).select('id') }
scope :non_null_ids, -> { where('path IS NOT NULL').select('DISTINCT ON (path) id').order('path, value desc, id') }
scope :stuff, -> {
subquery = [null_ids, non_null_ids].map{|q| "(#{q.to_sql})"}.join(' UNION ')
where("#{table_name}.id IN (#{subquery})")
}
如果您使用的是其他数据库,则可能需要对non_nulls范围使用group / order而不是distinct。如果查询运行缓慢,请在路径和值上添加索引。
您只获得1个查询,并且它是可链接的范围。
答案 1 :(得分:1)
将您的描述直接转换为SQL,如下所示:
select name, path, value
from (
select name, path, value,
row_number() over (partition by path order by value desc) as r
from your_table
where path is not null
) as dt
where r = 1
union all
select name, path, value
from your_table
where path is null
您可以将其包裹在find_by_sql
中,并将对象从另一侧取出。
该查询的工作原理如下:
row_number
window function允许我们按path
对行进行分组,按value
对每个组进行排序,然后对每个组中的行进行编号。在psql
内稍微使用SQL,你会看到它是如何工作的,有other window functions available可以让你做各种奇妙的事情。path
值与非NULL path
分开处理,因此内部查询中的path is not null
。path
)来剥离每个where r = 1
组中的第一行。path is null
行。我想不出使用ActiveRecord构建这样一个查询的方法,我也不会想到任何方法将这样的查询与ActiveRecord的范围机制集成。如果您可以轻松访问ActiveRecord::Relation
的WHERE组件,那么您可以使用范围链的WHERE组件来扩充该查询的where path is not null
和where path is null
组件。我不知道怎么做。
事实上,我倾向于放弃ActiveRecord。我发现ActiveRecord对于我做的大多数复杂事情来说都相当麻烦,并且不像SQL那样具有表现力。这适用于我曾经使用的每个ORM,因此问题不是特定于ActiveRecord。
答案 2 :(得分:0)
我没有使用ActiveRecord的经验,但是这里有一个SQLAlchemy的例子,用于沉默刚才使用的SQL群;)
q1 = Session.query(Record).filter(Record.path != None)
q1 = q1.distinct(Record.path).order_by(Record.path, Record.value.desc())
q2 = Session.query(Record).filter(Record.path == None)
query = q1.from_self().union(q2)
# Further chaining, e.g. query = query.filter(Record.value > 3) to return B, E
for record in query:
print record.name