Get MAX value of a BIT column - sql

I have a SELECT request with 'inner join' in the joined table is a column with bit type.
I want to select 1 if in the joined table is at most one value with 1. If it is not the case the value will be 0.
So If I have:
PERSID | NAME
1 | Toto
2 | Titi
3 | Tata
And the second table
PERSID | BOOL
1 | 0
1 | 0
2 | 0
2 | 1
I would like to have for result
Toto -> 0
Titi -> 1
Tata -> 0
I try this:
SELECT
sur.*
,MAX(bo.BOOL)
FROM SURNAME sur
INNER JOIN BOOL bo
ON bo.IDPERS = sur.IDPERS
But MAX is not available on BIT column.. So how can I do that?
Thanks,

you can cast it to an INT, and even cast it back to a BIT if you need to
SELECT
sur.*
,CAST(MAX(CAST(bo.BOOL as INT)) AS BIT)
FROM SURNAME sur
INNER JOIN BOOL bo
ON bo.IDPERS = sur.IDPERS

Try:
max(cast(bo.BOOL as int))

One way
SELECT
sur.*
,MAX(convert(tinyint,bo.BOOL))
FROM SURNAME sur
INNER JOIN BOOL bo
ON bo.IDPERS = sur.IDPERS

You can avoid the messy looking double cast by forcing an implicit cast:
SELECT
sur.*
,CAST(MAX(1 * bo.BOOL) AS BIT)
FROM SURNAME sur
INNER JOIN BOOL bo
ON bo.IDPERS = sur.IDPERS

If you want only those people with exactly one set bit:
declare #Surname as Table ( PersId Int, Name VarChar(10) )
insert into #Surname ( PersId, Name ) values
( 1, 'Toto' ), ( 2, 'Titi' ), ( 3, 'Tata' ), ( 4, 'Tutu' )
declare #Bool as Table ( PersId Int, Bool Bit )
insert into #Bool ( PersId, Bool ) values
( 1, 0 ), ( 1, 0 ),
( 2, 0 ), ( 2, 1 ),
( 4, 1 ), ( 4, 0 ), ( 4, 1 )
select Sur.PersId, Sur.Name, Sum( Cast( Bo.Bool as Int ) ) as [Sum],
case Sum( Cast( Bo.Bool as Int ) )
when 1 then 1
else 0
end as [Only One]
from #Surname as Sur left outer join
#Bool as Bo on Bo.PersId = Sur.PersId
group by Sur.PersId, Sur.Name
order by Sur.Name

Related

SQL: Get Parent Tree Hierarchy from child

I have a table with Id, ParentId, Tree, TopParentId. Here's an example structure:
0
___/ \___
/ \
1 4
/ \ / \
2 7 5 8
/ /
3 6
Input Table:
Id ParentId Tree TopParentId
--- ---------- -------------------- --------
1 0 NULL NULL
2 1 NULL NULL
7 1 NULL NULL
3 2 NULL NULL
4 0 NULL NULL
5 4 NULL NULL
6 5 NULL NULL
8 4 NULL NULL
This is the output I am looking for when I pass Id = 3
OUTPUT:
Id ParentId Tree TopParentId
--- ---------- -------------------- --------
1 2 3 > 2 > 1 > 0 0
The CTE query should be able to handle multiple ids like 3,7
OUTPUT:
Id ParentId Tree TopParentId
--- ---------- -------------------- --------
3 2 3 > 2 > 1 > 0 0
7 1 7 > 1 > 0 0
The end goal is to take the Tree and TopParentId columns and update the corresponding Id
Here's the query I have tried till now:
WITH CTE AS (
SELECT Id, ParentId,0 AS [Level], CAST(Id AS varchar(1000)) AS Heirarchy,Id AS TopParentId
FROM dbo.table
WHERE Id IN (SELECT Id FROM table WHERE ParentId IS NULL)
UNION ALL
SELECT mgr.Id, mgr.ParentId, TASKCTE.[Level] +1 AS [Level],
CAST(( CAST(mgr.Id AS VARCHAR(1000)) + '>' + CTE.Heirarchy) AS varchar(1000)) AS Heirarchy, CTE.TopParentId
FROM CTE
INNER JOIN dbo.table AS mgr
ON CTE.Id = mgr.ParentId
)
UPDATE t SET t.[LEVEL] = TC.[LEVEL], t.ParentTree = TC.Heirarchy, t.TopParentId = TC.TopParentId
FROM dbo.table AS t
JOIN (SELECT * FROM CTE WHERE Id IN(SELECT DISTINCT Id FROM INSERTED) AND ParentId IS NOT NULL) TC
ON
t.Id = TC.Id
The above query works but its CPU/RAM intensive as it starts from Parent. I need the CTE to start from the Child but the Tree needs to be exactly same as the example outputs.
This appears to work:
drop table if exists #t;
with cte as (
select * from (values
(1, 0),
(2, 1),
(7, 1),
(3, 2),
(4, 0),
(5, 4),
(6, 5),
(8, 4)
)
as x(ID, ParentID)
)
select *
into #t
from cte;
with cte as (
select * ,
ID as [start] ,
ParentID as [FirstParent] ,
1 as [level] ,
cast(ID as varchar(max)) as Tree
from #t
where ID in (3, 7)
union all
select p.*,
c.[start] ,
c.FirstParent ,
c.[level] + 1 ,
cast(concat(c.Tree, ' > ', p.ID) as varchar(max))
from cte as c
join #t as p
on c.ParentID = p.ID
), top_level as (
select *, row_number() over (partition by [start] order by [level] desc) as rn
from cte
)
select [start] as ID ,
FirstParent as ParentID ,
concat(Tree, ' > ', ParentID) ,
ParentID as TopParentID
from top_level
where rn = 1;
By way of exposition, the first part just creates your test data (pro-tip: if you do this, people are more likely to be able to help since you've lowered the friction to do so!). The meat of the solution just uses the desired IDs as the "base case" for the recursion and the recursive step says "take the previous level's ParentID to find the next level's ID". The rest is just to keep track of the starting and ending point.
You simply need to create procedure to get the desired result.
Try this or variant of this will help.
Create proc CalculateLevel
( #passid nvarchar(max) )
As
Begin
declare #query nvarchar(max)
set #query = '
WITH CTE AS (
SELECT Id, ParentId,0 AS [Level], CAST(Id AS varchar(1000)) AS Heirarchy,Id AS TopParentId
FROM dbo.tab
WHERE Id IN (SELECT Id FROM tab WHERE ParentId IS NULL)
UNION ALL
SELECT mgr.Id, mgr.ParentId, CTE.[Level] +1 AS [Level],
CAST(( CAST(mgr.Id AS VARCHAR(1000)) + ''>'' + CTE.Heirarchy) AS varchar(1000)) AS Heirarchy, CTE.TopParentId
FROM CTE
INNER JOIN dbo.tab AS mgr
ON CTE.Id = mgr.ParentId
)
UPDATE t SET t.[LEVEL] = TC.[LEVEL], t.ParentTree = TC.Heirarchy, t.TopParentId = TC.TopParentId
FROM dbo.tab AS t
JOIN (SELECT * FROM CTE WHERE ParentId IS NOT NULL) TC
ON
t.Id = TC.Id where t.id in ( ' + #passid + ')'
print #query
exec sp_executesql #query
End

T-SQL How to "Flatten" top 3 rows into a single row

I've searched for an answer to this question and found questions similar to my own, however I do not have a "ColumnHeader" column to denote which field the record should go into. Ex:
TSQL Pivot without aggregate function
trying to flatten rows into columns
Fetching Columns of a multiple rows in one row
My problem is thus - I have data in this format (selected as a top 3 result from a product recommendation query):
------------------------------
CustID | StyleNo | Brand | ID
------------------------------
1 | ABC | BrandA| 1
------------------------------
1 | DEF | BrandB| 2
------------------------------
1 | GHI | BrandC| 3
------------------------------
2 | JKL | BrandA| 4
------------------------------
2 | MNO | BrandB| 5
------------------------------
2 | PQR | BrandD| 6
------------------------------
That I'd like to make look like this:
-----------------------------------------------------------------
CustID | StyleNo1| StyleNo2| StyleNo3 | Brand1 | Brand2 | Brand3
-----------------------------------------------------------------
1 | ABC | DEF | GHI | BrandA | BrandB | BrandC
-----------------------------------------------------------------
2 | JKL | MNO | PQR | BrandA | BrandB | BrandD
-----------------------------------------------------------------
In order for my program to simply read the row of recommendations for each customer.
What I have attempted is a PIVOT - however I have nothing to really aggregate upon. I've also attempted the Min(Case...When...Then...End) as outlined in the second linked question, but as stated I don't have reference to a "Header" column.
The ID column is completely inconsequential for the time being, but it may help to solve this problem. It is NOT needed in the end result.
I am currently using SQLServer 2012
With the window function Row_Number() and a conditional aggregation
Select CustID
,StyleNo1 = max(case when RN=1 then StyleNo else null end)
,StyleNo2 = max(case when RN=2 then StyleNo else null end)
,StyleNo3 = max(case when RN=3 then StyleNo else null end)
,Brand1 = max(case when RN=1 then Brand else null end)
,Brand2 = max(case when RN=2 then Brand else null end)
,Brand3 = max(case when RN=3 then Brand else null end)
From (
Select *,RN = Row_Number() over (Partition By CustID Order by StyleNo,Brand)
From YourTable
) A
Where RN<=3
Group By CustID
Returns
What you are doing is called "pivoting" - for this you could use PIVOT. A better way IMHO is to use approach that Jeff Moden talks about in this article.
WITH idSort AS
(
SELECT *, rn = ROW_NUMBER() OVER (PARTITION BY CustID ORDER BY ID) FROM #yourTable
)
SELECT
CustID,
StyleNo1 = MAX(CASE rn WHEN 1 THEN StyleNo END),
StyleNo2 = MAX(CASE rn WHEN 2 THEN StyleNo END),
StyleNo3 = MAX(CASE rn WHEN 3 THEN StyleNo END),
Brand1 = MAX(CASE rn WHEN 1 THEN Brand END),
Brand2 = MAX(CASE rn WHEN 2 THEN Brand END),
Brand3 = MAX(CASE rn WHEN 3 THEN Brand END)
FROM idSort
GROUP BY CustID;
Other approach can be using CTE's and Cross Apply.
CREATE TABLE #UnFlattenedData
(
CustID TINYINT ,
StyleNo CHAR(3) ,
Brand CHAR(6) ,
ID TINYINT
);
INSERT INTO #UnFlattenedData
( CustID, StyleNo, Brand, ID )
VALUES ( 1, -- CustID - tinyint
'ABC', -- StyleNo - char(3)
'BrandA', -- Brand - char(6)
1 -- ID - tinyint
),
( 1, -- CustID - tinyint
'DEF', -- StyleNo - char(3)
'BrandB', -- Brand - char(6)
2 -- ID - tinyint
),
( 1, -- CustID - tinyint
'GHI', -- StyleNo - char(3)
'BrandC', -- Brand - char(6)
3 -- ID - tinyint
),
( 2, -- CustID - tinyint
'JKL', -- StyleNo - char(3)
'BrandA', -- Brand - char(6)
4 -- ID - tinyint
),
( 2, -- CustID - tinyint
'MNO', -- StyleNo - char(3)
'BrandB', -- Brand - char(6)
5 -- ID - tinyint
),
( 2, -- CustID - tinyint
'PQR', -- StyleNo - char(3)
'BrandD', -- Brand - char(6)
6 -- ID - tinyint
);
WITH cte
AS ( SELECT * ,
ROW_NUMBER() OVER ( PARTITION BY u1.CustID ORDER BY u1.ID ) AS R1
FROM #UnFlattenedData AS u1
),
u1
AS ( SELECT C1.CustID ,
U1.StyleNo ,
U1.Brand
FROM cte AS C1
INNER JOIN #UnFlattenedData AS U1 ON U1.CustID = C1.CustID
AND U1.ID = C1.ID
WHERE C1.R1 = 1
),
u2
AS ( SELECT C1.CustID ,
U1.StyleNo ,
U1.Brand
FROM cte AS C1
INNER JOIN #UnFlattenedData AS U1 ON U1.CustID = C1.CustID
AND U1.ID = C1.ID
WHERE C1.R1 = 2
),
u3
AS ( SELECT C1.CustID ,
U1.StyleNo ,
U1.Brand
FROM cte AS C1
INNER JOIN #UnFlattenedData AS U1 ON U1.CustID = C1.CustID
AND U1.ID = C1.ID
WHERE C1.R1 = 3
)
SELECT u1.CustID ,
u1.StyleNo AS StyleNo1 ,
u2.StyleNo AS StyleNo2 ,
u3.StyleNo AS StyleNo3 ,
u1.Brand AS Brand1 ,
u2.Brand AS Brand2 ,
u3.Brand AS Brand3
FROM u1
CROSS APPLY ( SELECT *
FROM u2
WHERE u2.CustID = u1.CustID
) AS u2
CROSS APPLY ( SELECT *
FROM u3
WHERE u3.CustID = u1.CustID
) AS u3;

Comma separated values from multi columns as rows

I have a table tbl_commaseperate with columns ID, MONNAME, IP and POLICY whose values are:
ID | MONNAME | IP | POLICY
-------------------------------
X | NOV | 1,2,3 | 4,5,6,7
where IP and POLICY have comma separated values.
My desired result looks like as below:
ID | MONNAME | IP | POLICY
------------------------------
X | NOV | 1 | 4
X | NOV | 2 | 5
X | NOV | 3 | 6
X | NOV | null | 7
The output is in no particular order. Also, in your desired output you don't seem to care which pair was first, which second etc. (but that can be preserved in the query, if needed).
I added a row for more testing; I have NULL for policy - which is how I realized I needed the coalesce() around the regexp_count.
with
inputs ( id ,monname, ip , policy ) as (
select 'X', 'NOV', '1,2,3' , '4,5,6,7' from dual union all
select 'X', 'DEC', '6,3,8', null from dual
)
-- end of test data; solution (SQL query) begins below this line
select id, monname,
regexp_substr(ip , '[^,]+', 1, level) as ip,
regexp_substr(policy, '[^,]+', 1, level) as policy
from inputs
connect by level <= 1 + greatest( coalesce(regexp_count(ip , ','), 0),
coalesce(regexp_count(policy, ','), 0) )
and prior id = id
and prior monname = monname
and prior sys_guid() is not null
;
ID MONNAME IP POLICY
-- ------- ----- -------
X DEC 6
X DEC 3
X DEC 8
X NOV 1 4
X NOV 2 5
X NOV 3 6
X NOV 7
7 rows selected
Please try this one.
Create a function to split comma separated string.
CREATE FUNCTION [dbo].[fnSplit](
#sInputList VARCHAR(max) -- List of delimited items
, #sDelimiter VARCHAR(max) = ',' -- delimiter that separates items
) RETURNS #List TABLE (SplitValue VARCHAR(max))
BEGIN
DECLARE #sItem VARCHAR(max)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT
#sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
And then write your query like this.
select * from (
select SplitValue,ROW_NUMBER() over(order by SplitValue) rowNo FROM dbo.fnSplit('1,2,3',',')
) as a
full join (
select SplitValue,ROW_NUMBER() over(order by SplitValue) rowNo FROM dbo.fnSplit('4,5,6,7',',')
) as b on a.rowNo=b.rowNo
replace your column in hardcore string and. Note: You can also write with query instead of function.
Oracle
with r (id,monname,ip,policy,n,max_tokens)
as
(
select id,monname,ip,policy
,1
,greatest (nvl(regexp_count(ip,'[^,]+'),0),nvl(regexp_count(policy,'[^,]+'),0))
from tbl_commaseperate
union all
select id,monname,ip,policy
,n+1
,max_tokens
from r
where n < max_tokens
)
select r.id
,r.monname
,regexp_substr (ip ,'[^,]+',1,n) as ip
,regexp_substr (policy,'[^,]+',1,n) as policy
from r
;
CREATE TABLE #Table ( ID VARCHAR(100),MONNAME VARCHAR(100), IP VARCHAR(100) , POLICY VARCHAR(100))
INSERT INTO #Table (ID ,MONNAME, IP, POLICY)SELECT 'X','NOV',1,2,3,4','4,5,6,7'
;WITH _CTE ( _id ,_MONNAME , _IP , _POLICY , _RemIP , _RemPOLICY) AS (
SELECT ID ,MONNAME , SUBSTRING(IP,0,CHARINDEX(',',IP)), SUBSTRING(POLICY,0,CHARINDEX(',',POLICY)),
SUBSTRING(IP,CHARINDEX(',',IP)+1,LEN(IP)),
SUBSTRING(POLICY,CHARINDEX(',',POLICY)+1,LEN(POLICY))
FROM #Table
UNION ALL
SELECT _id ,_MONNAME , CASE WHEN CHARINDEX(',',_RemIP) = 0 THEN _RemIP ELSE
SUBSTRING(_RemIP,0,CHARINDEX(',',_RemIP)) END, CASE WHEN CHARINDEX(',',_RemPOLICY) = 0 THEN _RemPOLICY ELSE SUBSTRING(_RemPOLICY,0,CHARINDEX(',',_RemPOLICY)) END,
CASE WHEN CHARINDEX(',',_RemIP) = 0 THEN '' ELSE SUBSTRING(_RemIP,CHARINDEX(',',_RemIP)+1,LEN(_RemIP)) END,
CASE WHEN CHARINDEX(',',_RemPOLICY) = 0 THEN '' ELSE SUBSTRING(_RemPOLICY,CHARINDEX(',',_RemPOLICY)+1,LEN(_RemPOLICY)) END
FROM _CTE WHERE _RemIP <> '' OR _RemPOLICY <> ''
)
SELECT _id id,_MONNAME MONNAME, _IP IP , _POLICY POLICY
FROM _CTE

Remove 'cross' duplicate result for double cross apply

I have the following data in a column:
5;ABC|1;XYZ
I would like to split the value on the '|' delimiter and then split every result on the ';' delimiter.
I have the following query, but unfortenately it gives me (sort of) duplicate results.
DECLARE #MyTable TABLE ( Code VARCHAR(100) )
INSERT INTO #MyTable
VALUES ( '5;ABC|1;XYZ' );
WITH Query AS
(
SELECT T1.RowNum, SubSplit.Value FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY Code) as RowNum, Split.Value FROM #MyTable
CROSS APPLY SplitString(Code, '|') AS Split
) T1
CROSS APPLY SplitString(Value, ';') AS SubSplit
)
SELECT q1.Value AS [Left], q2.Value AS [Right] FROM Query q1
INNER JOIN Query q2 ON q1.RowNum = q2.RowNum AND q1.Value <> q2.Value
The result is:
But what I would like is:
How can I achieve this?
Edit
For completeness, this is the SplitString function I use:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION SplitString
(
-- Add the parameters for the function here
#input varchar(8000),
#delimiter varchar(1)
)
RETURNS TABLE
AS
RETURN
(
WITH cte AS
(
SELECT 0 a, 1 b
UNION ALL
SELECT b, CHARINDEX(#delimiter, #input, b) + LEN(#delimiter)
FROM CTE
WHERE b > a
)
SELECT SUBSTRING(#input, a,
CASE WHEN b > LEN(#delimiter)
THEN b - a - LEN(#delimiter)
ELSE LEN(#input) - a + 1 END) Value
FROM cte WHERE a > 0
)
GO
Add another ROW_NUMBER in the outer query:
SQL Fiddle
Query 1:
DECLARE #MyTable TABLE ( Code VARCHAR(100) )
INSERT INTO #MyTable
VALUES ( '5;ABC|1;XYZ|6;HXS|7;GGH' )
;WITH Query AS
(
SELECT T1.RowNum, SubSplit.Value,
ROW_NUMBER() OVER (PARTITION BY T1.RowNum ORDER BY SubSplit.Value) as RowNum1
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY Code) as RowNum, Split.Value FROM #MyTable
CROSS APPLY SplitString(Code, '|') AS Split
) T1
CROSS APPLY SplitString(Value, ';') AS SubSplit
)
SELECT q1.Value AS [Left], q2.Value AS [Right] FROM Query q1
INNER JOIN Query q2 ON q1.RowNum = q2.RowNum AND q1.RowNum1 = 1 AND q2.RowNum1 = 2
Results:
| Left | Right |
|------|-------|
| 5 | ABC |
| 1 | XYZ |
| 6 | HXS |
| 7 | GGH |
Update:
The use of the ROW_NUMBER() in the outer query will only work if the Left is less then Right when diong the string comparison. It will not work correctly for the '6;123' value. Therefore, there is a better approach by using an enhanced SplitString function as per below:
SQL Fiddle
MS SQL Server 2014 Schema Setup:
CREATE FUNCTION SplitString
(
-- Add the parameters for the function here
#input varchar(8000),
#delimiter varchar(1)
)
RETURNS TABLE
AS
RETURN
(
WITH cte AS
(
SELECT 0 a, 1 b, 0 rn
UNION ALL
SELECT b, CHARINDEX(#delimiter, #input, b) + LEN(#delimiter), rn + 1
FROM CTE
WHERE b > a
)
SELECT SUBSTRING(#input, a,
CASE WHEN b > LEN(#delimiter)
THEN b - a - LEN(#delimiter)
ELSE LEN(#input) - a + 1 END) Value,
rn
FROM cte WHERE a > 0
)
Query 1:
DECLARE #MyTable TABLE ( Code VARCHAR(100) )
INSERT INTO #MyTable
VALUES ( '5;ABC|1;XYZ|6;123|7;GGH' )
;WITH Query AS
(
SELECT T1.RowNum, SubSplit.Value,
SubSplit.rn as RowNum1
FROM
(
SELECT Split.rn as RowNum, Split.Value FROM #MyTable
CROSS APPLY SplitString(Code, '|') AS Split
) T1
CROSS APPLY SplitString(Value, ';') AS SubSplit
)
SELECT q1.Value AS [Left], q2.Value AS [Right] FROM Query q1
INNER JOIN Query q2 ON q1.RowNum = q2.RowNum AND q1.RowNum1 = 1 AND q2.RowNum1 = 2
Results:
| Left | Right |
|------|-------|
| 5 | ABC |
| 1 | XYZ |
| 6 | 123 |
| 7 | GGH |

SQL Server - Complex Dynamic Pivot multiple columns

FYI, this question is already answered but I have some new requirements which is very complex to implement so, I am posting it as a new question instead of editing the old question: (Previous Question)
I have two tables "Controls" and "ControlChilds" (in the ControlChilds table we have added a new column called ControlChildComments which we need to show in PIVOT output)
Parent Table Structure:
Create table Controls(
ProjectID Varchar(20) NOT NULL,
ControlID INT NOT NULL,
ControlCode Varchar(2) NOT NULL,
ControlPoint Decimal NULL,
ControlScore Decimal NULL,
ControlValue Varchar(50)
)
Sample Data
ProjectID | ControlID | ControlCode | ControlPoint | ControlScore | ControlValue
P001 1 A 30.44 65 Invalid
P001 2 C 45.30 85 Valid
Child Table Structure:
Create table ControlChilds(
ControlID INT NOT NULL,
ControlChildID INT NOT NULL,
ControlChildValue Varchar(200) NULL,
ControlChildComments Varchar(200) NULL
)
Sample Data
ControlID | ControlChildID | ControlChildValue | ControlChildComments
1 100 Yes Something
1 101 No NULL
1 102 NA Others
1 103 Others NULL
2 104 Yes New one
2 105 SomeValue NULL
Based on my previous question (Previous Question) I got this output (You can refer to the PIVOT queries which produces this output in the answer given by #bluefeet. Thanks again #bluefeet.)
But now my requirement is changed and I need ControlChildComments after each Child values. For example, A_Child1, A_Child1Comments, A_Child2, A_Child2Comments etc...
Another tricky thing is I need to show the comments only when they are not null otherwise I shouldn't show the column. For example, in this case, it should be like this:
A_Child1, A_Child1Comments, A_Child2, A_Child3, A_Child3Comments, A_Child4, C_Child1, C_Child1Comments, C_Child2
Is this possible? I tried lot of things but the results are not accurate.
Since you now have multiple columns in your ControlChilds table that you need to PIVOT, you will need to use the similar method of unpivoting them first that you applied with the Controls table.
You will need to unpivot both the ChildControlValue and ChildControlComments using code similar to:
select
projectId,
col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', ControlChildValue union all
select 'ChildComments', ControlChildComments
) c (subCol, value);
See SQL Fiddle with Demo. This gets your data in the format:
| PROJECTID | COL | VALUE |
|-----------|------------------|-----------|
| P001 | A_ChildValue1 | Yes |
| P001 | A_ChildComments1 | Something |
| P001 | A_ChildValue2 | No |
| P001 | A_ChildComments2 | (null) |
| P001 | A_ChildValue3 | NA |
You then use this code in your existing query:
select ProjectId,
A_ControlPoint, A_ControlScore, A_ControlValue,
A_ChildValue1, A_ChildComments1, A_ChildValue2,
A_ChildComments2, A_ChildValue3, A_ChildComments3,
A_ChildValue4, A_ChildComments4,
C_ControlPoint, C_ControlScore, C_ControlValue,
C_Child1, C_Child2
from
(
select
ProjectId,
col = ControlCode +'_'+col,
val
from
(
select
c.ProjectId,
c.ControlCode,
c.ControlPoint,
c.ControlScore,
c.ControlValue
from controls c
) d
cross apply
(
select 'ControlPoint', cast(controlpoint as varchar(10)) union all
select 'ControlScore', cast(ControlScore as varchar(10)) union all
select 'ControlValue', ControlValue
) c (col, val)
union all
select
projectId,
col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', ControlChildValue union all
select 'ChildComments', ControlChildComments
) c (subCol, value)
) src
pivot
(
max(val)
for col in (A_ControlPoint, A_ControlScore, A_ControlValue,
A_ChildValue1, A_ChildComments1, A_ChildValue2,
A_ChildComments2, A_ChildValue3, A_ChildComments3,
A_ChildValue4, A_ChildComments4,
C_ControlPoint, C_ControlScore, C_ControlValue,
C_Child1, C_Child2)
) piv;
See SQL Fiddle with Demo. Finally, you'll implement this in your dynamic SQL script:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select ControlCode,
col = ControlCode +'_'+col,
seq,
so
from controls
cross apply
(
select 'ControlPoint', 0, 0 union all
select 'ControlScore', 0, 1 union all
select 'ControlValue', 0, 2
) c (col, seq, so)
union all
select ControlCode,
col = ControlCode+'_'+subcol+cast(rn as varchar(10)),
rn,
so
from
(
select ControlCode,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', seq, 3 union all
select 'ChildComments', seq, 4
) c (subcol, rn, so)
) src
group by ControlCode, seq, col, so
order by ControlCode, seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ProjectId, ' + #cols + '
from
(
select ProjectId,
col = ControlCode +''_''+col,
val
from
(
select
c.ProjectId,
c.ControlCode,
c.ControlPoint,
c.ControlScore,
c.ControlValue
from controls c
) d
cross apply
(
select ''ControlPoint'', cast(controlpoint as varchar(10)) union all
select ''ControlScore'', cast(ControlScore as varchar(10)) union all
select ''ControlValue'', ControlValue
) c (col, val)
union all
select
projectId,
col = ControlCode+''_''+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select ''ChildValue'', ControlChildValue union all
select ''ChildComments'', ControlChildComments
) c (subCol, value)
) x
pivot
(
max(val)
for col in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. Both of these gives a result:
| PROJECTID | A_CONTROLPOINT | A_CONTROLSCORE | A_CONTROLVALUE | A_CHILDVALUE1 | A_CHILDCOMMENTS1 | A_CHILDVALUE2 | A_CHILDCOMMENTS2 | A_CHILDVALUE3 | A_CHILDCOMMENTS3 | A_CHILDVALUE4 | A_CHILDCOMMENTS4 | C_CONTROLPOINT | C_CONTROLSCORE | C_CONTROLVALUE | C_CHILDVALUE1 | C_CHILDCOMMENTS1 | C_CHILDVALUE2 | C_CHILDCOMMENTS2 |
|-----------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|---------------|------------------|---------------|------------------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|
| P001 | 30.44 | 65.00 | Invalid | Yes | Something | No | (null) | NA | Others | Others | (null) | 45.30 | 85.00 | Valid | Yes | New one | SomeValue | (null) |
Here is an example of a dynamic crosstab. Since you have multiple columns you would need to adjust the dynamic portion of this to suit.
if OBJECT_ID('Something') is not null
drop table Something
create table Something
(
ID int,
Subject1 varchar(50)
)
insert Something
select 10868952, 'NUR/3110/D507' union all
select 10868952, 'NUR/3110/D512' union all
select 10868952, 'NUR/4010/D523' union all
select 10868952, 'NUR/4010/HD20' union all
select 12345, 'asdfasdf'
declare #MaxCols int
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *, ROW_NUMBER() over(partition by ID order by Subject1) as RowNum
from Something
)
select ID';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by ID order by ID';
with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
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
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Subject1 end) as Subject' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from Something
group by ID
order by COUNT(*) desc
)
select #StaticPortion + #DynamicPortion + #FinalStaticPortion
--declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
--exec sp_executesql #SqlToExecute