1

我有一组记录,其中一些(但不是全部)具有“路径”字段,并且都具有“值”字段。我希望只选择那些没有路径,或者在具有特定路径的所有记录中具有最大值的那些。

也就是说,鉴于这些记录:

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 来实现这一点?理想情况下,我想要一个充当范围的解决方案。

4

3 回答 3

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 on。如果查询运行缓慢,请在路径和值上放置索引。

你只得到 1 个查询,它是一个可链接的范围。

于 2013-03-03T13:43:09.947 回答
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

您可以将其包装在 a 中find_by_sql,然后将您的对象从另一侧取出。

该查询的工作方式如下:

  1. row_number 窗口函数允许我们按 对行进行分组,path按 对每个组进行排序value,然后对每个组中的行进行编号。在里面玩一下 SQL psql,你会看到它是如何工作的,还有其他可用的窗口函数可以让你做各种美妙的事情。
  2. 您将 NULLpath值与非 NULL 分开处理path,因此path is not null在内部查询中。
  3. path我们可以通过从派生表中选择行号为 1(即where r = 1)的行来剥离每个组中的第一行。
  4. 部分查询很容易处理path is null行的处理。
  5. UNION 用于将查询的结果集连接在一起。

我想不出任何方法来使用 ActiveRecord 构造这样的查询,也想不出任何方法将这样的查询与 ActiveRecord 的作用域机制集成。如果您可以轻松地仅访问 an 的 WHERE 组件,ActiveRecord::Relation那么您可以使用范围链的 WHERE 组件来扩充该查询的where path is not nullandwhere path is null组件。我不知道该怎么做。

事实上,我倾向于放弃 ActiveRecord。我发现 ActiveRecord 对于我所做的大多数复杂的事情都相当麻烦,而且几乎不像 SQL 那样富有表现力。这适用于我曾经使用过的每个 ORM,因此问题并不特定于 ActiveRecord。

于 2013-03-03T06:38:59.290 回答
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
于 2013-03-03T08:49:17.040 回答