Turning rows into columns over a many-to-many table - sql

So I have three tables:
Students:
StuID
------
1
2
StuCon:
StuConId StuID ConID StuConType Priority
----------------------------------------------
1 1 1 Parent 1
2 1 2 Guardian 2
3 2 3 Parent 1
Contacts:
ConID ConName ConPhn
----------------------
1 John 5555555
2 Sally 4444444
3 Dana 3333333
I'm trying to get results that look like this:
StuID ConID1 StuConType1 ConName1 ConPhone1 ConID2 StuConType2 ConName2 ConPhone2
----------------------------------------------------------------------------------------
1 1 Parent John 5555555 2 Guardian Sally 4444444
2 3 Parent Dana 3333333 Null Null Null Null
So far the only way I can figure to do that is by making a LOT of left joins (some students have up to 10 contacts so that's 10 left joins for stucon and 10 more for contacts)
I'm quite sure there is a pivot that can be applied here, I just can't figure out how to do it.

Here's one way to do this dynamically:
DECLARE #selects VARCHAR(MAX) = '', #SQL VARCHAR(MAX) = '';
SELECT #selects += '
, MAX(CASE WHEN SC.[Priority] = ' + CAST([Priority] AS VARCHAR(255)) + ' THEN C.ConID END) [ConID' + CAST([Priority] AS VARCHAR(255)) + ']
, MAX(CASE WHEN SC.[Priority] = ' + CAST([Priority] AS VARCHAR(255)) + ' THEN SC.StuConType END) [StuConType' + CAST([Priority] AS VARCHAR(255)) + ']
, MAX(CASE WHEN SC.[Priority] = ' + CAST([Priority] AS VARCHAR(255)) + ' THEN C.ConName END) [ConName' + CAST([Priority] AS VARCHAR(255)) + ']
, MAX(CASE WHEN SC.[Priority] = ' + CAST([Priority] AS VARCHAR(255)) + ' THEN C.ConPhn END) [ConPhone' + CAST([Priority] AS VARCHAR(255)) + ']'
FROM StuCon
GROUP BY [Priority]
ORDER BY [Priority];
SET #SQL = 'SELECT StuID' + #selects + ' FROM StuCon SC LEFT JOIN Contacts C ON C.ConID = SC.ConID GROUP BY StuID;';
EXEC(#SQL);
Notes: It probably should be a normal JOIN on contacts rather than an LEFT JOIN, but it's a left join here just in case there's some inconsistency between tables. The Students table hasn't been joined to because it's not necessary.

Related

How to select records from a table when a condition of another table is satisfied

I have this query that runs regularly.
SELECT REPLICATE('0', 10-LEN(PolicyNumber)) + PolicyNumber AS PolicyNumber,
REPLICATE('0', 7-LEN(BOCBranch)) + BOCBranch AS BOCBranch,
CIFNumber+ REPLICATE(' ', 8-LEN(CIFNumber)) AS CIFNumber,
REPLICATE('0', 7-LEN(EmployeeNumber)) + EmployeeNumber AS EmployeeNumber,
PremiumSign,
REPLACE(REPLICATE('0',16-LEN(CAST(Premium AS VARCHAR))) + CAST(Premium AS VARCHAR),'.','') AS Premium,
CASE WHEN RegistrationDate IS NULL
THEN REPLICATE(' ', 8)
ELSE REPLACE(CONVERT(VARCHAR(10),RegistrationDate,103),'/','')
END AS RegistrationDate,
ActivityCode + REPLICATE(' ', 10-LEN(ActivityCode)) AS ActivityCode,
ActivityDescription + REPLICATE(' ', 255-LEN(ActivityDescription)) AS ActivityDescription,
PolicyTypeCode + REPLICATE(' ', 10-LEN(PolicyTypeCode)) AS PolicyTypeCode,
PolicyTypeDescription + REPLICATE(' ', 255-LEN(PolicyTypeDescription)) AS PolicyTypeDescription,
ContributionCode + REPLICATE(' ', 10-LEN(ContributionCode)) AS ContributionCode,
ContributionDescription + REPLICATE(' ', 255-LEN(ContributionDescription)) AS ContributionDescription,
ActivityMilimetra + REPLICATE(' ', 1-LEN(ActivityMilimetra)) AS ActivityMilimetra,
REPLICATE('0', 8-LEN(SourceCode)) + CAST(SourceCode AS varCHAR) AS SourceCode
FROM FileExtraction.EXTR_MILIMETRA
ORDER BY PolicyNumber
I have created a new table called FIELD_ACTIVATIONS as per managerial instructions like so:
FieldName CategoryID IsActive
-------------------------------------------------- ----------- --------
PolicyNumber 1 1
BOCBranch 1 1
CIFNumber 1 1
EmployeeNumber 1 0
PremiumSign 1 0
RegistrationDate 1 0
ActivityCode 1 0
ActivityDescription 1 0
PolicyTypeCode 1 0
PolicyTypeDescription 1 0
ContributionCode 1 0
ContributionDescription 1 0
ActivityMilimetra 1 0
SourceCode 1 0
Premium 1 0
PolicyNumber 2 0
BOCBranch 2 0
CIFNumber 2 0
EmployeeNumber 2 1
PremiumSign 2 1
RegistrationDate 2 1
ActivityCode 2 0
ActivityDescription 2 0
PolicyTypeCode 2 0
PolicyTypeDescription 2 0
ContributionCode 2 0
ContributionDescription 2 0
ActivityMilimetra 2 0
SourceCode 2 0
Premium 2 0
PolicyNumber 3 0
BOCBranch 3 0
CIFNumber 3 0
EmployeeNumber 3 0
PremiumSign 3 0
RegistrationDate 3 0
ActivityCode 3 1
ActivityDescription 3 1
PolicyTypeCode 3 1
PolicyTypeDescription 3 0
ContributionCode 3 0
ContributionDescription 3 0
ActivityMilimetra 3 0
SourceCode 3 0
Premium 3 0
PolicyNumber 4 0
BOCBranch 4 0
CIFNumber 4 0
EmployeeNumber 4 0
PremiumSign 4 0
RegistrationDate 4 0
ActivityCode 4 0
ActivityDescription 4 0
PolicyTypeCode 4 0
PolicyTypeDescription 4 1
ContributionCode 4 1
ContributionDescription 4 1
ActivityMilimetra 4 1
SourceCode 4 1
Premium 4 1
As you may notice, each column in the SELECT statement, is a FieldName in the table.
What I need to do is to run that SELECT statement only for the columns that appear in FieldName that have a status of IsActive = 1. For the columns in the SELECT query that have a status of IsActive = 0, I would still like to select the column, but display it as an empty column.
This is all without permanently deleting or altering anything from any tables.
I've tried using Cases, Subqueries, IFs and I cannot seem to come up with a solution that will not require future alteration if any details in the FIELD_ACTIVATIONS table change.
I've also looked at this link Select records in on table based on conditions from another table? but this link presumes that there is a common field in both tables.
The main table named as "EXTR_MILIMETRA" displayed in the SELECT query has nothing in common with FIELD_ACTIVATION apart from the column name and the field name.
Here is a sample of the columns in "EXTR_MILIMETRA". (Not all columns are being shown because of limited screen space.) Each column show below is a FieldName is the table above.
By asking this I'm risking being blocked due to consecutive previous downvotes. If any extra information is needed please let me know first instead of downvoting. If that's ok. I've really tried to describe my problem well enough.
Happy to make any clarifications.
How you would write that depends on if you need performance or not. If you don't have too many rows then you can do it like:
WITH active AS
(
SELECT FieldName
FROM Field_Activations
WHERE CategoryId=1 AND IsActive=1
)
SELECT
CASE WHEN EXISTS (SELECT * FROM active WHERE FieldName='PolicyNumber')
THEN REPLICATE('0', 10-LEN(PolicyNumber)) + PolicyNumber
ELSE '' END AS PolicyNumber,
CASE WHEN EXISTS (SELECT * FROM active WHERE FieldName='BOCBranch')
then REPLICATE('0', 7-LEN(BOCBranch)) + BOCBranch
ELSE '' end AS BOCBranch,
--...
FROM FileExtraction.EXTR_MILIMETRA
ORDER BY PolicyNumber;
If you need some performance then you can write the above query as a dynamic query with a series of if and execute:
DECLARE #SQLString nvarchar(4000);
declare #active table (FieldName varchar(100));
insert into #active (FieldName)
SELECT FieldName
FROM Field_Activations
WHERE CategoryId=1 AND IsActive=1;
SET #SQLString = N'
SELECT ' +
CASE when EXISTS
(SELECT * FROM #active WHERE FieldName = 'PolicyNumber')
THEN 'REPLICATE(''0'', 10-LEN(PolicyNumber)) + PolicyNumber'
ELSE '''''' END + ' AS PolicyNumber,' +
CASE WHEN EXISTS
(SELECT * FROM #active WHERE FieldName = 'BOCBranch')
THEN 'REPLICATE(''0'', 7-LEN(BOCBranch)) + BOCBranch'
ELSE '''''' end + ' AS BOCBranch,' +
--... +
' FROM FileExtraction.EXTR_MILIMETRA
ORDER BY PolicyNumber;'
print #SQLString
EXECUTE sp_executesql #SQLString;
#NikosV you need is a dynamic query. I have called it #DynamicQuery and a method to assign those columns that you are intersted in hiding will have NULL added to them, rendering their output to be blanks. Have a look at this mock up, just change the TABLENAME to the actual name of the table you want to pull the data from and use the actual FIELD_ACTIVATIONS table you said you creatd.
DECLARE #FIELD_ACTIVATIONS TABLE (FieldName varchar(200), CategoryID int, IsActive bit)
INSERT INTO #FIELD_ACTIVATIONS
SELECT 'PolicyNumber', 1, 1 UNION ALL
SELECT 'BOCBranch', 1, 1 UNION ALL
SELECT 'CIFNumber', 1, 1 UNION ALL
SELECT 'EmployeeNumber', 1, 0 UNION ALL
SELECT 'PremiumSign', 1, 0 UNION ALL
SELECT 'RegistrationDate', 1, 0 UNION ALL
SELECT 'ActivityCode', 1, 0 UNION ALL
SELECT 'ActivityDescription', 1, 0 UNION ALL
SELECT 'PolicyTypeCode', 1, 0 UNION ALL
SELECT 'PolicyTypeDescription', 1, 0 UNION ALL
SELECT 'ContributionCode', 1, 0
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += FieldName+','
FROM (SELECT p.FieldName+''+CASE WHEN IsActive=0 THEN '=NULL' ELSE '' END FieldName
FROM #FIELD_ACTIVATIONS p
) AS x;
DECLARE #Querycolumns VARCHAR(MAX)=(select left (#columns, Len ( #columns) - 1 ))
DECLARE #Dynamicquery NVARCHAR(MAX) = '
SELECT '+ #Querycolumns +'
FROM
TABLENAME
'
The output query will be run like this :
SELECT PolicyNumber,BOCBranch,CIFNumber,EmployeeNumber=NULL,PremiumSign=NULL,RegistrationDate=NULL,ActivityCode=NULL,ActivityDescription=NULL,PolicyTypeCode=NULL,PolicyTypeDescription=NULL,ContributionCode=NULL
FROM
TABLENAME

How to count rows to a single column value

I'm trying to get two rows count and assign it to a single column.
select distinct
userreferenceid, 'Level(' + cast(level as varchar(255))+')' GroupLevel,
referenceid
from
(select
t0.userreferenceid,
case level.level
when 1 then t1.userreferenceid
when 2 then t2.userreferenceid
when 3 then t3.userreferenceid
when 4 then t4.userreferenceid
end referenceid,
level.level
from
member_details t0
left outer join
member_details t1 on t1.referenceid = t0.userreferenceid
left outer join
member_details t2 on t2.referenceid = t1.userreferenceid
left outer join
member_details t3 on t3.referenceid = t2.userreferenceid
left outer join
member_details t4 on t4.referenceid = t3.userreferenceid
cross join
(select 1 level
union all
select 2
union all
select 3
union all
select 4) level) t
where
t.referenceid is not null
Where my refernceid is the one which user uses to register (referalid), userreferenceid is the own id of the user (own referenceid).
For the above query my result set is displaying like this
user
ref
ere
nceid GroupLevel Referenceid
-------------------------------------------
REF101 Level(1) REF143
REF101 Level(2) REF113
REF101 Level(3) REF119
REF101 Level(3) REF227
REF101 Level(4) REF245
REF101 Level(4) REF251
REF107 Level(1) REF221
REF107 Level(1) REF257
REF107 Level(2) REF119
REF107 Level(2) REF227
REF107 Level(3) REF125
REF107 Level(3) REF161
REF107 Level(4) REF191
REF113 Level(3) REF191
REF119 Level(1) REF125
I need the result set exactly look like this.
I need to assign each and every refid and count of all users in level 1 ,2,3,4,
Userreferenceid | Level1users|Level2Users|level3Users|Level4Users
-----------------+------------+-----------+-----------+------------
REF101 1 1 2 2
REF107 2 2 2 1
REF113 0 0 1 0
REF119 1 0 0 0
My table structure is
userreferenceid (varchar) || referenceid(varchar)
Please help me out of this.. Thanks in advance
try this
Aggregate summation
select Userreferenceid,
sum(case when GroupLevel='Level(1)' then 1 else 0 end) Level1users,
sum(case when GroupLevel='Level(2)' then 1 else 0 end) Level2users,
sum(case when GroupLevel='Level(3)' then 1 else 0 end) Level3users,
sum(case when GroupLevel='Level(4)' then 1 else 0 end) Level4users
from mytable group by Userreferenceid
You can do it by executing dynamic sql query also. If lot of GroupLevel values are there, then writing query would be hectic.
Query
declare #sql as varchar(max);
select #sql = stuff((
select distinct
', sum(case when [GroupLevel] = ' + char(39) + [GroupLevel] + char(39)
+ ' then 1 else 0 end) as [' + replace(replace([GroupLevel], '(', ''),
')', '') + 'Users]'
from [your_table_name]
for xml path('')
)
, 1, 2, ''
);
select #sql = 'select [userreferenceid], ' + #sql
+ 'from [your_table_name] '
+ 'group by [userreferenceid];';
exec(#sql);

How to create columns from a list of values

I have the following data:
Table 1
Row ID Value Cost
1 1 Priority 1 10,000
2 2 Priority 2 9,000
3 3 Priority 3 8,000
4 4 Priority 4 6,000
Table 2
Row Name Priority Cost
1 Jon 1 10,000
2 Bob 3 8,000
3 Dan 4 7,000
4 Steve 2 9,000
5 Bill 3 8,000
...
I want the table to look like this:
Table 3
Row Name Priotity 1 Priority 2 Priority 3 Priority 4
1 Jon 10,000
2 Bob 8,000
3 Dan 7,000
4 Steve 9,000
5 Bill 8,000
...
How can I create rows from Table 1 as columns, and fill in the output as shown in Table 3.
I am hoping this is not as basic as it sounds, but my SQL is terrible!
you can try this for dynamic pivot table.
DECLARE #columns VARCHAR(8000)
SELECT #columns = COALESCE(#columns + ',[' + cast(Value as varchar) + ']',
'[' + cast(Value as varchar)+ ']')
FROM Table1
GROUP BY Value
DECLARE #query VARCHAR(8000)
SET #query = 'with Priorites as
(select a.Name,b.Value,b.Cost from Table2 a left join Table1 b on a.Priority =b.id)
SELECT *
FROM Priorites
PIVOT
(
MAX(Cost)
FOR [Value]
IN (' + #columns + ')
)
AS p'
EXECUTE(#query)
Here is the link for more details http://www.tsqltutorials.com/pivot.php
Pivot is always useful in this sort of scenario, but if the actual data is as simple as it's in question (like there are only 4 unique Priority and/or only 1 Priority is assigned to a particular user),then you can achieve this task with following query:
select t.row,t.name
(case when t.priority = 1 then t.cost
else ' '
end
) as Priority1,
(case when t.priority = 2 then t.cost
else ' '
end
) as Priority2,
(case when t.priority = 3 then t.cost
else ' '
end
) as Priority3,
(case when t.priority = 4 then t.cost
else ' '
end
) as Priority4
From
(select Row,name,priority,cost
from Table2
group by name) t
group by t.name;

T-SQL PIVOT on one column to append the others

Given this table (there's always 2 duplicates of Combinations):
Combination Variable Value
-----------------------------
0 a 1
0 b 2
1 c 3
1 d 4
2 e 5
2 f 6
...
I want to query it to get this:
Variable 0 Value 0 Variable 1 Value 1 Variable 2 Value 2 ...
---------------------------------------------------------------------------
a 1 c 3 e 5
b 2 d 4 f 6
I've tried using PIVOT with dynamic query but cannot come up with a satisfying result.
Can somebody please advise?
EDIT: though Ullas solution works perfectly for combination pairs, I was wondering if it's possible to achieve the same result with combination N-uplets (e.g. (0, 0, 0), (1, 1, 1), (2, 2, 2) should result in 3 rows)? I reckon dynamic query is still the way to go, maybe with PIVOT this time.
Use dynamic sql.
I just created one. Don't know how efficient it is.
Query
declare #query1 varchar(max);
declare #query2 varchar(max);
select #query1 = 'select ' +
STUFF
(
(
select distinct
',min(t.Variable' + cast(Combination as varchar(6)) + ') as Variable' +
cast(Combination as varchar(6)) +
',min(t.Value' + cast(Combination as varchar(6)) + ') as Value' +
cast(Combination as varchar(6))
from tblComb
for xml path('')
),
1,1,'');
select #query1 += ' from('
select #query1 += 'select '+
stuff
(
(
select distinct
',max(case when Combination = ' + cast(Combination as varchar(6)) +'
then Variable end) as Variable' + cast(Combination as varchar(6)) +
',max(case when Combination = ' + cast(Combination as varchar(6)) +'
then Value end) as Value' + cast(Combination as varchar(6))
from tblComb
for xml path('')
),
1, 1, '');
select #query1 += ' from tblComb group by Combination, Variable)t union all ';
select #query2 = 'select ' +
STUFF
(
(
select distinct
',max(t.Variable' + cast(Combination as varchar(6)) + ') as Variable' +
cast(Combination as varchar(6)) +
',max(t.Value' + cast(Combination as varchar(6)) + ') as Value' +
cast(Combination as varchar(6))
from tblComb
for xml path('')
),
1, 1, '');
select #query2 += ' from('
select #query2 += 'select '+
stuff
(
(
select distinct
',max(case when Combination = ' + cast(Combination as varchar(6)) +'
then Variable end) as Variable' + cast(Combination as varchar(6)) +
',max(case when Combination = ' + cast(Combination as varchar(6)) +'
then Value end) as Value' + cast(Combination as varchar(6))
from tblComb
for xml path('')
),
1, 1, '');
select #query2 += ' from tblComb group by Combination, Variable)t;';
select #query1 += #query2;
execute(#query1);
Sample table
+-------------+----------+-------+
| Combination | Variable | Value |
+-------------+----------+-------+
| 0 | a | 1 |
| 0 | b | 2 |
| 1 | c | 3 |
| 1 | d | 4 |
| 2 | e | 5 |
| 2 | f | 6 |
+-------------+----------+-------+
Result set
+-----------+--------+-----------+--------+-----------+--------+
| Variable0 | Value0 | Variable1 | Value1 | Variable2 | Value2 |
+-----------+--------+-----------+--------+-----------+--------+
| a | 1 | c | 3 | e | 5 |
| b | 2 | d | 4 | f | 6 |
+-----------+--------+-----------+--------+-----------+--------+

Pivot with dynamic columns

There are 4 tables:
Suppl, fields: (Code_name, Code_name_arch, Tasknum, Pki_num, Group_eng, Name, Descr, Cost, Quan, shop);
Maker, fields : (Code_maker, Code_maker_arch, Code_name, provider);
Arrival, fields: (Code_arr, Code_maker, quan_arr);
Accounts, fields: (Code_acc, Code_maker, num_acc, quan_acc, summ)
my query is:
ALTER Procedure [dbo].[pr_tblz] As
Set NOCOUNT ON
Declare #task VARCHAR(4000)
Select #task = coalesce(#task + ',[' + [Tasknum] + ']',
'[' + [Tasknum] + ']')
FROM [db_pki].[dbo].[Suppl]
Group BY [Tasknum]
Order BY [Tasknum]
Declare #query VARCHAR(8000)
Set #query='
Alter View Amountzz As
SELECT Shop, Name, Desc, Group_eng as GI, '+ #task +', quan_arr As specif, quan_acc As acns
FROM
(
select
Shop, Name, Desc, Group_eng, Tasknum, Quan, quan_arr, quan_acc
from [db_pki].[dbo].[Suppl] as deliveries
LEFT JOIN [db_pki].[dbo].[Maker] ON (deliveries.[Code_name] = [db_pki].[dbo].[Maker].[Code_name])
LEFT JOIN [db_pki].[dbo].[Arrival] ON ([db_pki].[dbo].[Maker].[Code_maker] = [db_pki].[dbo].[Arrival].[Code_maker])
LEFT JOIN [db_pki].[dbo].[Accounts] ON ([db_pki].[dbo].[Maker].[Code_maker] = [db_pki].[dbo].[Accounts].[Code_maker])
)date_to_pivot
PIVOT
(
Max([Quan])
For [Tasknum]
IN (' + #task + ')
)AS p'
Execute (#query)
result:
Shop Name Descr GI n1 n2 n3 ... specif acns
1 name1 1 5 4 1 1
2 10 name2 2 3 8 2 2
3 name3 3 501 11 3 3
1 8 name1 1 5 16 7 10
a 2 10 name2 2 3 3 5 6
5 name1 1 2 5 6 3
How can I get the following result?
Shop Name Descr GI n1 n2 n3 ... specif acns
1 8 name1 1 5 4 16 8(1+7) 11
a 2 10 name2 2 3 8 3 7 8(2+6)
3 name3 3 501 11 3 3
5 name1 1 2 5 6 3
n1, n2, n3,... - Tasknum
Not tested. Try aggregating the values of Shop, specif and acns in the subselect that pulls the columns before the pivoting, like this (the necessary changes are highlighted in bold):
ALTER Procedure [dbo].[pr_tblz] As
Set NOCOUNT ON
Declare #task VARCHAR(4000)
Select #task = coalesce(#task + ',[' + [Tasknum] + ']',
'[' + [Tasknum] + ']')
FROM [db_pki].[dbo].[Suppl]
Group BY [Tasknum]
Order BY [Tasknum]
Declare #query VARCHAR(8000)
Set #query='
Alter View Amountzz As
SELECT Shop, Name, Desc, Group_eng as GI, '+ #task +', quan_arr As specif, quan_acc As acns
FROM
(
select
MAX(Shop ) OVER (PARTITION BY Name) AS Shop,
Name, Desc, Group_eng, Tasknum, Quan,
SUM(quan_arr) OVER (PARTITION BY Name) AS quan_arr,
SUM(quan_acc) OVER (PARTITION BY Name) AS quan_acc
from [db_pki].[dbo].[Suppl] as deliveries
LEFT JOIN [db_pki].[dbo].[Maker] ON (deliveries.[Code_name] = [db_pki].[dbo].[Maker].[Code_name])
LEFT JOIN [db_pki].[dbo].[Arrival] ON ([db_pki].[dbo].[Maker].[Code_maker] = [db_pki].[dbo].[Arrival].[Code_maker])
LEFT JOIN [db_pki].[dbo].[Accounts] ON ([db_pki].[dbo].[Maker].[Code_maker] = [db_pki].[dbo].[Accounts].[Code_maker])
)date_to_pivot
PIVOT
(
Max([Quan])
For [Tasknum]
IN (' + #task + ')
)AS p'
Execute (#query)