postgresql 9.5 - 跟踪值随时间的变化

postgresql 9.5 - tracking changes to a value over time

我想以一种很好的方式跟踪网站上某件商品随时间的价格。一种选择是 table 像

create table prices (
     name text,
     price decimal,
     updated timestamp
)

每次我更新价格时,都会在此 table 中创建一个新行。 但是,我认为这种方法在我的情况下不是很'nice',原因如下:

  1. 我在任何给定时间跟踪几 1000 个项目,
  2. 我每 5 分钟左右更新一次价格,并且
  3. 价格通常不会经常变化,有时甚至根本不会变化。

由于这些原因,对于特定商品,例如 dove bodywash,我的价格 table 可能包含 200 行,看起来都像

'dove soap' | 3.00 | <a new timestamp every 5 minutes>

这似乎有点可笑。 在我看来,一个更好的解决方案是添加一个额外的 price_history table 来存储商品具有特定价格的时间范围。例如,price_history 可能有列

name | price | created_at | updated_at 

并且期望的行为是每当我更新 dove soap 的价格时,都会有一个触发器自动检查价格是否已更改 - 如果没有,则只需更改prices_history 中相应条目的 updated_at,如果有,则在 prices_history 中为新价格创建一个新行。作为执行的一个例子,我想要:

1) 在 time1 我做了 insert into prices ('dove soap', 3.00)。此时,price_history table 将包含一行

|'dove soap' | 3.00 | time1 | null |

2) 我在时间 2 update prices set price = 3.00 where name = 'dove soap'。现在 price_history table 看起来像

'dove soap' | 3.00 | time1 | time2 

3) 在时间 3 价格仍然是 3.00。 price_history 应该像

'dove soap' | 3.00 | time1| time3 

4) 在时间 4 的价格是 3.50。 price_history 现在应该看起来像:

'dove soap' | 3.00 | time1 | time3 
'dove soap' | 3.50 | time4 | null

我的问题是

  1. 我不确定这是否是解决此问题的好方法,
  2. 我不是 100% 确定实现它的好方法是什么。

非常感谢任何关于以上任何一点的建议!

谢谢 :-)

编辑:我应该包括我看过的一件事是 temporal_tables postgresql 扩展,它使用类似的 prices/ price_history/ 设置。它的问题是它似乎会在每次更新价格时在 price_history table 中创建一个新行,如果它没有改变,这就违背了目的。在我看来,没有办法修改此默认行为,但如果有人知道,请告诉我!

这是一个可行的设计,使用一个 table 和一个视图...我做了几个假设,即您并不真正关心跟踪上次更新时间(但请参阅下文),并且最新条目的结束时间是 2999-12-31 23:59:59。 (您可以将其留空,但我不喜欢空值并且其中有日期意味着您可以在查询之间执行...)。

创建 price_history_table:

create table price_history(

article_id integer, -- I like using article ids
article_name text,  -- I don't like using reserved words for columns
price decimal not null,
start_time timestamp not null,
end_time timestamp not null default '2999-12-31 23:59:59')

(如果您不想使用 article_id,请将下面的 article_id 替换为 article_name,尽管您可以考虑将您的项目描述存储在单独的 table 并仅将 id 存储在 "big" table 中。在磁盘上占用更少 space 并减少一列写入)。

在 article_id 和结束时间上创建唯一约束:

alter table price_history add constraint article_id_end_time unique  (article_id,end_time)

... 以及 article_id 和 start_time

上的主键
alter table price_history add constraint pk_price_history primary key (article_id,start_time);

我认为拥有这些约束以防止您将垃圾输入 table 很重要,因为重复的时间会破坏您的逻辑。

现在触发功能。如果价格没有改变,触发器什么都不做,否则它会将最后一条记录的 end_time 更新为新的 start_time.

CREATE FUNCTION update_enddate()
    RETURNS trigger
    LANGUAGE 'plpgsql'
    COST 100.0
    VOLATILE NOT LEAKPROOF 
AS $BODY$

BEGIN



    if EXISTS (select * from price_history where article_id = NEW.article_id AND end_time ='2999-12-31 23:59:59'::timestamp AND price = NEW.price) THEN
    -- the price hasn't changed, don't do anything

    RETURN NULL;

    ELSE --Set the end date to the new startdate
            update price_history set end_time = NEW.start_time where article_id = new.article_id AND end_time ='2999-12-31 23:59:59'::timestamp;
         RETURN NEW;   
    END IF;



    END;

$BODY$;

还有触发器本身。

CREATE TRIGGER trigger_update_enddate BEFORE INSERT on price_history FOR EACH ROW EXECUTE PROCEDURE update_enddate();

以及最近记录的视图。

 CREATE VIEW prices AS
   SELECT article_id,article_name,price,start_time from price_history where end_time  ='2999-12-31 23:59:59'::timestamp;

如果您想了解给定更新的价格是否发生变化,您可以尝试

SELECT * from price_history where start_time <= mytime and end_time > mytime;

请注意,您需要小心处理 "between" 查询,因为它们包含起点和终点,如果您的时间恰好与 start_time 匹配,您可能会得到重复的结果。

start_time 等于价格上次更改的时间。您可以将更新时间存储在不同的 table 中,只需加入 start_time <= update_time 和 end_time > update_time 即可获得 "full history".

如果您不断添加记录,不确定索引的性能,因此如果您没有索引,您可能会获得更好的性能。