Pivot on two aggregate fields - sql

Hi thanks for any help
Gerry
How do I pivot on LAVG and Patient for the months. Example
Facility Jan Feb Mar ect
Downey 30 25 28
Downey 10 9 8
I know this doesn't work but this is what I what to do
PIVOT (SUM(LAVG), Count(Patient) FOR months IN (jan, feb, mar ect
ALTER PROCEDURE [dbo].[Spoclos]
-- Add the parameters for the stored procedure here
#Year INT,
#PayerCode VARCHAR(3),
#ID INT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT *
FROM (SELECT LEFT(Months, 3) AS Months,
FacilityName,
LAVG,
Patient,
Region,
ID,
payercode,
facilityCode
FROM dbo.LOSGroup
WHERE PayerCode = #PayerCode
AND years = #year
AND ID = #id) AS s
PIVOT (Sum(LAVG)
FOR months IN (jan,feb,mar,apr,
may,jun,jul,aug,
sep,oct,nov,dec)) AS piv
ORDER BY ID
END

As you mentioned you cannot have two Aggregate functions inside pivot.
Have two separate Pivot queries one to find Sum and another to find Count. Then use Union All to Combine the result.
SELECT 'Sum' [Aggregate],*
FROM (SELECT LEFT(Months, 3) AS Months,
FacilityName,LAVG,Patient,
Region,ID,payercode,
facilityCode
FROM dbo.LOSGroup
WHERE PayerCode = #PayerCode
AND years = #year
AND ID = #id) AS s
PIVOT (Sum(LAVG)
FOR months IN (jan,feb,mar,apr,
may,jun,jul,aug,
sep,oct,nov,dec)) AS piv
UNION ALL
SELECT 'Count' [Aggregate],*
FROM (SELECT LEFT(Months, 3) AS Months,
FacilityName,LAVG,Patient,
Region,ID,payercode,
facilityCode
FROM dbo.LOSGroup
WHERE PayerCode = #PayerCode
AND years = #year
AND ID = #id) AS s
PIVOT (Count(LAVG)
FOR months IN (jan,feb,mar,apr,
may,jun,jul,aug,
sep,oct,nov,dec)) AS piv
ORDER BY ID
Note: To differentiate the Sum and Count I have used a extra column Aggregate

Related

How to remove duplicates in CTE from VARIABLES and TEMPORARY TABLE

I was looking for this solution in internet and none of them worked properly.
I decided to create variables, then accommodates it in the user defined function:
CREATE FUNCTION Top10CustomerByCategoryInYear
(
#Category varchar(MAX),
#StartYear int,
#EndYear int
)
RETURNS #CustomerTop10TableByCategoryInYears TABLE
(
[Customer Name] VARCHAR(MAX),
[Category] VARCHAR(MAX),
Sales int,
Year int
)
AS
BEGIN
INSERT INTO #CustomerTop10TableByCategoryInYears
SELECT
DISTINCT TOP 10 WITH TIES
[Customer Name],
[Category],
SUM(Sales) OVER (PARTITION BY [Customer Name]) as Sales,
YearOfOrderDate as [Year]
FROM [Project4].[dbo].[SuperStore]
WHERE [Category] = #Category AND YearOfOrderDate BETWEEN #StartYear AND #EndYear
ORDER BY Sales DESC
RETURN;
END
GO
It works properly, because for example I am able to return the top 10 Customer in the Furniture Category in 2011 and 2012
SELECT *
FROM Top10CustomerByCategoryInYear('Furniture',2011,2011)
SELECT *
FROM Top10CustomerByCategoryInYear('Furniture',2012,2012)
Then, I would like to have all records (2011-2014) in one Table. I decided to use CTE, then JOIN these Tables by Category:
WITH Furniture2011 AS (
SELECT DISTINCT *
FROM Top10CustomerByCategoryInYear('Furniture',2011,2011)
), Furniture2012 AS (
SELECT DISTINCT *
FROM Top10CustomerByCategoryInYear('Furniture',2012,2012)
), Furniture2013 AS (
SELECT DISTINCT *
FROM Top10CustomerByCategoryInYear('Furniture',2013,2013)
), Furniture2014 AS (
SELECT DISTINCT *
FROM Top10CustomerByCategoryInYear('Furniture',2014,2014)
)
SELECT DISTINCT *
FROM Furniture2011 F1
JOIN Furniture2012 F2
ON F1.Category = F2.Category
JOIN Furniture2013 F3
ON F1.Category = F3.Category
JOIN Furniture2014 F4
ON F1.Category = F4.Category
Unfortunately there are a lot of duplicates:
[
I tried a lot of solutions from my mind and internet and unfortunately these did not work.
I was wondering if it is possible to combine these Tables using UDF/Variables, TEMP TABLE and finally CTE.
Could you please tell me if it is possible?
If so, would you be so kind as to support me in modifying my codes in SQL to have the desired result without any duplicates?
I would appreciate it if you could advise me on it.
You can have an inline Table function taking a start and end year. Inline is faster than multi-statement.
Start with a virtual table of years, then CROSS APPLY the top 10 results for each year.
CREATE FUNCTION Top10CustomerByCategoryInYear
(
#Category varchar(MAX),
#StartYear int,
#EndYear int
)
RETURNS TABLE
AS RETURN
WITH L0 AS (
SELECT #StartYear - 1 + ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS Year
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c)
),
Years AS (
SELECT * FROM L0 WHERE Year <= #EndYear
)
SELECT
s.[Customer Name],
#Category AS [Category],
s.Sales,
y.Year
FROM Years y
CROSS APPLY (
SELECT TOP 10 WITH TIES
[Customer Name],
SUM(Sales) AS Sales
FROM [dbo].[SuperStore] s
WHERE [Category] = #Category
AND y.Year = s.YearOfOrderDate
GROUP BY [Customer Name]
ORDER BY Sales DESC
) s;
GO

Returning the following season and year

For a particular season and year when it started I have to get the following season and year when it starts, e.g. for Summer 2021 I should get Autumn 2021, and for Winter 2021 I should get Spring 2022.
"Seasons" table has the "Order" column which indicates the order of seasons in a year, so it looks like this:
SeasonID
Order
Name
1
1
Spring
2
2
Summer
3
3
Autumn
4
4
Winter
Remark: "Order" column may seem redundant, but this is actually simplified/adapted version of the problem I have, where "Order" column is neccesary.
I have a stored procedure that has #Year and #SeasonID as input parameters. I have to get following season/year in #FollowingSeasonID and #FollowingYear parameters, which I use later in the stored procedure. I'm not sure if I've used the best technique for that:
(...)
DECLARE #FollowingSeasonID int;
DECLARE #FollowingYear int;
if object_id('tempdb..#Years') is not null drop table #Years
CREATE TABLE #Years ([Year] int)
INSERT INTO #Years ([Year]) VALUES (#Year), (#Year + 1)
SELECT #FollowingSeasonID = FollowingSeasonID, #FollowingYear = FollowingYear
FROM
(SELECT SeasonID,
[Year],
LEAD(SeasonID) OVER (ORDER BY [Year], Order) AS FollowingSeasonID,
LEAD([Year]) OVER (ORDER BY [Year], Order) AS FollowingYear
FROM Seasons
CROSS JOIN #Years) t
WHERE SeasonID = #SeasonID AND [Year] = #Year
(...)
Is there a better way to do that? Could it be achieved in just one query?
I need those values in multiple stored procedures/views/... so I wanted to extract that part of code if a function. Scalar valued function can't return two values, so I have to create a table valued function (and I have to use table variable instead of temp table #Years). However, is there a better way to do that instead of having a table valued function that always returns just one row?
You can wrap this code in UDF and you should be able to get seasonId, year
DECLARE #season table(SeasonId int, SeasonOrder int, Name varchar(10))
INSERT INTo #season values
(1, 1 ,'Spring')
,(2, 2 ,'Summer')
,(3, 3 ,'Autumn')
,(4, 4 ,'Winter');
DECLARE #FollowingSeasonID int;
DECLARE #FollowingYear int;
DECLARE #year int = 2021, #SeasonId int = 2;
;WITH CTE_YearSeason AS
(
SELECT y,s.* FROM #season as s
CROSS APPLY
(VALUES (#year), (#year+1)
) as t(y)
), cte_ranking as
(
SELECT *, row_number() over (order by y,SeasonOrder) as rnk
FROM CTE_YearSeason)
SELECT SeasonId, Y as year
FROM cte_ranking as c
where c.rnk = (SELECT c1.rnk
from cte_ranking as c1
where c1.SeasonId = #SeasonId and c1.y = #year) +1
SeasonId
year
3
2021
Unless I've misunderstood, given a single seasonId you just need the following Id and reset it to 1 and increment the year if necessary?
declare #seasonid int=1, #Year int=2021
select FollowingSeasonID, #Year + yr FollowingYear
from (
select *,
IsNull(Lead(SeasonId) over(order by [order]),1) FollowingSeasonID,
case when Lead(SeasonId) over(order by [order]) is null then 1 else 0 end yr
from seasons
)s
where SeasonId=#SeasonId
If you don't want to use a TVF you could just use this as a view/cte, joining on currentId and return the followingId and yr value to add to your current year.

How to add an additional column to the result set returned by a SP without modifying the SP?

I have a Stored Procedure (SP), named myStoredProcedure, returning me such output based on startDate and endDate user-defined parameters:
PrimaryName SecondaryName Volume
A B 20
C D 30
A D 50
...
So, Volume represents the sum of all the cases between the dates defined.
In another SP, named mySecondStoredProcedure, I am using the first SP to get the result there. However, my problem is that I need an additional attribute in my output, which is year, I want to see year based volumes. Therefore, the output I would like to see is something like that
assume startDate: 2014, endDate: 2015:
PrimaryName SecondaryName Volume Year
A B 12 2014
C D 14 2014
A D 20 2014
A B 8 2015
C D 16 2015
A D 30 2015
...
I am not allowed to modify myStoredProcedure. Therefore I build a while loop in the second SP to receive it. My code is like:
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int
)
while #startDate < #endDate
begin
insert into #temp_table
exec myStoredProcedure #startDate #endDate
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp_table
This is giving me the result without the year column. I need a year column like I showed in my example output above. I could not find a way to add it. There is no primary key in the result set returned by myStoredProcedure. Also, SQL Server 2008 does not let me add a year column in #temp_table, saying that fields are not matching. How can I add the year column properly? Any help would be appreciated!
EDIT: When I add year column in the definition of #temp_table, the error I receive: Column name or number of supplied values does not match table definition.
You're close with the syntax you currently have, you'll just need to add the year to the temp table and supply it after calling the stored procedure. In addition, you will also need to specify the columns being inserted (a practice well worth getting in the habit of) as your procedure doesn't return the same number of columns.
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int,
Year int
)
while #startDate < #endDate
begin
insert into #temp_table (PrimaryGroup, SecondaryGroup, Volume)
exec myStoredProcedure #startDate #endDate
Update #temp_table
Set Year = #StartDate
Where Year Is Null
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp_table
Add a Year column to your temp table, and apply the structured insert
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int,
Year int
)
while #startDate < #endDate
begin
insert into #temp_table (PrimaryName,SecondaryName,Volume)
exec myStoredProcedure #startDate #endDate
Update #temp_table set Year = #startDate where Year is Null
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp
Create a second table variable that will hold the result:
declare #result_table table
(
Year int,
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int
)
Then in the while loop after fetching the result into #temp_table:
insert into #result_table
select <year>, PrimaryGroup, SecondaryGroup, Volume from #temp_table;
truncate #temp_table;

Show 0 in count SQL

This is my result :
Year matches
2005 1
2008 2
and this is my expected result:
Year matches
2005 1
2006 0
2007 0
2008 2
This is what I have tried:
SELECT DATEPART(yy,A.match_date) AS [Year], COUNT(A.match_id) AS "matches"
FROM match_record A
INNER JOIN match_record B ON A.match_id = B.match_id
WHERE (score) IS NULL OR (score) = 0
GROUP BY DATEPART(yy,A.match_date);
I want to get zero as count in the years where score have some values(not null and zero, anything greater than 0) . Can someone help me?
This might do what you're looking for:
SELECT DATEPART(yy,A.match_date) AS [Year],
SUM(CASE WHEN score=0 or score is null THEN 1 ELSE 0 END) AS "matches"
FROM match_record A
INNER JOIN match_record B ON A.match_id = B.match_id
GROUP BY DATEPART(yy,A.match_date);
Assuming you have any data in the missing years, this should now produce your expected results.
If, instead, you need 0s for years where you have no data, you'll need to provide the list of years separately (say, via a numbers table) and then LEFT JOIN that source to your existing query.
Consider following is your table
SELECT * INTO #TEMP FROM
(
SELECT 2005 [YEARS],1 [MATCHES]
UNION ALL
SELECT 2008,2
)T
Declare two variables to get min and max date in your table
DECLARE #MINYEAR int;
DECLARE #MAXYEAR int;
SELECT #MINYEAR = MIN(YEARS) FROM #TEMP
SELECT #MAXYEAR = MAX(YEARS) FROM #TEMP
Do the following recursion to get years between the period in your table and LEFT JOIN with your table.
; WITH CTE as
(
select #MINYEAR as yr FROM #TEMP
UNION ALL
SELECT YR + 1
FROM CTE
WHERE yr < #MAXYEAR
)
SELECT DISTINCT C.YR,CASE WHEN T.MATCHES IS NULL THEN 0 ELSE T.MATCHES END MATCHES
FROM CTE C
LEFT JOIN #TEMP T ON C.yr=T.YEARS
DECLARE #t table(Year int, matches int)
DECLARE #i int=2005
WHILE #i <=2008
BEGIN
IF NOT exists (SELECT matches FROM tbl WHERE year=#i)
BEGIN
INSERT INTO #t
SELECT #i,'0'
SET #i=#i+1
END
else
BEGIN
INSERT INTO #t
SELECT year,[matches] from tbl
SET #i=#i+1
END
END
SELECT DISTINCT * FROM #t
how about,
SELECT
[year],
COUNT(*) [matches]
FROM (
SELECT
DATEPART(yy, [A].[match_date]) [year]
FROM
[match_record] [A]
LEFT JOIN
[match_record] [B]
ON [A].[match_id] = [B].[match_id]
WHERE
COALESCE([B].[score], 0) = 0) [Nils]
GROUP BY
[Year];

Populating data using a Pivot table

I have a table like this,
country 2007 2008 2009
UK 5 10 20
uk 5 10 20
us 10 30 40
us 10 30 40
But I want to populate the table like this,
country year Total volumn
uk 2007 10
uk 2008 20
uk 2009 40
us 2007 20
us 2008 60
us 2009 80
How do I do this in SQL server 2008 using Pivot table or any other method.
http://sqlfiddle.com/#!3/2499a
SELECT country, [year], SUM([Total volumn]) AS [Total volumn]
FROM (
SELECT country, [2007], [2008], [2009]
FROM dbo.test137
) p
UNPIVOT
([Total volumn] FOR [year] IN ([2007], [2008], [2009])
) AS unpvt
GROUP BY country, [year]
ORDER BY country, [year]
See demo on SQLFiddle
Try the following - and remember to change [DB] with your own database name. In my example you do not need to write the years, but they are extracted automatically for you.
-- will contain the temporary total
create table #tempResult (total int)
-- get all countries
declare #countries table (country varchar(50))
insert into #countries
select country from table1
-- get all years
declare #years table(id int identity(1,1), [year] int)
insert into #years
SELECT column_name
FROM [DB].INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'Table1'
and column_name != 'country'
-- get all combinations
declare #result table(id int identity(1,1),country varchar(50),[year] int,total int)
insert into #result
select distinct upper(c.country),y.[year],0
from #years as y,#countries as c
-- will be used for the looping
declare #counter int
select #counter = 1
while #counter <= (select count(*) from #result)
begin
declare #year int
declare #country varchar(50)
-- get year and country in question
select #year = [year] from #result where id = #counter
select #country = country from #result where id = #counter
declare #total int
select #total = (select sum(#year) from table1 where country = #country)
-- insert temp result
declare #sql nvarchar(max)
set #sql = N'insert into #tempResult select sum([' + cast(#year as varchar(50)) + ']) from table1 where country = ''' + #country + ''''
print #sql
exec (#sql)
-- extract
select top 1 #total = total from #tempResult
-- update respectively
update #result set total = #total
where country=#country and [year]=#year
-- clear
delete from #tempResult
select #counter = #counter + 1
end
-- select result
select * From #result
drop table #tempResult
Since you are using SQL Server 2008+, then you can use CROSS APPLY to unpivot the data from columns into rows.
You can use the VALUES clause with CROSS APPLY:
select distinct t.country,
c.year,
c.totalvolumn
from yourtable t
cross apply
(
values
('2007', 2007),
('2008', 2008),
('2009', 2009)
) c(year, TotalVolumn)
order by t.country, c.year;
See SQL Fiddle with Demo
Or you can use UNION ALL with CROSS APPLY:
select distinct t.country,
c.year,
c.totalvolumn
from yourtable t
cross apply
(
select '2007', 2007 union all
select '2008', 2008 union all
select '2009', 2009
) c(year, TotalVolumn)
order by t.country, c.year;
See SQL Fiddle with Demo.
This can also be written using a UNION query:
select country, '2007' year, 2007 totalVolumn
from yourtable
union
select country, '2008' year, 2008 totalVolumn
from yourtable
union
select country, '2009' year, 2009 totalVolumn
from yourtable
order by country, year;
See SQL Fiddle with Demo