0

I see a lot of similar questions, but almost all of them wind up grouping results as column names (Column names based on results), mine is a more simple list. I don't care if it uses dynamic SQL or not (I'd think it has to).

Please don't tell me I need to restructure the tables, I'm working from a legacy system and don't have that option.

Basically, I just need a list of all valid table "B" entries that match a given record from table "A", in a row.

I don't have any code sample yet, because I'm not seeing a way to set this up correctly.

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)

Basically, I probably have to collect some kind of count of the most number of records for any specific "CustomerID", and define the output columns based on that. If this has already been answered, feel free to link and close this out, I did not see this specific scenario when I did my search.

PoloHoleSet
  • 157
  • 1
  • 1
  • 10
  • Here is yet another dynamic approach. http://www.sqlservercentral.com/articles/Crosstab/65048/ – Sean Lange Jun 15 '17 at 16:53
  • Possible duplicate of [SQL Server dynamic PIVOT query?](https://stackoverflow.com/questions/10404348/sql-server-dynamic-pivot-query) – Tab Alleman Jun 15 '17 at 18:29
  • @TabAlleman - No, not a duplicate because it gathers values that match a certain entry and the column names are derived from that, which I did see, and does not quite fit, because I would not want a column for each and every "creditor" name. – PoloHoleSet Jun 15 '17 at 18:34
  • The concept is adaptable to your need. Instead of creditor name, you would use creditor row_number() related to your customer, based on some arbitrary order. Both of the answers below simply extend the answer in the duplicate to be more specific to your situation, but the general concept is the same. – Tab Alleman Jun 15 '17 at 18:42
  • "adaptable" - but then I'd have to ask a question about the method of adaptation to row numbers, which is essentially what this is, so, still, not a duplicate, IMO. I specifically looked at questions with those solutions, and needed to ask this question, and the answers here very clearly outline what I need to do. An adaptable answer falls more under "similar," not "duplicate." – PoloHoleSet Jun 15 '17 at 19:32

2 Answers2

3

Here is another dynamic approach. We use Row_Number() to create the minimal number of columns.

Example

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

enter image description here

If if Helps, the Generated SQL Looks Like This:

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

Just for the Visualization, the query "feeding" the Pivot generates:

enter image description here

John Cappelletti
  • 79,615
  • 7
  • 44
  • 66
2

I will guess you are already know how to use cross tab so you only need to prepare your data to use it.

STEP 1: Join both tables:

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

STEP 2: Include a row number to each element related to the customer you are going to use to cross tab

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

Now you have:

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

STEP 3: Create the SOURCE for the cross tab

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  

Now you have:

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 

EDIT: I use CustID instead of Name on the example but too lazy to change now.

Juan Carlos Oropeza
  • 47,252
  • 12
  • 78
  • 118
  • Nice! This looks exactly like what I was looking for. I'll leave the question open, because, what's the hurry?, and to see if anyone has different approaches. – PoloHoleSet Jun 15 '17 at 16:48
  • last thing, hope you notice I convert `balance` to string so I can make the `UNION` In the cross tab you can covert to your desire type. Also you can change the number of padding `0` to one or two if fit your requirement. – Juan Carlos Oropeza Jun 15 '17 at 16:50
  • I did notice, and we're not performing calculations (actually, my real-life problem lists document "numbers" that are alpha-numeric, but I thought this would be a simpler example format that would not require additional explanations), so that's not a problem, even if I didn't. – PoloHoleSet Jun 15 '17 at 16:53