Phil Factor's Phrenetic Phoughts

Simple-Talk columnist
The wilder shores of Transact SQL    Phil on Twitter   Phil on SQL Server Central  Phil on BOS

Enumerating Group Members

Published Tuesday, September 25, 2007 10:20 AM


/*

Problem: You have a table like this, recording what people eat.

jane   pie
fred   sandwich
fred   sausage
fred   biscuits
jane   cake
Rajiv  pastie
Rajiv Croissants

You want it to look like this...

jane   pie, cake
fred   sandwich, sausage, biscuits
Rajiv  pastie, Croissants

or maybe even you have the latter and you want the former!

Robyn and I showed how this sort of thing was done in the SQL Server
Grouping Workbench
 under the title 'enumerating group members'. Let's
use a slightly more complicated example to show how it is done....

*/

CREATE TABLE #schedule
 
(
 
EventStart DATETIME,
 
description VARCHAR(2000)
 )
/* so now we can get some sample data into the table */
INSERT INTO #schedule EventStartdescription )
  
SELECT '12 Oct 2007 10:00''Meeting with Bill'
INSERT INTO #schedule EventStartdescription )
  
SELECT '12 Oct 2007 12:00''visit Crawley site'
INSERT INTO #schedule EventStartdescription )
  
SELECT '12 Oct 2007 13:00''Lunch with Evelyn'
INSERT INTO #schedule EventStartdescription )
  
SELECT '12 Oct 2007 14:00''Review of CDW27'
INSERT INTO #schedule EventStartdescription )
  
SELECT '12 Oct 2007 17:00''admin'
INSERT INTO #schedule EventStartdescription )
  
SELECT '13 Oct 2007 10:00''Finance committee'
INSERT INTO #schedule EventStartdescription )
  
SELECT '13 Oct 2007 12:00''lunch with Bob'
INSERT INTO #schedule EventStartdescription )
  
SELECT '13 Oct 2007 14:00''Weekly SH meeting'
INSERT INTO #schedule EventStartdescription )
  
SELECT '14 Oct 2007 11:00''interviews'
INSERT INTO #schedule EventStartdescription )
  
SELECT '14 Oct 2007 15:00''office plan presentation'
INSERT INTO #schedule EventStartdescription )
  
SELECT '14 Oct 2007 16:00''performance reviews'


/* now we produce the report that enumerates the group members */
DECLARE @list VARCHAR(8000

SELECT @List COALESCE(@list ',''') + '|' 
                 
CONVERT(CHAR(11), eventStart113) + '| ' 
                 
RIGHT(CONVERT(CHAR(17), eventStart113), 5
                 + 
' - ' description '|' 
                 
CONVERT(CHAR(11), eventStart113) + '|'
FROM #schedule
ORDER BY eventstart
/* we'll put the results in a temporary table so we can show the
reverse process 
*/
CREATE TABLE #DailySchedule
 
(
 
[date] CHAR(11),
 
events VARCHAR(2000)
 )

INSERT INTO #DailySchedule
  
[date]events )
  
SELECT 
    
[Date] CONVERT(CHAR(11), TheDate),
    
[Events] LEFT(membersCHARINDEX(',|'members ',|') - 1)
  
FROM SELECT TheDate,
       
'members' REPLACE(SUBSTRING(@list
                           
CHARINDEX('|' dates.theDate '|'@list),
                           
8000),
                       
'|' dates.theDate '|''')
     
FROM  SELECT [theDate] CONVERT(CHAR(11), eventStart113)
             
FROM #schedule
             
GROUP BY CONVERT(CHAR(11), eventStart113)
           ) 
dates
    

SELECT FROM #DailySchedule

/* job done, but you may want to do the process in reverse. 
no problem let's take the end-results and get back to the
original */

DECLARE @rollout VARCHAR(8000)

SELECT @rollout COALESCE(@rollout'
'
) + [date] 
                  
' ' REPLACE(REPLACE(events', ''
[date] ' '), ' - ''|') + '
'
FROM #DailySchedule


SELECT 
    
[EventStart] CONVERT(DATETIMESUBSTRING(entry
                                        
1
                                        
CHARINDEX('|'entry) - 1)),
    
[Description] SUBSTRING(entry,
         
CHARINDEX('|'entry) + 1,
         
8000)
FROM SELECT [entry] LEFT(line,
         
CHARINDEX(CHAR(13) + CHAR(10),
           
line) - 1)
   
FROM SELECT [line] SUBSTRING(@rolloutnumber 28000)
     
FROM numbers
     
WHERE SUBSTRING(@rolloutnumber2= CHAR(13) + CHAR(10
         AND 
number LEN(@rollout)) - 2
     
g
  
h
ORDER BY eventstart


--Remember that you'll need a number table for this 
CREATE PROCEDURE spMaybeBuildNumberTable
@size INT=10000
AS
BEGIN
SET 
NOCOUNT ON
IF 
NOT EXISTS (SELECT FROM dbo.sysobjects
  
WHERE id OBJECT_ID(N'[dbo].[Numbers]')
   AND 
OBJECTPROPERTY(idN'IsUserTable'1)
    
BEGIN
    CREATE TABLE 
[dbo].[Numbers](
     
[number] [int],
    
CONSTRAINT [Index_Numbers] PRIMARY KEY CLUSTERED
    
(
     
[number] ASC
    
ON [PRIMARY]
    
ON [PRIMARY]

    
DECLARE @ii INT
    SELECT 
@ii=1
    
WHILE (@ii<=@size)
     
BEGIN
     INSERT INTO 
NUMBERS(NUMBERSELECT @II
     
SELECT @II=@II+1
     
END
    END
by Phil Factor
Attachment(s): initialData.jpg

Comments

 

András said:

I like XML for various reasons, among others for allowing the following query:

SELECT  DATEADD(dd, DATEDIFF(day, GETDATE(), t1.EventStart), GETDATE())
     ,
STUFF(( SELECT DISTINCT TOP 100 PERCENT
                       
',' + CAST(DATEPART(hh, t2.EventStart) AS VARCHAR)
                       +
':' + CAST(DATEPART(mm, t2.EventStart) AS VARCHAR)
                       +
' - ' + t2.description
               
FROM    #schedule AS t2
               
WHERE   DATEDIFF(day, GETDATE(), t1.EventStart) = DATEDIFF(day, GETDATE(), t2.EventStart)
               
ORDER BY ',' + CAST(DATEPART(hh, EventStart) AS VARCHAR) + ':'
                       
+ CAST(DATEPART(mm, EventStart) AS VARCHAR) + ' - '
                       
+ t2.description
             
FOR
               
XML PATH('')
             ),
1, 1, '')
FROM    #schedule AS t1
GROUP BY DATEDIFF(day, GETDATE(), t1.EventStart)


This returns your table in the blog (one needs to prettify the first column a tiny bit).
The execution plan contains two scans, but it is a single statement :)

Andras

September 26, 2007 6:30 AM
 

Phil Factor said:

Wonderful! It beats my attempt hands down. I wouldn't have thought of the XML trick. It is quite a head-scratcher to see how it works, but mine isn't exactly simple!
September 26, 2007 7:14 AM
 

Phil Factor said:

--come to think of it, what about this?
DECLARE @accumulation VARCHAR(7000), @Date CHAR(11) 
DECLARE  @grouping TABLE (MyID INT IDENTITY(1,1), 
   
date CHAR(11), 
   
description VARCHAR(990), 
   
[next] INT
   
accumulation VARCHAR(7000))
INSERT INTO @grouping(date,description)
   
SELECT CONVERT(CHAR(11),EventStart,113),  
       
RIGHT(CONVERT(CHAR(17),EventStart,113),5)
+
'- '+description 
   
FROM #schedule ORDER BY eventstart
UPDATE @grouping SET 
   
@Accumulation=[accumulation]=
          
CASE WHEN date<>COALESCE(@date,''
          
THEN description 
          
ELSE @Accumulation+', '+description 
          
END,
   
@Date=date=date
SELECT Date,[events]=MAX(accumulation
FROM @grouping GROUP BY date
October 15, 2007 11:52 AM
You need to sign in to comment on this blog


















<September 2007>
SuMoTuWeThFrSa
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456
Finding Stuff in SQL Server Database DDL
 You'd have thought that nothing would be easier than using SQL Server Management Studio (SSMS) for... Read more...

Mission Critical: SQL Server 2008 Performance Tuning Task List
 In which Buck Woody imagines how the US military would have tackled DBA checklists for... Read more...

Simple Query tuning with STATISTICS IO and Execution plans
 A great deal can be gleaned from the use of the STATISTICS IO and the execution plan, when you are... Read more...

Switching rows and columns in SQL
 When they use SQL Server, one the commoner questions that Ms Access programmers ask is 'Where's the... Read more...

Writing Efficient SQL: Set-Based Speed Phreakery
 Phil Factor's SQL Speed Phreak challenge is an event where coders battle to produce the fastest code to... Read more...