【问题标题】:SQL Crosstab with undetermined columns具有未确定列的 SQL 交叉表
【发布时间】:2017-11-18 05:39:11
【问题描述】:

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

请不要告诉我我需要重组表格,我使用的是旧系统并且没有该选项。

基本上,我只需要连续匹配表“A”中的给定记录的所有有效表“B”条目的列表。

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

表:客户 c 客户 ID 名称 1 比尔·史密斯 2 吉姆·琼斯 3 玛丽·亚当斯 4 温迪·威廉姆斯 表:债务 d CustID 债权人余额 1 ABC 贷款 245 1 花旗银行 815 2 女高音金融 74000 3 花旗银行 24 3 女高音金融 93000 3 富国银行 275 3 中西部 S&L 2500 4 ABC贷款1500 4 弗雷德的发薪日贷款 1000 期望的输出: 名称 Cred1 Bal1 Cred2 Bal2 Cred3 Bal3 Cred4 Bal4 比尔史密斯 ABC 贷款 245 花旗银行 815 (NULL) (NULL) (NULL) (NULL) Jim Jones Soprano Financial 74000 (NULL) (NULL) (NULL) (NULL) (NULL) (NULL) Mary Adams 花旗银行 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”收集某种最多记录数,并基于此定义输出列。如果这个问题已经得到解答,请随意链接并关闭它,我在搜索时没有看到这个特定场景。

【问题讨论】:

  • 这是另一种动态方法。 sqlservercentral.com/articles/Crosstab/65048
  • @TabAlleman - 不,不是重复的,因为它收集与某个条目匹配的值,并且列名是从中派生的,我确实看到了,但不太合适,因为我不想每个“债权人”名称的列。
  • 这个概念可以适应您的需要。根据任意顺序,您将使用与您的客户相关的债权人 row_number() 而不是债权人名称。以下两个答案都只是将副本中的答案扩展为更适合您的情况,但一般概念是相同的。
  • "adaptable" - 但是我不得不问一个关于适应行号的方法的问题,这本质上就是这样,所以仍然不是重复的,IMO。我专门研究了这些解决方案的问题,需要提出这个问题,这里的答案非常清楚地概述了我需要做什么。一个适应性强的答案更多地属于“相似”,而不是“重复”。

标签: sql sql-server tsql pivot crosstab


【解决方案1】:

这是另一种动态方法。我们使用 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);

退货

如果有帮助,生成的 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

仅用于可视化,查询“馈送”Pivot 生成:

【讨论】:

  • 您是否为此制作了 DDL,以便我可以在 rextester 上对其进行测试?不确定CROSS APPLY 是如何工作的
  • @JuanCarlosOropeza 刚刚为交叉应用添加了第二张图片
  • 请做。我使用来自sqlFiddle 的那个。但是因为我不打算做交叉表,所以认为不需要它。
  • @JuanCarlosOropeza 我想我误解了你最初的问题。 dbfiddle.uk/… ...
  • 是的,这更像我用的
【解决方案2】:

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

第 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 步:为交叉表创建 SOURCE

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,但现在懒得更改。

【讨论】:

  • 不错!这看起来和我要找的完全一样。我会留下这个问题,因为,有什么急事?,看看是否有人有不同的方法。
  • 最后一件事,希望你注意到我将balance 转换为字符串,这样我就可以制作UNION 在交叉表中你可以转换为你想要的类型。如果符合您的要求,您也可以将填充 0 的数量更改为一或二。
  • 我确实注意到了,我们没有执行计算(实际上,我的现实问题列出了字母数字的文档“数字”,但我认为这将是一个更简单的示例格式不需要额外的解释),所以这不是问题,即使我没有。
猜你喜欢
  • 2012-03-09
  • 1970-01-01
  • 2012-10-10
  • 1970-01-01
  • 2017-08-30
  • 1970-01-01
  • 1970-01-01
  • 2021-08-01
  • 2015-12-28
相关资源
最近更新 更多