情况
我们有一个应用程序,我们将机器设置存储在SQL表中。当用户更改机器的参数时,我们创建一个“修订版”,这意味着我们在表中插入一行。此表包含大约200个列。 在我们的应用程序中,用户可以查看每个修订版本。
问题
我们想要突出显示自上次修订以来已更改的参数。
问题
是否有一种只使用SQL的方法来获取两行之间差异的列名?
示例
ID | p_x | p_y | p_z
--------------------
11 | xxx | yyy | zzz
12 | xxy | yyy | zzy
查询应返回p_x
和p_z
。
修改
该表有200列,而不是行......
MY WAY OUT
我的目的是为这个问题找到一个“一行SQL语句”。
我在下面的答案中看到,它在SQL中更重要。 由于没有针对此问题的简短的SQL解决方案,因此在我们的软件(c#)的后端解决它当然要容易得多!
但由于这不是我问题的真正“答案”,我不会将其标记为已回答。
感谢您的帮助。
答案 0 :(得分:5)
你说:
We want to highlight the parameters that have changed since the last revision.
这意味着您希望显示(或报告)使更改的参数突出显示。
如果您要显示所有参数,那么在前端以编程方式执行此操作会容易得多。在编程语言中这将是一个更简单的问题。不幸的是,我不知道你的前端是什么,我不能给你特别的建议。
如果您真的无法在前端执行此操作但必须在数据库查询中接收此信息(您确实说“仅限SQL”),则需要指定您希望数据的格式in。两列记录之间发生变化的列的单列列表?带有标志的列表列表,指示哪些列已更改或未更改?
但这是一种可行的方法,但在此过程中,它会在进行比较之前将所有字段转换为nvarchars:
在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)
答案 1 :(得分:0)
这是使用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。如果表中有很多列,也可以动态构建查询。
答案 2 :(得分:0)
对于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
for 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
答案 3 :(得分:0)
我和您有类似的问题。这是我使用unpivot and 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]
如果表中只有3列,则很容易将
[p_x],[p_y],[p_z]
放进去,但是显然,键入 200列并不方便。即使您可以使用trick来拖放或复制/粘贴表中的列名,它的体积仍然很大。因此,您需要使用SELECT * EXCEPT
技巧和dynamic sql。
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