如何比较同一表中的两行并使用存储过程返回数据作为响应
Posted
技术标签:
【中文标题】如何比较同一表中的两行并使用存储过程返回数据作为响应【英文标题】:How to compare two row in same table and return the data in response using stored procedure 【发布时间】:2021-12-30 07:19:26 【问题描述】:我必须在同一个表中使用两个 Id 比较两行,我想获取存储过程中不匹配的列及其值,我需要以 JSON 格式返回。
|Col1|Col2|Col3|Col4|
Id-1 |ABC |123 |321 |111 |
Id-2 |ABC |333 |321 |123|
输出:
|col2|col4|
Id-1 |123 |111 |
Id-2 |333 |123 |
JSON OUTPUT Expected
[
"ColumnName":"COL2",
"Value1":"123",
"Value2":"333"
,
"ColumnName":"COL4",
"Value1":"111",
"Value2":"123"
]
我没有这方面的专业知识,但是我尝试了下面的 SQL 代码,但我需要它以一种非常好的方式,并且在存储过程中也是如此,它应该以 JSON 格式返回,请帮助!
我已经尝试过,请查看下面的链接以及示例和查询。
SQL Fiddle
【问题讨论】:
请首先标记您正在使用的 DBMS 你真的需要存储过程吗? @jarlh 是的,我愿意,因为我会从 .net 调用存储过程,并且我需要在存储过程中进行多个表比较,就好像现在我只是在寻求帮助一张桌子 您使用的是哪个 dbms?并非所有 dbms 产品都支持 ISO/ANSI SQL 存储过程。 @Mysterious288,预期的 JSON 输出是什么,SQL Server 版本是什么? SQL Server 2016 中引入了 JSON 支持。 【参考方案1】:您需要取消透视所有列,然后将每一行彼此连接。
您可以使用 CROSS APPLY (VALUES
手动旋转所有内容
SELECT
aId = a.id,
bId = b.id,
v.columnName,
v.value1,
v.value2
FROM @t a
JOIN @t b
ON a.id < b.id
-- alternatively
-- ON a.id = 1 AND b.id = 2
CROSS APPLY (VALUES
('col1', CAST(a.col1 AS nvarchar(100)), CAST(b.col1 AS nvarchar(100))),
('col2', CAST(a.col2 AS nvarchar(100)), CAST(b.col2 AS nvarchar(100))),
('col3', CAST(a.col3 AS nvarchar(100)), CAST(b.col3 AS nvarchar(100))),
('col4', CAST(a.col4 AS nvarchar(100)), CAST(b.col4 AS nvarchar(100)))
) v (ColumnName, Value1, Value2)
WHERE EXISTS (SELECT v.Value1 EXCEPT SELECT v.Value2)
FOR JSON PATH;
WHERE EXISTS (SELECT a.Value1 INTERSECT SELECT a.Value2)
的使用意味着将正确考虑空值。
或者您可以使用SELECT t.* FOR JSON
并使用OPENJSON
取消透视
WITH allValues AS (
SELECT
t.id,
j2.[key],
j2.value,
j2.type
FROM @t t
CROSS APPLY (
SELECT t.*
FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER
) j1(json)
CROSS APPLY OPENJSON(j1.json) j2
WHERE j2.[key] <> 'id'
)
SELECT
aId = a.id,
bId = b.id,
columnName = a.[key],
value1 = a.value,
value2 = b.value
FROM allValues a
JOIN allValues b ON a.[key] = b.[key]
AND a.id < b.id
-- alternatively
-- AND a.id = 1 AND b.id = 2
WHERE a.type <> b.type
OR a.value <> b.value
FOR JSON PATH;
db<>fiddle
SQL Fiddle of actual data
【讨论】:
我在 SQLFiddle 中尝试了您的解决方案,似乎 SQL 版本是一个问题 sqlfiddle.com/#!9/7cf413/5 想知道 Zhorov 作为解决方案发布的问题 显然您需要使用 SQL Server 而不是 mysql,并且您需要将正确的列名放入 sqlfiddle.com/#!18/7cf41/5 非常感谢,这很有意义,但是有一个问题,如果所有列都是具有特定大小的字符串,我们需要 CAST 吗?? 你说得对,我们没有。但在许多情况下,您希望使用不同的类型或大小列来做到这一点 对不起,我接受了这两个答案,谢谢。【参考方案2】:答案:
一个可能的解决方案是一个CROSS JOIN
和一个额外的APPLY
运算符:
来自小提琴的测试数据:
CREATE TABLE companies (
Id int,
company_name VARCHAR(40),
address_type VARCHAR(40),
address VARCHAR(40)
);
INSERT INTO companies VALUES (1,'Company A','Billing','111 Street');
INSERT INTO companies VALUES (2,'Company A','Shipping','112 Street');
存储过程:
CREATE PROCEDURE uspColumnsAsJson
@Id1 int,
@Id2 int
AS
BEGIN
SELECT a.ColumnName, a.Value1, a.Value2
FROM (
SELECT
c1.company_name AS company_name1,
c1.address_type AS address_type1,
c1.address AS address1,
c2.company_name AS company_name2,
c2.address_type AS address_type2,
c2.address AS address2
FROM companies c1
CROSS JOIN companies c2
WHERE c1.Id = @Id1 AND c2.Id = @Id2
) t
CROSS APPLY (VALUES
('company_name', CONVERT(varchar(max), t.company_name1), CONVERT(varchar(max), t.company_name2)),
('address_type', CONVERT(varchar(max), t.address_type1), CONVERT(varchar(max), t.address_type2)),
('address', CONVERT(varchar(max), t.address1), CONVERT(varchar(max), t.address2))
) a (ColumnName, Value1, Value2)
WHERE a.Value1 <> a.Value2
FOR JSON AUTO
END
EXEC uspColumnsAsJson 1, 2
结果:
[
"ColumnName":"address_type","Value1":"Billing","Value2":"Shipping",
"ColumnName":"address","Value1":"111 Street","Value2":"112 Street"
]
更新:
如果要包括所有列,则需要基于系统目录视图的动态语句:
CREATE PROCEDURE uspColumnsAsJson
@Id1 int,
@Id2 int
AS
BEGIN
DECLARE @stmt nvarchar(max)
DECLARE @prms nvarchar(max)
DECLARE @err int
-- APPLY part
SELECT @stmt = STRING_AGG(
CONCAT(
N'(''',
col.[name],
N''', CONVERT(varchar(max), c1.',
QUOTENAME(col.[name]),
N'), CONVERT(varchar(max), c2.',
QUOTENAME(col.[name]),
N'))'
),
N','
)
FROM sys.columns col
JOIN sys.tables tab ON col.object_id = tab.object_id
JOIN sys.schemas sch ON tab.schema_id = sch.schema_id
WHERE (tab.[name] = 'companies') AND (sch.[name] = 'dbo') AND (col.[name] <> 'Id')
-- Final statement
SET @stmt = CONCAT(
N'SELECT a.ColumnName, a.Value1, a.Value2 ',
N'FROM companies c1 ',
N'CROSS JOIN companies c2 ',
N'CROSS APPLY (VALUES ',
@stmt,
N') a (ColumnName, Value1, Value2) ',
N'WHERE (c1.Id = @Id1) AND (c2.Id = @Id2) AND (a.Value1 <> a.Value2) ',
N'FOR JSON AUTO '
)
-- Execution
SET @prms = N'@Id1 int, @Id2 int'
EXEC @err = sp_executesql @stmt, @prms, @Id1, @Id2
RETURN @err
END
【讨论】:
这不只是一个普通的JOIN companies c2 ON c1.Id = 1 AND c2.Id = 2
你可能还想将列值显式转换为相同的类型
@Zhorov 感谢您的解决方案,只是想知道我们是否可以使查询动态化,这样我们就不必对列进行硬编码。
@Charlieface,您对演员阵容的看法是正确的,但在这种特定情况下,所有列都是varchar(40)
,所以我错过了那部分。谢谢,这是一个重要的提示。
@Zhorov 是的,现在应该没问题,因为所有都是字符串,我会测试并告诉你,如果一切正常,我会将其标记为答案
@Mysterious288,包含动态语句的更新。以上是关于如何比较同一表中的两行并使用存储过程返回数据作为响应的主要内容,如果未能解决你的问题,请参考以下文章
mysql连接具有相同列值的两行并计算某些列值并返回一行中的所有行