情况
我们有一个应用程序,将机器设置存储在 SQL 表中。当用户更改机器的参数时,我们创建一个“修订版”,这意味着我们在表中插入一行。该表大约有 200 个列。 在我们的应用程序中,用户可以查看每个修订版。
问题
我们想要突出显示自上次修订以来已更改的参数。
问题
是否有仅 SQL 的方法来获取两行之间差异的列名?
示例
ID | p_x | p_y | p_z
--------------------
11 | xxx | yyy | zzz
12 | xxy | yyy | zzy
查询应返回
p_x
和 p_z
。
编辑
该表有 200 列,而不是行...
我的出路
我的目的是找到一个“一行SQL语句”来解决这个问题。
我在下面的答案中看到,这是 SQL 中的一个更大的事情。 由于这个问题没有简短的、包含 SQL 的解决方案,因此在我们的软件后端(c#)解决它当然要容易得多!
但是由于这不是我的问题的真正“答案”,因此我不会将其标记为已回答。
感谢您的帮助。
你说:
We want to highlight the parameters that have changed since the last revision.
这意味着您希望显示(或报告)使更改的参数脱颖而出。
如果您无论如何都要显示所有参数,那么在前端以编程方式执行此操作会容易得多。对于编程语言来说,这将是一个简单得多的问题。不幸的是,不知道你的前端是什么,我无法给你具体的建议。
如果您确实无法在前端执行此操作,但必须在数据库查询中接收此信息(您确实说过“仅 SQL”),则需要指定您想要的数据格式。两条记录之间更改的列的单列列表?带有标志的列列表,指示哪些列已更改或未更改?
但是这是一种可行的方法,尽管在此过程中它会在进行比较之前将所有字段转换为 nvarchar:
将结果数据集与 ID 本身连接起来,以便您可以比较值并打印已更改的值:
with A as (
-- We're going to return the product ID, plus an XML version of the
-- entire record.
select ID
, (
Select *
from myTable
where ID = pp.ID
for xml auto, type) as X
from myTable pp )
, B as (
-- We're going to run an Xml query against the XML field, and transform it
-- into a series of name-value pairs. But X2 will still be a single XML
-- field, associated with this ID.
select Id
, X.query(
'for $f in myTable/@*
return
<data name="{ local-name($f) }" value="{ data($f) }" />
')
as X2 from A
)
, C as (
-- We're going to run the Nodes function against the X2 field, splitting
-- our list of "data" elements into individual nodes. We will then use
-- the Value function to extract the name and value.
select B.ID as ID
, norm.data.value('@name', 'nvarchar(max)') as Name
, norm.data.value('@value', 'nvarchar(max)') as Value
from B cross apply B.X2.nodes('/myTable') as norm(data))
-- Select our results.
select *
from ( select * from C where ID = 123) C1
full outer join ( select * from C where ID = 345) C2
on C1.Name = c2.Name
where c1.Value <> c2.Value
or not (c1.Value is null and c2.Value is null)
您可以使用unpivot 和pivot。关键是转置数据,这样就可以使用
where [11] != [12]
。
WITH CTE AS (
SELECT *
FROM
(
SELECT ID, colName, val
FROM tblName
UNPIVOT
(
val
FOR colName IN ([p_x],[p_y],[p_z])
) unpiv
) src
PIVOT
(
MAX(val)
FOR ID IN ([11], [12])
) piv
)
SELECT colName
--SELECT *
FROM CTE WHERE [11] != [12]
如果表格中只有几列,简单地输入
很容易,但显然输入50或更多列就不方便了。即使您可以使用这个技巧来拖放或复制/粘贴表中的列名称,它仍然很庞大。为此,您可以使用[p_x],[p_y],[p_z]
策略和 动态 sql。SELECT * EXCEPT
DECLARE @TSQL NVARCHAR(MAX), @colNames NVARCHAR(MAX)
SELECT @colNames = COALESCE(@colNames + ',' ,'') + [name]
FROM syscolumns WHERE name <> 'ID' and id = (SELECT id FROM sysobjects WHERE name = 'tablelName')
SET @TSQL = '
WITH CTE AS (
SELECT *
FROM
(
SELECT ID, colName, val
FROM tablelName
UNPIVOT
(
val
FOR colName IN (' + @colNames + ')
) unpiv
) src
PIVOT
(
MAX(val)
FOR ID IN ([11], [12])
) piv
)
--SELECT colName
SELECT *
FROM CTE WHERE [11] != [12]
'
EXEC sp_executesql @TSQL
这是使用
UNPIVOT
的一种方法:
;WITH
cte AS
(
SELECT CASE WHEN t1.p_x <> t2.p_x THEN 1 ELSE 0 END As p_x,
CASE WHEN t1.p_y <> t2.p_y THEN 1 ELSE 0 END As p_y,
CASE WHEN t1.p_z <> t2.p_z THEN 1 ELSE 0 END As p_z
FROM MyTable t1, MyTable t2
WHERE t1.ID = 11 AND t2.ID = 12 -- enter the two revisions to compare here
)
SELECT *
FROM cte
UNPIVOT (
Changed FOR ColumnName IN (p_x, p_y, p_z)
) upvt
WHERE upvt.Changed = 1
您必须在比较过程中添加代码来处理 NULL。如果表中有很多列,您还可以动态构建查询。
对于 sql server 2012 你可以做类似的事情(复制它 每列):
SELECT iif((p_x != lead(p_x) over(ORDER BY p_x)),
(SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tbl'
AND
TABLE_SCHEMA='schema'
AND
ORDINAL_POSITION='1')
,NULL)
FROM tbl
对于 sql server 2008 尝试
DECLARE @x int =11 -- first id
WHILE @x!=(SELECT count(1) FROM tbl)
BEGIN --comparison of two adjacent rows
if (SELECT p_x FROM tbl WHERE id=@x)!=(SELECT p_x FROM tbl WHERE id=@x+1)
BEGIN
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tbl' --insert your table
AND
TABLE_SCHEMA='schema' --insert your schema
AND
ORDINAL_POSITION='1' --first column 'p_x'
END
set @x=@x+1
END
我没有足够的声誉来发表评论,所以我把它放在这里。
Anna L 的解决方案需要稍作修改。 WHERE 语句未正确返回更改后的值
where c1.Value <> c2.Value or not (c1.Value is null and c2.Value is null)
如果 c1.Value = 1 且 c2.Value = 1,它将返回 true,因为即使它们是相同的值,它们也不为 null。
选项 1 - 检查 case 语句中是否相等并反转它
WHERE (CASE WHEN c1.Value = c2.Value OR (c1.Value IS NULL AND c2.Value IS NULL)
THEN 0 ELSE 1 END) = 1
THEN 1 ELSE 0 END
选项 2 - 检查一个是否为空且另一个不为空
WHERE c1.Value <> c2.Value
OR (c1.Value IS NOT NULL AND c2.Value IS NULL)
OR (c1.Value IS NULL AND c2.Value IS NOT NULL)
另一方面,我发现这个答案很有帮助。您现在可以使用 json 检查差异。