【问题标题】:Scope: Sort by difference between two dates范围:按两个日期之间的差异排序
【发布时间】:2012-12-29 12:38:18
【问题描述】:

我想创建一个范围,从最近的日期到最远的日期对数据进行排序。

例如我有 3 个值:

<Value id: 1, date: '2012-12-20'>
<Value id: 2, date: '2012-12-28'>
<Value id: 3, date: '2012-12-31'>

然后我想对最接近给定日期的日期进行排序:2012-12-29
结果我应该有这个订单:2, 3, 1.
如果我选择2012-12-30,结果必须是:3, 2, 1

我尝试过这样的事情:

scope :order_by_closest_date, lambda{|time| 
  select("*, (date - DATE('#{time}')) AS time").order("time ASC")
}

但它不起作用。
有关信息:Rails 3.2.9 Ruby 1.9.3 Postgresql 9.1.4。
有什么想法吗?

【问题讨论】:

    标签: sql ruby-on-rails ruby ruby-on-rails-3 postgresql


    【解决方案1】:

    简单查询

    根据初始问题,第一个示例使用 date 列。

    不确定 Ruby 语法,但正确的 SQL 语句应该是:

    SELECT * 
    FROM   tbl
    ORDER  BY @(date_col - '2012-12-29'::date)
    

    @ being the "absolute value" operator.

    切勿使用datetime 作为标识符。虽然在 PostgreSQL 中被允许(有一些限制),但它们是 reserved words in the SQL standard,它会导致令人困惑的错误消息和可能的意外错误。

    卓越的性能

    根据评论中的更新,其余部分与 timestamp 列一起使用。

    对于小型表或即席查询,上述解决方案就可以了。对于中型或大型表,如果性能很重要,我建议采用更复杂的方法。

    Condicio sine qua non 是datetimestamp 列上的索引。像这样:

    CREATE INDEX tbl_my_timestamp_idx ON tbl(my_timestamp);
    

    在索引到位后,以下查询将影响简单查询对较大表的性能:

    SELECT *
    FROM  (
        (
        SELECT *
        FROM   tbl
        WHERE  my_timestamp >= '2012-12-30 11:32'::timestamp
        ORDER  BY my_timestamp
        LIMIT  3
        )
    
        UNION ALL
        (
        SELECT *
        FROM   tbl
        WHERE  my_timestamp < '2012-12-30 11:32'::timestamp
        ORDER  BY my_timestamp DESC
        LIMIT  3
        )
        ) x
    ORDER  BY @extract('epoch' FROM (my_timestamp - '2012-12-28 11:32'::timestamp))
    LIMIT  3;
    
    • UNION ALL - SELECT 的两条腿周围的括号不是可选的。需要为每条腿申请LIMIT

    • 如果您按其他列排序,请将其反映在您的索引中 - 在这种情况下使用 multi-column index

    怎么会?

    第一个查询使用表达式作为条件。 Postgres 必须为每一行计算一个值,然后按结果排序并选择前几行。小桌子没问题,但大桌子非常昂贵。 O(n); n 是表中的行数。它不能使用普通索引。加上在所有行中排序和挑选获胜者的一些不小的成本。
    可以在表达式上创建一个索引,这将是最快的,但这只适用于一个恒定的时间戳来比较 - 这几乎不是一个现实的用例。

    第二个查询根据您在索引中的时间戳找到位置,顺序读取接下来几行的元组指针并直接从表中获取它们(或者甚至直接从索引中获取,在 9.2 中使用仅索引扫描)。两次,一次上升,一次下降,因为我们不知道同行如何比较。但这只是 2 x O(log(n)) (typical b-tree look-up cost) 仅对少数预先选择的行进行计算。从小样本中挑选获胜者的成本不变。

    只需使用EXPLAIN ANALYZE 进行测试。在对真实表格的快速测试中,我得到了 1000 倍的 50k 行表格。并且它不断扩大以适应更大的桌子。

    【讨论】:

    • 它适用于日期。但是我有时间戳,所以我不得不像这样修改ORDER BY @date_part('epoch',(my_timestamp - '2012-12-28 11:32'::timestamp));
    • @CupraR_On_Rails:我明白了。如果性能很重要,您可能会对我在回答中添加的内容感兴趣。
    【解决方案2】:

    试试

    scope :order_by_closest_date, lambda{|time| 
      select("*, DATEDIFF(date,DATE('#{time}')) AS time").order("time ASC")
    }
    

    【讨论】:

    猜你喜欢
    • 2013-04-12
    • 2019-04-17
    • 2011-10-29
    • 1970-01-01
    • 1970-01-01
    • 2016-05-05
    • 1970-01-01
    • 2014-02-12
    • 2015-09-14
    相关资源
    最近更新 更多