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.
Related
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
This question already has answers here:
Simple way to transpose columns and rows in SQL?
(9 answers)
Closed 6 years ago.
I have a table
ID | Customer | Type | Value |
---+----------+---------+-------+
1 | John | Income | 50 |
2 | John | Income | 20 |
3 | Mike | Outcome | 150 |
4 | Robert | Income | 100 |
5 | John | Outcome | 300 |
Want a table like that;
| John | Mike | Robert |
--------+------+------+--------+
Income | 70 | 0 | 100 |
Outcome| 300 | 150 | 0 |
What should be the SQL Query? Thanks
The problem is Customers and Type are not static they are dynamic.
What I tried:
SELECT 'TotalIncome' AS TotalSalaryByDept,
[John], [Mike]
FROM
(SELECT Customer, Income
FROM table001) AS a
PIVOT
(
SUM(Income)
FOR ID IN ([John], [Mike])
) AS b;
Here is a quick dynamic pivot. We use a CROSS APPLY to unpivot the desired measures.
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName(Customer) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Type],' + #SQL + '
From (
Select Item=A.Customer,B.*
From YourTable A
Cross Apply (
Select Type=''Income'' ,Value=A.Income Union All
Select Type=''Outcome'',Value=A.Outcome
) B
) A
Pivot (sum(value) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
EDIT - For the Revised Question
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Customer) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Type],' + #SQL + '
From (Select Customer,Type,Value from YourTable ) A
Pivot (Sum(Value) For [Customer] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
The Table as you have it is how it should be in your SQL database. Columns are reserved for classifying your data, and rows are where you add new instances.
What you need to do is set up your ASP, Excel Pivot Table, or whatever you are using to display the data to format it into a horizontal table. I would need to know what you are using to interface with your database to give you an example.
I have a SQL Server table with 3 columns: UserID, SettingID, SettingValue.
Example
UserID | SettingID | Value
1 | 10 |0
1 | 11 |1
1 | 14 |0
2 | 10 |1
2 | 13 |1
Need to convert into columns per Setting ID
Can be that there is no row for Setting ID -> want to grab that non exisitng and Display as "not set"
Desired result:
UserID | Setting10 | Setting11 | Setting13 | Setting14
1 | Off | On | not set | off
2 | On | not set | on | not set
The list of SettingID is given, there is no need to analyze and automatically find them.
Have no idea how to approach this
SQL Fiddle
MS SQL Server 2008 Schema Setup:
DECLARE #Table TABLE (UserID INT, SettingID INT, Value INT )
INSERT INTO #Table VALUES
(1 , 10 ,0),
(1 , 11 ,1),
(1 , 14 ,0),
(2 , 10 ,1),
(2 , 13 ,1)
Query 1:
SELECT UserID
,COALESCE( Setting10 , 'not set') Setting10
,COALESCE( Setting11 , 'not set') Setting11
,COALESCE( Setting13 , 'not set') Setting13
,COALESCE( Setting14 , 'not set') Setting14
FROM (
SELECT UserID
, 'Setting' + CAST(SettingID AS VARCHAR(10)) AS Settings
,CASE Value WHEN 0 THEN 'off'
WHEN 1 THEN 'on'
END AS Value
FROM #Table
) t
PIVOT (MAX(Value)
FOR Settings
IN (Setting10 , Setting11 , Setting13 , Setting14)
)p
Results:
| UserID | Setting10 | Setting11 | Setting13 | Setting14 |
|--------|-----------|-----------|-----------|-----------|
| 1 | off | on | not set | off |
| 2 | on | not set | on | not set |
As you say the list of settingid is fixed, you can get the desired result with conditional aggregation.
select userid,
max(case when settingid = '10' then
case when value = 0 then 'Off'
when value = 1 then 'On'
else 'not set' end
end) as setting10,
--proceed similarly for other settings
from yourtable
group by userid;
Here is the answer of your question
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
DECLARE #ColumnCaseName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(SettingID)
FROM (SELECT DISTINCT SettingID FROM TableName) AS TableName
SELECT #ColumnCaseName= ISNULL(#ColumnCaseName + ',','')
+ 'case when '+QUOTENAME(SettingID)+' = 0 then ''Off'' when '+QUOTENAME(SettingID)+' = 1 then ''On'' else ''not set'' end '
+ QUOTENAME(SettingID)
FROM (SELECT DISTINCT SettingID FROM TableName) AS TableName
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT UserID, ' + #ColumnCaseName + '
FROM TableName
PIVOT(Max(Value)
FOR SettingID IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
This will work for any number of rows you add in your table.
Happy Coding
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)) + ''/'' +
TRANSFORM Avg(CASE WHEN [temp].[sumUnits] > 0
THEN [temp].[SumAvgRent] / [temp].[sumUnits]
ELSE 0
END) AS Expr1
SELECT [temp].[Description]
FROM [temp]
GROUP BY [temp].[Description]
PIVOT [temp].[Period];
Need to convert this query for sql server
I have read all other posts but unable to convert this into the same
Here is the equivalent version using the PIVOT table operator:
SELECT *
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN(Period1, Period2, Period3)
) p;
SQL Fiddle Demo
For instance, this will give you:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |
Note that When using the MS SQL Server PIVOT table operator, you have to enter the values for the pivoted column. However, IN MS Access, This was the work that TRANSFORM with PIVOT do, which is getting the values of the pivoted column dynamically. In this case you have to do this dynamically with the PIVOT operator, like so:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct
',' +
QUOTENAME(Period)
FROM temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query = ' SELECT Description, ' + #cols + '
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN( ' + #cols + ')
) p ';
Execute(#query);
Updated SQL Fiddle Demo
This should give you the same result:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |