SQL: calculate column with dynamic pivot queries - sql

I have a table in my database with all sensor measurements. The names of these sensors are stated in another table in the database. I would like to create a pivotted view with the datetime stamp of the measurements and the sensor names as colums. I would like to use some of these colums to calculate a new 'variable'.
The original table:
Id | DateTime | SensorId | Value
100 | 2019-01-01 00:00:00 | 1 | 19
101 | 2019-01-01 00:05:00 | 1 | 19
102 | 2019-01-01 00:10:00 | 1 | 19
109 | 2019-01-01 00:00:00 | 2 | 44,4
110 | 2019-01-01 00:05:00 | 2 | 44,3
111 | 2019-01-01 00:10:00 | 2 | 44,1
118 | 2019-01-01 00:00:00 | 3 | 338
119 | 2019-01-01 00:05:00 | 3 | 335
120 | 2019-01-01 00:10:00 | 3 | 330
I have two pieces of code, but I don't know how to 'merge' the parts that i need.
First I created a a simple pivotted table with:
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
DECLARE #query2 AS NVARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME([SensorId]) + ',' FROM (select distinct [SensorId] from [dbo].[Measurement] ) as tmp
select #cols = substring(#cols, 0, len(#cols)) --trim "," at end
set #query =
'SELECT [DateTime], [1] , [2], [3], [1] * [2] from
(
select [DateTime], [Value], [SensorId] from [dbo].[Measurement]
) src
pivot
(
max([Value]) for [SensorId] in (' + #cols + ')
) piv'
execute(#query)
This creates:
DateTime | 1 | 2 | 3 | Calculated column
2019-01-01 00:00:00 | 19 | 44,4 | 338 | 843,6
2019-01-01 00:05:00 | 19 | 44,3 | 335 | 841,7
2019-01-01 00:10:00 | 19 | 44,1 | 330 | 837,9
But in this table the columns are numbered (instead of named), and i need to know how many sensors I have.
I also managed to get a pivotted dynamic table with the names of the sensors, with this query:
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME(Unicode)
FROM (SELECT distinct dbo.Unicode.Code as Unicode
FROM dbo.Measurement INNER JOIN
dbo.Sensor ON Sensor.Id = Measurement.SensorId INNER JOIN
dbo.SensorModel on SensorModel.Id = Sensor.Id INNER JOIN
dbo.Unicode on Unicode.Id = SensorModel.UnicodeId) AS x;
SET #sql = N'
SELECT ' + STUFF(#columns, 1, 2, '') + '
FROM
(
Select dbo.Measurement.DateTime, dbo.Unicode.Code as Unicode, dbo.Measurement.Value as value
FROM dbo.Measurement INNER JOIN
dbo.Sensor ON Sensor.Id = Measurement.SensorId INNER JOIN
dbo.SensorModel on SensorModel.Id = Sensor.Id INNER JOIN
dbo.Unicode on Unicode.Id = SensorModel.UnicodeId
) AS j
PIVOT
(
max(value) FOR Unicode IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
PRINT #sql;
EXEC sp_executesql #sql;
Result:
Temperature1 | Temperature2
44,4 | 338
44,3 | 335
44,1 | 330
However I dont know how to add the Datetime and calculate an extra variable. I know which sensornames I would like to multiply. So i figured it would be something like ... SELECT [DateTime], ' + STUFF(#columns, 1, 2, '') + ' [Temperature1]*[Temperature2] FROM ... but this, ofcourse, doesn't do the trick.
To be clear, I woul like to have this table:
DateTime | Temperature1 | Temperature2 | Calculated column
2019-01-01 00:00:00 | 19 | 44,4 | 843,6
2019-01-01 00:05:00 | 19 | 44,3 | 841,7
2019-01-01 00:10:00 | 19 | 44,1 | 837,9

Related

SQL Pivot with Month and Year

I'm trying to sort some shipment data using a SQL Pivot but i can not figure it out.
I've the data sorted in this way (one row with the total items shipped for a family for each month of each year starting from 2015 to ):
TABLE A
Year | Month | ItemFamilyCode | TotalShipped
2018 | 9 | FA01 | 5
2018 | 9 | FA04 | 4
2018 | 10 | FA01 | 2
2018 | 11 | FA02 | 1
2018 | 12 | FA03 | 3
2019 | 1 | FA04 | 7
and so on. I want to achieve the following result:
ItemFamilyCode | 2018-9 | 2018-10 | 2018-11 | 2018-12 | 2019-1 | [..]
FA01 | 5 | 2 | 0 | 0 | 0 |
FA02 | 0 | 0 | 1 | 0 | 0 |
FA03 | 0 | 0 | 0 | 3 | 0 |
FA04 | 4 | 0 | 1 | 0 | 7 |
and so on ... the family code in order and all the values for each month of each year, from the older month/year to now. Is it possible? Thanks to anyone who can help.
If you want to use it as view :
SELECT * FROM
(
SELECT
Concat([Year],'-', [Month]) as [Date],
ItemFamilyCode,
TotalShipped
FROM Shipping -- Or any Table Name
) t
PIVOT(
Sum(TotalShipped)
FOR [Date] IN (
[2018-9],
[2018-10],
[2018-11],
[2018-12],
[2019-1],
[2019-2] -- You have to type all months until today
)
) AS pivot_table;
And, dynamic sql if you can use it in stored procedure :
Make a table with the content of date list to generate date list string
DECLARE
#columns NVARCHAR(MAX) = '',
#sql NVARCHAR(MAX) = '';
-- select the category names
SELECT
#columns+=QUOTENAME(Date) + ','
FROM
DateList
ORDER BY
DateList;
-- remove the last comma
SET #columns = LEFT(#columns, LEN(#columns) - 1);
-- construct dynamic SQL
SET #sql ='
SELECT * FROM
(
SELECT
Concat([Year],'-', [Month]) as [Date],
ItemFamilyCode,
TotalShipped
FROM Shipping -- Or any Table Name
) t
PIVOT(
Sum(TotalShipped)
FOR [Date] IN ('+ #columns +')
) AS pivot_table;';
-- execute the dynamic SQL
EXECUTE sp_executesql #sql;
Source : sqlservertutorial

How to transpose the columns in a SQL select statement

I have a table named Ingresos_Ppto with this definition:
ID int PK
IdCliente int
FechaPpto Datetime
Rubro varchar(max)
Valor numer(18,0)
When I run a select statement for IdCliente = 1, I get this result:
SELECT
*
FROM
[dbo].[Ingresos_Ppto] [Y]
WHERE
([Y].[IdCliente] = #idc)
Output:
ID| IdCliente | FechaPpto | Rubro | Valor
+-+-----------+------------+----------------------------+--------------
1 | 1 | 2019-01-01 | Portal web WP | 9148489.00
2 | 1 | 2019-01-01 | Portal web WP + ecommerce | 3785304.00
3 | 1 | 2019-01-01 | Renting tecnológico | 7223406.00
4 | 1 | 2019-01-01 | Branding | 3280937.00
5 | 1 | 2019-01-01 | Mercadeo y publicidad | 3372619.00
6 | 1 | 2019-02-01 | Portal web WP | 9172295.00
7 | 1 | 2019-02-01 | Portal web WP + ecommerce | 9140700.00
8 | 1 | 2019-02-01 | Renting tecnológico | 7298693.00
9 | 1 | 2019-02-01 | Branding | 4912017.00
I need to transpose the columns, so that I can get Rubro, and columns according to the date of the budget, this will be the desired output:
Rubro | 2019-01 | 2019-02
+--------------------------+--------------+-------------
Portal web WP | 9148489.00 | 9172295.00
Portal web WP + ecommerce | 3785304.00 | 9140700.00
Renting tecnológico | 7223406.00 | 72986693.00
Branding | 3280937.00 | 4912017.00
Mercadeo y publicidad | 3372619.00 | 0.00
This is my code so far, but it won't run:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(FechaPpto)
FROM Ingresos_Ppto
GROUP BY FechaPpto, id
ORDER BY id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = N'SELECT ' + #cols + N' from
(
select Rubro, FechaPpto
from Ingresos_Ppto
) x
pivot
(
max(Rubro)
for FechaPpto in (' + #cols + N')
) p '
exec sp_executesql #query;
I get this error:
Msg 8156, Level 16, State 1, Line 12
The column 'Jan 1 2019 12:00AM' was specified multiple times for 'p'
How can I rewrite the code to achieve the desired result?

How to use SUM function in SQL with dynamic columns

I have arranged a result with Dynamic PIVOT into Temp Table, which looks like:
--------------------------------------------------------
| City | 2018-07-14 | 2018-07-15 | 2018-07-16 |
--------------------------------------------------------
| Satara | 3 | 9 | 1 |
| Maharashtra | 0 | 4 | 1 |
| Ghatkopar | 10 | 1 | 1 |
--------------------------------------------------------
Expected output :
----------------------------------------------------------------------
| City | 2018-07-14 | 2018-07-15 | 2018-07-16 | Total |
---------------------------------------------------------------------
| Satara | 3 | 9 | 1 | 13 |
| Maharashtra | 0 | 4 | 1 | 5 |
| Ghatkopar | 10 | 1 | 1 | 12 |
----------------------------------------------------------------------
NULL | 13 | 14 | 3 | 30 |
--------------------------------------------------------------------
That last three columns is date which is dynamic Today's Date - 3 Days
You can use GROUP BY ROLLUP to get your vertical totals. In this example, I'm calculating Total as a computed column, but you could aggregate before the pivot instead.
DECLARE #tbl TABLE (City VARCHAR(25), [2018-07-14] int, [2018-07-15] int,
[2018-07-16] int, Total AS [2018-07-14] + [2018-07-15] + [2018-07-16])
INSERT INTO #tbl VALUES ('Satara', 3, 9, 1)
INSERT INTO #tbl VALUES ('Maharashtra', 0, 4, 1)
INSERT INTO #tbl VALUES ('Ghatkopar', 10, 1, 1)
SELECT City, SUM([2018-07-14]) AS [2018-07-14],
SUM([2018-07-15]) AS [2018-07-15],
SUM([2018-07-16]) AS [2018-07-16],
SUM(Total) AS Total
FROM #tbl
GROUP BY ROLLUP (City);
Returns:
City 2018-07-14 2018-07-15 2018-07-16 Total
Ghatkopar 10 1 1 12
Maharashtra 0 4 1 5
Satara 3 9 1 13
NULL 13 14 3 30
If you're worried about dynamic column headers, you can change them programmatically with dynamic sql:
DECLARE #D1 VARCHAR(10) = FORMAT(GETDATE() - 2, 'yyyy-MM-dd')
DECLARE #D2 VARCHAR(10) = FORMAT(GETDATE() - 1, 'yyyy-MM-dd')
DECLARE #D3 VARCHAR(10) = FORMAT(GETDATE(), 'yyyy-MM-dd')
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = '
SELECT City, SUM([' + #D1 + ']) AS [' + #D1 + '],
SUM([' + #D2 + ']) AS [' + #D2 + '],
SUM([' + #D3 + ']) AS [' + #D3 + '],
SUM([' + #D1 + '] + [' + #D2 + '] + [' + #D3 + ']) AS Total
FROM tbl
GROUP BY ROLLUP (City);'
EXEC sp_executesql #SQL

Dynamically month and year pivot with bad sorting

From the answer from here I build a solution that is good for me but I have still one problem.
I had table:
ID | Year | Month | Multiply | Future | Current
123 | 2017 | 1 | 1.0 | 25 | null
123 | 2017 | 2 | 1.0 | 19 | 15
123 | 2017 | 3 | 1.0 | 13 | 0
123 | 2017 | 4 | 1.0 | 22 | 14
123 | 2017 | 5 | 1.0 | 13 | null
... | .... | ... | ... | .. | ..
123 | 2018 | 1 | 1.0 | 25 | 10
123 | 2018 | 2 | 1.0 | 25 | 10
... | .... | ... | ... | .. | ..
124 | 2017 | 1 | 1 | 10 | 5
124 | 2017 | 2 | 1 | 15 | 2
... | .... | ... | ... | .. | ..
124 | 2018 | 1 | 1 | 20 | 0
I build this view to concatenate Year + Month and make IF statement:
value in the new Value column I'm getting from Future and Current column - when the Current value is null get the Future value and multiply by Multiply, else get Current value and multiply by Multiply (even 0). Next to it I need to add a 'F' prefix when the value is got from Future column.
ID | Date | Value |
123 | 2017 - 1 | F25 |
123 | 2017 - 2 | 15 |
123 | 2017 - 3 | 0 |
.. | .. | .. |
Code for it:
SELECT ID = ID,
[Date] = [Date],
[Value] = [Value]
FROM ( SELECT ID,
cast([Year] as varchar(30)) + ' - ' + cast([Month]as varchar(30)) as [Date],
[Multiply],
case when [Current] IS NULL /*OR [Current] = 0*/
then 'F' + CAST([Future] * [Multiply] as varchar(30))
else CAST([Current] * [Multiply] as varchar(30))
end as Value
FROM dbo.CurrentFuture
) AS t
And from this I make this view via dynamically pivot.
ID | 2017 - 1 | 2017 - 10 | 2017 - 11 | 2017 - 12 | 2017 - 2 | ... | 2018 - 1 | ...
123 | F25 | .. | .. | .. | 15 | ... | 10 | ...
124 | 5 | 2 | .. | .. | .. | ... | 0 | ...
Code for it:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME([Date])
from dbo.UpperView
group by [Date]
order by [Date]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [ID],' + #cols + ' from
(
select [ID], [Date],[Value]
from [dbo].[UpperView]
) x
pivot
(
max([Value])
for [Date] in (' + #cols + ')
) p '
execute(#query);
As you can see columns in the new view are not sorting in a good way.. instead of 2017 - 1, 2017 - 2, 2017 - 3 I have 2017 - 1, 2017 - 10, 2017 - 11, 2017 - 12, 2017 - 2. Can you help me how to sort it properly?
From the limited information, What you want is the ordering of the column based on the Concatenated string of Year+ Month.
What you need is to prefix the month with "0" for January - September and no prefix for October-December.
so in effect you will achieve this.
ID | 2017 - 01 | 2017 - 02 | 2017 - 03 | ..... | 2017 - 09 |2018 - 10 |2018 - 11||2018 - 12|
SELECT ID = ID,
[Date] = [Date],
[Value] = [Value]
FROM
(
SELECT ID,
CAST([Year] AS VARCHAR(30))+' - '+RIGHT('0'+CAST([Month] AS VARCHAR(30)), 2) AS [Date],
[Multiply],
CASE
WHEN [Current] IS NULL
/*OR [Current] = 0*/
THEN 'F'+CAST([Future] * [Multiply] AS VARCHAR(30))
ELSE CAST([Current] * [Multiply] AS VARCHAR(30))
END AS Value
FROM dbo.CurrentFuture
) AS t;
Add new column to UpperView for sorting like this
cast([Year] as varchar(30)) + RIGHT('0' + cast([Month] as varchar(30)), 2) as [DateOrder]
and use this column for sorting at your column query instead of [Date]
select #cols = STUFF((SELECT ',' + QUOTENAME([Date])
from dbo.UpperView
group by [Date], [DateOrder]
order by [DateOrder]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

How use count for value from another table

How to use count joining with different tables. Please have a look at my queries. Here i am using CROSS APPLY. But i am not getting the actual result.
how i can to get all the item from item table not in incident table.
Tabel : Inc_cat
+------------+--------------+--+
| inc_cat_id | inc_cat_n | |
+------------+--------------+--+
| 1 | Support | |
| 2 | PM | |
| 3 | Installation | |
+------------+--------------+--+
Table:incident
+-------------+---------+------------+-----------------+
| incident_id | item_id | inc_cat_id | date_logged |
+-------------+---------+------------+-----------------+
| 100 | 555 | 1 | 2016-01-01 |
| 101 | 555 | 2 | 2016-01-18 |
| 103 | 444 | 3 | 2016-02-10 |
| 104 | 444 | 2 | 2016-04-01 |
| 105 | 666 | 1 | 2016-04-09 |
| 106 | 555 | 2 | 2016-04-20 |
+-------------+---------+------------+-----------------+
Table:item
+---------+---------+--+
| item_id | cust_id | |
+---------+---------+--+
| 444 | 34 | |
| 555 | 34 | |
| 666 | 76 | |
| 333 | 34 | |
| 222 | 34 | |
| 111 | 34 | |
+---------+---------+--+
Result:
+---------+----------------+-----------+---------------------+
| item_id | count(Support) | count(PM) | count(Installation) |
+---------+----------------+-----------+---------------------+
| 555 | 0 | 1 | 0 |
| 444 | 0 | 1 | 0 |
| 666 | 0 | 0 | 0 |
| 333 | 0 | 0 | 0 |
| 222 | 0 | 0 | 0 |
| 111 | 0 | 0 | 0 |
+---------+----------------+-----------+---------------------+
My Query:
SELECT i.item_ID,
COUNT(CASE WHEN i.inc_cat_id = ic.inc_cat_id AND i.inc_cat_id = 1 THEN 1 END) AS cntSupport,
COUNT(CASE WHEN i.inc_cat_id = ic.inc_cat_id AND i.inc_cat_id = 2 THEN 1 END) AS cntPM,
COUNT(CASE WHEN i.inc_cat_id = ic.inc_cat_id AND i.inc_cat_id = 3 THEN 1 END) AS cntInstallation
FROM #incident i
CROSS APPLY #incCat ic
WHERE (i.date_logged BETWEEN '2016-04-01' AND '2016-04-30')AND i.cust_id='34'
GROUP BY i.item_ID
You don't need a CROSS APPLY. A simple LEFT JOIN will do:
SELECT i.item_id,
COUNT(CASE WHEN inc.inc_cat_id = 1 THEN 1 END) AS cntSupport,
COUNT(CASE WHEN inc.inc_cat_id = 2 THEN 1 END) AS cntPM,
COUNT(CASE WHEN inc.inc_cat_id = 3 THEN 1 END) AS cntInstallation
FROM Item AS i
LEFT JOIN Incident AS inc ON i.item_id = inc.item_id AND
inc.date_logged BETWEEN '2016-04-01' AND '2016-04-30'
WHERE i.cust_id = 34
GROUP BY i.item_id
You just need to start by table Item, so as to get all items returned, as in the expected result set in the OP.
Demo here
Unless I'm missing something, your query is over complicated:
SELECT item_ID,
COUNT(CASE WHEN .inc_cat_id = 1 THEN 1 END) AS cntSupport,
COUNT(CASE WHEN inc_cat_id = 2 THEN 1 END) AS cntPM,
COUNT(CASE WHEN inc_cat_id = 3 THEN 1 END) AS cntInstallation
FROM #incident
GROUP BY item_ID
UPDATE:
With the addition of logged_date column, you just need to add it in the ON clause. You need to use sp_executesql instead of EXEC now to prevent sql injection:
DECLARE #fromDate DATE = '2016-04-01',
#toDate DATE = '2016-04-30';
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql =
'SELECT
i.item_id' + CHAR(10) +
(SELECT
' , COUNT(CASE WHEN inc.inc_cat_id = ' + CONVERT(VARCHAR(10), inc_cat_id) +
' THEN 1 END) AS ' + QUOTENAME('count(' + inc_cat_n + ')') + CHAR(10)
FROM #Inc_cat
ORDER BY inc_cat_id
FOR XML PATH('')
) +
'FROM #item AS i
LEFT JOIN #incident AS inc
ON i.item_id = inc.item_id
AND inc.date_logged BETWEEN #fromDate AND #toDate
GROUP BY i.item_id;';
PRINT (#sql);
EXEC sp_executesql
#sql,
N'#fromDate DATE, #toDate DATE',
#fromDate,
#toDate
ONLINE DEMO
Giorgos answer is good if you only have that 3 Inc_cats. However, if you have unknown number of Inc_cats, you need to do it dynamically. Here is a method using a dynamic crosstab:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql =
'SELECT
i.item_id' + CHAR(10) +
(SELECT
' , COUNT(CASE WHEN inc.inc_cat_id = ' + CONVERT(VARCHAR(10), inc_cat_id) +
' THEN 1 END) AS ' + QUOTENAME('count(' + inc_cat_n + ')') + CHAR(10)
FROM Inc_cat
ORDER BY inc_cat_id
FOR XML PATH('')
) +
'FROM Item AS i
LEFT JOIN Incident AS inc
ON i.item_id = inc.item_id
GROUP BY i.item_id;';
PRINT (#sql);
EXEC (#sql);
ONLINE DEMO
Basically, it's just a dynamic version of Giorgos' answer.