Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Concatenating Row Values into 1 Record/String

T-SQL Hints and Tips

Concatenating Row Values into 1 Record/String

by  Catadmin  Posted    (Edited  )
How do I take multiple records results and make the result one record?

This question, and many variations on it, have been asked frequently on this forum. The questioner provides the forum with the data and the desired result set (see below example) asking how one can combine results:

[quote Nameless]
I have this record set returned for a query.

Name Phone
Bobby 555-2225
Bobby 888-2495

Because Bobby has both a house phone and a cell phone, I get two records. But I only want one row with both phone #s on the same line, like below. How do I do this?

Name Phone
Bobby 555-2225 / 888-2495
[/quote]

For something small, with a finite # of records (I.E., you're never going to have more than 2 records for each person), you can simply join the table to itself as listed below:

Code:
[i]--This is a simple, finite table concatenation
--Assumes CustID is Identity/PK/Unique identifier[/i]

Select Name, cp.Phone + ' / ' + cp1.Phone as Phone
from CustName cn
join CustPhone cp
on cn.CustID = cp.CustID
left outer join CustPhone cp1
on cn.CustID = cp1.CustID
[i]--Second join is left outer because not all customers
--will have a cell phone[/i]

Essentially, you're just re-joining the same table, CustPhone, the exact number of times you'll have records for. However, after about 3 joins, this gets tedious. It also doesn't address the possibility that someone could have 10 or 20 or 30 phones.

I've come up with a piece of code to address the possibility for "infinite" records that need to be joined together. Be warned, though, if the pre-concatenated result set is high, this query will take a LONG time to run. I've only tested it with up to seven possibilities.

In this example, I'm concatenating the columns used in Combined Primary Keys because I only want the TableName and a single string listing all the columns involved in the Primary Key. I use this code for a Data Dictionary report I'm working on:

Code:
If exists (Select * from tempdb.sys.objects where type = 'U' and [name] like '#PKConcat%')
Drop Table #PKConcat
[i]--Makes sure temp table doesn't already exist in this session and deletes it if it does[/i]

Create Table #PKConcat (TableName varchar(200), 
PKCols varchar(800) Default '', CntCols int Default 0, RowDone bit Default 0)
[i]--Creates temp table for storing values.  Defaults bit value to FALSE, column string (PKCols) to blank,
--and the column count for each line (CntCols) to 0[/i]

Insert into #PKConcat (TableName)
(Select Distinct [name]
 from sys.objects
 where type = 'U'
 and [name] <> 'dtproperties')
[i]--Inserts all user created tables into temp table.  Defaults from Create statement are applied[/i]

Update pkc
Set CntCols = A.CntCols
from #PKConcat pkc
join (Select Distinct t.table_name, Max(k.Ordinal_Position) as CntCols
                    from Information_Schema.Table_Constraints t
					JOIN Information_Schema.Key_Column_Usage k
					ON t.Constraint_Name = k.Constraint_Name
					Group By t.Table_Name) A
on pkc.TableName = a.Table_Name
[i]--For each table name, the highest column # (ordinal_position) is taken as a value for
--how many columns are involved in the Primary Key[/i]

Declare @CntDown int, @TableCnt int, @TblName varchar(200)
[i]--Declare variables for below looping[/i]

Set @CntDown = 0
Set @TblName = ''
[i]--Want to make sure the countdown for the columns is initialized as zero and the tablename is
--initialized as blank so nothing weird happens in the below code[/i]

Set @TableCnt = (Select Distinct Count(TableName)
								from #PKConcat)
[i]--Sets the table counter with the total of table names in the temp table[/i]

While @TableCnt > 0  [i]--while we still have tables to process[/i]
  Begin
     Set @CntDown = (Select Top 1 CntCols from #PKConcat where RowDone = 0)
	 Set @TblName = (Select Top 1 TableName from #PKConcat where RowDone = 0)
[i]--Sets countdown value with the max # of columns for this specific table
--Sets "current" table name[/i]

     While @CntDown > 0  [i]--While we still have columns to process[/i]
        Begin
			Update pkc
			Set PKCols = ltrim(PKCols) + '  ' + (Select Distinct k.Column_Name
                                                                from Information_Schema.Table_Constraints t
																JOIN Information_Schema.Key_Column_Usage k
																ON t.Constraint_Name = k.Constraint_Name
																Where t.Table_Name = @TblName
																and k.Ordinal_Position = @CntDown
																and t.Constraint_Type = 'Primary Key')
			from #PKConcat pkc
			Where TableName = @TblName
[i]--The above Select a column name and adds it to the string with two spaces between each column
--name[/i]
			Set @CntDown = @CntDown - 1
[i]--Subtract 1 from our column count to make sure While Loop will actually end[/i]
		End
	 
	 Update #PKConcat  
	 Set RowDone = 1
	 Where RowDone = 0 and TableName = @TblName
[i]--Set bit flag indicating this row in temp table has been processed[/i]

	 Set @TableCnt = @TableCnt - 1
[i]--Subtract 1 from our table count to make sure outer While Loop will actually end[/i]	
	End

Update #PKConcat
Set PKCols = 'No Primary Key on this Table'
where PKCols is NULL
or PKCols = ' '
[i]--Updates tables with a NULL or blank PKCols values to reflect there is no Primary Key available[/i]

Select TableName, ltrim(PKCols)
from #PKConcat
[i]--Selects result set and trims the left-side blanks on the single column value PKCol strings[/i]

Drop Table #PKConcat
[i]--Drops the temp table[/i]

I wanted to do this without a cursor. You could use a cursor to go through the record set, but I hate doing cursors unless I have no other way of accomplishing my task.

I hope the comments in the query are clear and understandable. If they are not, please let me know. Also, the above method should work for normal record results based off of user tables such in the phone number scenario listed first.

Good luck with your coding!! I hope this helps.

Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top