Sort data before concatenating using STUFF FOR XML - tsql

I have the following query that I am using for an SSRS Report:
SELECT ROW_NUMBER() OVER ( ORDER BY Judge.EventJudgeID ) AS JudgeRow ,
Judge.EventID ,
Judge.Judge_PersonID ,
STUFF(( SELECT DISTINCT
',' + CAST(Fights.FightNumber AS VARCHAR(MAX)) AS [text()]
FROM dbo.tblFights Fights ,
dbo.tblFightJudge FRJudge
WHERE Fights.FightID = FRJudge.fightid
AND ( Judge.Judge_PersonID = FRJudge.judge1id
OR Judge.Judge_PersonID = FRJudge.judge2id
OR Judge.Judge_PersonID = FRJudge.judge3id
)
FOR
XML PATH('')
), 1, 1, '') AS BoutsJudged ,
Persons.LastName + ' ' + Persons.FirstName AS JudgeName ,
Events.EventName ,
Events.EventDate
FROM dbo.tblEventJudge Judge
INNER JOIN dbo.tblPersons Persons ON PersonID = Judge_PersonID
INNER JOIN dbo.tblEvents Events ON Events.EventID = Judge.EventID
WHERE Judge.EventID = 1278;
The problem is that the STUFF command returns the following string:
1,10,11,12,13,14,15,16,17,18,19,2,3,4,5,6,7,8,9
How can I make it sort the numbers before concatenating it into a string?

Try this
SELECT ROW_NUMBER() OVER ( ORDER BY Judge.EventJudgeID ) AS JudgeRow ,
Judge.EventID ,
Judge.Judge_PersonID ,
STUFF(Select ',' + CAST(Fights.FightNumber AS VARCHAR(MAX)) AS [text()] From ( SELECT DISTINCT Fights.FightNumber
FROM dbo.tblFights Fights ,
dbo.tblFightJudge FRJudge
WHERE Fights.FightID = FRJudge.fightid
AND ( Judge.Judge_PersonID = FRJudge.judge1id
OR Judge.Judge_PersonID = FRJudge.judge2id
OR Judge.Judge_PersonID = FRJudge.judge3id
)
) X
ORDER BY Fights.FightNumber
FOR
XML PATH('')
), 1, 1, '') AS BoutsJudged ,
Persons.LastName + ' ' + Persons.FirstName AS JudgeName ,
Events.EventName ,
Events.EventDate
FROM dbo.tblEventJudge Judge
INNER JOIN dbo.tblPersons Persons ON PersonID = Judge_PersonID
INNER JOIN dbo.tblEvents Events ON Events.EventID = Judge.EventID
WHERE Judge.EventID = 1278;
You can check below sqls,
Before :
Select *,
STUFF((Select Distinct ','+Cast(high as varchar(MAX))
from master..spt_values where type = 'p' and number < 20
for xml Path('')),1,1,'')
from master..spt_values where type = 'p' and number < 20
After :
Select *,
STUFF((Select ','+Cast(high as varchar(MAX)) from (Select distinct high
from master..spt_values where type = 'p' and number < 20) x Order by high for xml Path('')),1,1,'')
from master..spt_values where type = 'p' and number < 20

I apologize for this solution being pedantic, but I have a hard time parsing code and need to see things in steps. Also, Microsoft adds a feature to do this in the 2012 release, but this code should work in most releases. First, use a database open to users in most SQLServers...
USE MASTER; SELECT TOP 3 TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION FROM INFORMATION_SCHEMA.COLUMNS;
/*TABLE_NAME COLUMN_NAME ORDINAL_POSITION
spt_fallback_db xserver_name 1
spt_fallback_db xdttm_ins 2
spt_fallback_db xdttm_last_ins_upd 3
*/
Now, breaking down this approach (to sorting a list within a column)...
(1) Adding FOR XML PATH('') to a 1 column query pivots it to one row, but adds XML tags for the column header...
SELECT TOP 3 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS FOR XML PATH('');
/*<COLUMN_NAME>xserver_name</COLUMN_NAME><COLUMN_NAME>xdttm_ins</COLUMN_NAME><COLUMN_NAME>xdttm_last_ins_upd</COLUMN_NAME>*/
(2) Concatination nullifies the column header, eliminating the tags. Any string will work, I want comma space...
SELECT TOP 3 ', ' + COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS FOR XML PATH('');
/*, xserver_name, xdttm_ins, xdttm_last_ins_upd*/
(3) Other columns will need their own SELECT, so FOR XML must be a subquery, and ORDER BY is a legal prefix in a FOR XML subquery ;)...
SELECT TOP 2 TABLE_NAME
, (SELECT ', ' + COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMNS.TABLE_NAME = TABLES.TABLE_NAME
ORDER BY ORDINAL_POSITION FOR XML PATH('')
) LIST_OF_COLUMNS
FROM INFORMATION_SCHEMA.TABLES
ORDER BY TABLE_NAME;
/*TABLE_NAME LIST_OF_COLUMNS
spt_fallback_db , xserver_name, xdttm_ins, xdttm_last_ins_upd, xfallback_dbid, name, dbid, status, version
spt_fallback_dev , xserver_name, xdttm_ins, xdttm_last_ins_upd, xfallback_low, xfallback_drive, low, high, status, name, phyname
*/
(4) Finally, SUBSTRING is more familiar to me than STUFF for removing a known prefix...
SELECT TOP 2 TABLE_NAME
, SUBSTRING((SELECT ', ' + COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMNS.TABLE_NAME = TABLES.TABLE_NAME
ORDER BY ORDINAL_POSITION FOR XML PATH('')
)
, 2+1--Add 1 to start substring after the first 2 characters
, 99999) LIST_OF_COLUMNS
FROM INFORMATION_SCHEMA.TABLES
ORDER BY TABLE_NAME;
/*TABLE_NAME LIST_OF_COLUMNS
spt_fallback_db xserver_name, xdttm_ins, xdttm_last_ins_upd, xfallback_dbid, name, dbid, status, version
spt_fallback_dev xserver_name, xdttm_ins, xdttm_last_ins_upd, xfallback_low, xfallback_drive, low, high, status, name, phyname
*/
Pedantically yours - Jim Gettman

Related

How to move the data to the next line based on spaces in sqlserver 2008 R2

Input : Keep the column value into next line if word to word space is 3 space and length of the word is >9 .
declare #Table table(CL1 varchar(50))
INSERT INTO #Table
SELECT 'Ohh my GOD'
UNION ALL
SELECT 'hindunewspaer is no1 paper'
select * from #Table
o/p :
CL1
ohh
my god
hindunewpaer
is no1 paper
Used a Split/Parse function. Can be inline if needed.
EDIT - Switch to a Parser which is not limited to 8K because the final
string could easily be larger than 8K
Example
;with cte0 as (
Select Seq=Row_Number() over (Order by (Select null)),RetSeq,RetVal
From #Table A
Cross Apply (
Select RetSeq
,RetVal=case when len(RetVal)>9 then '~~~' else '' end+RetVal+case when len(RetVal)>9 then '~~~' else '' end
From [dbo].[udf-Str-Parse](Replace(CL1,' ','~~~ '),' ')
) B ),
cte1 as ( Select S=Stuff((Select ' '+RetVal From cte0 Order by Seq For XML Path ('')),1,1,'') )
Select CL1 = RetVal
From cte1 A
Cross Apply [dbo].[udf-Str-Parse](A.S,'~~~') B
Order By RetSeq
Returns
CL1
Ohh
my GOD
hindunewspaer
is no1 paper
The Split/Parse Function if Needed
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')

partition over two columns

I'm wanting to partition by two columns (PROJECT_ID, AND CATEGORY_NAME) and I'm having trouble writing the correct syntax. My query below is functional but when I attempt to add an additional over clause it doesn't work correctly. The recursive query was used to concatenate rows partitioning over project_id, creating a list of admins combining and concatenating name_last and name_first to make a list. I need to use an additional over clause to include the CATEGORY_NAME due to admins in the list that work in different categories ('INVISION' AND 'INSIGHT') but are under the same project_id. The first subquery
SELECT
RowNumber() over (PARTITION BY F13.DIM_PROJECT_ID, F13.CATEGORY_NAME ORDER BY F13.PROJECT_NAME),
F13.DIM_PROJECT_ID.....etc.
extracts the correct data, I'm just unsure of how to pull that correct data out partitioning by both project and category. I'm using db2.
with
t1(rowNum, PROJECT_ID, NAME_LAST, NAME_FIRST, POINT_OF_CONTACT, PROJECT_NAME, BUSINESS_NAME) as
(
SELECT
RowNumber() over (PARTITION BY F13.DIM_PROJECT_ID, F13.CATEGORY_NAME ORDER BY F13.PROJECT_NAME),
F13.DIM_PROJECT_ID,
F2P.NAME_LAST,
F2P.NAME_FIRST,
REPLACE(F2P.POINT_OF_CONTACT, ',', ' |') AS POINT_OF_CONTACT,
F13.PROJECT_NAME,
F2H.CATEGORY_NAME,
FROM FACT_TABLE AS F13
INNER JOIN ADMIN AS F2P ON F13.DIM_PROJECT_ID = F2P.DIM_PROJECT_ID
LEFT JOIN HOURS AS F2H ON F13.DIM_PROJECT_ID = F2H.DIM_PROJECT_ID
WHERE F2H.CATEGORY_NAME = ('INVISION')
group by
F13.DIM_PROJECT_ID,
F13.PROJECT_NAME,
F2P.NAME_LAST,
F2P.NAME_FIRST,
F2P.POINT_OF_CONTACT,
F2H.CATEGORY_NAME
) ,
t2(PROJECT_ID, LIST, POINT_OF_CONTACT, PROJECT_NAME, BUSINESS_NAME, cnt) AS
( SELECT PROJECT_ID,
VARCHAR(NAME_FIRST CONCAT ' ' CONCAT NAME_LAST, 6000),
POINT_OF_CONTACT,
PROJECT_NAME,
CATEGORY_NAME,
1
FROM t1
WHERE rowNum = 1
UNION ALL
SELECT t2.PROJECT_ID,
t2.list || ' | ' || t1.NAME_FIRST CONCAT ' ' CONCAT t1.NAME_LAST,
t1.POINT_OF_CONTACT,
t1.PROJECT_NAME,
t1.CATEGORY_NAME
FROM t2, t1
WHERE t2.project_id = t1.project_id
AND t2.cnt + 1 = t1.rowNum )
SELECT PROJECT_ID,
PROJECT_NAME,
POINT_OF_CONTACT,
CATEGORY_NAME
list
FROM t2
WHERE ( PROJECT_ID, cnt ) IN (
SELECT PROJECT_ID, MAX(rowNum)
FROM t1
GROUP BY PROJECT_ID )
The results that I'm getting are producing duplicates but only when the second column (category_name is included in the partition clause. Current results:
Desired results:
I figured it out. I added an ID for category and partitioned by category_id and project_id.
with
t1(rowNum, PROJECT_ID, NAME_LAST, NAME_FIRST, POINT_OF_CONTACT, PROJECT_NAME, CATEGORY_ID, CATEGORY_NAME) as
(
SELECT
RowNumber() over (PARTITION BY F13.DIM_PROJECT_ID, F13.CATEGORY_ID ORDER BY F13.PROJECT_NAME, F13.CATEGORY_NAME),
F13.DIM_PROJECT_ID,
F2P.NAME_LAST,
F2P.NAME_FIRST,
REPLACE(F2P.POINT_OF_CONTACT, ',', ' |') AS POINT_OF_CONTACT,
F13.PROJECT_NAME,
F13.CATEGORY_ID
F13.CATEGORY_NAME,
FROM FACT_TABLE AS F13
INNER JOIN ADMIN AS F2P ON F13.DIM_PROJECT_ID = F2P.DIM_PROJECT_ID
LEFT JOIN HOURS AS F2H ON F13.DIM_PROJECT_ID = F2H.DIM_PROJECT_ID
WHERE F13.CATEGORY_NAME = ('INVISION')
group by
F13.DIM_PROJECT_ID,
F13.PROJECT_NAME,
F2P.NAME_LAST,
F2P.NAME_FIRST,
F2P.POINT_OF_CONTACT,
F13.CATEGORY_ID
F13.CATEGORY_NAME
) ,
t2(PROJECT_ID, LIST, POINT_OF_CONTACT, PROJECT_NAME, CATEGORY_ID, CATEGORY_NAME, cnt) AS
( SELECT PROJECT_ID,
VARCHAR(NAME_FIRST CONCAT ' ' CONCAT NAME_LAST, 6000),
POINT_OF_CONTACT,
PROJECT_NAME,
CATEGORY_ID,
CATEGORY_NAME,
1
FROM t1
WHERE rowNum = 1
UNION ALL
SELECT t2.PROJECT_ID,
t2.list || ' | ' || t1.NAME_FIRST CONCAT ' ' CONCAT t1.NAME_LAST,
t1.POINT_OF_CONTACT,
t1.PROJECT_NAME,
t1.CATEGORY_ID,
t1.CATEGORY_NAME
FROM t2, t1
WHERE t2.project_id = t1.project_id
AND t2.category_id = t1.category_id
AND t2.cnt + 1 = t1.rowNum )
SELECT PROJECT_ID,
PROJECT_NAME,
POINT_OF_CONTACT,
CATEGORY_ID,
CATEGORY_NAME
list
FROM t2
WHERE ( PROJECT_ID, CATEGORY_ID, cnt ) IN (
SELECT PROJECT_ID, CATEGORY_ID, MAX(rowNum)
FROM t1
GROUP BY PROJECT_NAME )

Percentage of Values for Top 3 from a Character Field

I have an unusual situation. Please consider the following code:
IF OBJECT_ID('tempdb..#CharacterTest') IS NOT NULL
DROP TABLE #CharacterTest
CREATE TABLE #CharacterTest
(
[ID] int IDENTITY(1, 1) NOT NULL,
[CharField] varchar(50) NULL
)
INSERT INTO #CharacterTest (CharField)
VALUES ('A')
, ('A')
, ('A')
, ('A')
, ('B')
, ('B')
, ('B')
, ('C')
, ('C')
, ('D')
, ('D')
, ('F')
, ('G')
, ('H')
, ('I')
, ('J')
, ('K')
, ('L')
, ('M')
, ('N')
, (' ')
, (' ')
, (' ')
, (NULL)
, ('');
I would like a query which gives me a character string like this:
A (16%), B (12%), C(8%)
Please notice the following:
I don't want to have empty strings, strings with all blanks, or nulls listed in the top 3, but I do want the percentage of values calculated using the entire record count for the table.
Ties can be ignored, so if there were 22 values in the list with 8% frequency, it's alright to simply return whichever one is first.
Percentages can be rounded to whole numbers.
I'd like to find the easiest way to write this query while still retaining T-SQL compatibility back to SQL Server 2005. What is the best way to do this? Window Functions?
I'd go for.
WITH T1
AS (SELECT [CharField],
100.0 * COUNT(*) OVER (PARTITION BY [CharField]) /
COUNT(*) OVER () AS Pct
FROM #CharacterTest),
T2
AS (SELECT DISTINCT TOP 3 *
FROM T1
WHERE [CharField] <> '' --Excludes all blank or NULL as well
ORDER BY Pct DESC)
SELECT STUFF((SELECT ',' + [CharField] + ' (' + CAST(CAST(ROUND(Pct,1) AS INT) AS VARCHAR(3)) + ')'
FROM T2
ORDER BY Pct DESC
FOR XML PATH('')), 1, 1, '') AS Result
My first attempt would probably be this. Not saying that it's the best way to handle it, but that it would work.
DECLARE #TotalCount INT
SELECT #TotalCount = COUNT(*) FROM #CharacterTest AS ct
SELECT TOP(3) CharField, COUNT(*) * 1.0 / #TotalCount AS OverallPercentage
FROM #CharacterTest AS ct
WHERE CharField IS NOT NULL AND REPLACE(CharField, ' ', '') <> ''
GROUP BY CharField
ORDER BY COUNT(*) desc
DROP TABLE #CharacterTest
This should get the character string you need:
declare #output varchar(200);
with cte as (
select CharField
, (count(*) * 100) / (select count(*) from #CharacterTest) as CharPct
, row_number() over (order by count(*) desc, CharField) as RowNum
from #CharacterTest
where replace(CharField, ' ', '') not like ''
group by CharField
)
select #output = coalesce(#output + ', ', '') + CharField + ' (' + cast(CharPct as varchar(11)) + '%)'
from cte
where RowNum <= 3
order by RowNum;
select #output;
-- Returns:
-- A (16%), B (12%), C (8%)
I would draw attention to storing a single character in a varchar(50) column, however.

concatenating single column in TSQL

I am using SSMS 2008 and trying to concatenate one of the rows together based on a different field's grouping. I have two columns, people_id and address_desc. They look like this:
address_desc people_id
---------- ------------
Murfreesboro, TN 37130 F15D1135-9947-4F66-B778-00E43EC44B9E
11 Mohawk Rd., Burlington, MA 01803 C561918F-C2E9-4507-BD7C-00FB688D2D6E
Unknown, UN 00000 C561918F-C2E9-4507-BD7C-00FB688D2D6E Jacksonville, NC 28546 FC7C78CD-8AEA-4C8E-B93D-010BF8E4176D
Memphis, TN 38133 8ED8C601-5D35-4EB7-9217-012905D6E9F1
44 Maverick St., Fitchburg, MA 8ED8C601-5D35-4EB7-9217-012905D6E9F1
Now I want to concatenate the address_desc field / people_id. So the first one here should just display "Murfreesboro, TN 37130" for address_desc. But second person should have just one line instead of two which says "11 Mohawk Rd., Burlington, MA 01803;Unknown, UN 00000" for address_desc.
How do I do this? I tried using CTE, but this was giving me ambiguity error:
WITH CTE ( people_id, address_list, address_desc, length )
AS ( SELECT people_id, CAST( '' AS VARCHAR(8000) ), CAST( '' AS VARCHAR(8000) ), 0
FROM dbo.address_view
GROUP BY people_id
UNION ALL
SELECT p.people_id, CAST( address_list +
CASE WHEN length = 0 THEN '' ELSE ', ' END + c.address_desc AS VARCHAR(8000) ),
CAST( c.address_desc AS VARCHAR(8000)), length + 1
FROM CTE c
INNER JOIN dbo.address_view p
ON c.people_id = p.people_id
WHERE p.address_desc > c.address_desc )
SELECT people_id, address_list
FROM ( SELECT people_id, address_list,
RANK() OVER ( PARTITION BY people_id ORDER BY length DESC )
FROM CTE ) D ( people_id, address_list, rank )
WHERE rank = 1 ;
Here was my initial SQL query:
SELECT a.address_desc, a.people_id
FROM dbo.address_view a
INNER JOIN (SELECT people_id
FROM dbo.address_view
GROUP BY people_id
HAVING COUNT(*) > 1) t
ON a.people_id = t.people_id
order by a.people_id
You can use FOR XML PATH('') like this:
DECLARE #TestData TABLE
(
address_desc NVARCHAR(100) NOT NULL
,people_id UNIQUEIDENTIFIER NOT NULL
);
INSERT #TestData
SELECT 'Murfreesboro, TN 37130', 'F15D1135-9947-4F66-B778-00E43EC44B9E'
UNION ALL
SELECT '11 Mohawk Rd., Burlington, MA 01803', 'C561918F-C2E9-4507-BD7C-00FB688D2D6E'
UNION ALL
SELECT 'Unknown, UN 00000', 'C561918F-C2E9-4507-BD7C-00FB688D2D6E'
UNION ALL
SELECT 'Memphis, TN 38133', '8ED8C601-5D35-4EB7-9217-012905D6E9F1'
UNION ALL
SELECT '44 Maverick St., Fitchburg, MA', '8ED8C601-5D35-4EB7-9217-012905D6E9F1';
SELECT a.people_id,
(SELECT SUBSTRING(
(SELECT ';'+b.address_desc
FROM #TestData b
WHERE a.people_id = b.people_id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,2
,4000)
) GROUP_CONCATENATE
FROM #TestData a
GROUP BY a.people_id
Results:
people_id GROUP_CONCATENATE
------------------------------------ ------------------------------------------------------
F15D1135-9947-4F66-B778-00E43EC44B9E Murfreesboro, TN 37130
C561918F-C2E9-4507-BD7C-00FB688D2D6E 11 Mohawk Rd., Burlington, MA 01803;Unknown, UN 00000
8ED8C601-5D35-4EB7-9217-012905D6E9F1 Memphis, TN 38133;44 Maverick St., Fitchburg, MA

Dynamic field definitions - can this be done in T-SQL?

I have a requirement that I'm struggling to implement. If possible, I'd like to achieve this with native T-SQL.
I have the following tables:
CUSTOMER
========
ID,
Name
FIELDDEF
========
ID,
Name
FieldType (Char T, N, D for Text, Number or Date)
CUSTOMERFIELD
=============
ID,
CustomerID,
FieldDefID,
CaptureDate,
ValueText,
ValueNumber,
ValueDate
Basically, the purpose of these tables is to provide an extensible custom field system. The idea is that the user creates new field definitions that can be a text, number or date field. Then, they create values for these fields in the ValueText, ValueNumber OR ValueDate field.
Example:
*Customer*
1,BOB
2,JIM
*FieldDef*
1,Mobile,T
1,DateOfBirth,D
*CustomerField*
ID,CustomerID,FieldDefID,CaptureDate,ValueText,ValueNumber,ValueDate
1,1,1,2011-01-1,07123456789,NULL,NULL
2,1,2,2011-01-1,NULL,NULL,09-DEC-1980
3,1,1,2011-01-2,07123498787,NULL,NULL
I need to create a view that looks like this:
*CustomerView*
ID,Name,Mobile,DateOfBirth
1,BOB,07123498787,09-DEC-1980
Note that Bob's mobile is the second one in the list, because it uses the most recent capture date.
Ideally, I need this to be extensible, so if I create a new field def in the future, it is automatically picked up in the CustomerView.
Is this possible in T-SQL at all?
Thanks,
Simon.
This would not be possible with a view, unless the view is dynamically recreated on the fly every time FieldDef changes because view schemas are locked-in at creation time. However, it may be possible with a stored procedure, which may or may not work depending on how you are using it.
Edit 1
Here is a sample query that works just for your current field names, and would have to be modified by dynamic SQL to work in general:
Edit 2
Modified to grab the newest values from the customer field table
with CustomerFieldNewest as (
select
cf1.*
from
customerfield cf1
inner join
(
select
customerid,
fielddefid,
max(capturedate) as maxcapturedate
from
customerfield cf2
group by
customerid,
fielddefid
) cf2 on cf1.customerid = cf2.customerid
and cf1.fielddefid = cf2.fielddefid
and cf1.capturedate = cf2.maxcapturedate
)
,CustomerFieldPivot as (
select
C.ID as ID
,max(case when F.Name = 'Mobile' then CF.ValueText end) as Mobile
,max(case when F.Name = 'DateOfBirth' then CF.ValueDate end) as DateOfBirth
from
Customer C
left join
CustomerFieldNewest CF on C.ID = CF.CustomerID
left join
FieldDef F on F.ID = CF.FieldDefID
group by
C.ID
)
select
C.*
,P.Mobile
,P.DateOfBirth
from
Customer C
left join
CustomerFieldPivot P on C.ID = P.ID
Edit 3
Here is T-SQL code to generate the view on the fly based on the current set of fields in FieldDef (this assumes the view CustomerView already exists, so you will need to create it first as a blank definition or you will get an error). I'm not sure about the performance of all this, but it should work correctly.
declare #sql varchar(max)
declare #fielddef varchar(max)
declare #fieldlist varchar(max)
select
#fielddef = coalesce(#fielddef + ', ' + CHAR(13) + CHAR(10), '') +
' max(case when F.Name = ''' + F.Name + ''' then CF.' +
case F.FieldType
when 'T' then 'ValueText'
when 'N' then 'ValueNumber'
when 'D' then 'ValueDate'
end
+ ' end) as [' + F.Name + ']'
,#fieldlist = coalesce(#fieldlist + ', ' + CHAR(13) + CHAR(10), '') +
' [' + F.Name + ']'
from
FieldDef F
set #sql = '
alter view [CustomerView] as
with CustomerFieldNewest as (
select
cf1.*
from
customerfield cf1
inner join
(
select
customerid,
fielddefid,
max(capturedate) as maxcapturedate
from
customerfield cf2
group by
customerid,
fielddefid
) cf2 on cf1.customerid = cf2.customerid
and cf1.fielddefid = cf2.fielddefid
and cf1.capturedate = cf2.maxcapturedate
)
,CustomerFieldPivot as (
select
C.ID as ID,
' + #fielddef + '
from
Customer C
left join
CustomerFieldNewest CF on C.ID = CF.CustomerID
left join
FieldDef F on F.ID = CF.FieldDefID
group by
C.ID
)
select
C.*,
' + #fieldlist + '
from
Customer C
left join
CustomerFieldPivot P on C.ID = P.ID
'
print #sql
exec(#sql)
select * from CustomerView
You need to build a crosstab which you do with the Pivot statement in TSQL. Here's an article that talks about how to build the pivot dynamically.
http://sqlserver-qa.net/blogs/t-sql/archive/2008/08/27/4809.aspx
Just for completeness there is sql_variant:
declare #t table (typ varchar(1), yuk sql_variant)
insert #t values ('d', getdate())
insert #t values ('i', 1234)
insert #t values ('s', 'bleep bloop')
select
yuk,
case typ
when 'd' then convert(datetime, yuk, 106)+50
when 'i' then cast(yuk as int) * 2
when 's' then reverse(cast(yuk as varchar))
else yuk
end
from #t