如何比较同一表中的两行并使用存储过程返回数据作为响应

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连接具有相同列值的两行并计算某些列值并返回一行中的所有行

matlab怎么找到一个矩阵中所有相同的两行并返回行号

如何在 MySQL 中使用自动增量字段复制行并插入到同一个表中?

如何用通配符比较同一张表中的两条记录?

比较两个字段并在另一个表中返回不匹配的存储过程

在 Oracle 存储过程中比较 IF 语句中的两列