【问题标题】:Postgres Common Table Expression query with Ruby on Rails使用 Ruby on Rails 进行 Postgres 公用表表达式查询
【发布时间】:2014-12-15 19:46:57
【问题描述】:

我正在努力寻找在 Rails 应用程序中使用公用表表达式进行 Postgres 查询的最佳方法,因为我知道 ActiveRecord 显然不支持 CTE。

我有一个名为user_activity_transitions 的表,其中包含一系列正在启动和停止的用户活动记录(每一行指的是状态变化:例如启动或停止)。

一个user_activity_id 可能有很多夫妻开始-停止,它们在 2 个不同的行中。 如果活动当前正在进行并且尚未停止,也可能只有“开始”。 sort_key 从第一个状态的 0 开始,每次状态更改都会增加 10。

id      to_state     sort_key     user_activity_id    created_at
1       started      0            18                  2014-11-15 16:56:00
2       stopped      10           18                  2014-11-15 16:57:00
3       started      20           18                  2014-11-15 16:58:00
4       stopped      30           18                  2014-11-15 16:59:00
5       started      40           18                  2014-11-15 17:00:00

我想要的是以下输出,将几对开始-停止组合在一起以便能够计算持续时间等。

user_activity_id     started_created_at      stopped_created_at
18                   2014-11-15 16:56:00     2014-11-15 16:57:00
18                   2014-11-15 16:58:00     2014-11-15 16:59:00
18                   2014-11-15 17:00:00     null

表的实现方式使得运行该查询变得更加困难,但对于未来的变化(例如新的中间状态)更加灵活,因此不会进行修改。

我的 Postgres 查询(以及 Rails 中的相关代码):

query = <<-SQL
    with started as (
    select 
        id,
        sort_key,
        user_activity_id,
        created_at as started_created_at
    from
        user_activity_transitions
    where  
        sort_key % 4 = 0
    ), stopped as (
    select 
        id,
        sort_key-10 as sort_key2,
        user_activity_id,
        created_at as stopped_created_at
    from
    user_activity_transitions
    where
        sort_key % 4 = 2
    )
    select
        started.user_activity_id AS user_activity_id,
        started.started_created_at AS started_created_at,
        stopped.stopped_created_at AS stopped_created_at
    FROM
        started
    left join stopped on stopped.sort_key2 = started.sort_key
    and stopped.user_activity_id = started.user_activity_id
SQL

results = ActiveRecord::Base.connection.execute(query)

它的作用是根据排序键的模数检查“欺骗”SQL 来连接 2 个连续的行。

查询工作正常。但是使用这个原始的 AR 调用让我很恼火,特别是因为 connection.execute 返回的内容非常混乱。我基本上需要遍历结果并将其放入正确的哈希中。

2 个问题:

  1. 有没有办法摆脱 CTE 并运行相同的查询使用 Rails 魔法?
  2. 如果没有,有没有更好的方法来获得我想要的漂亮哈希结果?

请记住,我是 Rails 的新手,而不是查询专家,所以可能会有明显的改进......

非常感谢!

【问题讨论】:

标签: ruby-on-rails postgresql ruby-on-rails-4 common-table-expression


【解决方案1】:

虽然 Rails 不直接支持 CTE,但您可以模拟单个 CTE 并仍然利用 ActiveRecord。使用 from 子查询代替 CTE。

Thing
  .from(
    # Using a subquery in place of a single CTE
    Thing
      .select(
        '*',
        %{row_number() over(
            partition by
              this, that
            order by
              created_at desc
          ) as rank
        }
      )
    :things
  )
  .where(rank: 1)

这不完全一样,而是等价于...

with ranked_things as (
  select
    *,
    row_number() over(
      partition by
        this, that
      order by
        created_at desc
    ) as rank
)
select *
from ranked_things
where rank = 1

【讨论】:

    【解决方案2】:

    我正在尝试寻找在 Rails 应用程序中使用公用表表达式进行 Postgres 查询的最佳方法,因为我知道 ActiveRecord 显然确实支持 CTE。

    据我所知 ActiveRecord 不支持 CTE。 AR 在底层使用的 Arel 支持它们,但它们不会暴露给 AR 的界面。

    有没有办法摆脱 CTE 并使用 Rails 魔法运行相同的查询?

    不是真的。您可以在 AR 的 API 中编写它,但只需将相同的 SQL 拆分为几个方法调用即可。

    如果没有,有没有更好的方法可以在漂亮的哈希中获得我想要的结果?

    我尝试运行查询并且得到以下内容,这对我来说似乎已经足够好了。你得到了不同的结果吗?

    [
      {"user_activity_id"=>"18", "started_created_at"=>"2014-11-15 16:56:00", "stopped_created_at"=>"2014-11-15 16:57:00"},
      {"user_activity_id"=>"18", "started_created_at"=>"2014-11-15 16:58:00", "stopped_created_at"=>"2014-11-15 16:59:00"},
      {"user_activity_id"=>"18", "started_created_at"=>"2014-11-15 17:00:00", "stopped_created_at"=>nil}
    ]
    

    我假设您有一个名为 UserActivityTransition 的模型用于处理数据。您也可以使用模型来获得结果。

    results = UserActivityTransition.find_by_sql(query)
    results.size # => 3
    results.first.started_created_at # => 2014-11-15 16:56:00 UTC
    

    请注意,这些“虚拟”属性在检查结果时将不可见,但它们就在那里。

    【讨论】:

    • 谢谢 - 我拼错了“确实支持 CTE”,我的意思是不支持。您的意思是我可以以任何方式使用 Arel 来重写查询吗?我的数组与您的数组略有不同:我得到重复的结果,例如 {"user_activity_id"=&gt;18, "started_created_at"=&gt;"2014-11-15 16:56:50.136922", "stopped_created_at"=&gt;"2014-11-15 16:57:02.552191", 0=&gt;18, 1=&gt;"2014-11-15 16:56:50.136922", 2=&gt;"2014-11-15 16:57:02.552191"}。我可以忍受它,但有两倍的结果看起来很奇怪。
    • @Davor 您可以使用 Arel,但老实说,我认为这不会有太大的改进,因为 Arel 也可能非常冗长,而且我通常发现原始 SQL 对于更长的查询更具可读性。您当然可以尝试,this 应该会有所帮助。您是否尝试直接使用该模型?在我看来,这是迄今为止最好的方式。
    • 非常感谢!我会检查你的链接,但可能会按照你的建议去做,并坚持使用原始 SQL。
    猜你喜欢
    • 1970-01-01
    • 2019-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多