Need to pivot or crosstab a table but not in the conventional way. please - tsql

I have a small situation here.. hope you guys can help me out.
I'm supposed to query a table wich has 4 columns
AccountNo, ResourceNo, ProductNo, CustomerNo.
A few accountNo's have 2 ResourceNo's (115 and 134)
I have to Query it in such a way that I have to show two dynamic columns for the resourceNo values and put an 'X' against the accountNo which has those ResourceNo's.. So that the AccountNo is not repeated.. Pivoting doesn't help in this situation. Please look into this and help me.
See also
Poor Man's SQL Pivot.
See also
Sql Pivot Query with Dynamic Columns

You need poor man's pivot:
Poor Man's SQL Pivot. List Questions as Columns and Answers per User in one row
Static Columns
For example:
select
AccountNo,
case when sum(case when ResourceNo = 134 then 1 else 0 end) = 0 then '' else 'X' end as Resource_134,
case when sum(case when ResourceNo = 115 then 1 else 0 end) = 0 then '' else 'X' end as Resource_115
from
AccountResource
group by
AccountNo
Example Data:
AccountNo ResourceNo ProductNo CustomerNo
A1 134 P1 C1
A1 134 P2 C1
A1 134 P3 C2
A2 134 P1 C1
A2 115 P1 C4
A2 115 P2 C1
A3 115 P5 C2
Example output:
AccountNo Resource_134 Resource_115
A1 X
A2 X X
A3 X
Dynamic Columns
For dynamic columns you can do this:
declare #sql nvarchar(max)
set #sql = ''
select #sql = #sql + ',' + char(13)+char(10)+
'case when sum(case when ResourceNo = ''' + replace(cast(ResourceNo as nvarchar(10)), '''', '''''') + ''' then 1 else 0 end) = 0 then '''' else ''X'' end as "Resource_' + replace(cast(ResourceNo as nvarchar(10)), '"', '""') + '"'
from AccountResource
group by ResourceNo
order by Resourceno
set #sql = 'select AccountNo' + #sql
+ char(13)+char(10)
+ 'From AccountResource '
+ char(13)+char(10)
+ 'group by AccountNo'
+ char(13)+char(10)
+ 'order by AccountNo'
select * from datacheck.dbo.splitmax(#sql, null,null)
exec sp_executesql #sql

Related

SSRS Expression Split string in rows and column

I am working with SQL Server 2008 Report service. I have to try to split string values in different columns in same row in expression but I can't get the excepted output. I have provided input and output details. I have to split values by space (" ") and ("-").
Input :
Sample 1:
ASY-LOS,SLD,ME,A1,A5,J4A,J4B,J4O,J4P,J4S,J4T,J7,J10,J2A,J2,S2,S3,S3T,S3S,E2,E2F,E6,T6,8,SB1,E1S,OTH AS2-J4A,J4B,J4O,J4P,J4S,J4T,J7,J1O,J2A,S2,S3,J2,T6,T8,E2,E4,E6,SLD,SB1,OTH
Sample 2:
A1 A2 A3 A5 D2 D3 D6 E2 E4 E5 E6 EOW LH LL LOS OTH P8 PH PL PZ-1,2,T1,T2,T3 R2-C,E,A RH RL S1 S2-D S3
Output should be:
Thank you.
I wrote this before I saw your comment about having to do it in the report. If you can explain why you cannot do this in the dataset query then there may be a way around that.
Anyway, here's one way of doing this using SQL
DECLARE #t table (RowN int identity (1,1), sample varchar(500))
INSERT INTO #t (sample) SELECT 'ASY-LOS,SLD,ME,A1,A5,J4A,J4B,J4O,J4P,J4S,J4T,J7,J10,J2A,J2,S2,S3,S3T,S3S,E2,E2F,E6,T6,8,SB1,E1S,OTH AS2-J4A,J4B,J4O,J4P,J4S,J4T,J7,J1O,J2A,S2,S3,J2,T6,T8,E2,E4,E6,SLD,SB1,OTH'
INSERT INTO #t (sample) SELECT 'A1 A2 A3 A5 D2 D3 D6 E2 E4 E5 E6 EOW LH LL LOS OTH P8 PH PL PZ-1,2,T1,T2,T3 R2-C,E,A RH RL S1 S2-D S3'
drop table if exists #s1
SELECT RowN, sample, SampleIdx = idx, SampleValue = [Value]
into #s1
from #t t
CROSS APPLY
spring..fn_Split(sample, ' ') as x
drop table if exists #s2
SELECT
s1.*
, s2idx = Idx
, s2Value = [Value]
into #s2
FROM #s1 s1
CROSS APPLY spring..fn_Split(SampleValue, '-')
SELECT SampleKey = [1],
Output = [2] FROM #s2
PIVOT (
MAX(s2Value)
FOR s2Idx IN ([1],[2])
) p
This produced the following results
If you do not have a split function, here is the script to create the one I use
CREATE FUNCTION [dbo].[fn_Split]
/* Define I/O parameters WARNING! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE! */
(#pString VARCHAR(8000)
,#pDelimiter CHAR(1)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
/*"Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000: enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)--10E+1 or 10 rows
,E2(N) AS (SELECT 1 FROM E1 a,E1 b)--10E+2 or 100 rows
,E4(N) AS (SELECT 1 FROM E2 a,E2 b)--10E+4 or 10,000 rows max
/* This provides the "base" CTE and limits the number of rows right up front
for both a performance gain and prevention of accidental "overruns" */
,cteTally(N) AS (
SELECT TOP (ISNULL(DATALENGTH(#pString), 0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM E4
)
/* This returns N+1 (starting position of each "element" just once for each delimiter) */
,cteStart(N1) AS (
SELECT 1 UNION ALL
SELECT t.N + 1 FROM cteTally t WHERE SUBSTRING(#pString, t.N, 1) = #pDelimiter
)
/* Return start and length (for use in SUBSTRING later) */
,cteLen(N1, L1) AS (
SELECT s.N1
,ISNULL(NULLIF(CHARINDEX(#pDelimiter, #pString, s.N1), 0) - s.N1, 8000)
FROM cteStart s
)
/* Do the actual split.
The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. */
SELECT
idx = ROW_NUMBER() OVER (ORDER BY l.N1)
,value = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l

SQL Server select from temp table into another temp table

I have this in a stored procedure:
select * into #temp_UNION from
(
SELECT 6 AS InteractionType, * FROM history.GetHistoryRewardPointsForIncentiveOption (588,1,6)
UNION all
SELECT 8 AS InteractionType, * FROM history.GetHistoryRewardPointsForIncentiveOption (558,1,8)
) a
which gives:
interaction type historyid incentiveprogramid points
6 1 1 50
6 1 4 50
6 1 5 50
8 1 3 100
8 1 4 100
then i have:
select tu.InteractionType,ipc.Name,tu.Points from #temp_UNION tu
inner join Incentive.IncentiveProgramCultures ipc
on tu.IncentiveProgramId = ipc.IncentivePrograms_IncentiveProgramId
inner join Zinc.Users zu
on zu.Cultures_DefaultCultureId = ipc.IncentiveProgramCultureId
where zu.UserId = 588
6 India - Q2 Incentive 50
8 India - Q2 Incentive 100
now i need to make up HintText which is a field in my #CategoriesTable(previously defined) by using the name i got from above and the points
UPDATE #CategoriesTable
SET HintText = CASE WHEN HasAssessment = 1
THEN 'Program ' + tu.name + ' will earn you ' + tu.points
ELSE 'With No Assessment'
END
but i get an error on tu.Name: multi part identifier could not be bound?
how can i achieve? should i make use of another temp table with the 2 rows in it?
here new code:
select * into #temp_UNION from
(
SELECT 6 AS InteractionType, * FROM history.GetHistoryRewardPointsForIncentiveOption (588,1,6)
UNION all
SELECT 8 AS InteractionType, * FROM history.GetHistoryRewardPointsForIncentiveOption (558,1,8)
) a
select tu.InteractionType,ipc.Name,tu.Points,zu.UserId into #temp1 from #temp_UNION tu
inner join Incentive.IncentiveProgramCultures ipc
on tu.IncentiveProgramId = ipc.IncentivePrograms_IncentiveProgramId
inner join Zinc.Users zu
on zu.Cultures_DefaultCultureId = ipc.IncentiveProgramCultureId
where zu.UserId = 588
Select * from #temp1 t
UPDATE #CategoriesTable
SET HintText = CASE WHEN HasAssessment = 1
THEN 'Program ' + t.Name + ' and points = ' + t.Points
ELSE 'With No Assessment'
END
FROM #temp1 t
WHERE t.userId = #CategoriesTable.UserId
DROP TABLE #temp_UNION
DROP TABLE #temp1
SELECT *
FROM #CategoriesTable
im not getting the #CategoriesTable?
You must specify the "tu" alias in the update clause
UPDATE #CategoriesTable
SET HintText = CASE WHEN HasAssessment = 1
THEN 'Program ' + tu.name + ' will earn you ' + tu.points
ELSE 'With No Assessment'
END
FROM #temp_UNION tu
WHERE tu.? = #CategoriesTable.? --JOIN condition

TSQL pivot a single column table

Sorry for the late response I was off for few days, and for not specifying the exact table structure. Please ignore the previous description above. I have more information so my original question is not any longer valid as I obtain more information regarding what I really need as described below:
I have the following table (simplified version for the sake of discussion). First line is the headers:
VariableID DocumentID Revision Value
44 12 2 Val1
45 12 2 Val2
45 12 3 Val3
44 13 1 Val4
46 13 2 Val5
47 14 1 Val6
I’d like to convert it (assuming n number of rows) to the following grouped by (DocumentId, revision) table:
Documentid revision variable1 (44) variable2 (45) variable3(46) variable(47) variable (n)
12 2 Val1 Val2 null null
12 3 null Val3 null null
13 1 Val4 null null null
13 2 null null Val5 null
14 1 null null null Val6
Number of variable will be retrieved dynamically. I do not know how many variable will be in source table as input.
Please advise.
You did not provide a lot of details about your current table structure or sample data. So I will provide you with some samples of the PIVOT function that will perform this.
There are two options for PIVOT, a Static if you know the number of columns to transform or a Dynamic which will get the list of columns at run-time to transform.
EDIT:
Based on the change to your question, you can still perform a PIVOT
A Static Pivot would look like this (Sql Fiddle sample):
select *
from
(
select *
from t
) x
pivot
(
max(value)
for variableid in([44], [45], [46], [47])
) p
As Dynamic Pivot looks like this (Sql Fiddle Sample):
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsAlias AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(variableid)
from t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #colsAlias = STUFF((SELECT DISTINCT ',' + QUOTENAME(m.variableid) + ' AS ' + QUOTENAME('variable' + cast(n.variableid as varchar(10)))
FROM t m INNER JOIN t n ON m.variableid = n.variableid
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query = 'SELECT documentid, revision, ' + #colsAlias + ' from
(
select *
from t
) x
pivot
(
max(value)
for variableid in (' + #cols + ')
) p '
execute(#query)
The PIVOT operator sounds like it is what you are looking for, it is available in both Microsoft SQL Server and Microsoft Access, I'm not sure if it is available in Sybase though. Here is the MSDN article on it: T-SQL Pivot Operator

Troubles with splitting values into multiple rows using T-SQL

I have a Sql Server 2K8 R2 DB with a table that have a column containings multiples values, separated by (char 13 and char 10).
I'm building a script to import the data in a properly normalized schema.
My source table contains something like this :
ID | Value
________________
1 | line 1
line 2
________________
2 | line 3
________________
3 | line 4
line 5
line 6
________________
and so on.
[edit] FYI, Id is integer and value is nvarchar(3072) [/edit]
What I want is to query the table to ouput somethnig like this :
ID | Value
________________
1 | line 1
________________
1 | line 2
________________
2 | line 3
________________
3 | line 4
________________
3 | line 5
________________
3 | line 6
________________
I've read many answer here on SO, and also around the web, and I find that using master..sptvalues should be the solution. Especially, I tried to reprodude the solution of the question Split one column into multiple rows.
However, without success (suspecting having two chars causing problems).
By now, I wrote this query :
SELECT
T.ID,
T.Value,
RIGHT(LEFT(T.Value,spt.Number-1),
CHARINDEX(char(13)+char(10),REVERSE(LEFT(char(13)+char(10)+T.Value,spt.Number-1)))) as Extracted
FROM
master..spt_values spt,
ContactsNew T
WHERE
Type = 'P' AND
spt.Number BETWEEN 1 AND LEN(T.Value)+1
AND
(SUBSTRING(T.Value,spt.Number,2) = char(13)+char(10) OR SUBSTRING(T.Value,spt.Number,2) = '')
This query, unfortunately is returning :
ID | Value | Extracted
________________________________
1 | line 1 | <blank>
line 2 |
________________________________
1 | line 1 | line 2
line 2 |
________________________________
2 | line 3 | <blank>
________________________________
3 | line 4 | <blank>
line 5 |
line 6 |
________________________________
3 | line 4 | line 5
line 5 | line 6
line 6 |
________________________________
3 | line 4 | line 6
line 5 |
line 6 |
________________________________
<blank> is an empty string, not null string.
I'd appreciate some help to tune my query.
[Edit2] My source table contains less than 200 records, and performance is not a requirement, so I'm targeting a simple solution rather than an efficient one [Edit2]
[Edit3] The source database is readonly. I can't add stored procedure, function, or clr type. I have to do this in a single query. [Edit3]
[Edit4] Something strange... it seems that whitespaces are also considered as separators.
If I run the following query :
SELECT
T.ID,
replace(T.Value, '#', ' '),
replace(RIGHT(
LEFT(T.Value,spt.Number-1),
CHARINDEX( char(13) + char(10),REVERSE(LEFT(char(10) + char(13)+T.Value,spt.Number-0)))
), '#', ' ')
FROM
master..spt_values spt,
(
select contactID,
replace(Value,' ', '#') Value
from ContactsNew where Value is not null
) T
WHERE
Type = 'P' AND
spt.Number BETWEEN 1 AND LEN(T.Value)+1
AND
(SUBSTRING(T.Value,spt.Number,2) = char(13) + char(10) OR SUBSTRING(T.Value,spt.Number,1) = '')
I got the correct number of returns (however, still having wrong values), while running this query :
SELECT
T.ID,
T.Value,
RIGHT(
LEFT(T.Value,spt.Number-1),
CHARINDEX( char(13) + char(10),REVERSE(LEFT(char(10) + char(13)+T.Value,spt.Number-0)))
)
FROM
master..spt_values spt,
(
select contactID,
Value
from ContactsNew where Value is not null
) T
WHERE
Type = 'P' AND
spt.Number BETWEEN 1 AND LEN(T.Value)+1
AND
(SUBSTRING(T.Value,spt.Number,2) = char(13) + char(10) OR SUBSTRING(T.Value,spt.Number,1) = '')
splits on spaces also
EDIT #1: I've deleted original answer text. Try following query. I slightly modified your logic. If you should have any questions about it, don't hesitate to ask in comment. If You need another split delimiter just introduce another nested query to replace that delimiter with CHAR(13)+CHAR(10).
SELECT
*
FROM
(
SELECT
T.ID,
T.Value,
CASE
WHEN CHARINDEX(CHAR(13) + CHAR(10), SUBSTRING(T.Value, spt.number, LEN(T.Value) - spt.Number + 1)) > 0 THEN
LEFT(
SUBSTRING(T.Value, spt.number, LEN(T.Value) - spt.Number + 1),
CHARINDEX(CHAR(13) + CHAR(10), SUBSTRING(T.Value, spt.number, LEN(T.Value) - spt.Number + 1)) - 1)
/* added by Steve B. see comments for the reasons */
when len(T.Value) = spt.Number then right(t.Value, spt.number -1)
/* end of edit */
ELSE
SUBSTRING(T.Value, spt.number, LEN(T.Value) - spt.Number + 1)
END EXTRACTED
FROM
master..spt_values spt,
ContactsNew T
WHERE
Type = 'P' AND
spt.Number BETWEEN 1 AND LEN(T.Value)+1
) X
WHERE
EXTRACTED <> '' AND
(
LEFT(X.VALUE, LEN(EXTRACTED)) = EXTRACTED OR
X.Value LIKE '%' + CHAR(13) + CHAR(10) + EXTRACTED + CHAR(13) + CHAR(10) + '%' OR
X.Value LIKE '%' + CHAR(13) + CHAR(10) + EXTRACTED
)
A sample query showing how to perform this kind of operation against some test data similar to described.
If you aren't able to declare variables in your final statement you can find/replace them for their values, but it makes things a bit simpler.
This works by replacing CR+LF with a single character before doing the split.
If '|' is in use in your data, select another single character which isn't to use as the temporary delimiter.
declare #crlf nvarchar(2) = char(10) + char(13)
declare #cDelim nvarchar(1) = N'|'
-- test data
declare #t table
(id int
,value nvarchar(3072))
insert #t
select 1, 'line1' + #crlf + 'line2'
union all select 2, 'line3'
union all select 3, 'line4' + #crlf + 'line5' + #crlf + 'line6'
-- /test data
;WITH charCTE
AS
(
--split the string into a dataset
SELECT D.id, D.value, SUBSTRING(D.s,n,CHARINDEX(#cDelim, D.s + #cDelim,n) -n) AS ELEMENT
FROM (SELECT id, value, REPLACE(value,#crlf,#cDelim) as s from #t) AS D
JOIN (SELECT TOP 3072 ROW_NUMBER() OVER (ORDER BY a.type, a.number, a.name) AS n
FROM master.dbo.spt_values a
CROSS
JOIN master.dbo.spt_values b
) AS numsCte
ON n <= LEN(s)
AND SUBSTRING(#cDelim + s,n,1) = #cDelim
)
SELECT id, ELEMENT
FROM charCTE
order by id, element

T-SQL Query to convert rows to coumns based on mutiple tables

I have two master tables CompanyMaster, ActivityMaster for a child table CompanyActivities
ActivityMaster
ACTIVITYID ACTIVITYNAME
A1 testActivity
A2 someActivity
A3 otheractivity
A4 someotheractivity
A5 anyotheractivity
CompanyMaster
COMPANYID COMPANYNAME
C1 testcompany
C2 ACompany
C3 MyCompany
C4 SomeCompany
C5 ZCompany
C6 Company123
C7 ComapnyABC
CompanyActivities - The COMPANYID in CompanyActivities is having a primarykey-foreighkey relation ship with COMPANYID in CompanyMaster (primary key table) and ACTIVITYID is having a primarykey-foreighkey relation ship with ACTIVITYID in ActivityMaster(primary key table)
COMPANYID ACTIVITYID
C1 A1
C1 A3
C3 A1
C3 A2
C4 A5
C5 A1
C6 A3
C7 A3
I want to do write a query to get the following output where all the rows in ACTIVITYID column of the ActivityMaster table will be converted to columns
Output
Companies A1 A2 A3 A4 A5
C1 Y N Y N N
C2 N N N N N
C3 Y Y N N N
C4 N N N N Y
C5 Y N N N N
C6 N N Y N N
C7 N N Y N N
The output table displays all the companies as rows in the first column and all the activities are shown as columns that start after the first column, if there is row that contains both ACTIVITYID and COMPANYID it will set to Y in output otherwise it would be set to N
eg- COMPANYID C1 is having an activity ACTIVITYID A1 in CompanyActivities table so the first row in the second column that comes just below A1 and in the right to C1 is set Y, whereas C1 and A2 are not having a row, so the third column in the first row is set to N
I am using C#.net and 4 for loops to achieve the output now which is talking a heavy toll on the performance of the application, So i would like to do this using a query, I have searched for pivot queries, but all the examples i found knows the column names before-hand, which i don't i only get the names of the column names by querying the ActivityMaster.
create table #CompanyMaster (COMPANYID int, COMPANYNAME varchar(30))
create table #ActivityMaster (ACTIVITYID int, ACTIVITYNAME varchar(30))
create table #CompanyActivities (COMPANYID int, ACTIVITYID int)
insert into #CompanyMaster
SELECT 1, 'Company A'
union all
SELECT 2, 'Company B'
insert into #ActivityMaster
SELECT 101, 'Activity X'
union all
SELECT 102, 'Activity Y'
union all
SELECT 103, 'Activity Z'
insert into #CompanyActivities
select 1, 102
union all
select 2, 101
-- build activities column names
--case [Activity X] when 0 then ''N'' else ''Y'' end as [Activity X],
--case [Activity Y] when 0 then ''N'' else ''Y'' end as [Activity Y],
--case [Activity Z] when 0 then ''N'' else ''Y'' end as [Activity Z]
declare #activities nvarchar(max)
set #activities
= (
select 'case [' + ACTIVITYNAME + '] when 0 then ''N'' else ''Y'' end as [' + ACTIVITYNAME + '],' + char(10)
from #ActivityMaster
for xml path('')
)
set #activities = substring(#activities, 0, len(#activities)-1)
declare #activities_for nvarchar(max)
-- build activities column names in for
--[Activity X], [Activity Y], [Activity Z]
set #activities_for
= (
select '[' + ACTIVITYNAME + '],' + char(10)
from #ActivityMaster
for xml path('')
)
set #activities_for = substring(#activities_for, 0, len(#activities_for)-1)
declare #sql nvarchar(MAX) = N'
select COMPANYNAME,
<activities>
From
(select c.COMPANYNAME, a.ACTIVITYNAME,
(case
when ca.ACTIVITYID is not null and ca.COMPANYID is not null then 1
else 0
end) as STATUS
from #CompanyMaster c
cross join #ActivityMaster a
left join #CompanyActivities ca on ca.COMPANYID = c.COMPANYID and a.ACTIVITYID = ca.ACTIVITYID) p
pivot
(
sum(STATUS) for ACTIVITYNAME IN (<activities_for>)
) as pvt
'
set #sql = replace(#sql, '<activities>', #activities)
set #sql = replace(#sql, '<activities_for>', #activities_for)
print #sql
exec sp_executesql #sql
drop table #CompanyMaster
drop table #ActivityMaster
drop table #CompanyActivities