I am facing the problem tree view to flat view, I have some data that has come from a tree, those are stored in a table that is presenting in my picture below left. I am saying the top level of tree is level1, and second level is level2 and so on. My expected result that is representing in my picture below right.
How can I convert my data to my expected result dynamically in SQL Server? We have to create dynamic column level1, level2, level3 and so on. Do you have any idea? Thanks.
Here is the sample data
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
Id INT NOT NULL,
SomeName VARCHAR(3) NOT NULL,
ParentId INT NULL
);
INSERT #TestData (Id, SomeName, ParentId) VALUES
(1, 'O', NULL),
(2, 'D1', 1),
(3, 'D2', 1),
(4, 'S1', 2),
(5, 'S2', 2),
(6, 'S1', 3),
(7, 'SP1', 3);
SELECT * FROM #TestData td;
You can query as below:
;With Cte as ( --Recursive CTE for traversing tree
Select *, convert(varchar(max),[name]) as NameLevel, 1 as Levl from #TreeData where ParentId is Null
Union all
Select t.Id, t.[Name], t.[ParentId], concat(c.NameLevel,',',t.[name]) as NameLevel, c.Levl + 1 as Levl from Cte c
inner join #TreeData t on c.Id = t.ParentId
)
Select * from (
select c.Id, c.Levl, a.[value]
,RowN = row_number() over(partition by Id order by Levl) from cte c
cross apply string_split(c.NameLevel,',') a
) sq
pivot(max([value]) for RowN in([1],[2],[3])) p --Pivot for getting all data
Output as below:
+---+------+------+----+
| 1 | 2 | 3 | Id |
+---+------+------+----+
| O | NULL | NULL | 1 |
| O | D1 | NULL | 2 |
| O | D2 | NULL | 3 |
| O | D1 | S1 | 4 |
| O | D1 | S2 | 5 |
| O | D2 | S1 | 6 |
| O | D2 | SP1 | 7 |
+---+------+------+----+
Below the input table and data I used:
Create Table #TreeData(Id int, [name] varchar(10), ParentId int)
Insert into #TreeData(id, [name], ParentId) values
(1,'O', null)
,(2,'D1', 1)
,(3,'D2', 1)
,(4,'S1', 2)
,(5,'S2', 2)
,(6,'S1', 3)
,(7,'SP1', 3)
IF OBJECT_ID('tempdb..#TreeData', 'U') IS NOT NULL
DROP TABLE #TreeData;
CREATE TABLE #TreeData (
Id INT NOT NULL,
SomeName VARCHAR(3) NOT NULL,
ParentId INT NULL
);
INSERT #TreeData (Id, SomeName, ParentId) VALUES
(1, 'O', NULL),
(2, 'D1', 1),
(3, 'D2', 1),
(4, 'S1', 2),
(5, 'S2', 2),
(6, 'S1', 3),
(7, 'SP1', 3);
--SELECT * FROM #TestData td;
;With Cte as ( --Recursive CTE for traversing tree
Select Id,SomeName,ParentId, convert(varchar(max),[SomeName]) as NameLevel, 1 as Levl from #TreeData where ParentId is Null
Union all
Select t.Id, t.[SomeName], t.[ParentId], (c.NameLevel +','+ t.[SomeName]) as NameLevel, c.Levl + convert(int, 1) as Levl
from Cte c
inner join #TreeData t on c.Id = t.ParentId
)
--select * from cte
Select * from (
select c.Id, c.Levl, a.Items
,RowN = row_number() over(partition by Id order by Levl) from cte c
cross apply split(c.NameLevel,',') a
) sq
pivot(max([Items]) for RowN in([1],[2],[3])) p --Pivot for getting all data
The following should give you the requested results.
Note: This solution relies on the use of an iTVF called tfn_Tally, I'll post the code for that below my answer.
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
Id INT NOT NULL,
SomeName VARCHAR(3) NOT NULL,
ParentId INT NULL
);
INSERT #TestData (Id, SomeName, ParentId) VALUES
(1, 'O', NULL),
(2, 'D1', 1),
(3, 'D2', 1),
(4, 'S1', 2),
(5, 'S2', 2),
(6, 'S1', 3),
(7, 'SP1', 3);
-- SELECT * FROM #TestData td;
--===========================================
--===========================================
IF OBJECT_ID('tempdb..#RecursionResults', 'U') IS NOT NULL
DROP TABLE #RecursionResults;
WITH
cte_Recursion AS (
SELECT
td.Id,
SomeName = CAST(CAST(td.SomeName AS BINARY(5)) AS VARBINARY(1000)),
--td.ParentId,
NodeLevel = 1
FROM
#TestData td
WHERE
td.ParentId IS NULL
UNION ALL
SELECT
td.Id,
SomeName = CAST(CONCAT(r.SomeName, CAST(td.SomeName AS BINARY(5))) AS VARBINARY(1000)),
NodeLevel = r.NodeLevel + 1
FROM
cte_Recursion r
JOIN #TestData td
ON r.Id = td.ParentId
)
SELECT
Id = ISNULL(r.Id, 0), r.SomeName, r.NodeLevel
INTO #RecursionResults
FROM
cte_Recursion r;
-- adding a clustered index Id eliminates the sort operation on final select.
ALTER TABLE #RecursionResults ADD PRIMARY KEY CLUSTERED (Id);
-----------------------------------------
DECLARE
#PivotCount INT = (SELECT MAX(rr.NodeLevel) FROM #RecursionResults rr),
#PivotCols VARCHAR(1000) = '',
#PivotCAV VARCHAR(8000) = '',
#sql VARCHAR(8000),
#Debug BIT = 0; -- set to 0 to execute, 1 to DeBug.
SELECT TOP (#PivotCount)
#PivotCols = CONCAT(#PivotCols, CHAR(13), CHAR(10), CHAR(9), 'L', t.n, '.Level_', t.n, ','),
#PivotCAV = CONCAT(#PivotCAV, CHAR(13), CHAR(10), CHAR(9), 'CROSS APPLY ( VALUES (CAST(SUBSTRING(rr.SomeName,', (t.n - 1) * 5 + 1, ', ', 5, ') AS VARCHAR(5))) ) L', t.n, ' (Level_', t.n, ')'
)
FROM
dbo.tfn_Tally(#PivotCount, 1) t;
SET #sql = CONCAT('
SELECT ',
STUFF(#PivotCols, 1, 1, ''), '
rr.Id
FROM
#RecursionResults rr',
#PivotCAV, '
ORDER BY
rr.Id;');
IF #Debug = 1
BEGIN
PRINT(#sql);
END;
ELSE
BEGIN
EXEC (#sql);
END;
Function code for tfn_Tally...
CREATE FUNCTION dbo.tfn_Tally
/* ============================================================================
07/20/2017 JL, Created. Capable of creating a sequense of rows
ranging from -10,000,000,000,000,000 to 10,000,000,000,000,000
============================================================================ */
(
#NumOfRows BIGINT,
#StartWith BIGINT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)), -- 10 rows
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b), -- 100 rows
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b), -- 10,000 rows
cte_n4 (n) AS (SELECT 1 FROM cte_n3 a CROSS JOIN cte_n3 b), -- 100,000,000 rows
cte_Tally (n) AS (
SELECT TOP (#NumOfRows)
(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) + #StartWith
FROM
cte_n4 a CROSS JOIN cte_n4 b -- 10,000,000,000,000,000 rows
)
SELECT
t.n
FROM
cte_Tally t;
GO
HTH,
Jason
Related
I have table table_1 with data below:
| WordID| Add |
+-------+------+
| 1 | Hello|
| 2 | Hi |
| 3 | Go |
| 4 | Come |
And I have a String "Hello, Please Go to John's House."
I want to remove hello and go from the string using SQL Server and output result should be
", Please to John's House"
How can I do it ?
I'm using a function created by Chris Morris and explained by Dwain Camps in here.
I changed the string to test for false positives.
declare #list varchar(255) = 'Hello, Please Go to Phillip''s House who left long ago.';
create table table_1(WordID int, [Add] varchar(255));
insert into table_1(WordID, [Add]) values
(1, 'Hello'),
(2, 'Hi'),
(3, 'Go'),
(4, 'Come');
SELECT ( SELECT Item + ''
FROM dbo.PatternSplitCM(#list, '%[^a-zA-Z'']%') ps
WHERE NOT EXISTS( SELECT * FROM table_1 t WHERE ps.Item = t.[Add])
ORDER BY ItemNumber
FOR XML PATH(''), TYPE).value('./text()[1]', 'varchar(max)')
The function definition is here:
-- PatternSplitCM will split a string based on a pattern of the form
-- supported by LIKE and PATINDEX
--
-- Created by: Chris Morris 12-Oct-2012
CREATE FUNCTION [dbo].[PatternSplitCM]
(
#List VARCHAR(8000) = NULL
,#Pattern VARCHAR(50)
) RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
WITH numbers AS (
SELECT TOP(ISNULL(DATALENGTH(#List), 0))
n = ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) e (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) f (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) g (n))
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY MIN(n)),
Item = SUBSTRING(#List,MIN(n),1+MAX(n)-MIN(n)),
[Matched]
FROM (
SELECT n, y.[Matched], Grouper = n - ROW_NUMBER() OVER(ORDER BY y.[Matched],n)
FROM numbers
CROSS APPLY (
SELECT [Matched] = CASE WHEN SUBSTRING(#List,n,1) LIKE #Pattern THEN 1 ELSE 0 END
) y
) d
GROUP BY [Matched], Grouper;
You need a (fake) update for the table:
declare #list varchar(255) = 'Hello, Please Go to John''s House.';
create table table_1(WordID int, [Add] varchar(255));
insert into table_1(WordID, [Add]) values
(1, 'Hello'),
(2, 'Hi'),
(3, 'Go'),
(4, 'Come');
update table_1
set #list = replace(#list, [Add], '');
select #list;
Result:
, Please to John's House.
I'm new to stackoverflow, but I'm stuck with my query.
I've got a SQL table whitch looks like this:
+-------+------------+
| col1 | col2 |
+-------+------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
| 1 | 6 |
+-------+------------+
I don't know how to get the following resultset:
+-------+------------+
| col1 |SerialNumber|
+-------|------------+
| 1 | 1 to 4, 6 |
+--------------------+
With XML Path i can get this:
+-------+------------+
| col1 |SerialNumber|
+-------|------------+
| 1 | 1,2,3,4,6, |
+--------------------+
This is my query for it:
SELECT DISTINCT O.Col1,
(SELECT CAST(P.Col2 As varchar(5)) + ',' AS [text()]
FROM #Test P
WHERE P.Col1 = O.Col1
ORDER BY P.Col1
FOR XML PATH('')) AS 'SerialNumber'
FROM #Test O
I'm sorry if my question was already asked. I'm also lacking Keywords for this topic.
Test data:
CREATE TABLE t(col1 int,col2 int)
INSERT t(col1,col2)VALUES
(1,1),(1,2),(1,3),(1,4),
(1,6),(1,7),(1,8),(1,9),
(1,11),
(1,13),
(2,3),(2,4),(2,5),
(2,7)
A variant with FOR XML PATH:
SELECT col1,col2,outVal
INTO #temp
FROM
(
SELECT
col1,
col2,
outVal,
ISNULL(LEAD(outVal)OVER(PARTITION BY col1 ORDER BY col2),'') nextOutVal
FROM
(
SELECT
col1,
col2,
CASE
WHEN col2-1=LAG(col2)OVER(PARTITION BY col1 ORDER BY col2) AND col2+1=LEAD(col2)OVER(PARTITION BY col1 ORDER BY col2)
THEN 'to'
ELSE CAST(col2 AS varchar(10))
END outVal
FROM t
) q
) q
WHERE outVal<>nextOutVal
ORDER BY col1,col2
SELECT
t1.col1,
REPLACE(STUFF(
(
SELECT ','+t2.outVal
FROM #temp t2
WHERE t2.col1=t1.col1
ORDER BY t2.col2
FOR XML PATH('')
),1,1,''),',to,',' to ') SerialNumber
FROM (SELECT DISTINCT col1 FROM #temp) t1
DROP TABLE #temp
A variant for SQL Server 2017 (with STRING_AGG):
SELECT
col1,
REPLACE(STRING_AGG(outVal,',')WITHIN GROUP(ORDER BY col2),',to,',' to ')
FROM
(
SELECT
col1,
col2,
outVal,
ISNULL(LEAD(outVal)OVER(PARTITION BY col1 ORDER BY col2),'') nextOutVal
FROM
(
SELECT
col1,
col2,
CASE
WHEN col2-1=LAG(col2)OVER(PARTITION BY col1 ORDER BY col2) AND col2+1=LEAD(col2)OVER(PARTITION BY col1 ORDER BY col2)
THEN 'to'
ELSE CAST(col2 AS varchar(10))
END outVal
FROM t
) q
) q
WHERE outVal<>nextOutVal
GROUP BY col1
Result:
col1 SerialNumber
1 1 to 4,6 to 9,11,13
2 3 to 5,7
Solution:
Another possible approach using CTE for start and end values for each sequence and group concatenation:
T-SQL:
-- Table creation
CREATE TABLE #ValuesTable (
Col1 int,
Col2 int
)
INSERT INTO #ValuesTable VALUES (1, 1)
INSERT INTO #ValuesTable VALUES (1, 2)
INSERT INTO #ValuesTable VALUES (1, 3)
INSERT INTO #ValuesTable VALUES (1, 4)
INSERT INTO #ValuesTable VALUES (1, 6)
INSERT INTO #ValuesTable VALUES (2, 1)
INSERT INTO #ValuesTable VALUES (2, 2)
INSERT INTO #ValuesTable VALUES (2, 3)
INSERT INTO #ValuesTable VALUES (2, 4)
INSERT INTO #ValuesTable VALUES (2, 6)
INSERT INTO #ValuesTable VALUES (2, 7);
INSERT INTO #ValuesTable VALUES (2, 10);
-- Find sequences
WITH
TableStart AS (
SELECT t.Col1, t.Col2, ROW_NUMBER() OVER (ORDER BY t.Col1, t.Col2) AS RN
FROM #ValuesTable t
LEFT JOIN #ValuesTable b ON (t.Col1 = b.Col1) AND (t.Col2 = b.Col2 + 1)
WHERE (b.Col2 IS NULL)
),
TableEnd AS (
SELECT t.Col1, t.Col2, ROW_NUMBER() OVER (ORDER BY t.Col1, t.Col2) AS RN
FROM #ValuesTable t
LEFT JOIN #ValuesTable b ON (t.Col1 = b.Col1) AND (t.Col2 = b.Col2 - 1)
WHERE (b.Col2 IS NULL)
),
TableSequences AS (
SELECT
TableStart.Col1 AS Col1,
CASE
WHEN (TableStart.Col2 <> TableEnd.Col2) THEN CONVERT(nvarchar(max), TableStart.Col2) + N' to ' + CONVERT(nvarchar(max), TableEnd.Col2)
ELSE CONVERT(nvarchar(max), TableStart.Col2)
END AS Sequence
FROM TableStart
LEFT JOIN TableEnd ON (TableStart.RN = TableEnd.RN)
)
-- Select with group concatenation
SELECT
t1.Col1,
(
SELECT t2.Sequence + N', '
FROM TableSequences t2
WHERE t2.Col1 = t1.Col1
ORDER BY t2.Col1
FOR XML PATH('')
) SerialNumber
FROM (SELECT DISTINCT Col1 FROM TableSequences) t1
Output:
Col1 SerialNumber
1 1 to 4, 6,
2 1 to 4, 6 to 7, 10,
Notes:
Tested on SQL Server 2005, 2012, 2016.
This is the code I used to concatenate the first two rows which each have 6 characters each. Each row of data is set at 6.
My problem is it only returns the first concatenate needed and doesn't effect the next rows.
DECLARE #r_strands VARCHAR(MAX)
SET #r_strands=''
SELECT #r_strands= #r_strands + R_Strands
FROM rs_table
SELECT LEFT(#r_strands, +12) AS text
rs_table
r_strands
thedog
wentto
hisbed
wherei
placed
foodto
eatfor
supper
the above is an example of the single column table I want to concat every two rows
EX. result desired
r_strands
thedogwentto
hisbedwherei
placedfoodto
eatforsupper
You can use ROW_NUMBER to make pairs of two consecutive r_strands. Then use FOR XML PATH('') for concatenation:
If you have an Id to determine the order, you can replace the ORDER BY with Id instead of SELECT NULL.
SQL Fiddle
;WITH Cte AS(
SELECT *,
RN = (ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + 1) / 2
FROM rs_table
)
SELECT
r_strands = (
SELECT '' + r_strands
FROM Cte
WHERE RN = c.RN
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'
)
FROM Cte c
GROUP BY RN
RESULT:
| r_strands |
|--------------|
| thedogwentto |
| hisbedwherei |
| placedfoodto |
| eatforsupper |
Supposing that you have an row number column (call it "id"), you can self join adjacent columns on the id, and concatenate the strings using the + string concatination operator:
select a.r_strands + b.r_strands
from rs_table a
join rs_table b
on a.id = b.id - 1
declare #t table (id int, r_strands varchar(100));
insert into #t values
(1, 'thedog'),
(2, 'wentto'),
(3, 'hisbed'),
(4, 'wherei'),
(5, 'placed'),
(6, 'foodto'),
(7, 'eatfor'),
(8, 'supper');
select r_strands + next_row
from
(
select *,
next_row = lead(r_strands) over(order by id),
is_mod = id % 2
from #t
) x
where is_mod != 0;
I am working with SQL Server 2008. I have a table which does not contain any unique columns; how to get alternate rows from it?
SQL Server table:
+-----+--------+
| id | name |
|-----+--------|
| 1 | abc |
| 2 | pqr |
| 2 | pqr |
| 3 | xyz |
| 4 | lmn |
| 5 | efg |
| 5 | efg |
+-----+--------+
As we've to come with at least one working suggestion with the question, I've tried below code; which is not so proper technique when fetching from a huge amount of data.
Trial:
create table #tmp
(
id int, name varchar(10), srNo int
)
insert into #tmp
select
id, name,
ROW_NUMBER() OVER (ORDER BY id) % 2 as srNo --,alternate rows
from
Employee
select *
from #tmp
where srNo = 1 --or srNo = 0
Above query gives out alternate rows i.e. 1st, 3rd, 5th OR 2nd, 4th, 6th etc.
Please help me out with proper way without #tmp to achieve the goal!
You can just use your select statement as an in-line view. You don't need the #tmp table.
select t.id, name
from (select id, name, ROW_NUMBER() over (order by id) as srNo from Employee) t
where (t.srNo % 2) = 1
SqlFiddle
--To fetch ALTERNATE records from a table (EVEN NUMBERED)
Select * from TableName where ColumnName % 2 = 0
For Eg : select * from HumanResources.Employee where BusinessEntityID % 2 = 0
--To fetch ALTERNATE records from a table (ODD NUMBERED)
Select * from TableName where ColumnName % 2 = 1
For Eg : select * from HumanResources.Employee where BusinessEntityID % 2 = 1
I'm taking student as a table name.
Here is my answer ->
For Even Row Number -
> SELECT id from (SELECT rowno, id from student) where mod(rowno,2)=0
For Odd Row Number -
> SELECT id from (SELECT rowno, id from student) where mod(rowno,2)=1
Same also can be achieved using having clause; but it adds group by task:
SELECT id, name
FROM (SELECT id, name, ROW_NUMBER()over(order by id) AS srNo FROM Employee) x
GROUP BY srNo, id, name
HAVING (srNo % 2) = 0
You can just use your select statement as an in-line view. You don't need the #tblCities table.
select tbl1.CityID,tbl1.CityName from (select ROW_NUMBER() over(order by CityID asc) as row_no,CityID,CityName from tblCities) as tbl1 where tbl1.row_no%2=1
declare #t table
(
id int,
name nvarchar(20)
)
insert into #t
Select 1, 'abc'
union all
Select 2, 'pqr'
union all
Select 2, 'pqr'
union all
Select 3, 'xyz'
union all
Select 4, 'lmn'
union all
Select 5, 'efg'
union all
Select 2, 'efg'
Select * from(
Select *, row_number() over(order by id) as rnum from #t ) t where rnum % 2 <> 0
create table t (id bigint NOT NULL, input_1 boolean not null, data_gps timestamp(0) not null);
insert into t (id, input_1,data_gps) values
(1, false , '2022-01-01 15:42:07'),
(2, true , '2022-01-02 15:42:07'),
(3, true , '2022-01-03 15:42:07'),
(4, false , '2022-01-04 15:42:07'),
(5, true , '2022-01-05 15:42:07'),
(6, true , '2022-01-06 15:42:07'),
(7, true , '2022-01-07 15:42:07'),
(8, true , '2022-01-08 15:42:07'),
(9, false , '2022-01-09 15:42:07'),
(10 ,true , '2022-01-10 15:42:07'),
(11, true , '2022-01-11 15:42:07'),
(12, true , '2022-01-12 15:42:07'),
(13, false , '2022-01-13 15:42:07'),
(14, true , '2022-01-14 15:42:07');
you will have
Here is the query that will group by value change
select input_1, min(data_gps) as mind, max(data_gps) as maxd
from (
select input_1, data_gps,
row_number() over (order by data_gps)
- row_number() over (partition by input_1 order by data_gps) as grp
from t
) as tmp
group by input_1, grp
order by min(data_gps);
The results
DEMO
https://dbfiddle.uk/6Ajy3H5O
I'm using MS SQL 2008 R2, have three tables with following schema:
Table 1: Contains workshift info for each worker
CREATE TABLE workshift (
[ws_id] [bigint] NOT NULL,
[start_date] [datetime] NOT NULL,
[end_date] [datetime] NOT NULL,
[worker_id] [bigint] NOT NULL
)
INSERT INTO workshift VALUES (1, '2012-08-20 08:30:00', '2012-08-20 14:30:00', 1)
INSERT INTO workshift VALUES (2, '2012-08-20 14:30:00', '2012-08-20 22:30:00', 2)
Table 2: Contains monetary denominations
CREATE TABLE currency_denom (
[cd_id] [decimal](7, 2) NOT NULL,
[name] [nchar](100) NOT NULL
)
INSERT INTO currency_denom VALUES (1, '100.00')
INSERT INTO currency_denom VALUES (2, '50.00')
INSERT INTO currency_denom VALUES (3, '20.00')
INSERT INTO currency_denom VALUES (4, '10.00')
INSERT INTO currency_denom VALUES (5, '5.00')
INSERT INTO currency_denom VALUES (6, '1.00')
Table 3: Contains the quantity of each denomination the worker has received in every workshift
CREATE TABLE currency_by_workshift (
[cd_id] [decimal](7, 2) NOT NULL,
[ws_id] [bigint] NOT NULL,
[qty] [int] NOT NULL
)
INSERT INTO currency_by_workshift VALUES (1, 1, 1)
INSERT INTO currency_by_workshift VALUES (2, 1, 2)
INSERT INTO currency_by_workshift VALUES (3, 1, 2)
INSERT INTO currency_by_workshift VALUES (2, 2, 3)
INSERT INTO currency_by_workshift VALUES (4, 2, 4)
INSERT INTO currency_by_workshift VALUES (5, 2, 2)
I need to get the currency_by_workshift values in columns instead of rows, along with the workshift values, that is:
workshift | workshift | workshift | 100.00 | 50.00 | 20.00 | 10.00 | 5.00 | 1.00
ws_id | start_date | end_date | | | | | |
1 | 2012-08-20 08:30:00 | 2012-08-20 14:30:00 | 1 | 2 | 2 | 0 | 0 | 0
2 | 2012-08-20 14:30:00 | 2012-08-20 22:30:00 | 0 | 2 | 0 | 4 | 2 | 0
I'm not able to use a case to count quantities for each currency denomination because they are configurable, if a new denomination is added, the query should be modified. Same applies if using PIVOT function, or I'm wrong?
How can I get the info that way?
What you are trying to do is called a PIVOT. There are two ways to do this, either with a Static Pivot or a Dynamic Pivot.
Static Pivot - is where you will hard-code the values of the rows to transform to columns (See SQL Fiddle with Demo):
select ws_id,
start_date,
end_date,
IsNull([100.00], 0) [100.00],
IsNull([50.00], 0) [50.00],
IsNull([20.00], 0) [20.00],
IsNull([10.00], 0) [10.00],
IsNull([5.00], 0) [5.00],
IsNull([1.00], 0) [1.00]
from
(
select ws.ws_id,
ws.start_date,
ws.end_date,
cd.name,
cbw.qty
from workshift ws
left join currency_by_workshift cbw
on ws.ws_id = cbw.ws_id
left join currency_denom cd
on cbw.cd_id = cd.cd_id
) x
pivot
(
sum(qty)
for name in ([100.00], [50.00], [20.00], [10.00], [5.00], [1.00])
) p
Dynamic pivot is where the columns are determined at run-time (see SQL Fiddle with Demo):
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot AS NVARCHAR(MAX)
select #colsPivot =
STUFF((SELECT ', IsNull(' + QUOTENAME(rtrim(name)) +', 0) as ['+ rtrim(name)+']'
from currency_denom
GROUP BY name
ORDER BY cast(name as decimal(10, 2)) desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #cols = STUFF((SELECT distinct ', ' + QUOTENAME(name)
from currency_denom
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'SELECT ws_id, start_date, end_date,' + #colsPivot + ' from
(
select ws.ws_id,
ws.start_date,
ws.end_date,
cd.name,
cbw.qty
from workshift ws
left join currency_by_workshift cbw
on ws.ws_id = cbw.ws_id
left join currency_denom cd
on cbw.cd_id = cd.cd_id
) x
pivot
(
sum(qty)
for name in (' + #cols + ')
) p '
execute(#query)
Both versions will produce the same results.
#bluefeet provided a very good answer utilizing the built in PIVOT functionality. However, I frequently find the PIVOT and UNPIVOT nomenclature confusing and I have yet to encounter a situation where the same results can't be achieved with standard aggregations:
select w.ws_id, w.start_date, w.end_date,
[100.00] = isnull(sum(case when c.name='100.00' then cw.qty else null end), 0),
[50.00] = isnull(sum(case when c.name='50.00' then cw.qty else null end), 0),
[20.00] = isnull(sum(case when c.name='20.00' then cw.qty else null end), 0),
[10.00] = isnull(sum(case when c.name='10.00' then cw.qty else null end), 0),
[5.00] = isnull(sum(case when c.name='5.00' then cw.qty else null end), 0),
[1.00] = isnull(sum(case when c.name='1.00' then cw.qty else null end), 0)
from workshift w
join currency_by_workshift cw on w.ws_id=cw.ws_id
join currency_denom c on cw.cd_id=c.cd_id
group by w.ws_id, w.start_date, w.end_date
If you want to do a dynamic pivot, you only need to build a string of the pivot columns once:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols =
stuff(( select replace(',[#name] = isnull(sum(case when c.name=''#name'' then cw.qty else null end), 0)'
, '#name', rtrim(name))
from currency_denom
order by cd_id
for xml path(''), type
).value('.', 'nvarchar(max)')
,1,1,'')
select #query = '
select w.ws_id, w.start_date, w.end_date, '+#cols+'
from workshift w
join currency_by_workshift cw on w.ws_id=cw.ws_id
join currency_denom c on cw.cd_id=c.cd_id
group by w.ws_id, w.start_date, w.end_date
'
execute(#query)