Multiple Aggregates Pivot Table - sql

I have a few columns of data I am wanting to put into a Pivot table. This is what the data currently looks like once the following query is ran:
SELECT
t.clinic,
t.fiscal_year,
t.total,
n.new_pats,
(t.total - n.new_pats) AS active
FROM (SELECT DISTINCT
clinic,
fiscal_year,
COUNT(DISTINCT patient_id) AS total
FROM transactions t
JOIN period p
ON (t.date_entered BETWEEN p.period_start AND p.period_end)
GROUP BY clinic, fiscal_year) t
JOIN (SELECT DISTINCT
clinic,
per.fiscal_year,
COUNT(DISTINCT patient_id) AS new_pats
FROM patient pat
JOIN period per
ON (pat.first_visit_date BETWEEN per.period_start AND per.period_end)
GROUP BY clinic, fiscal_year) n
ON (t.clinic = n.clinic AND t.fiscal_year = n.fiscal_year)
And I would like the data to be broken up by the three aggregates in their own rows and the years being the columns where it looks something like this:
This could also be broken into 3 separate columns per aggregate per year, but this is what I'm ideally aiming for. I haven't put together a Pivot table within SQL before and am at a loss. Is there a better way to go about formatting this data in the desired way?

You could try this
SELECT clinic,
aggregate_field,
ISNULL([2009],0) AS [2009],
ISNULL([2010],0) AS [2010],
ISNULL([2016],0) AS [2016],
ISNULL([2017],0) AS [2017]
FROM (SELECT clinic,
fiscal_year,
aggregate_field,
value
FROM (
------- This part is your original query -----------------
SELECT t.clinic,
t.fiscal_year,
t.total,
n.new_pats,
( t.total - n.new_pats ) AS active
FROM (SELECT DISTINCT clinic,
fiscal_year,
Count(DISTINCT patient_id) AS total
FROM transactions t
JOIN period p
ON ( t.date_entered BETWEEN
p.period_start AND p.period_end )
GROUP BY clinic, fiscal_year) t
JOIN (SELECT DISTINCT clinic,
per.fiscal_year,
Count(DISTINCT patient_id) AS new_pats
FROM patient pat
JOIN period per
ON ( pat.first_visit_date BETWEEN
per.period_start AND per.period_end )
GROUP BY clinic, fiscal_year) n
ON ( t.clinic = n.clinic
AND t.fiscal_year = n.fiscal_year )
-------------------------------------------------------------
)
unpivot_source
UNPIVOT ( value
FOR aggregate_field IN (total,
new_pats,
active) ) unpivot_result) AS
pivot_source
PIVOT ( Max(value)
FOR fiscal_year IN ([2009],
[2010],
[2016],
[2017]) ) AS pivot_result
ORDER BY clinic, aggregate_field DESC
I've create a demo here http://rextester.com/MFHWV68715.

Try this:
CREATE TABLE #ActualData
(
Clinic varchar(50),
fiscal_year int,
total int,
new_pats int,
active int
)
INSERT INTO #ActualData VALUES ('A', 2016, 3538,1787,1751), ('A', 2017, 1218,373,845), ('B', 2009, 1,2,3), ('B', 2010, 1,2,3)
SELECT * FROM #ActualData ad
;WITH temps AS
(
SELECT Clinic, fiscal_year, aggregateF, Number FROM
(
SELECT ad.Clinic, ad.fiscal_year, ad.total,ad.new_pats,ad.active FROM #ActualData ad
) src
UNPIVOT
(
Number FOR aggregateF IN (total,new_pats ,active)
) unpvt
)
SELECT Clinic, aggregateF, [2009],[2010],[2016],[2017]
FROM
(
SELECT t.Clinic, t.aggregateF, t.fiscal_year, t.Number FROM temps t
) src
PIVOT
(
MIN (Number) FOR fiscal_year IN ([2009],[2010],[2016],[2017])
) pvt
ORDER BY pvt.Clinic, pvt.aggregateF DESC
DROP TABLE #ActualData

Related

How to select multiple max values from a sql table

I am trying to get the top performers from a table, grouped by the company but can't seem to get the grouping right.
I have tried to use subqueries but this goes beyond my knowledge
I am trying to make a query that selects the rows in green. In other words I want to include the name, the company, and what they paid but only the top performers of each company.
Here is the raw data
create table test (person varchar(50),company varchar(50),paid numeric);
insert into
test
values
('bob','a',200),
('jane','a',100),
('mark','a',350),
('susan','b',650),
('thabo','b',100),
('thembi','b',210),
('lucas','b',110),
('oscar','c',10),
('janet','c',20),
('nancy','c',30)
You can use MAX() in a subquery as
CREATE TABLE T(
Person VARCHAR(45),
Company CHAR(1),
Paid INT
);
INSERT INTO T
VALUES ('Person1', 'A', 10),
('Person2', 'A', 20),
('Person3', 'B', 10);
SELECT T.*
FROM T INNER JOIN
(
SELECT Company, MAX(Paid) Paid
FROM T
GROUP BY Company
) TT ON T.Company = TT.Company AND T.Paid = TT.Paid;
Demo
Or using a window function as
SELECT Person,
Company,
Paid
FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY Company ORDER BY Paid DESC) RN
FROM T
) TT
WHERE RN = 1;
Demo
Here's your query.
select a.person, a.company, a.paid from tableA a
inner join
(select person, company, row_number() over (partition by company order by paid desc) as rn from tableA) as t1
on t1.person = a.person and t1.company = a.company
where t1.rn = 1
Maybe something like
WITH ranked AS (SELECT person, company, paid
, rank() OVER (PARTITION BY company ORDER BY paid DESC) AS rnk
FROM yourtable)
SELECT person, company, paid
FROM ranked
WHERE rnk = 1
ORDER BY company;
You can use rank() function with partition by clause.
DENSE_RANK gives you the ranking within your ordered partition, but the ranks are consecutive. No ranks are skipped if there are ranks with multiple items.
WITH cte AS (
SELECT person, company, paid
rank() OVER (PARTITION BY company ORDER BY paid desc) rn
FROM yourtable
)
SELECT
*
FROM cte

Counting ID's for correct creation date time

I need to get the number of user ID's for each month, but they should only be counted for the month if the user's minimum month falls within that month.
So if customer A had a min(day) of 04/18 then for month and year, they would be counted.
My table looks like:
monthyear | id
02/18 A32
04/19 T39
05/19 T39
04/19 Y95
01/18 A32
12/19 I99
11/18 OPT
09/19 TT8
I was doing something like:
SELECT day, id
SUM(CASE WHEN month = min(day) THEN 1 ELSE 0)
FROM testtable
GROUP BY 1
But I'm not sure how to specify that for each user ID, so only user ID = 1, when their min(Day) = day
Goal table to be:
monthyear | count
01/18 1
02/18 0
11/18 1
04/19 2
05/19 0
09/19 1
12/19 1
Use window functions. Let me assume that your monthyear is really yearmonth, so it sorts correctly:
SELECT yearmonth, COUNT(*) as numstarts
FROM (SELECT tt.*, ROW_NUMBER() OVER (PARTITION BY id ORDER BY yearmonth) as seqnum
FROM testtable tt
) tt
WHERE seqnum = 1
GROUP BY yearmonth;
If you do have the absurd format of month-year, then you can use string manipulations. These depend on the database, but something like this:
SELECT yearmonth, COUNT(*) as numstarts
FROM (SELECT tt.*,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY RIGHT(monthyear, 2), LEFT(monthyear, 2) as seqnum
FROM testtable tt
) tt
WHERE seqnum = 1
GROUP BY yearmonth;
I assumed that you have a column that's a date (use of min() is necessary). You can do it by selecting a minimal date(subquery t2) for each id and then count only these rows that connect throught left join, so if there is no connection you will get zeros for these dates or monthyear as you have in your data.
select
monthyear
,count(t2.id) as cnt
from testtable t1
left join (
select
min(date) as date
,id
from testtable
group by id
) t2
on t2.date = t1.date
and t2.id = t1.id
group by monthyear
You are looking for the number of new users each month, yes?
Here is one way to do it.
Note that I had to use TO_DATE and TO_CHAR to make sure the month/year text strings sorted correctly. If you use real DATE columns that would be unnecessary.
An additional complexity was adding the empty months in (months with zero new users). Optimally that would not be done by using a SELECT DISTINCT on the base table to get all months.
create table x (
monthyear varchar2(20),
id varchar2(10)
);
insert into x values('02/18', 'A32');
insert into x values('04/19', 'T39');
insert into x values('05/19', 'T39');
insert into x values('04/19', 'Y95');
insert into x values('01/18', 'A32');
insert into x values('12/19', 'I99');
insert into x values('11/18', 'OPT');
insert into x values('09/19', 'TT8');
And the query:
with allmonths as(
select distinct monthyear from x
),
firstmonths as(
select id, to_char(min(to_date(monthyear, 'MM/YY')),'MM/YY') monthyear from x group by id
),
firstmonthcounts as(
select monthyear, count(*) cnt
from firstmonths group by monthyear
)
select am.monthyear, nvl(fmc.cnt, 0) as newusers
from allmonths am left join firstmonthcounts fmc on am.monthyear = fmc.monthyear
order by to_date(monthyear, 'MM/YY');

Pivot a table with multiple values

I'm trying to pivot an access table with multiple result values like the first table in the image below. Anyone has an idea on how to get a result like in the second table?
You canĀ“t use multiple agregations with Pivot table, but there is an easy solution for your problem
By creating two pivot tables and joining both tables separed by Price and Weight is posible.
/*PRICE*/
SELECT MONTH
,ITEM
,STORE
,MANAGER
,[TY] AS TY_PRICE
,[LY] AS LY_PRICE
,[PY] AS PY_PRICE
INTO ##TMP_PRICE
FROM(
select MONTH
,ITEM
,STORE
,MANAGER
,TYPE
,SUM(PRICE) AS PRICE
FROM TABLE_X
GROUP BY MONTH
,ITEM
,STORE
,MANAGER
,TYPE) AS TMP
PIVOT(
MAX(PRICE)
FOR TYPE IN([TY],[LY],[PY])
) AS PVT
/*WEIGHT*/
SELECT MONTH
,ITEM
,STORE
,MANAGER
,[TY] AS TY_WEIGHT
,[LY] AS LY_WEIGHT
,[PY] AS PY_WEIGHT
INTO ##TMP_WEIGHT
FROM(
select MONTH
,ITEM
,STORE
,MANAGER
,TYPE
,SUM(WEIGHT) AS WEIGHT
FROM TABLE_X
GROUP BY MONTH
,ITEM
,STORE
,MANAGER
,TYPE) AS TMP
PIVOT(
MAX(WEIGHT)
FOR TYPE IN([TY],[LY],[PY])
) AS PVT
After both tables are created join them
SELECT ISNULL(TP.month,TW.month) as MONTH
,ISNULL(TP.ITEM,TW.ITEM) AS ITEM
,ISNULL(TP.STORE,TW.STORE) AS STORE
,ISNULL(TP.MANAGER,TW.MANAGER) AS MANAGER
,TY_WEIGHT
,LY_WEIGHT
,PY_WEIGHT
,TY_PRICE
,LY_PRICE
,PY_PRICE
FROM ##TMP_PRICE TP
FULL JOIN ##TMP_WEIGHT TW ON (TP.MONTH = TW.MONTH AND TP.ITEM = TW.ITEM
AND TP.STORE = TW.ITEM AND TP.MANAGER = TW.MANAGER)
Because you can't use pivot queries as sub queries in a query in Microsoft Access, you would have to save two pivot queries to separate Microsoft Access query objects:
Pivot_Price
TRANSFORM Sum([Price]) AS SumPrice
SELECT [month], item, store, manager
FROM Table1
GROUP BY [month], item, store, manager
PIVOT [type];
Pivot_Weight
TRANSFORM Sum([Weight]) AS SumWeight
SELECT [month], item, store, manager
FROM Table1
GROUP BY [month], item, store, manager
PIVOT [type];
And then join them in a third query:
SELECT
PP.Month,
PP.item,
PP.store,
PP.manager,
PP.TY AS [TY-Price],
PP.LY AS [LY-Price],
PP.PY AS [PY-Price],
PW.TY AS [TY-Weight],
PW.LY AS [LY-Weight],
PW.PY AS [PY-Weight]
FROM
Pivot_Price AS PP INNER JOIN Pivot_Weight AS PW
ON
PP.month = PW.month AND
PP.item = PW.item AND
PP.store = PW.store AND
PP.manager = PW.manager

SQL code it's looks to complicate

Test Table
create table Test (
Id integer,
Store_N varchar(25),
Department varchar(25)
);
INSERT INTO Test (Id, Store_N, Department )
Values (25,'1','A'), (67,'1','A'), (34,'1','A'), (97,'1','C'),
(21,'1','C'), (268,'1','B'), (456,'2','A'), (349,'2','A'),
(935,'2','B'), (36,'3','B'), (637,'3','B'), (388,'3','B'),
(891,'3','B'), (344,'4','A'), (763,'4','A'), (836,'4','A')
SELECT * , ROW_NUMBER() OVER( Partition BY Store_N ORDER BY Store_N ) AS AA
FROM Test;
Result is
I need to exclude all stores which have only one department and have the only DISTINCT department for each store. The result looks like this
And this is code
SELECT DISTINCT TB4.Department, TB4.Store_N
From
(
SELECT TB0.Store_N, TB0.Department FROM Test TB0
INNER JOIN
(
SELECT TB2.Store_N , Count(*) AS AA1
FROM
(
SELECT DISTINCT TB1.Department , TB1.Store_N
FROM
( SELECT * , ROW_NUMBER() OVER( Partition BY Store_N ORDER BY Store_N ) AA
FROM Test ) TB1
) TB2
group by TB2.Store_N
HAVING
COUNT(*) > 1 ) TB3
ON TB0.Store_N = TB3.Store_N
) TB4
Now the question how to simplify this code?
Thank you
You can basically do:
select store_n, department
from test
group by store_n, department;
But, you want to exclude stores that have only one department, so lets do a count:
select store_n, department
from (select store_n, department, count(*) over (partition by store_n) as cnt
from test
group by store_n, department
) t
where cnt > 1;
Here is a SQL Fiddle.
You are going a long way round to get the functionality of the "GROUP BY" clause
SELECT TB2.Store_N , TB2.Department
FROM
(
SELECT Department , Store_N, count(Id) as c
FROM Test
GROUP BY Department, Store_N) as TB2
WHERE TB2.c > 1

Sql select distinct row by a columns highest value

I am having an issue trying to select one row per city name. This is the following collection I am getting:
This is my query so far:
select pl.PlaceId,
pl.Name,
pop.NumberOfPeople,
pop.Year
from dbo.Places pl
inner join dbo.Populations pop
on pop.PlaceId = pl.PlaceId
where pop.NumberOfPeople >= 1000
and pop.NumberOfPeople <= 99999
I am trying to get it to where it only selects a city one time, but uses the most recent date. So in the above picture, I would only see Abbeville for 2016 and not 2015. I believe I need to do either a group by or do a sub query to flatten the results. If anybody has any advice on how I can handle this, it will be greatly appreciated.
Assuming you are using SQLSERVER,you can use Rownumber
;with cte
as
(select pl.PlaceId,
pl.Name,
pop.NumberOfPeople,
pop.Year,
row_number() over(partition by pl.Name order by year desc) as rownum
from dbo.Places pl
inner join dbo.Populations pop
on pop.PlaceId = pl.PlaceId
where pop.NumberOfPeople >= 1000
and pop.NumberOfPeople <= 99999
)
select * from cte where rownum=1
The following query serves the purpose.
CREATE TABLE #TEMP_TEST
(
PlaceId INT,
Name VARCHAR(50),
NumberOfPeople INT,
YEAR INT
)
INSERT INTO #TEMP_TEST
SELECT 1,'Abbeville',2603,2016
UNION
SELECT 5,'Alabester',32948,2016
UNION
SELECT 9,'Aubum',63118,2016
UNION
SELECT 1,'Abbeville',2402,2015
UNION
SELECT 5,'Alabester',67902,2017
SELECT PlaceId, Name, NumberOfPeople, YEAR FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY PlaceId ORDER BY YEAR DESC) RNO,
PlaceId, Name, NumberOfPeople, YEAR
FROM #TEMP_TEST
)T
WHERE RNO = 1
DROP TABLE #TEMP_TEST