【问题标题】:Is there SQL Logic to reduce type 2 table along a dimension是否有 SQL Logic 沿维度减少类型 2 表
【发布时间】:2021-06-05 17:37:33
【问题描述】:

我有一个缓慢变化的类型 2 价格变化表,我需要减小它的大小以提高性能。即使没有发生价格变化(当某些其他维度字段发生变化时),通常也会将行写入表中,结果是对于任何产品,如果表仅包含价格变化,则表可能是所需大小的 3-10 倍.

我想压缩表格,使其仅包含每个价格的第一个生效日期和最后一个到期日期,直到该价格也可以更改

  • 处理未知数量的同价行
  • 处理恢复到旧价格的产品

作为一个例子,如果我有这个原始数据:

Product Price Effective Date Price Expiration Date Price
123456 6/22/18 9/19/18 120
123456 9/20/18 11/8/18 120
123456 11/9/18 11/29/18 120
123456 11/30/18 12/6/18 120
123456 12/7/18 12/19/18 85
123456 12/20/18 1/1/19 85
123456 1/2/19 2/19/19 85
123456 2/20/19 2/20/19 120
123456 2/21/19 3/19/19 85
123456 3/20/19 5/22/19 85
123456 5/23/19 10/10/19 85
123456 10/11/19 6/19/19 80
123456 6/20/20 12/31/99 80

我需要把它变成这样:

Product Price Effective Date Price Expiration Date Price
123456 6/22/18 12/6/18 120
123456 12/7/18 2/19/19 85
123456 2/20/19 2/20/19 120
123456 2/21/19 10/10/19 85
123456 10/11/19 12/31/99 80

【问题讨论】:

  • 用您正在使用的数据库标记您的问题。

标签: sql type-2-dimension


【解决方案1】:

您可以先找到价格不变的区间,然后对这些区间进行分组:

with to_r as (select row_number() over (order by (select 1)) r, t.* from data_table t),
     to_group as (select t.*, (select sum(t1.r < t.r and t1.price != t.price) from to_r t1) c from to_r t)
select t.product, min(t.effective), max(t.expiration), max(t.price) from to_group t group by t.c order by t.r;

输出:

Product Price Effective Date Price Expiration Date Price
123456 6/22/18 12/6/18 120
123456 12/7/18 2/19/19 85
123456 2/20/19 2/20/19 120
123456 2/21/19 10/10/19 85
123456 10/11/19 12/31/99 80

【讨论】:

    【解决方案2】:

    这是一种孤岛问题。我建议重建数据,将其保存在临时表中,然后重新加载现有表。

    重构数据的代码是:

    select product, price, min(effective_date), max(expiration_date)
    from (select t.*,
                 sum(case when prev_expiration_date = effective_date - interval '1 day' then 0 else 1 end) over (partition by product order by effective_date) as grp
          from (select t.*,
                       lag(expiration_date) over (partition by product, price order by effective_date) as prev_expiration_date
                from t
               ) t
         ) t
    group by product, price, grp;
    

    请注意,日期运算的逻辑因数据库而异。

    使用select into、create table as 或您的数据库支持的任何内容将此结果保存到一个临时表中,temp_t 或其他。

    然后清空当前表并重新加载:

    truncate table t;
    
    insert into t
        select product, price, effective_date, expiration_date
        from temp_t;
    

    注意事项:

    • 在使用truncate_table之前验证数据!
    • 如果存在具有默认值的触发器或列,您可能需要小心。

    【讨论】:

    • 获取grp字段的函数正是我所需要的,谢谢!
    【解决方案3】:

    听起来您要求的是时间模式?在给定日期,您可以在哪里知道资产的价格?

    这是通过两个表完成的; price_current 和 price_history。

    price_id item_id price rec_created
    1 1 100 '2015-04-18'
    price_id item_id from to price
    1 1 '2001-01-01' '2004-05-01' 114
    1 1 '2004-05-01' '2015-04-18' 102

    即对于任何项目,您都可以确定它的设置日期,而不会污染您的“当前”表。为了有效地工作,您需要在 current_table 上有 UPDATE 触发器。当您更新一条记录时,您会在历史记录表中插入详细信息以及它的有效期。

    CREATE OR REPLACE TRIGGER trg_price_current_update
    AS
    BEGIN
        INSERT INTO price_history(price_id, item_id, from, to, price)
        SELECT price_id, item_id, rec_created, GETDATE(), price
        FROM rows_updated
    END
    

    现在您可以区分当前表和历史表,而您的当前表(可能是更繁忙的表)不会因为维护历史状态而失控。希望我理解这个问题。

    要忽略“虚拟”更新,只需更改触发器以忽略空更改(如果 DBMS 无论如何都没有处理)。 Tbh,这应该并且可以很容易地在应用程序端完成,但要通过触发器进行管理:

    CREATE OR REPLACE TRIGGER trg_price_current_update
    AS
    BEGIN
        INSERT INTO price_history(price_id, item_id, from, to, price)
        SELECT price_id, item_id, rec_created, GETDATE(), price
        FROM rows_updated u
        INNER JOIN price_current ON u.price_id = p.price_id
        WHERE u.price <> p.price
    END
    

    即rows_updated 包含更新的记录,我们将前一行插入历史表中,前提是前一行的价格与当前行的价格不同。

    (已编辑以包含新触发器。我还更改了 rec_created 中保存的日期,这必须是创建行的日期,而不是产品分配价格的第一个实例。这是一个错误。关于日期,我懒得放完整的 DD-MM-YYYY hh:mm:ss:zzz,但这通常在查询之间很有用)

    【讨论】:

    • 我不明白您所描述的内容与我已有的当前表格有何不同。那已经有所有的价格历史了,我需要把它变小。我也不能只获取当前价格,因为我需要将其加入历史交易表以获取交易时的售价。
    • 不同之处在于 a) 两个表不是一个。 b) 不包含冗余记录 c) 使处理临时查询变得更加简单,例如在交易时获取售价
    • 重读我的问题我应该更清楚,我有当前表,看不到它的上游数据或编辑它。我需要一些逻辑来从第一个到第二个,而不是重新设计架构。
    • 这无法解决我的第一个问题。此逻辑将在 120 和 85 价格中包含从 12/7 到 2/19 的范围,无法区分哪个是正确的。
    • 查看我的最新答案;基本上确定每条记录,如果有效 -1 有不同的价格,或过期 + 1 有不同的价格。选择所有这些行 UNION ALL 查找该产品生效日期最早的行。
    【解决方案4】:

    您要求的是版本控制系统。许多 RDBMS 平台实现了开箱即用的支持(这是一个 SQL 标准),这可能是合适的,具体取决于您的要求。

    您尚未标记特定平台,因此无法针对您的具体情况。我在 MS Sql Server 中定期使用系统版本控制的概念,您可以在其中实现它:

    假设架构“history”存在,

    alter table dbo.MyTable add
            ValidFrom datetime2 generated always as row start hidden constraint DF_MyTableSysStart default sysutcdatetime(),
            ValidTo datetime2 generated always as row end hidden constraint DF_MyTableSysEnd default convert(datetime2, '9999-12-31 23:59:59.9999999'),
        period for system_time (ValidFrom, ValidTo);
    end
    alter table MyTable set (system_versioning = on (history_table = History.MyTable));
    
    create clustered index ix_MyTable on History.MyTable (ValidTo, ValidFrom) with (data_compression = page,drop_existing=on) on History;
    

    存在许多语法扩展来帮助查询时间数据,例如查找某个时间点的历史数据。

    或者,要使用单个表但处理重复,您可以创建一个instead of 触发器。

    这里的想法是触发器在插入数据之前拦截数据,您可以在其中检查该值是否与最后一个值不同,并酌情丢弃或插入。

    【讨论】:

      【解决方案5】:

      类似的东西:

      WITH keeps AS
      (
      SELECT p.product_id, p.effective, p.expires, p.price, CASE WHEN EXISTS(SELECT 1 FROM prices p1 WHERE p1.effective = DATEADD(DAY, p.exires, 1) AND p1.price <> p.price) THEN 1 ELSE 0 END AS has_after, CASE WHEN EXISTS(SELECT 1 FROM prices p1 WHERE p1.expires = DATEADD(DAY, p.effective, -1) AND p1.price <> p.price) THEN 1 ELSE 0 END AS has_before
      FROM prices p
      )
      SELECT * FROM keeps
      WHERE has_after = 1
      OR has_before = 1
      UNION ALL
      SELECT p.product_id, p.effective, p.exires, p.price
      FROM prices p
      WHERE p.effective = (SELECT MIN(effective) FROM prices p1 WHERE p1.product_id = p.product_id)
      

      它在做什么:

      查找所有存在另一个条目的条目,该条目的生效日期是前一个条目的到期日期+1,并且该新条目的价格不同。这为我们提供了价格的所有实际变化。但是我们错过了第一个价格条目,因此我们只是将其包含在结果中。

      例如:

      product_id effective expires price has_before has_after
      123456 6/22/18 9/19/18 120 0 0
      123456 9/20/18 11/8/18 120 0 0
      123456 11/9/18 11/29/18 120 0 0
      123456 11/30/18 12/6/18 120 0 1
      123456 12/7/18 12/19/18 85 1 0
      123456 12/20/18 1/1/19 85 0 0
      123456 2/1/19 2/19/19 85 0 1
      123456 2/20/19 2/20/19 120 1 1
      123456 2/21/19 3/19/19 85 1 0

      【讨论】:

      • 查询子查询可能比查询子查询更好:SELECT p.product_id, p.effective, p.expires, p.price, CASE WHEN p1.product_id IS NULL THEN 1 ELSE 0 END AS is_key_row FROM prices p LEFT JOIN (SELECT * FROM prices) p1 ON p.product_id = p1.product_id AND (p1.effective = DATEADD(DAY, p.expires, 1) OR p1.expires = DATEADD(DAY, p.有效,-1)) UNION ALL SELECT p.product_id, p.effective, p.exires, p.price 从价格 p WHERE p.effective = (SELECT MIN(effective) FROM prices p1 WHERE p1.product_id = p.product_id)
      猜你喜欢
      • 2016-05-11
      • 1970-01-01
      • 2013-04-15
      • 2018-12-09
      • 2022-10-14
      • 1970-01-01
      • 2014-08-11
      • 2015-07-27
      • 2018-09-06
      相关资源
      最近更新 更多