我有许多大表,每个表都有 >300 列。我正在使用的应用程序通过在辅助表中复制当前行来创建更改行的“存档”。
考虑一个简单的例子:
CREATE TABLE dbo.bigtable
(
UpdateDate datetime,
PK varchar(12) PRIMARY KEY,
col1 varchar(100),
col2 int,
col3 varchar(20),
.
.
.
colN datetime
);
存档表:
CREATE TABLE dbo.bigtable_archive
(
UpdateDate datetime,
PK varchar(12) NOT NULL,
col1 varchar(100),
col2 int,
col3 varchar(20),
.
.
.
colN datetime
);
在 上执行任何更新之前dbo.bigtable
,会在 中创建该行的副本dbo.bigtable_archive
,然后dbo.bigtable.UpdateDate
使用当前日期进行更新。
因此UNION
,将两个表放在一起并分组时,按 .PK
排序时创建更改时间线UpdateDate
。
我希望创建一个报告,详细说明行之间的差异,按 排序UpdateDate
,按 分组PK
,格式如下:
PK, UpdateDate, ColumnName, Old Value, New Value
Old Value
并且New Value
可以将相关列转换为 a (VARCHAR(MAX)
不涉及列),因为我不需要对值本身进行任何后处理。TEXT
BYTE
目前,我想不出一种合理的方法来对大量列执行此操作,而不求助于以编程方式生成查询——我可能不得不这样做。
对很多想法持开放态度,所以我会在 2 天后为这个问题增加赏金。
这看起来不会很漂亮,特别是考虑到超过 300 列和不可用
LAG
,它也不太可能表现得非常好,但作为开始,我会尝试以下方法:UNION
两张桌子。OUTER APPLY
+TOP (1)
作为穷人的LAG
)。varchar(max)
成对的和反透视它们,即当前值和先前值(CROSS APPLY (VALUES ...)
适用于此操作)。如我所见,上面的 Transact-SQL:
如果将数据反透视到临时表
PK
您可以通过,ColumnName
和上的自联接来匹配行以查找新值和旧值Version = Version + 1
。当然,不太漂亮的部分是将 300 列从两个基表中逆透视到临时表中。
XML 来拯救,让事情变得不那么尴尬。
可以使用 XML 对数据进行反透视,而不必知道表中有哪些实际列将被反透视。列名称必须作为 XML 中的元素名称有效,否则它将失败。
这个想法是为每一行创建一个 XML,其中包含该行的所有值。
elements xsinil
是否可以为带有NULL
.然后可以使用
nodes('*')
为每一列获取一行并使用local-name(.)
获取元素名称和text()
获取值来分解 XML。下面的完整解决方案。注意
Version
是反的。0 = 最新版本。我建议你另一种方法。
虽然您不能更改当前应用程序,但您可以更改数据库行为。
如果可能,我会在当前表中添加两个 TRIGGERS。
dbo.bigtable_archive 上的一个 INSTEAD OF INSERT 仅当新记录当前不存在时才添加它。
bigtable 上的 AFTER INSERT 触发器执行完全相同的工作,但使用 bigtable 的数据。
好的,我在这里用这个初始值设置了一个小例子:
现在您应该将 bigtable 中的所有未决记录插入到 bigtable_archive 中。
现在,下一次应用程序尝试在 bigtable_archive 表上插入记录时,触发器将检测它是否存在,并避免插入。
显然,现在您可以通过仅查询存档表来获取更改的时间线。而且应用程序永远不会意识到触发器正在幕后悄悄地完成工作。
dbfiddle在这里
Working proposal, w/ some sample data, can be found @ rextester: bigtable unpivot
The gist of the operation:
1 - Use syscolumns and for xml to dynamically generate our column lists for the unpivot operation; all values will be converted to varchar(max), w/ NULLs being converted to the string 'NULL' (this addresses issue with unpivot skipping NULL values)
2 - Generate a dynamic query to unpivot data into the #columns temp table
3 - Perform a self join of the #temp table to generate the desired output
Cutting-n-pasting from rextester ...
Create some sample data and our #columns table:
The guts of the solution:
And the results:
Note: apologies ... couldn't figure out an easy way to cut-n-paste the rextester output into a code block. I'm open to suggestions.
Potential issues/concerns:
1 - conversion of data to a generic varchar(max) can lead to loss of data precision which in turn can mean we miss some data changes; consider the following datetime and float pairs which, when converted/cast to the generic 'varchar(max)', lose their precision (ie, the converted values are the same):
While data precision could be maintained it would require a bit more coding (eg, casting based on source column datatypes); for now I've opted to stick with the generic varchar(max) per the OP's recommendation (and assumption that the OP knows the data well enough to know that we won't run into any issues of data precision loss).
2 - for really large sets of data we run the risk of blowing out some server resources, whether it be tempdb space and/or cache/memory; primary issue comes from the data explosion that occurs during an unpivot (eg, we go from 1 row and 302 pieces of data to 300 rows and 1200-1500 pieces of data, including 300 copies of the PK and UpdateDate columns, 300 column names)
这种方法使用动态查询生成 sql 来获取更改。SP 采用表和架构名称并提供您想要的输出。
假设 PK 和 UpdateDate 列存在于所有表中。并且所有存档表的格式为 originalTableName + "_archive"..
注意:我没有检查它的性能。
注意:因为这使用动态 sql,所以我应该添加关于安全/sql 注入的警告。限制对 SP 的访问并添加其他验证以防止 sql 注入。
示例调用:
I am using AdventureWorks2012`,Production.ProductCostHistory and Production.ProductListPriceHistory in my example.It may not be perfect history table example, "but script is able to put together the desire output and correct output".
You can take any other table name with fewer column name to understand my script.Any Explanation need then ping me.