Reputation: 15475
I'm trying to get averages of min and max for each categories in posnam column. This record set is of course only a sample, so there are many more records.
Given:
state position minrate maxrate
ny admin assistant 12.5000 14.5000
ny office manager 20.5000 25.5000
ca admin assistant 13.5000 15.5000
ca office manager 21.5000 26.5000
al admin assistant 11.5000 13.5000
al office manager 19.5000 24.5000
Expected:
position ny_min ny_max ca_min ca_max al_min al_max avg_min avg_max
admin assistant 12.5000 14.5000 13.5000 15.5000 11.5000 13.5000 12.5000 14.5000
office manager 20.5000 25.5000 21.5000 26.5000 19.5000 24.5000 20.5000 25.5000
Code:
declare @jobs table (
[state] nvarchar(25),
[position] nvarchar(25),
[minrate] decimal(18,4),
[maxrate] decimal(18,4)
)
insert @jobs
values
('ny','admin assistant',12.5, 14.5),
('ny','office manager',20.5, 25.5),
('ca','admin assistant',13.5, 15.5),
('ca','office manager',21.5, 26.5),
('al','admin assistant',11.5, 13.5),
('al','office manager',19.5, 24.5)
select * from @jobs
Upvotes: 0
Views: 146
Reputation: 972
In order to dynamically create field names, you will need to utilize dynamic SQL. To pair that with the additional aggregates that you need (the total avg min/max), you will need to perform an additional query across all rows and combine them.
In order to utilize dynamic SQL in this manner, we need an object outside of the current session's scope, so for the purpose of your example here I have swapped your provided table variable of @jobs
for a global temp table ##tmpjobs
. Assuming that you are actually pulling this from a database table in the real world, you can simply swap the global temp table ##tmpjobs
for your real table.
I accomplish this in the below example by unpivoting all state min/max values, adding unpivoted values for the total avg min/max, and then performing a single (fairly standard) PIVOT
command.
/*get list of columns that we want for our pivot*/
DECLARE @ColumnList nvarchar(max) = CONCAT((
SELECT
STRING_AGG(state_list.min_max_title,N',') WITHIN GROUP (ORDER BY state_list.min_max_title DESC) AS ColumnList
FROM
(SELECT
CONCAT(j.[state],N'_min,',j.[state],N'_max') AS min_max_title
FROM
##tmpjobs AS j
GROUP BY
j.[state]) AS state_list
),N',avg_min,avg_max');
/*build pivot query*/
DECLARE @Sql nvarchar(max) = CONCAT(
N'SELECT
pvt.*
FROM
/*subquery to unpivot data for min/max values for each state and the two totals*/
(/*add in min for each state*/
SELECT
CONCAT(j.[state],N''_min'') AS ColumnName
,j.position AS position
,j.minrate AS Amount
FROM
##tmpjobs AS j
UNION ALL
/*add max for each state*/
SELECT
CONCAT(j.[state],N''_max'') AS ColumnName
,j.position AS position
,j.maxrate AS Amount
FROM
##tmpjobs AS j
UNION ALL
/*add total min/max rows*/
SELECT
CASE /*conditionally return max/min column name*/
WHEN row_mult.RowId = 1 THEN ''avg_min''
WHEN row_mult.RowId = 2 THEN ''avg_max''
END AS ColumnName
,total_avgs.position
,CASE /*conditionally return max/min value*/
WHEN row_mult.RowId = 1 THEN total_avgs.AvgMinRate
WHEN row_mult.RowId = 2 THEN total_avgs.AvgMaxRate
END
FROM
/*subquery to calculate the total for all states for each position*/
(SELECT
j.position
,AVG(j.minrate) AS AvgMinRate
,AVG(j.maxrate) AS AvgMaxRate
FROM
##tmpjobs AS j
GROUP BY
j.position) AS total_avgs
/*generate an extra row per position*/
OUTER APPLY (SELECT 1 AS RowId
UNION ALL
SELECT 2 AS RowId) AS row_mult) AS src
PIVOT
(MAX(Amount) FOR ColumnName IN (',@ColumnList,N')) AS pvt');
/*now run query*/
EXEC sys.sp_executesql @stmt = @Sql;
The only thing to really note here is that I have the state columns currently in reverse alphabetic order (Z to A) to match your expected output. You can change that to A to Z by changing the DESC
order to ASC
in the WITHIN GROUP
statement, or really any other order you please by changing what the @ColumnList
variable outputs.
Upvotes: 1