SQL 具有未确定列的交叉表

SQL Crosstab with undetermined columns

我看到很多类似的问题,但几乎所有问题都将结果分组为列名(基于结果的列名),我的是一个更简单的列表。我不在乎它是否使用动态 SQL 或不(我认为它必须)。

请不要告诉我我需要重组 tables,我在旧系统上工作,没有那个选项。

基本上,我只需要一个列表,其中包含连续匹配 table "A" 中给定记录的所有有效 table "B" 条目。

我还没有任何代码示例,因为我没有找到正确设置它的方法。

Table: Customer c
CustID   Name
1        Bill Smith
2        Jim Jones
3        Mary Adams
4        Wendy Williams

Table: Debt d
CustID   Creditor             Balance
1        ABC Loans            245
1        Citibank             815
2        Soprano Financial   74000
3        Citibank             24
3        Soprano Financial   93000
3        Wells Fargo          275
3        Midwestern S&L       2500
4        ABC Loans            1500
4        Fred's Payday Loan   1000

Desired Output:
Name            Cred1                Bal1   Cred2               Bal2   Cred3        Bal3   Cred4            Bal4
Bill Smith      ABC Loans            245    Citibank            815    (NULL)       (NULL) (NULL)           (NULL)
Jim Jones       Soprano Financial    74000  (NULL)              (NULL) (NULL)       (NULL) (NULL)           (NULL)
Mary Adams      Citibank             24     Soprano Finanacial  93000  Wells Fargo   275   Midwestern S&L   2500
Wendy Williams  ABC Loans            1500   Fred's Payday Loan  1000   (NULL)       (NULL) (NULL)           (NULL)

基本上,我可能必须为任何特定 "CustomerID" 收集某种最多记录的计数,并基于此定义输出列。如果已经回答了这个问题,请随时 link 并关闭它,我在搜索时没有看到这种特定情况。

我猜你已经知道如何使用交叉表,所以你只需要准备数据就可以使用它。

第 1 步: 连接两个表:

 SELECT c.Name, d.Creditor, d.Balance
 FROM Customer c
 JOIN Debt d
   ON c.CustID = d.CustID

第 2 步: 在与您要用于交叉表的客户相关的每个元素中包含一个行号

SELECT c.Name, d.Creditor, d.Balance, 
       ROW_NUMBER() over (PARTITION BY Name ORDER BY creditor) as rndebt_tab
FROM Customer c
JOIN Debt d
  ON c.CustID = d.CustID

现在你有:

CustID   Creditor             Balance    rn
1        ABC Loans            245        1      
1        Citibank             815        2
2        Soprano Financial   74000       1
3        Citibank             24         1
3        Soprano Financial   93000       2
3        Wells Fargo          275        3
3        Midwestern S&L       2500       4
4        ABC Loans            1500       1
4        Fred's Payday Loan   1000       2

第 3 步:为交叉表创建源

WITH cte as (
     <query from step2>
)
SELECT Name, 
       'CREDITOR_' + RIGHT('000' + CAST(rn AS VARCHAR(3)),3) as cross_tab,
       Creditor as Value
FROM cte  
UNION all
SELECT Name, 
       'DEBT_' + RIGHT('000' + CAST(rn AS VARCHAR(3)),3) as cross_tab,
       CAST(Balance as VARCHAR(max)) as Value      
FROM cte  

现在你有:

CustID   cross_tab          Value             
1        CREDITOR_001       ABC Loans         
1        CREDITOR_002       Citibank          
2        CREDITOR_001       Soprano Financial 
3        CREDITOR_001       Citibank          
3        CREDITOR_002       Soprano Financial 
3        CREDITOR_003       Wells Fargo       
3        CREDITOR_004       Midwestern S&L    
4        CREDITOR_001       ABC Loans         
4        CREDITOR_002       Fred's Payday Loan
1        DEBT_001           245  
1        DEBT_002           815  
2        DEBT_001       `   74000
3        DEBT_001           24   
3        DEBT_002           93000
3        DEBT_003           275  
3        DEBT_004           2500 
4        DEBT_001           1500 
4        DEBT_002           1000 

编辑: 我在示例中使用 CustID 而不是 Name 但现在懒得更改了。

这是另一种动态方法。我们使用 Row_Number() 来创建最少数量的列。

例子

Declare @SQL varchar(max) = Stuff((Select Distinct ','+QuoteName(concat('Cred',ColNr)) 
                                                  +','+QuoteName(concat('Bal',ColNr)) 
                                    From (Select ColNr=Row_Number() over (Partition By CustID Order By Creditor) From Debt ) A  
                                    Order By 1 
                                    For XML Path('')),1,1,'') 

Select  @SQL = '
Select *
From (
        Select C.Name
              ,B.*
         From (
                Select *,ColNr=Row_Number() over (Partition By CustID Order By Creditor)
                 From  Debt
              ) A
         Cross Apply (values (concat(''Cred'',ColNr),[Creditor])
                            ,(concat(''Bal'' ,ColNr) ,cast(Balance as varchar(25)))
                     ) B (Item,Value)
         Join Customer C on A.CustID=C.CustID

    ) A
Pivot (max([Value]) For [Item] in (' + @SQL + ') ) p'
--Print @SQL
Exec(@SQL);

Returns

如果有帮助,生成的 SQL 如下所示:

Select *
From (
        Select C.Name
              ,B.*
         From (
                Select *,ColNr=Row_Number() over (Partition By CustID Order By Creditor)
                 From  Debt
              ) A
         Cross Apply (values (concat('Cred',ColNr),[Creditor])
                            ,(concat('Bal' ,ColNr) ,cast(Balance as varchar(25)))
                     ) B (Item,Value)
         Join Customer C on A.CustID=C.CustID

    ) A
Pivot (max([Value]) For [Item] in ([Cred1],[Bal1],[Cred2],[Bal2],[Cred3],[Bal3],[Cred4],[Bal4]) ) p

只是为了可视化,查询 "feeding" Pivot 生成: