我正在研究一些复杂的销售分析,这非常令人费解......而且很无聊......
所以对于这个问题,我将使用一个有趣的、含糖的比喻:自动售货机。
但是我的实际表格的结构是相同的。
(您可以假设有很多索引、约束等)
- 基本表 #1 - 库存
假设我们有一个包含自动售货机库存数据的表。
这张表简单地显示了目前每台自动售货机中每种糖果的数量。
我知道,通常会有一个ITEM_TYPE
包含'Snickers'、'Milky Way'等行的表格,但由于多种原因,这不是我们表格的构造方式。
实际上,这不是产品数量,而是预先汇总的销售数据:"Pipeline Total"、"Forecast Total"等。
因此,我需要一个简单的表格,其中包含不同“类型”总计的单独列一起工作。
对于此示例,我还添加了一些文本列,以证明我必须考虑各种数据类型。
(这使事情变得复杂。)
除了ID
,所有列都可以为空 - 这是一个真正的问题。
就我们而言,如果列是NULL
,那么NULL
就是我们需要用于分析和报告的官方值。
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
表中将记录 8 行 - 每个非 ID 列对应VENDING_MACHINES
.
(在我的真实场景中,有 90 多列被跟踪。
但通常在任何给定时间只有一两列被更新,因此不会失控。)
此“更改日志”旨在成为VENDING_MACHINES
表的永久历史记录,因此我们不会创建外键约束 - 如果删除了一行,VENDING_MACHINES
我们希望在更改日志中保留孤立的历史记录。
此外,Apex 不支持ON UPDATE CASCADE
(?),因此触发器必须检查ID
列的更新,并在相关表中手动传播更新(例如更改日志)。
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
- 所需结果 - 查询(视图)以从更改日志中重建历史表行
我需要构建一个视图来重建完整的历史VENDING_MACHINES
表,只使用VENDING_MACHINE_CHANGE_LOG
表中的数据。
即,由于允许孤立更改日志行,因此之前已删除的行VENDING_MACHINES
应该重新出现。
结果视图应该允许我检索任何VENDING_MACHINE
行,就像它在历史上的任何特定点存在一样。
的示例数据VENDING_MACHINE_CHANGE_LOG
非常短,不足以产生完整的结果......
但它应该足以证明预期的结果。
最终我认为分析功能将是必需的。
但我是 SQL 分析函数的新手,我也是 Oracle 和 Apex 的新手。
所以我不确定如何解决这个问题 - 重建原始表行的最佳方法是什么?
期望的结果如下所示(按 排序CHANGE_TIMESTAMP
):
这是相同的期望结果,另外按以下方式排序VENDING_MACHINE_ID
:
我已经构建了一个简单的查询来为 each 提取最新的列值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
表的唯一索引 - 这是设计使然。