范围:按两个日期之间的差异排序

时间:2012-12-29 12:38:19

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

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

例如,我有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。
有什么想法吗?

2 个答案:

答案 0 :(得分:2)

简单查询

根据初始问题,第一个示例使用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正弦值是datetimestamp列上的索引。像这样:

CREATE INDEX tbl_my_timestamp_idx ON tbl(my_timestamp);

随着索引到位,下面的查询将 nuke 更大表的简单表现:

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行。并且它不断扩大规模。

答案 1 :(得分:0)

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