【问题标题】:SQL store rate changing in time and SELECT rate valid for particular time instantSQL 存储速率随时间变化,SELECT 速率对特定时间有效
【发布时间】:2013-03-31 20:10:45
【问题描述】:

在 SQL 表 person_rate 中,我们存储了一个随时间变化的速率浮动值。列:

id (serial, PK)
person_id (int)
date_from (date)
rate (float)

(person_id, date_from) 是独一无二的,因为每天最多允许更改一次(可能是 PK,但这并不重要)

给定 person_id 的速率值在从date_from 日期到具有连续date_from 的下一个记录的时间域内有效,或者如果没有这样的记录,则到无限未来。示例:

id   person_id  date_from  rate
101  1          2011-01-01 100.0
145  1          2012-01-01 180.0
193  1          2012-05-01 140.0

现在我需要一个 SELECT 查询,它为每个 person_id 返回一个 rate,对于某些给定的 $date 有效。假设 2012-03-01 是 180; 2012-05-02 是 140 等等。

我测试过的解决方案:

1) 条件date_from <= $date + 使用窗口函数rank() OVER (PARTITION BY person_id ORDER BY date_from DESC + 在超选中WHERE rank = 1

2) 类似于 1) 但使用 SELECT DISTINCT ON (person_id) 而不是将排名限制为 1

1) 和 2) 都表现不佳,EXPLAIN 表明 db 需要对每个 person_id 的所有记录进行排序,然后首先限制为 1。可能这种查询不能充分利用date_from上的索引?

IDEA - 添加一个date_to 列,这会有点多余,因为该值将是“连续记录的date_from,减去1天”(如果没有连续记录,则为+infty) .但是查询可以使用date_from <= $date AND date_to >= $date - 它可能对date_from 和date_to 上的索引具有良好的性能。

但我有点害怕在这种情况下如何管理数据完整性 - 如何设置一个 person_id 的 [ date_from .. date_to ] 间隔不应重叠的约束?

对于这种类型的查询,postgresql 的最佳解决方案是什么?负载是读取最多的,对 person_rate 表的写入不多。典型的查询需要在内部获取每月每一天的费率......

也许这个SQL query for index/primary key ordinal 与 pg 9.2 上的新索引可以以某种方式提供帮助?

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:

    您可以使用LEAD 生成to_date

    with scd_table (
        select  a.*  , 
                lead(from_date,1,to_date('31/12/9999','dd/mm/yyyy')) over (partition by a order by from_date asc) as to_date
        from    YOUR_TABLE a
    )
    select  * 
    from    scd_table
    where   :d >= from_date
    and     :d < to_date
    

    (这是 oracle 语法,但 lead 是 ANSI 标准)

    我认为您应该重新考虑您的设计 - 寻找缓慢变化的维度表。 有很多关于其他设计的文章。你这样做的方式 - 你总是会获取下一个 from_date 以便只获取一个特定的客户端实例,因此选择有点繁重。

    【讨论】:

    • 感谢“慢慢更改维度表”,这似乎是我所需要的,类似于我解释的 IDEA。非重叠约束可能可以使用插入触发器来实现。
    • 触发器设计不佳。你需要问自己一个重要的问题——你的系统是在做更多的 DML 还是选择?如果您的系统生成更多 DML,您可能想要放弃 to_date,因为每个更新/插入都需要两次更新(当前行和前一个的 to_date)。如果您的系统进行更多选择 - 您可能需要考虑在插入期间支付罚款以简化选择。我会给你一个经验法则 - 如果你有触发器、同义词或太多视图 - 你做错了一些事情
    • 与选择相比,几乎没有更新/插入。所以两次更新肯定没问题。我认为触发器可能仅用于检查一致性约束,例如不允许插入与另一个重叠的 [from, to] 间隔。
    • 我会在插入过程中在应用程序中执行此操作。触发器实际上应该仅用于修补,也许是登录触发器(但这只是我)。你有一个名为overlaps 的未记录函数。我不建议你使用它,而是阅读它,因为找到重叠可能比你想象的要棘手(从经验中谈)。
    【解决方案2】:

    使用这个简单的查询:

    SELECT person_id, date_from, rate FROM person_rate where date_from in (SELECT MAX(date_from) FROM person_rate WHERE date_from <= 'provided Date' and person_id = provided_id) 
    

    获取所有人员费率使用。

    SELECT a.person_id, a.date_from, a.rate FROM person_rate a JOIN (SELECT person_id, MAX(date_from) as date_from FROM person_rate where date_from <= 'provided Date') b ON(a.date_from = b.date_from and a.person_id = b.person_id)
    

    【讨论】:

      【解决方案3】:

      SQL Fiddle

      select distinct on (person_id) person_id, date_from, rate
      from person_rate
      where date_from <= '2012-03-01'
      order by person_id, date_from desc
      

      如果 (person_id, date_from) 是唯一的,则创建该索引:

      create table person_rate (
          id serial primary key,
          person_id int,
          date_from date,
          rate float,
          unique (person_id, date_from)
      );
      

      如果那已经是生产表,请更改它:

      alter table person_rate add 
      constraint constraint_name unique (person_id, date_from);
      

      不要忘记在那之后运行analyze person_rate。如果满足正确的条件,它将仅使用索引。这包括一张足够大的桌子。

      【讨论】:

        【解决方案4】:
        SELECT person_id, rate FROM person_rate WHERE date_from <= '2012-05-02' ORDER BY date_from DESC LIMIT 0,1;
        

        LIMIT 的第二个数字是您要使用的最大 person_id(例如,前 5 个 person_id 的 LIMIT 0,5)和日期在这里:date_from

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-10-15
          • 1970-01-01
          • 2021-08-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-28
          相关资源
          最近更新 更多