SQL Server中具有不同列数的多个查询的联合结果

Posted

技术标签:

【中文标题】SQL Server中具有不同列数的多个查询的联合结果【英文标题】:union results of multiple queries with different number of columns in SQL Server 【发布时间】:2021-01-15 15:24:19 【问题描述】:

我正在编写 SQL 来报告类似于以下格式的数据。以“P”开头的行是特定供应商的汇总行,其下方与付款或该供应商相关联的相应汇款“R”行。例如,P 行上的 PAID_AMT 量是“NW Forest Supplies”的总和值(1000.50 = 对应的 R 行 110 + 800 + 100.50 = 1000.50)。我开始以 UNION 查询的形式执行此操作,但挑战在于“P”行表示的字段与“R”行表示的字段不同,因此我正在努力将数据组合在一起。

以“C”开头的最后一行是所有行的总体摘要,我也在努力并且不正确。根据示例,它应该将“P”行中的总 PAID_AMT 值相加,即 2056.00 (1000.50 + 55 + 1000.50),然后输出支付次数的总计数 (3),最后输出总计数汇款 - 在本例中为 7。

也许在最终结果中使用CTE和union更好,或者使用临时表将'P'和'R'数据的数据插入到自己的表中,然后查询?

报告输出的示例:

SELECT
    'P'
    , 'CRD'
    , 'PayerName.Payables'
    , A.ADDRESS4
    , A.PYMNT_ID_REF
    , SUM(B.PAID_AMT)
    , A.REMIT_VENDOR
    , CAST(B.REMIT_ADDR_SEQ_NUM AS VARCHAR)
    , A.NAME1
    , 'USD'
FROM
    PS_PAYMENT_TBL                A
    INNER JOIN PS_PYMNT_VCHR_XREF B
        ON B.PYMNT_ID = A.PYMNT_ID
           AND A.BANK_SETID = B.BANK_SETID
           AND A.BANK_CD = B.BANK_CD
           AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE A.PYMNT_DT > '01/12/2021'
GROUP BY
    A.REMIT_VENDOR
    , A.ADDRESS4
    , A.PYMNT_ID_REF
    , A.REMIT_VENDOR
    , CAST(B.REMIT_ADDR_SEQ_NUM AS VARCHAR)
    , A.NAME1
UNION
SELECT
    'R'
    , A.PYMNT_ID_REF
    , C.INVOICE_ID
    , C.INVOICE_DT
    , C.GROSS_AMT
    , C.DSCNT_AMT
    , B.VOUCHER_ID
    , C.PO_ID
    , ''
    , ''
FROM
    PS_PYMNT_VCHR_XREF        B
    INNER JOIN PS_PAYMENT_TBL A
        ON B.PYMNT_ID = A.PYMNT_ID
           AND A.BANK_SETID = B.BANK_SETID
           AND A.BANK_CD = B.BANK_CD
           AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
    INNER JOIN PS_VOUCHER     C
        ON B.VOUCHER_ID = C.VOUCHER_ID
           AND C.BUSINESS_UNIT = B.BUSINESS_UNIT
WHERE A.PYMNT_DT > '01/12/2021'
UNION
SELECT
    'C'
    , SUM(A.PAID_AMT)
    , COUNT(A.PAID_AMT) , COUNT(??)
FROM
    PS_PYMNT_VCHR_XREF        A
    INNER JOIN PS_PAYMENT_TBL B
        ON B.PYMNT_ID = A.PYMNT_ID
           AND A.BANK_SETID = B.BANK_SETID
           AND A.BANK_CD = B.BANK_CD
           AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE A.PYMNT_DT > '01/12/2021'
GROUP BY B.NAME1
ORDER BY 7, 1

21 年 1 月 17 日编辑:

我稍微修改了 SQL,并且相信我的代码的各个部分都可以正常工作,但我正在苦苦思考如何将这些部分以与示例所示相同的方式组合在一起,并在每个付款下方显示汇款。

UNION 是我能想到的唯一一个垂直组合数据的函数,但我需要能够将汇款分组到每个相应的付款行下方,我不知道有什么方法可以做到这一点。另外值得一提的是,Payment 和 Remittances 查询的列是不同的(列数和类型)并且并不总是相互关联。

--Payment:
SELECT B.ADDRESS4, B.PYMNT_ID_REF, SUM(A.PAID_AMT), B.REMIT_VENDOR, A.REMIT_ADDR_SEQ_NUM,
B.NAME1
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
     AND A.BANK_CD = B.BANK_CD
     AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE B.PYMNT_DT > '2021-01-03'
--AND A.PYMNT_ID = '0000263556'
GROUP BY B.ADDRESS4, B.PYMNT_ID_REF, B.REMIT_VENDOR, A.REMIT_ADDR_SEQ_NUM,
B.NAME1

--Remittances:
SELECT 2, A.PYMNT_ID_REF, C.INVOICE_ID, C.INVOICE_DT, C.GROSS_AMT, C.DSCNT_AMT, B.VOUCHER_ID,
C.PO_ID
FROM PS_PYMNT_VCHR_XREF B
INNER JOIN PS_PAYMENT_TBL A ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
     AND A.BANK_CD = B.BANK_CD
     AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
INNER JOIN PS_VOUCHER C ON B.VOUCHER_ID = C.VOUCHER_ID AND C.BUSINESS_UNIT = B.BUSINESS_UNIT
WHERE A.PYMNT_DT > '2021-01-03'
--AND A.PYMNT_ID = '0000263556'


--Control data:
WITH CONTROLTOTALS AS (
SELECT  SUM(A.PAID_AMT) AS TOT_PAID_AMT
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
     AND A.BANK_CD = B.BANK_CD
     AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE B.PYMNT_DT > '2021-01-03' )

, CONTROLCOUNT1 AS (

SELECT COUNT(PYMNT_ID_REF) AS PYMNT_COUNT
FROM PS_PAYMENT_TBL B
WHERE B.PYMNT_DT > '2021-01-03'
AND PYMNT_ID <> '' ) ,

CONTROLCOUNT2 AS (
SELECT COUNT(*) AS REMIT_COUNT
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
     AND A.BANK_CD = B.BANK_CD
     AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE PYMNT_DT > '2021-01-03'  )

SELECT 'C', A.TOT_PAID_AMT, B.PYMNT_COUNT, C.REMIT_COUNT
FROM CONTROLTOTALS A, CONTROLCOUNT1 B, CONTROLCOUNT2 C

21 年 1 月 18 日编辑:

以下是这些查询中使用的 3 个表的表结构:

以下是一些示例数据(两个供应商的 2 个付款行(Radiometer America - 付款 ID 023946 和 Highmark Inc. - 付款 ID 023943),以及与每个供应商关联的相应汇款行:

以下是与供应商的每笔付款相关的示例汇款数据(Highmark 以红色突出显示,有 2 笔汇款,Radiometer America 以绿色突出显示,有 6 笔汇款,示例输出总共有 8 笔):

这是底部的最终控制(大)总计行。 TOT_PAID_AMT 表示每个供应商的上述总付款 (8901.94 + 997,343.44)、总付款计数 (2) 和报告中的总汇款(2 来自 Highmark,6 来自 Radiometer America:

所以现在我需要将所有这些放在一起,并以与原始示例相同的方式输出,汇款显示在每个供应商付款的下方。

这是此特定示例的输出结果,请注意汇款与相应的付款一起显示:

所以我有所有单独的部分,但我现在需要以这种格式将它们联系在一起,为此我正在努力。希望这能提供更多的清晰度。

此处的表结构/示例文件:

https://pastebin.com/Ym9PuKXf

https://pastebin.com/F62467zE

https://pastebin.com/4EBrdyjN

https://pastebin.com/iU20L8Jf

https://pastebin.com/k9P75DAJ

https://pastebin.com/f4Ci6qXd

【问题讨论】:

Bad habits to kick : using table aliases like (a, b, c) or (t1, t2, t3) 【参考方案1】:

如果我对您的理解正确,您有三个包含各种列的查询结果,并且您希望将它们组合成一个由 Pipe("|") 分隔符分隔的列结果。 为了实现这一点,我在所有三个查询输出名称“ConcatedValue”中添加了一个附加列,其中我将所有列中的值合并为一个。 然后,为了在最后获得来自 controls 的值,我引入了一个名为 sortid 的列,并为了维护每个 pymnt_id_ref 的支付和汇款顺序,我引入了一个名为 internalsortid 的列。由于控制表没有 pymnt_id_ref 列,因此我已将其添加为空值。

我用您的结果创建了三个名为支付、汇款和控制的表,并通过以下查询获得您想要的结果:

with cte as(
SELECT 1 SorTID,1 internalsortid, pymnt_id_ref,('P|CRD|Hosp.Payables'+'|'+ ADDRESS4+'|'+ PYMNT_ID_REF+'|'+ colc+'|'+REMIT_VENDOR+'|'+ REMIT_ADDR_SEQ_NUM+'|'+NAME1+'|USD')concatedvalue from payment b
union all
SELECT 1 SortID,2 internalsortid, pymnt_id_ref,('R|'+PYMNT_ID_REF +'|'+  INVOICE_ID +'|'+  CONVERT(VARCHAR(10),INVOICE_DT ,120)+'|'+  GROSS_AMT +'|'+  DSCNT_AMT +'|'+  VOUCHER_ID +'|'+  PO_ID )
from Remittance
union all
SELECT 3 SortID,3 internalsortid, NULL pymnt_id_ref, ('C|'+ TOT_PAID_AMT+'|'+ PYMNT_COUNT+'|'+REMIT_COUNT)concatedvalue from control)
select  concatedvalue from cte order by sortid,pymnt_id_ref,internalsortid

期望的结果:

由于我没有您的实际表格,因此在更改查询后我无法执行您的查询。我在这里分享。但是,如果您有任何数据类型为 int、float 等的列,则需要在将它们组合在单个列中时进行转换。例如,如果 pymnt_id_ref 是一个 int 列,您需要在下面的行中使用支付表的 concatedvalue 列:

修改后的查询:

 With Payment as(
SELECT 'P', 'CRD', 'Hosp.Payables', B.ADDRESS4, B.PYMNT_ID_REF, SUM(A.PAID_AMT), B.REMIT_VENDOR, A.REMIT_ADDR_SEQ_NUM,
B.NAME1, 'USD',1 SorTID,1 internalsortid, 
('P|CRD|Hosp.Payables'+'|'+ b.ADDRESS4+'|'+ b.PYMNT_ID_REF+'|'+ SUM(A.PAID_AMT)+'|'+b.REMIT_VENDOR+'|'+ a.REMIT_ADDR_SEQ_NUM+'|'+b.NAME1+'|USD')concatedvalue 

FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
     AND A.BANK_CD = B.BANK_CD
     AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE B.PYMNT_DT = '2021-01-04'
 --AND A.PYMNT_ID = '0000263556'
   AND B.REMIT_VENDOR IN ('51503A', '71520A')
   --AND B.REMIT_VENDOR = '71520A'
  AND B.PYMNT_STATUS NOT IN ('S','V')
     AND (( A.BANK_SETID = 'SHARE'
     AND A.BANK_CD = 'MT'
     AND A.BANK_ACCT_KEY = 'TGC')
     OR ( A.BANK_SETID = 'SHARE'
     AND A.BANK_CD = 'PNC'
     AND A.BANK_ACCT_KEY = '1'))
     --AND D.VENDOR_CLASS <> 'E'
GROUP BY B.ADDRESS4, B.PYMNT_ID_REF, B.REMIT_VENDOR, A.REMIT_ADDR_SEQ_NUM,
B.NAME1
--ORDER BY B.NAME1, PYMNT_ID_REF
) ,
Remittance as(
 
--Remittances:
SELECT 2, A.PYMNT_ID_REF, C.INVOICE_ID, C.INVOICE_DT, C.GROSS_AMT, C.DSCNT_AMT, B.VOUCHER_ID,C.PO_ID,
1 SortID,2 internalsortid, ('R|'+A.PYMNT_ID_REF +'|'+  C.INVOICE_ID +'|'+   CONVERT(VARCHAR(10),c.INVOICE_DT ,120) +'|'+  C.GROSS_AMT +'|'+  C.DSCNT_AMT +'|'+      VOUCHER_ID +'|'+  PO_ID )

FROM PS_PYMNT_VCHR_XREF B
INNER JOIN PS_PAYMENT_TBL A ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
     AND A.BANK_CD = B.BANK_CD
     AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
INNER JOIN PS_VOUCHER C ON B.VOUCHER_ID = C.VOUCHER_ID AND C.BUSINESS_UNIT = B.BUSINESS_UNIT
WHERE A.PYMNT_DT = '2021-01-04'
 --AND A.PYMNT_ID = '0000263556'
 --AND B.REMIT_VENDOR = '51503A'
 AND A.PYMNT_ID_REF IN ('023946', '023943')
 --OR A.PYMNT_ID_REF =  '023943'
 --ORDER BY PYMNT_ID_REF
 ), 
 
--Control data:
CONTROLTOTALS AS (
SELECT  SUM(A.PAID_AMT) AS TOT_PAID_AMT
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
     AND A.BANK_CD = B.BANK_CD
     AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE B.PYMNT_DT = '2021-01-04'
   AND B.PYMNT_ID_REF IN ('023946','023943'))    
 
, CONTROLCOUNT1 AS (
 
SELECT COUNT(PYMNT_ID_REF) AS PYMNT_COUNT
FROM PS_PAYMENT_TBL B
WHERE B.PYMNT_DT = '2021-01-04'
AND PYMNT_ID <> ''
  AND  B.PYMNT_ID_REF IN ('023946','023943')) ,
    

CONTROLCOUNT2 AS (
SELECT COUNT(*) AS REMIT_COUNT
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
     AND A.BANK_CD = B.BANK_CD
     AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE PYMNT_DT = '2021-01-04'  
 AND B.PYMNT_ID_REF IN ('023946','023943') ),
finalCTE as(
SELECT SortID, internalsortid,pymnt_id_ref,concatedvalue FROM Payment
union all
SELECT SortID, internalsortid,pymnt_id_ref,concatedvalue FROM Remittance
union all
SELECT 3 SortID,3 internalsortid, NULL pymnt_id_ref, ('C|'+ A.TOT_PAID_AMT+'|'+ B.PYMNT_COUNT+'|'+C.REMIT_COUNT)concatedvalue 
FROM CONTROLTOTALS A, CONTROLCOUNT1 B, CONTROLCOUNT2 C)

select  concatedvalue from finalcte order by sortid,pymnt_id_ref,internalsortid

【讨论】:

我收到一条关于 ORDER BY 的错误消息和一些语法错误:此 ORDER BY 错误位于前两个 CTE(付款和汇款)的底部 - ORDER BY 子句在视图、内联函数、派生表、子查询和公用表表达式,除非还指定了 TOP、OFFSET 或 FOR XML。 CTE 表达式,即 SELECT 'P' 需要指定一个列名,我不知道这会如何影响对最终选择中列的引用。 -- 消息 8155,级别 16,状态 2,第 108 行没有为“付款”的第 1 列指定列名。消息 8155,级别 16,状态 2,第 108 行没有为“付款”的第 2 列指定列名。消息 8155,级别 16,状态 2,第 108 行没有为“付款”的第 3 列指定列名称。 我也得到一个无效的列名: pymnt_id_ref 。在支付 CTE 中的连接字符串之后,你有“来自支付 b 的连接值”我认为“来自支付 b”是一个错误,因为它在这方面出错了。这应该被删除吗?如果你喜欢我可以编辑帖子并粘贴我编辑过的代码...... 因为我没有桌子,所以我预料到了。让我们尝试做一些改变。你的输出好吗? 抱歉附加了错误的查询。请立即尝试。你仍然会得到一些错误。请在这里分享。或者是否可以共享您的屏幕,以便我可以尝试直接修复错误。【参考方案2】:

您无法在 SQL Server 中进行查询,以完全按照您想要的格式返回数据。在 SQL Server(以及任何其他关系 DBMS)中,架构是静态的。 Schema 是表的定义,它是表中列及其类型的列表。 SQL Server 中的查询返回一个表,其中包含特定的静态列列表及其类型。

在 SQL Server 中,您不能有一个查询在一行中返回 4 列,在另一行中返回 7 列。

在 SQL Server 中,您不能有一个查询在同一列的某一列中返回 money 类型值,而在同一列的另一行中返回 varchar 类型值。给定列中的所有值必须具有相同的类型。


如果您需要向用户显示此类混合数据,您最好运行三个单独的查询并使用一些报告工具,以您想要的方式整齐地显示数据。


如果您坚持将所有内容放在一个查询中,则需要决定将哪些值放在哪一列中。

在您的示例中,P 行有 10 列,R 行有 7 列,C 行有 4 列。因此,总的来说,您的查询将产生(至少)10 列。其中一些列的 RC 行将具有 NULL 值。

如果您想将不同类型的值放在同一列中,例如美元金额 (TOT_PAID_AMT) 和一些文本 (CRD),那么您可以使用内置的 ConvertFormat 函数。

最后你使用UNION ALL(不是UNION)将来自三个独立表/查询的数据放在一起。

放在一起

在您的示例中,您没有为查询结果中的所有列命名,所以我将把我的名字放在这里。

支付查询返回这些列:

QueryType Col1 Col2 ADDRESS4 PYMNT_ID_REF Col3 REMIT_VENDOR EMIT_ADDR_SEQ_NUM NAME1 Col4

汇款查询返回以下列:

QueryType PYMNT_ID_REF INVOICE_ID INVOICE_DT GROSS_AMT DSCNT_AMT VOUCHER_ID PO_ID

控制查询返回这些列:

QueryType TOT_PAID_AMT PYMNT_COUNT REMIT_COUNT

您可以将它们放入临时表中,或者将它们包装在 CTE 中。下面我将它们称为TablePTableRTableC

现在我们可以写了

SELECT
    QueryType
    ,Col1
    ,Col2
    ,ADDRESS4
    ,PYMNT_ID_REF
    ,Col3
    ,REMIT_VENDOR
    ,EMIT_ADDR_SEQ_NUM
    ,NAME1
    ,Col4
FROM TableP

UNION ALL

SELECT
    QueryType
    ,PYMNT_ID_REF
    ,INVOICE_ID
    ,INVOICE_DT
    ,GROSS_AMT
    ,DSCNT_AMT
    ,VOUCHER_ID
    ,PO_ID
    ,NULL
    ,NULL
FROM TableR

UNION ALL

SELECT
    QueryType
    ,TOT_PAID_AMT
    ,PYMNT_COUNT
    ,REMIT_COUNT
    ,NULL
    ,NULL
    ,NULL
    ,NULL
    ,NULL
    ,NULL
FROM TableC

为文本添加必要的转换,例如CONVERT(nvarchar(255), TOT_PAID_AMT)。您不必将NULL 值放在最后,您只需确保UNION ALL 中的每个SELECT 返回10 列。并且这些列具有相同的类型。

排序

当您看到所有三个查询的结果时,您可以继续进行最后一点 - 对它们进行排序。

您希望将 C 值放在最后,将 R 值放在对应的 P 值下方。通过“对应”,我猜我们可以使用PYMNT_ID_REF 来匹配它们。 所以,我在这里添加一个帮助列:

SELECT
    'P' AS QueryType
    ,Col1
    ,Col2
    ,ADDRESS4
    ,PYMNT_ID_REF
    ,Col3
    ,REMIT_VENDOR
    ,EMIT_ADDR_SEQ_NUM
    ,NAME1
    ,Col4
    ,NULL
    ,0 AS SortOrder
FROM TableP

UNION ALL

SELECT
    'R' AS QueryType
    ,NULL
    ,NULL
    ,NULL
    ,PYMNT_ID_REF
    ,INVOICE_ID
    ,INVOICE_DT
    ,GROSS_AMT
    ,DSCNT_AMT
    ,VOUCHER_ID
    ,PO_ID
    ,0 AS SortOrder
FROM TableR

UNION ALL

SELECT
    'C' AS QueryType
    ,TOT_PAID_AMT
    ,PYMNT_COUNT
    ,REMIT_COUNT
    ,NULL
    ,NULL
    ,NULL
    ,NULL
    ,NULL
    ,NULL
    ,NULL
    ,1 AS SortOrder
FROM TableC

ORDER BY
    SortOrder          -- C values last
    ,PYMNT_ID_REF      -- group by PYMNT_ID_REF
    ,QueryType         -- P before R
;

为此,我必须将 PYMNT_ID_REF 值移动到同一列(第 5 列)并移动 NULL 值。我最终总共有 12 列。

【讨论】:

以上是关于SQL Server中具有不同列数的多个查询的联合结果的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL 查询将数据插入到具有可变列数的表中

在 Excel 查询编辑器中合并具有不同列数的 CSV 文件文件夹

我们如何在 Spark 中使用 Dataframes(由 structtype 方法创建)合并具有不同列数的 2 个表?

组合具有不同列数的 Spark 数据帧

自动 CSV 文件生成,在 Oracle 10g 中具有不同列数的两个标题级别的标题

构建具有不同列数的html表