【问题标题】:Database stores historical price changes, how to calculate price at specific point in time?数据库存储历史价格变化,如何计算特定时间点的价格?
【发布时间】:2016-04-11 04:28:45
【问题描述】:

我正在处理许多不同产品的历史价格数据。

我有一张表products,列出了所有产品及其名称、描述等,并用 uuid 标识它们。然后还有另一个表history,它存储了每个产品的价格变化。价格可能(通常会)每天变化很多次。

现在我想计算每个产品在特定时间点的价格,比如 2015 年 3 月 14 日中午 12 点。如何在 SQL 中做到这一点?

我可以为一种产品做到这一点:

SELECT product_id, price, date 
FROM history 
WHERE product_id = 'aa6d9976-e9ae-4478-486e-097e86c1e5fe' 
AND (date-'2015-03-14 12:00:00+02') < interval '1 second' 
ORDER BY diff DESC LIMIT 1

  ->   aa6d9976-e9ae-4478-486e-097e86c1e5fe    109     2015-03-14 11:55:00+01

但我希望一组查询中的所有产品。我的想法是获取所有产品并将该表与历史记录连接起来,为每个产品选择合适的价格,但我在后者失败了:

SELECT products.product_id, name, price, date 
FROM products 
  LEFT JOIN history ON products.product_id = history.product_id 
WHERE date "is the greatest value that is still somewhat smaller than" '2015-03-14 12:00:00+01'

你如何正确地写出我试图在引号中表达的内容?

我使用 PostgreSQL(虽然我之前主要使用 MySQL)。这些表分别大约有 15000 行(产品)和 5000 万行(历史)。


如果你喜欢一些示例数据:

PRODUCTS

product_id                              name

aa6d9976-e9ae-4478-486e-097e86c1e5fe    One
8da97d50-540e-4fdb-d032-7f443a9869a0    Two
b51654ea-6190-4ed2-5e23-7075ffd3b472    Three


HISTORY

id  product_id                              price   date

1   aa6d9976-e9ae-4478-486e-097e86c1e5fe    100     2015-03-14 09:30:00+01
2   aa6d9976-e9ae-4478-486e-097e86c1e5fe    110     2015-03-14 10:48:00+01
3   b51654ea-6190-4ed2-5e23-7075ffd3b472    9       2015-03-14 11:01:00+01
4   8da97d50-540e-4fdb-d032-7f443a9869a0    49      2015-03-14 11:27:00+01
5   aa6d9976-e9ae-4478-486e-097e86c1e5fe    109     2015-03-14 11:55:00+01
6   b51654ea-6190-4ed2-5e23-7075ffd3b472    8       2015-03-14 13:59:00+01
7   aa6d9976-e9ae-4478-486e-097e86c1e5fe    110     2015-03-14 16:10:00+01
8   8da97d50-540e-4fdb-d032-7f443a9869a0    48      2015-03-14 19:34:00+01
9   8da97d50-540e-4fdb-d032-7f443a9869a0    49      2015-03-14 23:30:00+01
10  aa6d9976-e9ae-4478-486e-097e86c1e5fe    103     2015-03-14 23:33:00+01


DESIRED OUTPUT

id                                      name    price   date

aa6d9976-e9ae-4478-486e-097e86c1e5fe    One     109     2015-03-14 11:55:00+01
8da97d50-540e-4fdb-d032-7f443a9869a0    Two     49      2015-03-14 11:27:00+01
b51654ea-6190-4ed2-5e23-7075ffd3b472    Three   9       2015-03-14 11:01:00+01

【问题讨论】:

  • 如果您存储每个价格的开始结束日期(例如tsrange),此模型可能更容易(并且更有效)。您可以使用排除约束来确保不重叠的价格区间。这可能会使插入更复杂/更慢,但如果读取性能更重要,您应该考虑到这一点。查询就像select * from history where product_id = '...' and valid_during @&gt; timestamp '2015-03-14 12:00:00+02' 一样简单
  • 这不是我的结构,我必须照原样处理它......:/有什么想法吗?
  • @a_horse_with_no_name - 如果我这样做,我可能会计算 end 列(可能是 MQT 或视图)或触发,然后照常插入。以后更难搞砸了。
  • @Clockwork-Muse:是的,如果添加了新价格,您可以更新插入触发器中的结束列。但是通过排除约束,您至少可以确保您永远不会得到重叠的间隔。
  • @a_horse_with_no_name - 是的,这就是我可能会 MQT 的原因 - 保证没有重叠,并且价格“不经常”变化。无论如何,greatest-n-per-group 问题的简单变体 - 现有的answers 应该可以正常工作。

标签: sql database postgresql


【解决方案1】:

使用窗口函数 Lead() 查找给定 product_idnext 对应记录(顺便说一句:我将 date 重命名为 zdatedate 是一个坏名字列,因为它是数据类型的名称)

SELECT h0.* 
FROM history h0
JOIN (
    SELECT  id
    , zdate AS start_date
    , lead(zdate, 1, 'infinity' ) OVER (PARTITION BY product_id
                                        ORDER BY zdate) AS end_date
    FROM history
    ) h1 ON h0.id = h1.id
    AND h1.start_date <= '2015-03-14 12:00:00+01'
    AND h1.end_date > '2015-03-14 12:00:00+01'
    ;

{product_id , zdate} 上的索引可能会有所帮助 ;-)


结果:

 id |              product_id              | price |        zdate        
----+--------------------------------------+-------+---------------------
  4 | 8da97d50-540e-4fdb-d032-7f443a9869a0 |    49 | 2015-03-14 11:27:00
  5 | aa6d9976-e9ae-4478-486e-097e86c1e5fe |   109 | 2015-03-14 11:55:00
  3 | b51654ea-6190-4ed2-5e23-7075ffd3b472 |     9 | 2015-03-14 11:01:00
(3 rows)

【讨论】:

    【解决方案2】:

    首先,您编写一个查询来查找小于您为每个产品查询的日期的最大日期。看起来像这样:

        select product_id, MAX(date) date 
        from history
        where date < '3/14/2015 12:00:00'
        group by product_id
    

    然后您可以将该子查询与您的 productshistory 表连接起来以获得您想要的结果:

    select products.*, history.price, history.date
    from products
    left join
        (
        select product_id, MAX(date) date 
        from history
        where date < '3/14/2015 12:00:00'
        group by product_id
        ) PriceDates
    on products.product_id = PriceDates.product_id
    join history
    on PriceDates.product_id = history.product_id
        and PriceDates.date = history.date
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-19
      • 1970-01-01
      • 2020-06-13
      • 1970-01-01
      • 2019-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多