首先,我不会在Main 表中添加计数器列。我将创建一个单独的Audit 表,该表将保存Main 表中的项目的ID,加上请求ID 时的至少时间戳。本质上,Audit 表将存储请求的历史记录。在这种方法中,您可以轻松生成更有趣的报告。您始终可以计算每个项目的总计,也可以按天、周、月等计算每个项目或所有项目的摘要。根据数据量,您可以定期删除超过某个阈值(一个月、一年等)的审核条目。
此外,您可以根据需要在Audit 表中轻松存储更多信息,例如,用于计算每个用户的统计信息的用户 ID。
要“自动”填充Audit 表,我将创建一个存储过程。客户端代码将调用此存储过程,而不是执行原始的SELECT。存储过程将返回与原始 SELECT 完全相同的结果,但也会向客户端代码透明地向 Audit 表添加必要的细节。
所以,我们假设Audit 表如下所示:
CREATE TABLE AuditTable
(
ID int
IDENTITY -- SQL Server
SERIAL -- Postgres
AUTO_INCREMENT -- MySQL
NOT NULL,
ItemID int NOT NULL,
RequestDateTime datetime NOT NULL
)
你的主要SELECT 看起来像这样:
SELECT ItemID, Col1, Col2, ...
FROM MainTable
WHERE <complex criteria>
要在 SQL Server 的一个语句中同时执行 INSERT 和 SELECT,我会在 Postgres 中使用 OUTPUT 子句 - 在 MySQL 中使用 RETURNING 子句 - ???我不认为它有这样的东西。因此,MySQL 过程将有几个单独的语句。
MySQL
首先执行SELECT 并将结果插入temporary(可能是内存)表中。然后将项目 ID 从临时表复制到 Audit 表中。然后SELECT从临时表返回结果给客户端。
CREATE TEMPORARY TABLE TempTable
(
ItemID int NOT NULL,
Col1 ...,
Col2 ...,
...
)
ENGINE = MEMORY
SELECT ItemID, Col1, Col2, ...
FROM MainTable
WHERE <complex criteria>
;
INSERT INTO AuditTable (ItemID, RequestDateTime)
SELECT ItemID, NOW()
FROM TempTable;
SELECT ItemID, Col1, Col2, ...
FROM TempTable
ORDER BY ...;
SQL Server(只是为了取笑你。这条语句同时执行INSERT 和SELECT)
MERGE INTO AuditTable
USING
(
SELECT ItemID, Col1, Col2, ...
FROM MainTable
WHERE <complex criteria>
) AS Src
ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT
(ItemID, RequestDateTime)
VALUES
(Src.ItemID, GETDATE())
OUTPUT
Src.ItemID, Src.Col1, Src.Col2, ...
;
您可以将Audit 表保持原样,也可以设置 cron 定期对其进行汇总。这实际上取决于数据量。在我们的系统中,我们将单个行存储一周,另外我们每小时汇总统计数据并将其保存 6 周,另外我们将每日汇总保存 18 个月。但是,重要的是,所有这些摘要都是单独的 Audit 表,我们不会在 Main 表中保留审计信息,因此我们不需要更新它。
Joe Celko 在SQL Style Habits: Attack of the Skeuomorphs 中解释得很好:
现在去任何 SQL 论坛文本搜索帖子。你会找到
包含名为 createdby 的列的数以千计的 DDL 帖子,
createddate、modifiedby 和 modifieddate
行声明末尾的元数据。这是旧的磁带
用新语言编写的标题标签!似曾相识!
标题记录在磁带上只出现一次。但是这些元数据
值在表中的每一行上一遍又一遍地出现。主要之一
使用数据库(不仅仅是 SQL)的原因是为了消除冗余
从数据中;这只会增加更多冗余。但现在想想
当一行被删除时,审计跟踪会发生什么?会发生什么
更新行时的审计跟踪?小径被毁。这
审计数据应该与模式分开。你能把日志
与数据库在同一磁盘驱动器上的文件?会计会让
同一个人批准和接收付款?