SQL Server Pivot -> Sum rows by condition - sql-server

I have a question regarding the pivot function in SQL Server. I have a table which looks like the following:
L | SH | SUM | KTYPE
----------------------
L1 | A | 10 | 1
L1 | B | 12 | 1
L1 | A | 14 | 2
L1 | B | 19 | 2
L2 | A | 9 | 1
L2 | B | 25 | 1
L3 | A | 2 | 1
L3 | B | 2 | 1
L4 | A | 9 | 1
L4 | B | 23 | 1
...
As a result, I would like to have the following structure:
| 1 | 2 | Total |
L | A | B | A | B | A | B |
----------------------------------------
L1 | 10 | 12 | 14 | 19 | 24 | 31 |
L2 | 9 | 25 | 0 | 0 | 9 | 25 |
L3 | 2 | 2 | 0 | 0 | 2 | 2 |
L4 | 9 | 23 | 0 | 0 | 9 | 23 |
...
If I am serious, I have no idea how to do that. I was able to "pivot" the data by KTYPE, but not by KTYPE and SH. Can somebody tell me if this is possible and give me a hint?
Thanks in advance!
tommy

Here is a sample per my comment above about concatenating the fields.
DECLARE #TMP TABLE (L VARCHAR(2), SH VARCHAR(1), SUM INT, KTYPE INT)
INSERT INTO #TMP
SELECT 'L1','A',10,1 UNION ALL
SELECT 'L1','B',12,1 UNION ALL
SELECT 'L1','A',14,2 UNION ALL
SELECT 'L1','B',19,2 UNION ALL
SELECT 'L2','A',9,1 UNION ALL
SELECT 'L2','B',25,1 UNION ALL
SELECT 'L3','A',2,1 UNION ALL
SELECT 'L3','B',2,1 UNION ALL
SELECT 'L4','A',9,1 UNION ALL
SELECT 'L4','B',23,1
SELECT *
FROM (SELECT CAST(KTYPE AS VARCHAR)+SH AS KTYPESH,SUM VAL,L FROM #TMP) DATATABLE
PIVOT (SUM([VAL])
FOR KTYPESH IN ([1A],[1B],[2A],[2B])) PIVOTTABLE

Related

How to move columns to rows in a SQL Server select?

I have a SQL Server 2016 temp table that looks like this:
SECTION_NAME SORT_ORDER COL1 COL2 COL3 COL4 COL5 COL6 COL7 COL8 COL9 COL10
ONE 1 A B C D E F G H I J
ONE 2 C D E F G H I X Y Z
I am only selecting the COL columns and want each records COL columns to be on a separate row, like this:
SELECT * FROM #TEMPTABLE
A
B
C
D
E
etc
How can I do that? I don't know PIVOT very well so not sure if I can use that. Thanks!
You need to "unpivot" which I prefer to do using cross apply and values, like this
SELECT
row_number() over (order by SECTION_NAME, SORT_ORDER, ca.value) as ID
, section_name
, sort_order
, ca.value
FROM pivoted
CROSS APPLY (
VALUES
(col1)
, (col2)
, (col3)
, (col4)
, (col5)
, (col6)
, (col7)
, (col8)
, (col9)
, (col10)
) AS ca(value)
which produces:
+----+--------------+------------+-------+
| ID | section_name | sort_order | value |
+----+--------------+------------+-------+
| 1 | ONE | 1 | A |
| 2 | ONE | 1 | B |
| 3 | ONE | 1 | C |
| 4 | ONE | 1 | D |
| 5 | ONE | 1 | E |
| 6 | ONE | 1 | F |
| 7 | ONE | 1 | G |
| 8 | ONE | 1 | H |
| 9 | ONE | 1 | I |
| 10 | ONE | 1 | J |
| 11 | ONE | 2 | C |
| 12 | ONE | 2 | D |
| 13 | ONE | 2 | E |
| 14 | ONE | 2 | F |
| 15 | ONE | 2 | G |
| 16 | ONE | 2 | H |
| 17 | ONE | 2 | I |
| 18 | ONE | 2 | X |
| 19 | ONE | 2 | Y |
| 20 | ONE | 2 | Z |
+----+--------------+------------+-------+
see this demo

Divide selected value by count(*)

I have a Microsoft SQL Server with the following tables:
Projects
BookedHours (with fk_Project = Projects.ID)
Products
ProjectsToProducts (n:m with fk_Projects = Projects.ID and fk_Products = Products.ID)
I now want to select how many hours are booked to which product per month. The problem is, that one project can have multiple products (that's why I need the n:m table).
If I do the following, it will count the hours twice if a project has two products.
SELECT
P.ID AS fk_Product, MONTH(B.Datum) AS Monat, SUM(B.Hours) AS Stunden
FROM
tbl_BookedHours AS B
INNER JOIN
tbl_Projects AS M on B.fk_Project = M.ID
INNER JOIN
tbl_ProjectProduct AS PP ON PP.fk_Project = M.ID
INNER JOIN
tbl_Products AS P ON PP.fk_Product = P.ID
WHERE
YEAR(B.Datum) = 2020
GROUP BY
P.ID, MONTH(B.Datum)
ORDER BY
P.ID, MONTH(B.Datum)
I can get the number of products for each project with this SQL:
SELECT fk_Project, COUNT(*) AS Cnt
FROM tbl_ProjectProduct
GROUP By fk_MainProject
But how can I now divide the hours for each project by its individual factor and add it all up per product and month?
I could do it in my C# program or I could use a cursor and iterate through all projects, but I think there should be an more elegant way.
Edit with sample data:
|----------------| |----------------| |------------------------------|
| tbl_Projects | | tbl_Products | | tbl_ProjectProduct |
|----------------| |----------------| |------------------------------|
| ID | Name | | ID | Name | | ID | fk_Project | fk_Product |
|----+-----------| |----+-----------| |------------------------------|
| 1 | Project 1 | | 1 | Product 1 | | 1 | 1 | 1 |
| 2 | Project 2 | | 2 | Product 2 | | 2 | 1 | 2 |
| 3 | Project 3 | | 3 | Product 3 | | 3 | 2 | 1 |
| 4 | Project 4 | | 4 | Product 4 | | 4 | 3 | 3 |
|----------------| |----------------| | 5 | 4 | 1 |
| 6 | 4 | 2 |
| 7 | 4 | 4 |
|------------------------------|
|--------------------------------------|
| tbl_BookedHours |
|--------------------------------------|
| ID | fk_Project | Hours | Date |
|--------------------------------------|
| 1 | 1 | 10 | 2020-01-15 |
| 2 | 1 | 20 | 2020-01-20 |
| 3 | 2 | 10 | 2020-01-15 |
| 4 | 3 | 30 | 2020-01-18 |
| 5 | 2 | 20 | 2020-01-20 |
| 6 | 4 | 30 | 2020-01-25 |
| 7 | 1 | 10 | 2020-02-15 |
| 8 | 1 | 20 | 2020-02-20 |
| 9 | 2 | 10 | 2020-02-15 |
| 10 | 3 | 30 | 2020-03-18 |
| 11 | 2 | 20 | 2020-03-20 |
| 12 | 4 | 30 | 2020-03-25 |
|--------------------------------------|
The Result should be:
|----------------------------|
| fk_Product | Month | Hours |
|----------------------------|
| 1 | 1 | 55 |
| 2 | 1 | 25 |
| 3 | 1 | 30 |
| 4 | 1 | 10 |
| 1 | 2 | 25 |
| 2 | 2 | 15 |
| 1 | 3 | 30 |
| 2 | 3 | 10 |
| 3 | 3 | 30 |
| 4 | 3 | 10 |
|----------------------------|
For example booking Nr. 1 has to be divided by 2 (because Project 1 has two products) and one half of amount added to Product 1 and the other to Product 2 (Both in January). Booking Nr. 4 should not be divided, because Project 3 only has one product. Booking Numer 12 for example has to be divided by 3.
So that in total the Hours in the end add up to the same total.
I hope it's clearer now.
*** EDIT 2***
DECLARE #tbl_Projects TABLE (ID INT, [Name] VARCHAR(MAX))
INSERT INTO #tbl_Projects VALUES
(1,'Project 1'),
(2,'Project 2'),
(3,'Project 3'),
(4,'Project 4')
DECLARE #tbl_Products TABLE (ID INT, [Name] VARCHAR(MAX))
INSERT INTO #tbl_Products VALUES
(1,'Product 1'),
(2,'Product 2'),
(3,'Product 3'),
(4,'Product 4')
DECLARE #tbl_ProjectProduct TABLE (ID INT, fk_Project int, fk_Product int)
INSERT INTO #tbl_ProjectProduct VALUES
(1,1,1),
(2,1,2),
(3,2,1),
(4,3,3),
(5,4,1),
(6,4,2),
(7,4,4)
DECLARE #tbl_BookedHours TABLE (ID INT, fk_Project int, Hours int, [Date] Date)
INSERT INTO #tbl_BookedHours VALUES
(1,1,10,'2020-01-15'),
(2,1,20,'2020-01-20'),
(3,2,10,'2020-01-15'),
(4,3,30,'2020-01-18'),
(5,2,20,'2020-01-20'),
(6,4,30,'2020-01-25'),
(7,1,10,'2020-02-15'),
(8,1,20,'2020-02-20'),
(9,2,10,'2020-02-15'),
(10,3,30,'2020-03-18'),
(11,2,20,'2020-03-20'),
(12,4,30,'2020-03-25')
SELECT P.ID AS fk_Product, MONTH(B.Date) AS Month, SUM(B.Hours) AS SumHours
FROM #tbl_BookedHours AS B INNER JOIN #tbl_Projects AS M on B.fk_Project = M.ID
INNER JOIN #tbl_ProjectProduct AS PP ON PP.fk_Project = M.ID
INNER JOIN #tbl_Products AS P ON PP.fk_Product = P.ID
GROUP BY P.ID,MONTH(B.Date)
ORDER BY P.ID, MONTH(B.Date)
This gives me the wrong result, because it Counts the hours for both products:
| fk_Product | Month | SumHours |
|-------------------------------|
| 1 | 1 | 90 |
| 1 | 2 | 40 |
| 1 | 3 | 50 |
| 2 | 1 | 60 |
| 2 | 2 | 30 |
| 2 | 3 | 30 |
| 3 | 1 | 30 |
| 3 | 3 | 30 |
| 4 | 1 | 30 |
| 4 | 3 | 30 |
|-------------------------------|
Consider the following query. I modified your table variables to temp tables so it was easier to debug.
;WITH CTE AS
(
SELECT fk_Project, count(fk_Product) CNT
FROM #tbl_ProjectProduct
GROUP BY fk_Project
)
,CTE2 AS
(
SELECT t1.Date, t2.fk_Project, Hours/CNT NewHours
FROM #tbl_BookedHours t1
INNER JOIN CTE t2 on t1.fk_Project = t2.fk_Project
)
SELECT t4.ID fk_Product, MONTH(date) MN, SUM(NewHours) HRS
FROM CTE2 t1
INNER JOIN #tbl_Projects t2 on t1.fk_Project = t2.id
INNER JOIN #tbl_ProjectProduct t3 on t3.fk_Project = t2.ID
INNER JOIN #tbl_Products t4 on t4.ID = t3.fk_Product
GROUP BY t4.ID,MONTH(date)

Pivot the table along with sum total

I have a table with these data
+------------+----------------+------------+
| Department | ProgressStatus | TasksCount |
+------------+----------------+------------+
| A | Completed | 1 |
| C | Completed | 4 |
| D | Completed | 1 |
| B | Pending | 8 |
| A | Pending | 10 |
| C | Pending | 12 |
| D | Pending | 2 |
| C | Progress | 4 |
+------------+----------------+------------+
I need to write a query to get these outputs (It looks like a simple pivot table).
+-------------+-----------+---------+----------+--------------+
| Departments | Completed | Pending | Progress | Total Tasks |
+-------------+-----------+---------+----------+--------------+
| A | 1 | 10 | 0 | 11 |
| B | 0 | 8 | | 8 |
| C | 4 | 12 | 4 | 20 |
| D | 1 | 2 | | 3 |
+-------------+-----------+---------+----------+--------------+
Using conditional SUM and GROUP BY
select
department,
sum(case when ProgressStatus = 'Completed' then TasksCount end) Completed,
sum(case when ProgressStatus = 'Pending' then TasksCount end) Pending,
sum(case when ProgressStatus = 'Progress' then TasksCount end) Progress,
sum(TasksCount) Total
from your_table
group by department;
BY using pivot i tried like this
SELECT Department,isnull(Completed,0) Completed,isnull([Pending],0) [Pending],isnull([Progress],0) [Progress]
,isnull(Completed,0)+isnull([Pending],0)+isnull([Progress],0) as 'total'
FROM #Table2
PIVOT ( sum([TasksCount])
for [ProgressStatus] in ([Completed], [Pending], [Progress])) AS pvt
output
Department Completed Pending Progress total
A 1 10 0 11
B 0 8 0 8
C 4 12 4 20
D 1 2 0 3

Same ID in multiple rows using SQL Server

I am working on two tables with column headers ID_1, X1, Value as below
Table 1:
ID_1| X1 | Value
A01 | A | 10
A01 | B | 5
A02 | B | 3
A03 | A | 4
A02 | A | 8
Table 2:
ID_1| X1 | Value
Z01 | A | 7
Z01 | B | 2
Z01 | C | 9
Z03 | A | 1
Z02 | B | 3
Z03 | B | 6
I want to combine these two tables with and additional column with header N at the beginning as below (Expected Output):
N |ID_1| X1 | Value
1 |A01 | A | 10
1 |A01 | B | 5
2 |A02 | B | 3
2 |A02 | A | 8
3 |A03 | A | 4
4 |Z01 | A | 7
4 |Z01 | B | 2
4 |Z01 | C | 9
5 |Z02 | B | 3
6 |Z03 | A | 1
6 |Z03 | B | 6
I think you want union all and dense_rank():
select dense_rank() over (order by id_1) as n,
id_1, x, value
from ((select id_1, x, value from t1
) union all
(select id_1, x, value from t2
)
) t;

Create Tree Query From Numeric Mapping Table in SQL (Specific Format)

I have an exported table from accounting software like below.
AccountID AccountName
--------- -----------
11 Acc11
12 Acc12
13 Acc13
11/11 Acc11/11
11/12 Acc11/12
11/111 Acc11/111
11/11/001 Acc11/11/001
11/11/002 Acc11/11/002
12/111 Acc12/111
12/112 Acc12/112
I want to convert it to tree query in MS-SQL Server 2008 to use it as a Treelist datasource in my win aaplication.
I raised this question before and it's answered with a way that it was very very slow for my big table with more than 5000 records (Create Tree Query From Numeric Mapping Table in SQL). But I think counting "/" and separating AccountID field with "/" can solve my problem easier and very faster.
Anyway, My expected result must be like below:
AccountID AccountName ID ParentID Level HasChild
--------- ----------- --- --------- ------ --------
11 Acc11 1 Null 1 1
12 Acc12 2 Null 1 1
13 Acc13 3 Null 1 0
11/11 Acc11/11 4 1 2 1
11/12 Acc11/12 5 1 2 0
11/111 Acc11/111 6 1 2 0
11/11/001 Acc11/11/001 7 4 3 0
11/11/002 Acc11/11/002 8 4 3 0
12/111 Acc12/111 9 2 2 0
12/112 Acc12/112 10 2 2 0
Please Help Me.
I modified my answer given in the first question...
It would be best, if your table would keep the relation data directly in indexed columns. Before you change your table's structure you might try this:
A table with test data
DECLARE #tbl TABLE ( AccountID VARCHAR(100), AccountName VARCHAR(100));
INSERT INTO #tbl VALUES
('11','Acc11')
,('12','Acc12')
,('13','Acc13')
,('11/11','Acc11/11')
,('11/12','Acc11/12')
,('11/111','Acc11/111')
,('11/11/001','Acc11/11/001')
,('11/11/002','Acc11/11/002')
,('12/111','Acc12/111')
,('12/112','Acc12/112');
This will get the needed data into a newly created temp table called #tempHierarchy
SELECT AccountID
,AccountName
,ROW_NUMBER() OVER(ORDER BY LEN(AccountID)-LEN(REPLACE(AccountID,'/','')),AccountID) AS ID
,Extended.HierarchyLevel
,STUFF(
(
SELECT '/' + A.B.value('.','varchar(10)')
FROM Extended.IDsXML.nodes('/x[position() <= sql:column("HierarchyLevel")]') AS A(B)
FOR XML PATH('')
),1,2,'') AS ParentPath
,Extended.IDsXML.value('/x[sql:column("HierarchyLevel")+1][1]','varchar(10)') AS ownID
,Extended.IDsXML.value('/x[sql:column("HierarchyLevel")][1]','varchar(10)') AS ancestorID
INTO #tempHierarchy
FROM #tbl
CROSS APPLY(SELECT LEN(AccountID)-LEN(REPLACE(AccountID,'/','')) + 1 AS HierarchyLevel
,CAST('<x></x><x>' + REPLACE(AccountID,'/','</x><x>') + '</x>' AS XML) AS IDsXML) AS Extended
;
The intermediate result
+-----------+--------------+----+----------------+------------+-------+------------+
| AccountID | AccountName | ID | HierarchyLevel | ParentPath | ownID | ancestorID |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11 | Acc11 | 1 | 1 | | 11 | |
+-----------+--------------+----+----------------+------------+-------+------------+
| 12 | Acc12 | 2 | 1 | | 12 | |
+-----------+--------------+----+----------------+------------+-------+------------+
| 13 | Acc13 | 3 | 1 | | 13 | |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/11 | Acc11/11 | 4 | 2 | 11 | 11 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/111 | Acc11/111 | 5 | 2 | 11 | 111 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/12 | Acc11/12 | 6 | 2 | 11 | 12 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 12/111 | Acc12/111 | 7 | 2 | 12 | 111 | 12 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 12/112 | Acc12/112 | 8 | 2 | 12 | 112 | 12 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/11/001 | Acc11/11/001 | 9 | 3 | 11/11 | 001 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/11/002 | Acc11/11/002 | 10 | 3 | 11/11 | 002 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
And now a similar recursive approach takes place as in my first answer. But - as it is using a real table now and all the string splitting has taken place already - it should be faster...
WITH RecursiveCTE AS
(
SELECT th.*
,CAST(NULL AS BIGINT) AS ParentID
,CASE WHEN EXISTS(SELECT 1 FROM #tempHierarchy AS x WHERE x.ParentPath=th.AccountID) THEN 1 ELSE 0 END AS HasChild
FROM #tempHierarchy AS th WHERE th.HierarchyLevel=1
UNION ALL
SELECT sa.AccountID
,sa.AccountName
,sa.ID
,sa.HierarchyLevel
,sa.ParentPath
,sa.ownID
,sa.ancestorID
,(SELECT x.ID FROM #tempHierarchy AS x WHERE x.AccountID=sa.ParentPath)
,CASE WHEN EXISTS(SELECT 1 FROM #tempHierarchy AS x WHERE x.ParentPath=sa.AccountID) THEN 1 ELSE 0 END AS HasChild
FROM RecursiveCTE AS r
INNER JOIN #tempHierarchy AS sa ON sa.HierarchyLevel=r.HierarchyLevel+1
AND r.AccountID=sa.ParentPath
)
SELECT r.AccountID
,r.AccountName
,r.ID
,r.ParentID
,r.HierarchyLevel
,r.HasChild
FROM RecursiveCTE AS r
ORDER BY HierarchyLevel,ParentID;
And finally I clean up
DROP TABLE #tempHierarchy;
And here's the final result
+-----------+--------------+----+----------+----------------+----------+
| AccountID | AccountName | ID | ParentID | HierarchyLevel | HasChild |
+-----------+--------------+----+----------+----------------+----------+
| 11 | Acc11 | 1 | NULL | 1 | 1 |
+-----------+--------------+----+----------+----------------+----------+
| 12 | Acc12 | 2 | NULL | 1 | 1 |
+-----------+--------------+----+----------+----------------+----------+
| 13 | Acc13 | 3 | NULL | 1 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 11/11 | Acc11/11 | 4 | 1 | 2 | 1 |
+-----------+--------------+----+----------+----------------+----------+
| 11/111 | Acc11/111 | 5 | 1 | 2 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 11/12 | Acc11/12 | 6 | 1 | 2 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 12/111 | Acc12/111 | 7 | 2 | 2 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 12/112 | Acc12/112 | 8 | 2 | 2 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 11/11/001 | Acc11/11/001 | 9 | 4 | 3 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 11/11/002 | Acc11/11/002 | 10 | 4 | 3 | 0 |
+-----------+--------------+----+----------+----------------+----------+

Resources