gpt4 book ai didi

sql - 仅基于更改日志数据重建历史表行

转载 作者:行者123 更新时间:2023-12-01 09:42:01 25 4
gpt4 key购买 nike

我正在研究一些复杂的销售分析,这非常复杂……而且很无聊……

所以对于这个问题,我将使用一个有趣的、甜蜜的比喻:自动售货机。
但是我的实际表格的结构方式相同。
(您可以假设有很多索引、约束等。)

  • 基本表 #1 - 库存

  • 假设我们有一个包含自动售货机库存数据的表。
    这张表简单地显示了目前每台自动售货机中每种类型的糖果有多少。

    我知道,通常情况下,会有一个 ITEM_TYPE包含“士力架”、“银河”等行的表,但由于多种原因,我们的表不是这样构造的。
    实际上,它不是产品数量,而是预先汇总的销售数据:“管道总计”、“预测总计”等。
    因此,我必须使用一个简单的表格,其中包含针对不同“类型”总计的单独列。

    对于此示例,我还添加了一些文本列,以证明我必须考虑各种数据类型。
    (这使事情复杂化。)

    除了 ID ,所有列都可以为空 - 这是一个真正的问题。
    就我们而言,如果列是 NULL ,然后 NULL是我们需要用于分析和报告的官方值。

    enter image description here
    CREATE table "VENDING_MACHINES" (
    "ID" NUMBER NOT NULL ENABLE,
    "SNICKERS_COUNT" NUMBER,
    "MILKY_WAY_COUNT" NUMBER,
    "TWIX_COUNT" NUMBER,
    "SKITTLES_COUNT" NUMBER,
    "STARBURST_COUNT" NUMBER,
    "SWEDISH_FISH_COUNT" NUMBER,
    "FACILITIES_ADDRESS" VARCHAR2(100),
    "FACILITIES_CONTACT" VARCHAR2(100),

    CONSTRAINT "VENDING_MACHINES_PK" PRIMARY KEY ("ID") USING INDEX ENABLE
    )
    /

    示例数据:
    INSERT INTO VENDING_MACHINES (ID, SNICKERS_COUNT, MILKY_WAY_COUNT, TWIX_COUNT,
    SKITTLES_COUNT, STARBURST_COUNT, SWEDISH_FISH_COUNT,
    FACILITIES_ADDRESS, FACILITIES_CONTACT)
    SELECT 225, 11, 15, 14, 0, NULL, 13, '123 Abc Street', 'Steve' FROM DUAL UNION ALL
    SELECT 349, NULL, 7, 3, 11, 8, 7, NULL, '' FROM DUAL UNION ALL
    SELECT 481, 8, 4, 0, NULL, 14, 3, '1920 Tenaytee Way', NULL FROM DUAL UNION ALL
    SELECT 576, 4, 2, 8, 4, 9, NULL, '', 'Angela' FROM DUAL
  • 基本表 #2 - 更改日志

  • 自动售货机将定期连接到数据库并更新其库存记录。
    也许他们每次有人买东西时都会更新,或者他们每 30 分钟更新一次,或者他们只在有人补充糖果时更新 - 老实说这无关紧要。

    重要的是,每当 VENDING_MACHINES 中的记录更新时表,执行触发器将每个单独的更改记录在单独的日志表中 VENDING_MACHINES_CHANGE_LOG .
    这个触发器已经写好了,而且效果很好。
    (如果一列使用已经存在的相同值“更新”,则触发器应忽略更改。)

    为在 VENDING_MACHINES 中修改的每一列记录一个单独的行。表(除了 ID )。
    因此,如果在 VENDING_MACHINES 中插入一个全新的行表(即新的自动售货机),将在 VENDING_MACHINES_CHANGE_LOG 中记录八行表 - VENDING_MACHINES 中的每个非 ID 列对应一个表.

    (在我的真实场景中,有 90 多个列被跟踪。
    但通常在任何给定时间只更新一两列,因此不会失控。)

    此“更改日志”旨在成为 VENDING_MACHINES 的永久历史记录。表,所以我们不会创建外键约束 - 如果从 VENDING_MACHINES 中删除了一行我们希望在变更日志中保留孤立的历史记录。
    另外,Apex 不支持 ON UPDATE CASCADE (?),所以触发器必须检查 ID 的更新列,并在整个相关表(例如更改日志)中手动传播更新。

    enter image description here
    CREATE table "VENDING_MACHINE_CHANGE_LOG" (
    "ID" NUMBER NOT NULL ENABLE,
    "CHANGE_TIMESTAMP" TIMESTAMP(6) NOT NULL ENABLE,
    "VENDING_MACHINE_ID" NUMBER NOT NULL ENABLE,
    "MODIFIED_COLUMN_NAME" VARCHAR2(30) NOT NULL ENABLE,

    "MODIFIED_COLUMN_TYPE" VARCHAR2(30) GENERATED ALWAYS AS
    (CASE "MODIFIED_COLUMN_NAME" WHEN 'FACILITIES_ADDRESS' THEN 'TEXT'
    WHEN 'FACILITIES_CONTACT' THEN 'TEXT'
    ELSE 'NUMBER' END) VIRTUAL NOT NULL ENABLE,

    "NEW_NUMBER_VALUE" NUMBER,
    "NEW_TEXT_VALUE" VARCHAR2(4000),

    CONSTRAINT "VENDING_MACHINE_CHANGE_LOG_CK" CHECK
    ("MODIFIED_COLUMN_NAME" IN('SNICKERS_COUNT', 'MILKY_WAY_COUNT', 'TWIX_COUNT',
    'SKITTLES_COUNT', 'STARBURST_COUNT', 'SWEDISH_FISH_COUNT',
    'FACILITIES_ADDRESS', 'FACILITIES_CONTACT')) ENABLE,

    CONSTRAINT "VENDING_MACHINE_CHANGE_LOG_PK" PRIMARY KEY ("ID") USING INDEX ENABLE,

    CONSTRAINT "VENDING_MACHINE_CHANGE_LOG_UK" UNIQUE ("CHANGE_TIMESTAMP",
    "VENDING_MACHINE_ID",
    "MODIFIED_COLUMN_NAME") USING INDEX ENABLE

    /* No foreign key, since we want this change log to be orphaned and preserved.
    Also, apparently Apex doesn't support ON UPDATE CASCADE for some reason? */
    )
    /

    更改日志示例数据:
    INSERT INTO VENDING_MACHINE_CHANGE_LOG (ID, CHANGE_TIMESTAMP, VENDING_MACHINE_ID,
    MODIFIED_COLUMN_NAME, NEW_NUMBER_VALUE, NEW_TEXT_VALUE)
    SELECT 167, '11/06/19 05:18', 481, 'MILKY_WAY_COUNT', 5, NULL FROM DUAL UNION ALL
    SELECT 168, '11/06/19 05:21', 225, 'SWEDISH_FISH_COUNT', 1, NULL FROM DUAL UNION ALL
    SELECT 169, '11/06/19 05:40', 481, 'FACILITIES_ADDRESS', NULL, NULL FROM DUAL UNION ALL
    SELECT 170, '11/06/19 05:49', 481, 'STARBURST_COUNT', 4, NULL FROM DUAL UNION ALL
    SELECT 171, '11/06/19 06:09', 576, 'FACILITIES_CONTACT', NULL, '' FROM DUAL UNION ALL
    SELECT 172, '11/06/19 06:25', 481, 'SWEDISH_FISH_COUNT', 7, NULL FROM DUAL UNION ALL
    SELECT 173, '11/06/19 06:40', 481, 'FACILITIES_CONTACT', NULL, 'Audrey' FROM DUAL UNION ALL
    SELECT 174, '11/06/19 06:46', 576, 'SNICKERS_COUNT', 13, NULL FROM DUAL UNION ALL
    SELECT 175, '11/06/19 06:55', 576, 'FACILITIES_ADDRESS', NULL, '388 Holiday Street' FROM DUAL UNION ALL
    SELECT 176, '11/06/19 06:59', 576, 'SWEDISH_FISH_COUNT', NULL, NULL FROM DUAL UNION ALL
    SELECT 177, '11/06/19 07:00', 349, 'MILKY_WAY_COUNT', 3, NULL FROM DUAL UNION ALL
    SELECT 178, '11/06/19 07:03', 481, 'TWIX_COUNT', 8, NULL FROM DUAL UNION ALL
    SELECT 179, '11/06/19 07:11', 349, 'TWIX_COUNT', 15, NULL FROM DUAL UNION ALL
    SELECT 180, '11/06/19 07:31', 225, 'FACILITIES_CONTACT', NULL, 'William' FROM DUAL UNION ALL
    SELECT 181, '11/06/19 07:49', 576, 'FACILITIES_CONTACT', NULL, 'Brian' FROM DUAL UNION ALL
    SELECT 182, '11/06/19 08:28', 481, 'SNICKERS_COUNT', 0, NULL FROM DUAL UNION ALL
    SELECT 183, '11/06/19 08:38', 481, 'SKITTLES_COUNT', 7, '' FROM DUAL UNION ALL
    SELECT 184, '11/06/19 09:04', 349, 'MILKY_WAY_COUNT', 10, NULL FROM DUAL UNION ALL
    SELECT 185, '11/06/19 09:21', 481, 'SNICKERS_COUNT', NULL, NULL FROM DUAL UNION ALL
    SELECT 186, '11/06/19 09:33', 225, 'SKITTLES_COUNT', 11, NULL FROM DUAL UNION ALL
    SELECT 187, '11/06/19 09:45', 225, 'FACILITIES_CONTACT', NULL, NULL FROM DUAL UNION ALL
    SELECT 188, '11/06/19 10:16', 481, 'FACILITIES_CONTACT', 4, 'Lucy' FROM DUAL UNION ALL
    SELECT 189, '11/06/19 10:25', 481, 'SNICKERS_COUNT', 10, NULL FROM DUAL UNION ALL
    SELECT 190, '11/06/19 10:57', 576, 'SWEDISH_FISH_COUNT', 12, NULL FROM DUAL UNION ALL
    SELECT 191, '11/06/19 10:59', 225, 'MILKY_WAY_COUNT', NULL, NULL FROM DUAL UNION ALL
    SELECT 192, '11/06/19 11:11', 481, 'STARBURST_COUNT', 6, 'Stanley' FROM DUAL UNION ALL
    SELECT 193, '11/06/19 11:34', 225, 'SKITTLES_COUNT', 8, NULL FROM DUAL UNION ALL
    SELECT 194, '11/06/19 11:39', 349, 'FACILITIES_CONTACT', NULL, 'Mark' FROM DUAL UNION ALL
    SELECT 195, '11/06/19 11:42', 576, 'SKITTLES_COUNT', 8, NULL FROM DUAL UNION ALL
    SELECT 196, '11/06/19 11:56', 225, 'TWIX_COUNT', 2, NULL FROM DUAL
  • 所需结果 - 查询( View )从更改日志重建历史表行

  • 我需要构建一个 View 来重建完整的历史 VENDING_MACHINES表,仅使用来自 VENDING_MACHINE_CHANGE_LOG 的数据 table 。
    即,由于允许孤立更改日志行,因此先前已从 VENDING_MACHINES 中删除的行应该会重新出现。
    结果 View 应该允许我检索任何 VENDING_MACHINE行,就像它在历史上的任何特定点存在一样。
    VENDING_MACHINE_CHANGE_LOG 的示例数据很短,不足以产生完整的结果......
    但这应该足以证明所需的结果。

    最终,我认为将需要分析功能。
    但我是 SQL 分析功能的新手,我也是 Oracle 和 Apex 的新手。
    所以我不确定如何解决这个问题 - 重建原始表格行的最佳方法是什么?

    以下是所需的结果(按 CHANGE_TIMESTAMP 排序):

    enter image description here

    这是相同的期望结果,另外按 VENDING_MACHINE_ID 排序:

    enter image description here

    我构建了一个简单的查询来为每个 VENDING_MACHINE_ID 提取最新的列值。 ,但我认为这种方法不适合这项艰巨的任务。
    我想我需要改用分析函数,以获得更好的性能和灵活性。 (或者也许我错了?)
    select vmcl.ID,
    vmcl.CHANGE_TIMESTAMP,
    vmcl.VENDING_MACHINE_ID,
    vmcl.MODIFIED_COLUMN_NAME,
    vmcl.MODIFIED_COLUMN_TYPE,
    vmcl.NEW_NUMBER_VALUE,
    vmcl.NEW_TEXT_VALUE

    from ( select sqvmcl.VENDING_MACHINE_ID,
    sqvmcl.MODIFIED_COLUMN_NAME,
    max(sqvmcl.CHANGE_TIMESTAMP) as LAST_CHANGE_TIMESTAMP
    from VENDING_MACHINE_CHANGE_LOG sqvmcl
    where sqvmcl.CHANGE_TIMESTAMP <= /*[Current timestamp, or specified timestamp]*/
    group by sqvmcl.VENDING_MACHINE_ID, sqvmcl.MODIFIED_COLUMN_NAME ) sq

    left join VENDING_MACHINE_CHANGE_LOG vmcl on vmcl.VENDING_MACHINE_ID = sq.VENDING_MACHINE_ID
    and vmcl.MODIFIED_COLUMN_NAME = sq.MODIFIED_COLUMN_NAME
    and vmcl.CHANGE_TIMESTAMP = sq.LAST_CHANGE_TIMESTAMP

    请注意 left join特别命中 VENDING_MACHINE_CHANGE_LOG 的唯一索引表 - 这是设计使然。

    最佳答案

    我将忽略我认为这是一个“XY 问题”的感觉,只回答这个问题:

    [How do I] Reconstruct historical table rows, based only on change-log data[?]


    (有关我怀疑可能是“真正”问题的方法,请参阅有关 Oracle 12c 中闪回文件的链接: https://docs.oracle.com/database/121/ADFNS/adfns_flashback.htm#ADFNS01004)
    对于您所拥有的,我相信这是您正在寻找的查询(针对您的 View 定义):
    SELECT 
    c.id change_id,
    c.change_timestamp as_of_timestamp,
    c.vending_machine_id,
    NULLIF(last_value(case when c.modified_column_name = 'SNICKERS_COUNT' THEN nvl(c.new_number_value,-99999) ELSE NULL END) ignore nulls over ( partition by c.vending_machine_id order by c.change_timestamp asc range between unbounded preceding and current row),-99999) snickers_count,
    NULLIF(last_value(case when c.modified_column_name = 'MILKY_WAY_COUNT' THEN nvl(c.new_number_value,-99999) ELSE NULL END) ignore nulls over ( partition by c.vending_machine_id order by c.change_timestamp asc range between unbounded preceding and current row),-99999) MILKY_WAY_COUNT,
    NULLIF(last_value(case when c.modified_column_name = 'TWIX_COUNT' THEN nvl(c.new_number_value,-99999) ELSE NULL END) ignore nulls over ( partition by c.vending_machine_id order by c.change_timestamp asc range between unbounded preceding and current row),-99999) TWIX_COUNT,
    NULLIF(last_value(case when c.modified_column_name = 'SKITTLES_COUNT' THEN nvl(c.new_number_value,-99999) ELSE NULL END) ignore nulls over ( partition by c.vending_machine_id order by c.change_timestamp asc range between unbounded preceding and current row),-99999) SKITTLES_COUNT,
    NULLIF(last_value(case when c.modified_column_name = 'STARBURST_COUNT' THEN nvl(c.new_number_value,-99999) ELSE NULL END) ignore nulls over ( partition by c.vending_machine_id order by c.change_timestamp asc range between unbounded preceding and current row),-99999) STARBURST_COUNT,
    NULLIF(last_value(case when c.modified_column_name = 'SWEDISH_FISH_COUNT' THEN nvl(c.new_number_value,-99999) ELSE NULL END) ignore nulls over ( partition by c.vending_machine_id order by c.change_timestamp asc range between unbounded preceding and current row),-99999) SWEDISH_FISH_COUNT,
    NULLIF(last_value(case when c.modified_column_name = 'FACILITIES_ADDRESS' THEN nvl(c.new_text_value,'#NULL#') ELSE NULL END) ignore nulls over ( partition by c.vending_machine_id order by c.change_timestamp asc range between unbounded preceding and current row),'#NULL#') FACILITIES_ADDRESS,
    NULLIF(last_value(case when c.modified_column_name = 'FACILITIES_CONTACT' THEN nvl(c.new_text_value,'#NULL#') ELSE NULL END) ignore nulls over ( partition by c.vending_machine_id order by c.change_timestamp asc range between unbounded preceding and current row),'#NULL#') FACILITIES_CONTACT
    FROM
    VENDING_MACHINE_CHANGE_LOG c
    ORDER BY
    c.vending_machine_id, c.change_timestamp;
    基本上,你有三个问题:
  • 您如何考虑可能存储在每一列中的不同数据类型?
  • 你是如何记账的null值(value)观?
  • 如何使查询高效运行?

  • 问题 #1 的答案是您手动为每个 View 列编写逻辑,因此 View 定义使用 NEW_NUMBER_VALUE 很简单。例如, SNICKERS_COUNT列和使用 NEW_TEXT_VALUEFACILITIES_ADDRESS柱子。
    问题#2 更棘手。考虑 SNICKERS_COUNT柱子。您需要忽略不是对 SNICKERS_COUNT 的更改的更改。 .通过设置它们很容易忽略这些 null .但是,实际的变化值也可能是 null ,我们不想忽略这些。所以,我们必须指定一个非 null代表值(value) null我们不想忽略的值。该指定值必须是永远不会出现在实际数据中的值。对于数字列,我选择了 -99999,对于文本列,我选择了“#NULL#”。
    问题#3 我忽略了。您的问题的本质将要求您从一开始就阅读所有更改日志,以在给定时间点建立它们的值。如果没有对 VENDING_MACHINE_CHANGE_LOG 的全表扫描,我看不到您下车。 .
    所以,让我们分解查询中的一列,看看它在做什么:
    nullif(
    last_value(
    case when c.modified_column_name = 'SNICKERS_COUNT'
    THEN nvl(c.new_number_value,-99999)
    ELSE NULL END)
    ignore nulls
    over ( partition by c.vending_machine_id
    order by c.change_timestamp asc
    range between unbounded preceding and current row)
    ,-99999) snickers_count,
    从这个内部表达开始:
    case when c.modified_column_name = 'SNICKERS_COUNT' 
    THEN nvl(c.new_number_value,-99999)
    ELSE NULL END
    如果修改的列不是 SNICKERS_COUNT ,表达式为 NULL .这是它可以为空的唯一方法。如果 new_number_valueNULL ,我们将其转换为我们指定的替身 (-99999)。
    然后,
    last_value(...case expression above...)
    ignore nulls
    over ( partition by c.vending_machine_id
    order by c.change_timestamp asc
    range between unbounded preceding and current row)
    ...这告诉 Oracle 为 case 表达式采用最近的非空值,“最近的”被定义为具有最高 change_timestamp 的行。对于具有相同 vending_machine_id 的一组行作为当前行,并且只包括到当前行的更改。
    最后,
    nullif(... last_value expression above...
    ,-99999) snickers_count
    这将转换 null 的指定替代值回到真实 null .
    结果如下:
    +-----------+---------------------------------+--------------------+----------------+-----------------+------------+----------------+-----------------+--------------------+--------------------+--------------------+
    | CHANGE_ID | AS_OF_TIMESTAMP | VENDING_MACHINE_ID | SNICKERS_COUNT | MILKY_WAY_COUNT | TWIX_COUNT | SKITTLES_COUNT | STARBURST_COUNT | SWEDISH_FISH_COUNT | FACILITIES_ADDRESS | FACILITIES_CONTACT |
    +-----------+---------------------------------+--------------------+----------------+-----------------+------------+----------------+-----------------+--------------------+--------------------+--------------------+
    | 168 | 06-NOV-19 05.21.00.000000000 AM | 225 | | | | | | 1 | | |
    | 180 | 06-NOV-19 07.31.00.000000000 AM | 225 | | | | | | 1 | | William |
    | 186 | 06-NOV-19 09.33.00.000000000 AM | 225 | | | | 11 | | 1 | | William |
    | 187 | 06-NOV-19 09.45.00.000000000 AM | 225 | | | | 11 | | 1 | | |
    | 191 | 06-NOV-19 10.59.00.000000000 AM | 225 | | | | 11 | | 1 | | |
    | 193 | 06-NOV-19 11.34.00.000000000 AM | 225 | | | | 8 | | 1 | | |
    | 196 | 06-NOV-19 11.56.00.000000000 AM | 225 | | | 2 | 8 | | 1 | | |
    | 177 | 06-NOV-19 07.00.00.000000000 AM | 349 | | 3 | | | | | | |
    | 179 | 06-NOV-19 07.11.00.000000000 AM | 349 | | 3 | 15 | | | | | |
    | 184 | 06-NOV-19 09.04.00.000000000 AM | 349 | | 10 | 15 | | | | | |
    | 194 | 06-NOV-19 11.39.00.000000000 AM | 349 | | 10 | 15 | | | | | Mark |
    | 167 | 06-NOV-19 05.18.00.000000000 AM | 481 | | 5 | | | | | | |
    | 169 | 06-NOV-19 05.40.00.000000000 AM | 481 | | 5 | | | | | | |
    | 170 | 06-NOV-19 05.49.00.000000000 AM | 481 | | 5 | | | 4 | | | |
    | 172 | 06-NOV-19 06.25.00.000000000 AM | 481 | | 5 | | | 4 | 7 | | |
    | 173 | 06-NOV-19 06.40.00.000000000 AM | 481 | | 5 | | | 4 | 7 | | Audrey |
    | 178 | 06-NOV-19 07.03.00.000000000 AM | 481 | | 5 | 8 | | 4 | 7 | | Audrey |
    | 182 | 06-NOV-19 08.28.00.000000000 AM | 481 | 0 | 5 | 8 | | 4 | 7 | | Audrey |
    | 183 | 06-NOV-19 08.38.00.000000000 AM | 481 | 0 | 5 | 8 | 7 | 4 | 7 | | Audrey |
    | 185 | 06-NOV-19 09.21.00.000000000 AM | 481 | | 5 | 8 | 7 | 4 | 7 | | Audrey |
    | 188 | 06-NOV-19 10.16.00.000000000 AM | 481 | | 5 | 8 | 7 | 4 | 7 | | Lucy |
    | 189 | 06-NOV-19 10.25.00.000000000 AM | 481 | 10 | 5 | 8 | 7 | 4 | 7 | | Lucy |
    | 192 | 06-NOV-19 11.11.00.000000000 AM | 481 | 10 | 5 | 8 | 7 | 6 | 7 | | Lucy |
    | 171 | 06-NOV-19 06.09.00.000000000 AM | 576 | | | | | | | | |
    | 174 | 06-NOV-19 06.46.00.000000000 AM | 576 | 13 | | | | | | | |
    | 175 | 06-NOV-19 06.55.00.000000000 AM | 576 | 13 | | | | | | 388 Holiday Street | |
    | 176 | 06-NOV-19 06.59.00.000000000 AM | 576 | 13 | | | | | | 388 Holiday Street | |
    | 181 | 06-NOV-19 07.49.00.000000000 AM | 576 | 13 | | | | | | 388 Holiday Street | Brian |
    | 190 | 06-NOV-19 10.57.00.000000000 AM | 576 | 13 | | | | | 12 | 388 Holiday Street | Brian |
    | 195 | 06-NOV-19 11.42.00.000000000 AM | 576 | 13 | | | 8 | | 12 | 388 Holiday Street | Brian |
    +-----------+---------------------------------+--------------------+----------------+-----------------+------------+----------------+-----------------+--------------------+--------------------+--------------------+

    关于sql - 仅基于更改日志数据重建历史表行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59108948/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com