Nice, as always, Jeff. I notice you said you didn't do any testing on 2005. If you do more testing on 2005, be sure to try out the XML data type with FOR XML for concatenating strings. Here's some timings for the sample below (run on a 1.9 GHz box with 2 GB RAM, SQL 2005):
* Test with PK/CI on RowNum and NCI on SomeID ran in 9 seconds for 1,000,000 rows
* An alternate version using the data() node test as a column name and the REPLACE function instead of SUBSTRING ran in 11 seconds on 1,000,000 rows
* Changing the NCI to a covering index on (SomeID, SomeCode) the code executed in 4 seconds on 1,000,000 rows. I left RowNum out of the covering index create index statement since it's the CI already, so automatically added to the NCI.
Who knows Jeff, maybe XML will find a place in your heart after all? 🙂
--Statement to create covering index:
--CREATE INDEX IX_TestData_SomeInt ON dbo.TestData(SomeID, SomeCode)
-- Jeff's table creation code
SELECT TOP 1000000
RowNum = IDENTITY(INT,1,1),
SomeID = ABS(CHECKSUM(NEWID()))%2500+1,
SomeCode = CHAR(ABS(CHECKSUM(NEWID()))%26+65)
+ CHAR(ABS(CHECKSUM(NEWID()))%26+65)
INTO dbo.TestData
FROM Master.dbo.SysColumns t1,
Master.dbo.SysColumns t2 ;
ALTER TABLE dbo.TestData
ADD PRIMARY KEY CLUSTERED (RowNum);
CREATE INDEX IX_TestData_SomeInt ON dbo.TestData(SomeID);
-- End table creation code
GO
-- Begin function creation
CREATE FUNCTION dbo.fnConcatTest (@SomeID INT)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE @x XML;
DECLARE @Return VARCHAR(8000);
SET @x = (SELECT ',' AS '*',
SomeCode AS '*'
FROM dbo.TestData
WHERE SomeID = @SomeID
FOR XML PATH (''), TYPE);
SET @RETURN = SUBSTRING(CAST(@x AS VARCHAR(8000)), 2, 8000);
RETURN @Return;
END
GO
--Speed test
SELECT SomeID, dbo.fnConcatTest(SomeID) AS CSVString
FROM dbo.TestData
GROUP BY SomeID;