SQL SUM in PIVOT that uses MAX - sql

I am trying to get the SUM of 2 or more TIME fields within my PIVOT table, however because the SUM does not work with characters (converted so I can show EstimatedTime / ActualTime), I'm having difficulty.
The code below -
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + ID + ',' + QUOTENAME(Name)
FROM JobPhases
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'SELECT * FROM
(
SELECT j.JobID, c.Registration as ''Reg.'', p.Name,
CAST(MAX(j.EstimatedTime) as VARCHAR(MAX)) + ''/'' +
CAST(MAX(j.ActualTime) as VARCHAR(MAX)) as [x]
FROM JobDetails AS j
INNER JOIN JobPhases p ON p.ID = j.PhaseId
INNER JOIN Jobs job on job.ID = j.JobID
INNER JOIN Cars c on job.CarID = c.ID
WHERE (job.Status = 1 or job.Status = 0)
GROUP BY c.Registration, p.Name, j.JobID
) JobDetails
PIVOT
( MAX(x)
FOR Name IN (' + #cols + ')
) pvt'
execute(#query);
Generates -
JobID | Reg. | P13$ | Repair and Reshape | P15$ | Refit Stripped Parts
1065 | BJ11 2VT | NULL | 01:00:00.0000000/01:54:10.5387526 | NULL | NULL
Tables -
**JobDetails**
ID - PK Auto increment
JobID - Int (Joined to Jobs table)
PhaseID - String (joined to JobPhases table)
EstimatedTime - time(7)
ActualTime time(7)
**JobPhases****
ID - PK String
Name - VarChar(150)
The problem in this example is there are 2 JobDetails for JobID 1065 -
ID | JobID | PhaseID | EstimatedTime | ActualTime
25 | 1065 | P13$ | 01:00:00.0000000 | 01:54:10.5387526
26 | 1065 | P13$ | 00:30:00.0000000 | 00:00:00.0000000
So, the correct result should be (Note the 1:30 in the Repair & Reshape) -
JobID | Reg. | P13$ | Repair and Reshape | P15$ | Refit Stripped Parts
1065 | BJ11 2VT | NULL | 01:30:00.0000000/01:54:10.5387526 | NULL | NULL
Any ideas how I can get the total EstimatedTime for all rows for each phase id?
Thanks!

Summing TIME columns isn't straight forward, what you need is to rewrite your line;
CAST(MAX(j.EstimatedTime) as VARCHAR(MAX)) + ''/'' +
to something like (the untested)
CAST(CAST(DATEADD(ms, SUM(DATEDIFF(ms, '0:00:00', j.EstimatedTime)),
'00:00:00') AS TIME) AS VARCHAR(MAX)) + ''/'' +

Related

How to export report and mark a column with data?

This is hard to explain but I'll try. I need to export a report that shows which stores have locations in which states.
Suppose I have the following table:
+----------+-----------+
| STORE_ID | STATE_ABV |
+----------+-----------+
| 1 | AK |
| 1 | AL |
| 1 | AR |
| 2 | MI |
| 2 | OH |
| 2 | IN |
| 3 | CA |
| 3 | NV |
+----------+-----------+
The STORE_ID column is a key to another table where I just need to pull out the STORE_NAME column.
+----------+------------+
| STORE_ID | STORE_NAME |
+----------+------------+
| 1 | Walmart |
| 2 | Target |
| 3 | Kroeger's |
+----------+------------+
What I want is to export a list of each store along with columns for all states. If the store is available in that state, I want to place an "X" for the value.
So the desired output looks like this:
+------------+----+----+----+----+----+----+----+----+
| STORE_NAME | AK | AL | AR | CA | IN | OH | MI | NV |
+------------+----+----+----+----+----+----+----+----+
| Walmart | X | X | X | | | | | |
| Target | | | | | X | X | X | |
| Kroeger's | | | | X | | | | X |
+------------+----+----+----+----+----+----+----+----+
Is this possible in SQL Server? How would I write such a query? There should be a column for every STATE_ABV that exists in the table.
As mentioned, what you are after here is to pivot your data. Personally I dislike the PIVOT functionality of SQL Server, and much more prefer using a Cross-Tab (aka conditional aggregation).
As I suspect that this is going to require a dynamic pivot, I've done that as well:
--Sample tables
CREATE TABLE dbo.StoreLocations (StoreID int,
StateAbv char(2));
CREATE TABLE dbo.Stores (StoreID int IDENTITY,
StoreName varchar(20));
GO
--Sample data
INSERT INTO dbo.Stores (StoreName)
VALUES('Walmart'),('Target'),('Kroeger''s');
INSERT INTO dbo.StoreLocations (StoreID,StateAbv)
VALUES(1,'AK'),
(1,'AL'),
(1,'AR'),
(2,'MI'),
(2,'OH'),
(2,'IN'),
(3,'CA'),
(3,'NV');
GO
--Quick sample to get the format right
SELECT S.StoreName,
IIF(COUNT(CASE WHEN SL.StateAbv = 'AK' THEN 1 END) = 0,NULL, 'X') AS AK
FROM dbo.Stores S
LEFT JOIN dbo.StoreLocations SL ON S.StoreID = SL.StoreID
GROUP BY S.StoreName;
GO
--The real solution
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT S.StoreName,' + NCHAR(13) + NCHAR(10) +
STUFF((SELECT N',' + NCHAR(13) + NCHAR(10) +
N' IIF(COUNT(CASE WHEN SL.StateAbv = ' + QUOTENAME(SL.StateAbv,'''') + N' THEN 1 END) = 0, NULL,''X'') AS ' + QUOTENAME(SL.StateAbv)
FROM dbo.StoreLocations SL
GROUP BY SL.StateAbv --Could use DISTINCT too
ORDER BY SL.StateAbv
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,3,N'') + NCHAR(13) + NCHAR(10) +
N'FROM dbo.Stores S' + NCHAR(13) + NCHAR(10) +
N' LEFT JOIN dbo.StoreLocations SL ON S.StoreID = SL.StoreID' + NCHAR(13) + NCHAR(10) +
N'GROUP BY S.StoreName;';
PRINT #SQL; --Your best friend
EXEC sp_executesql #SQL;
GO
--Clean up
DROP TABLE dbo.Stores;
DROP TABLE dbo.StoreLocations;
db<>fiddle
I foolishly assumed that the state was unique in StoreLocations. Ideally, you should have a States table as well, then you don't need to get the distinct states from the StoreLocations table.
Example with a States table: db<>fiddle
Just in case you want the dynamic pivot. Personally, I don't mind PIVOT. It is just another screwdriver in the toolbox.
The UNION ALL portion can be removed if you don't mind NULL values
Example dbFiddle
Declare #SQL varchar(max) = '
Select *
From (
Select A.Store_ID
,A.State_Abv
,B.Store_Name
,Value = ''X''
From StoreLocations A
Join Stores B on A.Store_ID=B.Store_ID
Union All
Select B.Store_ID
,A.State_Abv
,B.Store_Name
,Value = ''''
From (Select Distinct State_Abv from StoreLocations) A
Cross Join Stores B
) A
Pivot (max(Value) For [State_Abv] in (' + Stuff((Select Distinct ',' + QuoteName(State_Abv) From StoreLocations Order By 1 For XML Path('')),1,1,'') + ') ) p
Order By Store_ID
'
Exec(#SQL)
Returns
Option with NULL Values
Declare #SQL varchar(max) = '
Select *
From (
Select A.Store_ID
,A.State_Abv
,B.Store_Name
,Value = ''X''
From StoreLocations A
Join Stores B on A.Store_ID=B.Store_ID
) A
Pivot (max(Value) For [State_Abv] in (' + Stuff((Select Distinct ',' + QuoteName(State_Abv) From StoreLocations Order By 1 For XML Path('')),1,1,'') + ') ) p
Order By Store_ID
'
Returns

How to get SQL query result column name as first row

I have a dynamic SQL query that gets me result sets after execution. However, the UI model that I am rendering results back from SQL server engine doesn't provide a way to render query column names.
Due to the dynamic nature of the query, I can't hard code the column names at design time. So my question is how do I get column names along with the data set returned by the query?
This Query:
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
convert(date, DATEADDED) DATEADDED
,COUNT(1) as NUMBEROFRECORDS
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
Gives me this table (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
But I want this (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
Thanks
It's doable, but not very pretty. A Stored Procedure where you pass the dynamic SQL would be much cleaner
We're essentially doing Dynamic SQL within Dynamic SQL
One caveat: I reserved the field RN
Example (Using my FRED Series Data)
-- This is Your Base/Initial Query, or the only portion you need to supply
Declare #SQL varchar(max) = 'Select Updated as Updated,Count(*) as NumberOfRecords From [dbo].[FRED-Series] Group By Updated'
Select #SQL = '
;with cte0 as ('+#SQL+')
, cte1 as (Select *,RN = Row_Number() over (Order By (Select null)) From cte0 )
, cte2 as (
Select A.RN,C.*
From cte1 A
Cross Apply (Select XMLData=cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value(''local-name(.)'',''varchar(100)'')
,Value = attr.value(''.'',''varchar(max)'')
,ColNr = Row_Number() over (Order By (Select Null))
From B.XMLData.nodes(''/row'') as A(r)
Cross Apply A.r.nodes(''./#*'') AS B(attr)
Where attr.value(''local-name(.)'',''varchar(100)'') not in (''RN'')
) C
)
Select Distinct RN=0,Item,Value=Item,ColNr Into #Temp From cte2 Union All Select * from cte2
Declare #SQL varchar(max) = Stuff((Select '','' + QuoteName(Item) From #Temp Where RN=0 Order by ColNr For XML Path('''')),1,1,'''')
Select #SQL = ''Select '' + #SQL + '' From (Select RN,Item,Value From #Temp ) A Pivot (max(Value) For [Item] in ('' + #SQL + '') ) p''
Exec(#SQL);
'
Exec(#SQL)
Returns
Updated NumberOfRecords
Updated NumberOfRecords
2017-03-22 597
2017-03-23 40
2017-03-20 228
2017-03-21 1404
Just some Commentary
cte0 is your primary query
cte1 will take the results of your initial query and add a Row Number
cte2 will dynamically unpivot your data
The results of cte2 are dropped into a #temp table for convenience (assuming this is allowed)
Then we perform a dynamic pivot
Union a static query with the column names. You have to cast the results of the second query to varchar or nvarchar so they are the same data type as your column names.
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
''DATEADDED'' AS [DATEADDED]
,''NUMBEROFRECORDS'' AS [NUMBEROFRECORDS]
SELECT
CAST(convert(date, DATEADDED) AS NVARCHAR(MAX)
,CAST(COUNT(1) AS NVARCHAR(MAX))
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
With this said, you should be able to reference the column names via code and not have to add them to the query. This way you could keep the data types of the result set.

Convert Decimal to String on Pivot

example Code
Declare
#table1 VARCHAR(MAX)
Set #table1 = 'Select * from #tempTbl'
Declare
#List VARCHAR(MAX),
#Pivot VARCHAR(MAX)
Select #List = ISNULL(#List + ',', '') + TrxCd From TransacMaster Where Module = 'CB'
Set #Pivot = '
SELECT ROW_NUMBER() OVER (ORDER BY DebtorCd ASC) As RowIndex, *
FROM (
Select Distinct
c.UserId,
d.Name,
b.TrxCd
SUM(b.Amount) As Amount
From tbl_InvH a
Inner Join tbl_InvD b on a.subCd = b.subCd and a.InvNo = b.InvNo
Left Join tbl_User c On a.UserId = c.UserId
Left Join tbl_Personnel d on c.PersonnelId = d.PersonnelId
Group By c.UserId, d.Name, b.TrxCd
) as s
PIVOT
(
SUM(Amount)
FOR TrxCd IN (' + #List + ')
)AS pvt'
Declare #Result nVarchar(MAX)
Set #Result = #table1 + '
Union All
' + #Pivot
Exec sp_executesql #Result
I want to Convert Field Amount from decimal to String, because after this I want to UNION with another tables, but field amount and field from another table is different type.
I have tried CAST(SUM(Amount) as Varchar) But Error :
'CAST' is not a recognized aggregate function.
I can't Convert on Main Select because Field of TrxCd is Dynamic
Result Of Pivot
RowIndex | UserId | Name | IT01 | IT02 | IT03 | IT04
--------------------------------------------------------------------------------
1 | John | John Ivy | 2,000 | 2,000 | 1,000 | 5,000
2 | Merry | Merry Ish | 1,000 | 1,000 | 1,000 | 6,000
other Table
RowIndex | UserId | Name | Transac1 | Transac2 | Transac3 | Transac4
-------------------------------------------------------------------------------------------------
1 | John | John Ivy | Trx Bank A | Trx Bank B | Trx Bank C | Trx Bank D
What should I do to Convert Field Amount from Pivot.
Because of the dynamic nature, you could take the same #List expression Select #List = ISNULL(#List + ',', '') + TrxCd From TransacMaster Where Module = 'CB' and create a second for the dynamic casting in the main select;
Select #List2 = ISNULL(#List2 + ',', '') + 'cast(' + TrxCd + 'as varchar)' From TransacMaster Where Module = 'CB'
Then use this and additional columns required to replace the asterix in the main select;
'SELECT ROW_NUMBER() OVER (ORDER BY DebtorCd ASC) As RowIndex, UserId, Name,' + #List2 +'....'

Display a different column value in a PIVOT table

I have the following code -
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(Name)
FROM JobPhases
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'SELECT *
FROM
(
SELECT c.Registration, p.Name, [x] = 1
FROM JobDetails AS j
INNER JOIN JobPhases p ON p.ID = j.PhaseId
INNER JOIN Jobs job on job.ID = j.JobID
INNER JOIN Cars c on job.CarID = c.ID
) JobDetails
PIVOT
( SUM(x)
FOR Name IN (' + #cols + ')
) pvt'
Which generates this -
JobID | Repair & Reshape | Refit Stripped Parts | Polishing
1000 | id | id | id
1001 | id | id | id
1002 | id | id | id
1003 | id | id | id
1004 | id | id | id
But, I would like it to show j.EstimatedTime instead of j.ID.
The table structure -
JobDetails
ID - PK Auto increment
JobID - Int (Joined to Jobs table)
PhaseID - String (joined to JobPhases table)
EstimatedTime - decimal
JobPhases
ID - PK String
Name - VarChar(150)
Any ideas how I can achieve this?
Thanks
Try changing [x] = 1 to [x] = j.EstimatedTime .

Dynamically create columns in SQL select query and join tables

I have 2 tables. They are as follows
Table : Grade
GradeID | Grade
-----------------
1 | Chopsaw
2 | Classic
3 | Chieve
Table : Moulded Quantity
Batch ID | Grade | Moulded | Date
-------------------------------------
1 | 1 | 150 | 21st May
2 | 1 | 150 | 22nd May
3 | 2 | 150 | 21st May
4 | 2 | 150 | 21st May
5 | 2 | 150 | 22nd May
I should get the Output like the following
Date | Moulded | Chopsaw | Classic | Cieve
--------------------------------------------------
21st May | 450 | 150 | 300 | 0
22nd May | 300 | 150 | 150 | 0
I am using MSSQL 2008 and i use Crystal report to display the same.
If the number of grades is known beforehand then you can do it with a static query.
SELECT date,
SUM(moulded) moulded,
SUM(CASE WHEN grade = 1 THEN moulded ELSE 0 END) Chopsaw,
SUM(CASE WHEN grade = 2 THEN moulded ELSE 0 END) Classic,
SUM(CASE WHEN grade = 3 THEN moulded ELSE 0 END) Chieve
FROM moulded_quantity
GROUP BY date
This query is not vendor specific so it should work on any major RDBMS.
Now, if the number of grades is unknown or you want it to work even if you make changes to grade table (without changing the query itself) you can resort to dynamic query. But dynamic SQL is vendor specific. Here is an example of how you can do that in MySql
SELECT CONCAT (
'SELECT date, SUM(moulded) moulded,',
GROUP_CONCAT(DISTINCT
CONCAT('SUM(CASE WHEN grade = ',gradeid,
' THEN moulded ELSE 0 END) ', grade)),
' FROM moulded_quantity GROUP BY date') INTO #sql
FROM grade;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Output (in both cases):
| DATE | MOULDED | CHOPSAW | CLASSIC | CHIEVE |
---------------------------------------------------
| 21st May | 450 | 150 | 300 | 0 |
| 22nd May | 300 | 150 | 150 | 0 |
Here is SQLFiddle demo (for both approaches).
UPDATE In Sql Server you can use STUFF and PIVOT to produce expected result with dynamic sql
DECLARE #colx NVARCHAR(MAX), #colp NVARCHAR(MAX), #sql NVARCHAR(MAX)
SET #colx = STUFF((SELECT ', ISNULL(' + QUOTENAME(Grade) + ',0) ' + QUOTENAME(Grade)
FROM grade
ORDER BY GradeID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #colp = STUFF((SELECT DISTINCT ',' + QUOTENAME(Grade)
FROM grade
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #sql = 'SELECT date, total moulded, ' + #colx +
' FROM
(
SELECT date, g.grade gradename, moulded,
SUM(moulded) OVER (PARTITION BY date) total
FROM moulded_quantity q JOIN grade g
ON q.grade = g.gradeid
) x
PIVOT
(
SUM(moulded) FOR gradename IN (' + #colp + ')
) p
ORDER BY date'
EXECUTE(#sql)
Output is the same as in MySql case.
Here is SQLFiddle demo.
I would suggest you before asking any question research first as it is very common question.
Updated
DECLARE #COLUMNS varchar(max)
SELECT #COLUMNS = COALESCE(#COLUMNS+'],[' ,'') + CAST(Grade as varchar)
FROM Grade
GROUP BY Grade
SET #COLUMNS = '[' + #COLUMNS + ']'
DECLARE #COLUMNS_WITH_NULL varchar(max)
SELECT #COLUMNS_WITH_NULL = COALESCE(#COLUMNS_WITH_NULL+',ISNULL([' ,'ISNULL([') + CAST(Grade as varchar) + '], 0) AS ' + CAST(Grade as varchar)
FROM Grade
GROUP BY Grade
DECLARE #COLUMNS_SUMS varchar(max)
SELECT #COLUMNS_SUMS = COALESCE(#COLUMNS_SUMS+' + ISNULL([' ,'ISNULL([') + CAST(Grade as varchar) + '], 0) '
FROM Grade
GROUP BY Grade
SET #COLUMNS_SUMS = '(' + #COLUMNS_SUMS + ') as Moulded'
PRINT #COLUMNS_SUMS
EXECUTE (
'
SELECT
Date, ' + #COLUMNS_SUMS + ', ' + #COLUMNS_WITH_NULL + '
FROM (
SELECT
m.Moulded,
m.date AS Date,
g.Grade
FROM Grade g
INNER JOIN [Moulded Quantity] m
ON m.GRADE = g.GradeID
) up
PIVOT (SUM(Moulded) FOR Grade IN ('+ #COLUMNS +')) AS pvt')