SQL Comma Separated List from Multiple Columns - sql

I have data that looks like this:
CUSTOMER_ID OPERDAYSJUL OPERDAYSAUG OPERDAYSSEP ... OPERDAYSJUN
1 30 15 2
2 5 1 0
3 6 0 12
4 12 5 23
For each customer_id, I want a comma-delimited list indicating which months the customer operates:
CUSTOMER_ID OPERATING_MONTHS
1 Jul, Aug, Sep
2 Jul, Aug
3 Jul, Sep
4 Jul, Aug, Sep
and so forth. How might I use SQL Server 2005 SQL (not T-SQL) to easily produce this comma-delimited list?
Most solutions I see here on Stack Overflow and elsewhere seem to create comma-separated lists based on joining multiple rows values, not column values:
T-SQL
FOR XML PATH('')
Correlated subquery combined with REPLACE/STUFF/SUBSTRING
Am I missing something obvious? Thanks in advance for assistance or pointer to appropriate existing solution here.

This strips off the extra comma
SELECT
CUSTOMER_ID,
SUBSTRING(
CASE WHEN OPERDAYSJUL > 0 THEN ', Jul' ELSE '' END +
CASE WHEN OPERDAYSAUG > 0 THEN ', Aug' ELSE '' END +
...
CASE WHEN OPERDAYSJUN > 0 THEN ', Jun' ELSE '' END,
3, 255)
FROM TheTable

declare #t table (CUSTOMER_ID int
, OPERDAYSJUL int
, OPERDAYSAUG int
, OPERDAYSSEP int
-- ... rest of 9 months here
);
insert into #t (CUSTOMER_ID, OPERDAYSJUL, OPERDAYSAUG, OPERDAYSSEP)
select 1, 30, 15, 22 union all
select 2, 0, 10, 10 union all
select 3, 0, 0, 10 union all
select 4, 0, 0, 0 union all
select 5, 10, 0, 10 union all
select 6, 10, 10, 0 union all
select 7, 0, 10, 0 union all
select 8, 10, 0, 0;
with cte_months as (
select CUSTOMER_ID
, case when OPERDAYSJUL=0 then '' else ', Jul' end
+ case when OPERDAYSAUG=0 then '' else ', Aug' end
+ case when OPERDAYSSEP=0 then '' else ', Sep' end
-- ... rest of 9 months here
as month_list
from #t)
select CUSTOMER_ID, substring(month_list, 3, 70)
from cte_months;

You can do something like this.
CONCAT(CASE OPERDAYSJUL > 0 THEN "Jul," ELSE "" END,CASE OPERDAYSAUG > 0 THEN "Aug" ELSE "" END ... )

Assuming your table has 13 columns (1 for each month of the year + CUSTOMER_ID), you can write something like:
SELECT
CUSTOMER_ID,
CASE OPERDAYSJUL > 0 THEN 'Jul,' ELSE '' END +
CASE OPERDAYSAUG > 0 THEN 'Aug,' ELSE '' END +
...
FROM MyTable
and build up a string that represents your comma-separated list using CASE statements, one for each month.

select
customer_id,
case when len(operating_month) > 0 then
left(operating_month, len(operating_month) - 1)
else
operating_month
end as operating_month
from
(
SELECT CUSTOMER_ID,
CASE OPERDAYSJUL > 0 THEN 'Jul,' ELSE '' END
+ CASE OPERDAYSAUG > 0 THEN 'Aug,' ELSE '' END
+ ...
as operating_month
FROM MyTable
) as x

Related

SQL code to project how many clients will reach age 55 each month for a year

I am trying to get the number of clients who are currently >= age 55, which I've managed, but next I want to find out how many people will be >= 55 in a month's time, then in 2 month's time etc up to 12 months.
Here is what I have so far, but I'm not sure how to go about the next bit. Any help greatly appreciated! Thanks.
SELECT
SUM(CASE
WHEN [C].[BirthDate] > dateadd(year, -54, getdate()) AND [E].[MfChk4] = '0'
THEN 0
ELSE 1
END) AS [Age Eligible]
FROM
[dbo].[CR_Entity] [E]
LEFT OUTER JOIN [dbo].[CR_RefMaster] [RM]
ON [RM].[EntCode] = [E].[EntClient]
AND [RM].[Status] = '0'
LEFT OUTER JOIN [dbo].[Compliance] [C]
ON [C].[AddrCode] = [RM].[RefCode]
The result I want for the next bit will be something like...
You would have to write multiple case statements like:
SUM
(CASE WHEN [C].[BirthDate] > dateadd(month,-54*12+1, getdate())
AND [E].[MfChk4] = '0'
THEN 0 ELSE 1 END) AS Month1,
SUM
(CASE WHEN [C].[BirthDate] > dateadd(month,-54*12+2, getdate())
AND [E].[MfChk4] = '0'
THEN 0 ELSE 1 END) AS Month2,
.
.
.
SUM
(CASE WHEN [C].[BirthDate] > dateadd(month,-54*12+12, getdate())
AND [E].[MfChk4] = '0'
THEN 0 ELSE 1 END) AS Month12
To create the Naming convention you are looking, for you would have to use Dynamic SQL. You might have to change the query a bit, but the basics of it is:
--Declaring and calculating the Column Names
declare #test1 nvarchar(max),
#test2 nvarchar(max),
#test12 nvarchar(max)
set #test1='['+CAST(FORMAT(DATEADD(month,1,GETDATE()),'MMM-yy') as Varchar(20))+']'
set #test2='['+CAST(FORMAT(DATEADD(month,2,GETDATE()),'MMM-yy') as Varchar(20))+']'
.
.
.
set #test12='['+CAST(FORMAT(DATEADD(month,12,GETDATE()),'MMM-yy') as Varchar(20))+']'
--Writing the Dynamic Sql text
DECLARE #sql nvarchar(max)
SELECT #sql = N'SELECT SUM (CASE WHEN [C].[BirthDate] > dateadd(year, -54, getdate())
AND [E].[MfChk4] = ''0''
THEN 0 ELSE 1 END) AS [Age Eligible],
SUM
(CASE WHEN [C].[BirthDate] > dateadd(month,-54*12+1, getdate())
AND [E].[MfChk4] = ''0''
THEN 0 ELSE 1 END) AS' + #test1 + ',
SUM
(CASE WHEN [C].[BirthDate] > dateadd(month,-54*12+2, getdate())
AND [E].[MfChk4] = ''0''
THEN 0 ELSE 1 END) AS' + #test2 + ',
.
.
.
SUM
(CASE WHEN [C].[BirthDate] > dateadd(month,-54*12+12, getdate())
AND [E].[MfChk4] = ''0''
THEN 0 ELSE 1 END) AS' + #test12 + '
FROM [dbo].[CR_Entity] [E]
LEFT OUTER JOIN [dbo].[CR_RefMaster] [RM] ON [RM].[EntCode] = [E].[EntClient] AND [RM].[Status] = ''0''
LEFT OUTER JOIN [dbo].[Compliance] [C] ON [C].[AddrCode] = [RM].[RefCode]'
--Executing the Dynamic Sql query
EXEC sp_executesql #sql
You might want to start with one case statement to understand how it works and then implement it for the entire data.

SQL Server : group by calculated column

I have a table with columns like this:
I want to know the average compliance rate for every question for that period.
I am passing start dates and end dates as parameters to query.
So if I want for two periods I am passing #StartDate (e.g. 1/6/17) and #EndDate (e.g. 30/6/17) for first period and #StartDate2 (1/10/17) and #EndDate2 (31/10/17) for second period.
My SQL query is:
;WITH tmpTab AS
(
SELECT
question,
SUM((CASE WHEN AnswerValue = 1 THEN 1 ELSE 0 END)) Met,
SUM((CASE WHEN AnswerValue = 3 THEN 1 ELSE 0 END)) NA,
SUM((CASE WHEN (ISNULL(AnswerValue,3) <> 3) THEN 1 ELSE 0 END)) MetNotMet,
DATENAME(DAY, #StartDate) + ' ' + DATENAME(MONTH, #StartDate) + ' ' +
DATENAME(YEAR, #StartDate) + ' To ' + DATENAME(DAY,#EndDate) + ' ' +
DATENAME(MONTH, #EndDate) + ' ' + DATENAME(YEAR, #EndDate) AS RepMonthAndYear
FROM
tableA
WHERE
startdate >= #StartDate AND endate <= #EndDate
GROUP BY
Question
UNION ALL
SELECT
question,
SUM((CASE WHEN AnswerValue = 1 THEN 1 ELSE 0 END)) Met,
SUM((CASE WHEN AnswerValue = 3 THEN 1 ELSE 0 END)) NA,
SUM((CASE WHEN (ISNULL(AnswerValue,3) <> 3) THEN 1 ELSE 0 END)) MetNotMet,
DATENAME(DAY,#StartDate2)+' '+DATENAME(MONTH,#StartDate2)+' '+DATENAME(YEAR,#StartDate2)+ ' To ' + DATENAME(DAY,#EndDate2)+' '+DATENAME(MONTH,#EndDate2)+' '+DATENAME(YEAR,#EndDate2) AS RepMonthAndYear
FROM
tableA
WHERE
startdate >= #StartDate2 AND endate <= #EndDate2
GROUP BY
Question
)
SELECT
Question, Met, NA, MetNotMet,
CASE WHEN (Met) = 0 THEN 0 ELSE ROUND(((CONVERT(FLOAT,(Met))/(MetNotMet))* 100),4) END as CompRate
FROM
tmpTab
In this SQL query, I need to group by RepMonthAndYear column also which I can not do as it is a calculated column. I get an error "invalid column".
And if I use this GROUP BY clause:
(DATENAME(DAY,#StartDate2)+' '+DATENAME(MONTH,#StartDate2)+' '+DATENAME(YEAR,#StartDate2)+ ' To ' + DATENAME(DAY,#EndDate2)+' '+DATENAME(MONTH,#EndDate2)+' '+DATENAME(YEAR,#EndDate2) )
I get this error:
Each GROUP BY expression must contain at least one column that is not an outer reference.
How can I solve this problem?
Is there any other way to know average rate group by particular periods?
Given the following Test Data and your SQL I get what you discibe.
CREATE TABLE tableA (
question VARCHAR(50),
AnswerValue INT,
startdate DATE,
endate DATE
);
INSERT INTO tableA VALUES ('Question A', 1, '2017-06-25', '2017-06-26');
INSERT INTO tableA VALUES ('Question A', 1, '2017-06-27', '2017-06-27');
INSERT INTO tableA VALUES ('Question A', 2, '2017-06-27', '2017-06-27');
INSERT INTO tableA VALUES ('Question B', 1, '2017-06-11', '2017-06-12');
INSERT INTO tableA VALUES ('Question B', 2, '2017-06-13', '2017-06-13');
INSERT INTO tableA VALUES ('Question B', 1, '2017-06-13', '2017-06-13');
INSERT INTO tableA VALUES ('Question C', 1, '2017-06-20', '2017-06-20');
INSERT INTO tableA VALUES ('Question C', 1, '2017-06-23', '2017-06-23');
INSERT INTO tableA VALUES ('Question D', 2, '2017-06-01', '2017-06-01');
INSERT INTO tableA VALUES ('Question E', 1, '2017-10-11', '2017-10-11');
INSERT INTO tableA VALUES ('Question E', 1, '2017-10-15', '2017-10-15');
INSERT INTO tableA VALUES ('Question F', 1, '2017-10-20', '2017-10-20');
INSERT INTO tableA VALUES ('Question F', 2, '2017-10-20', '2017-10-20');
INSERT INTO tableA VALUES ('Question F', 2, '2017-10-20', '2017-10-20');
INSERT INTO tableA VALUES ('Question G', 1, '2017-10-26', '2017-10-26');
INSERT INTO tableA VALUES ('Question H', 1, '2017-10-26', '2017-10-26');
INSERT INTO tableA VALUES ('Question H', 2, '2017-10-26', '2017-10-26');
INSERT INTO tableA VALUES ('Question I', 1, '2017-10-26', '2017-10-26');
Here the outcome of your Query:
Question A 2 0 3 66,6667
Question B 2 0 3 66,6667
Question C 2 0 2 100
Question D 0 0 1 0
Question E 2 0 2 100
Question F 1 0 3 33,3333
Question G 1 0 1 100
Question H 1 0 2 50
Question I 1 0 1 100
I could simplify your SQL and have removed some code duplicates, maybe that was your intended question.
DECLARE #StartDate DATE = '2017-06-01';
DECLARE #EndDate DATE = '2017-06-30';
DECLARE #StartDate2 DATE = '2017-10-01';
DECLARE #EndDate2 DATE = '2017-10-30';
WITH
periods AS (
SELECT #StartDate as startdate,
#EndDate as enddate
UNION
SELECT #StartDate2 as startdate,
#EndDate2 as enddate
)
SELECT t.Question, t.Met, t.NA, t.MetNotMet,
CASE WHEN (t.Met) = 0 THEN 0 ELSE ROUND(((CONVERT(FLOAT,(t.Met))/(t.MetNotMet))* 100),4) END as CompRate
FROM (SELECT question,
SUM((CASE WHEN AnswerValue = 1 THEN 1 ELSE 0 END)) Met,
SUM((CASE WHEN AnswerValue = 3 THEN 1 ELSE 0 END)) NA,
SUM((CASE WHEN (ISNULL(AnswerValue,3) <> 3) THEN 1 ELSE 0 END)) MetNotMet
FROM tableA AS a
JOIN ( SELECT p.startdate,
p.enddate,
DATENAME(DAY, p.startdate) + ' ' + DATENAME(MONTH, p.startdate) + ' ' +
DATENAME(YEAR, p.startdate) + ' To ' + DATENAME(DAY, p.enddate) + ' ' +
DATENAME(MONTH, p.enddate) + ' ' + DATENAME(YEAR, p.enddate) AS RepMonthAndYear
FROM periods p ) AS p ON a.startdate >= p.startdate AND a.endate <= p.enddate
GROUP BY a.Question, p.RepMonthAndYear) AS t

How to show monthly data even if there are no results yet SQL Server 2008

So I wrote a script that would show monthly premium. Say if you want to view the total premium up to November, you can pass through a parameter in in SSRS to pick 1/1/2016 - 11/30/2016. This would only show the data up until november, hoever, I would like to show it up until december even if there are no records there. How do I go about doing this in SQL? Here is my script so far:
SELECT lc.[Date]
,lc.Carrier
,lc.[Direct Ceded Written Premium]
,cast(cast(year(lc.[date]) as varchar(4)) + '-' + cast(month(lc.[date]) as varchar(2)) + '-01' as date) as [begofmonth]
from
(
SELECT
CASE
WHEN pd.TransactionEffDate < pd.TransactionDate THEN cast(pd.TransactionDate as DATE)
WHEN pd.TransactionEffDate < pd.EffectiveDate THEN cast(pd.EffectiveDate as DATE)
ELSE cast(pd.TransactionEffDate as date)
END AS [Date]
,CASE WHEN LEFT(PD.POLICYNUM, 3) = 'ORV'
THEN 'Palomar Value Select OR'
WHEN LEFT(PD.POLICYNUM, 3) = 'VSE'
THEN 'Palomar Value Select CA'
WHEN LEFT(PD.POLICYNUM, 3) = 'WAV'
THEN 'Palomar Value Select WA'
ELSE 'Palomar' END AS [Carrier]
,ISNULL(SUM(pd.WrittenPremium), 0) AS [Direct Ceded Written Premium]
FROM premdetail pd
JOIN transactionpremium tp ON pd.systemid = tp.systemid
AND pd.transactionpremiumid = tp.id
JOIN transactionhistory th ON tp.systemid = th.systemid
AND tp.cmmcontainer = th.cmmcontainer
AND tp.parentid = th.id
JOIN basicpolicy bp ON th.systemid = bp.systemid
AND th.cmmcontainer = bp.cmmcontainer
AND th.parentid = bp.id
WHERE
(CASE
WHEN pd.TransactionEffDate < pd.TransactionDate THEN pd.TransactionDate
WHEN pd.TransactionEffDate < pd.EffectiveDate THEN pd.EffectiveDate
ELSE pd.TransactionEffDate
END) > = CAST(#StartDate AS DATE)
AND (CASE
WHEN pd.TransactionEffDate < pd.TransactionDate THEN pd.TransactionDate
WHEN pd.TransactionEffDate < pd.EffectiveDate THEN pd.EffectiveDate
ELSE pd.TransactionEffDate
END) < CAST(#EndDate + 1 AS DATE)
AND (bp.carriercd = #ResEQCarrierCd
OR #ResEQCarrierCd = 'All')
GROUP BY
CASE
WHEN pd.TransactionEffDate < pd.TransactionDate THEN cast(pd.TransactionDate as DATE)
WHEN pd.TransactionEffDate < pd.EffectiveDate THEN cast(pd.EffectiveDate as DATE)
ELSE cast(pd.TransactionEffDate as date)
END
,CONVERT(VARCHAR, pd.EffectiveDate, 101)
,CONVERT(VARCHAR, pd.ExpirationDate, 101)
,CASE
WHEN LEFT(PD.POLICYNUM, 3) = 'ORV'
THEN 'Palomar Value Select OR'
WHEN LEFT(PD.POLICYNUM, 3) = 'VSE'
THEN 'Palomar Value Select CA'
WHEN LEFT(PD.POLICYNUM, 3) = 'WAV'
THEN 'Palomar Value Select WA'
ELSE 'Palomar'
END
,CASE
WHEN pd.TransactionCode = 'EN' THEN CONVERT(VARCHAR, th.TransactionEffectiveDt, 101)
ELSE ''
END
,CONVERT(VARCHAR, DATEADD(ms, -3, DATEADD(mm, DATEDIFF(m, 0, th.transactiondt) + 1, 0)), 101)
,CASE
WHEN pd.TransactionEffDate < CAST(CONVERT(VARCHAR, pd.TransactionDate, 101) AS SMALLDATETIME) THEN CONVERT(VARCHAR, pd.TransactionDate, 101)
WHEN pd.TransactionEffDate < pd.EffectiveDate THEN CONVERT(VARCHAR, pd.EffectiveDate, 101)
ELSE CONVERT(VARCHAR, pd.TransactionEffDate, 101)
END
) lc
ORDER BY lc.[Date], lc.[Carrier], lc.[Direct Ceded Written Premium]
With the parameter that I have, it would only show up until November. However, I would like it to show the whole year, up to December at in this case, even if there are no data there since I didn't pick the enddate variable to be december. I attached an example screenshot of what it should look like when exported to excel.
Just to give you an idea:
declare #tbl TABLE(ID INT IDENTITY,SomeValue VARCHAR(100),SomeDate DATE);
INSERT INTO #tbl VALUES('Some date in March',{d'2016-03-05'}),('Some date in June',{d'2016-06-30'});
WITH AllMonths AS
(
SELECT 1 AS MonthIndex
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9
UNION ALL SELECT 10
UNION ALL SELECT 11
UNION ALL SELECT 12
)
SELECT MonthIndex
,t.*
FROM AllMonths
LEFT JOIN #tbl AS t ON MONTH(t.SomeDate)=MonthIndex
The result
1 NULL NULL NULL
2 NULL NULL NULL
3 1 Some date in March 2016-03-05
4 NULL NULL NULL
5 NULL NULL NULL
6 2 Some date in June 2016-06-30
7 NULL NULL NULL
8 NULL NULL NULL
9 NULL NULL NULL
10 NULL NULL NULL
11 NULL NULL NULL
12 NULL NULL NULL
There are many ways to create a tally table
CTE with ROW_NUMBER()
A list like in my example
A physical table
It is a good idea to maintain a numbers/DATE table!
In a previous answer I showed one way to create such a table.

SQL Server : multi-part identifier could not be bound

I need a simple output of fields named "36" - "1" with the extended inventory value for everything in the specified item classes. I don't need itemization.
E.g.
Error is:
"Database Connector Error. '42000:[Microsoft][ODBC SQL Server Driver][SQL Server]The multi-part identifier "InvenFiscPerHistTable.FiscalPeriod" could no be bound. [Database Vendor Code: 4104 ]'
Here's my SQL.
DECLARE #CalMonth INT
DECLARE #CalYear INT
DECLARE #CalMoYear DATETIME
SET #CalMonth = CASE WHEN "InvenFiscPerHistTable"."FiscalPeriod" IN (1,2,3) THEN "InvenFiscPerHistTable"."FiscalPeriod" + 9 ELSE "InvenFiscPerHistTable"."FiscalPeriod" - 3 END
SET #CalYear = CASE WHEN "InvenFiscPerHistTable"."FiscalPeriod" IN (1,2,3) THEN "InvenFiscPerHistTable"."FiscalYear" + 1 ELSE "InvenFiscPerHistTable"."FiscalYear" END
SET #CalMoYear = CAST(CONVERT(NVARCHAR, #CalYear) + '-' + CONVERT(NVARCHAR, #CalMonth) + '-' + CONVERT(NVARCHAR,1) AS DATETIME)
--our fiscal year is not the same as the calendar year
--e.g. FiscalPeriod = 1 and FiscalYear = 2016 will be October 2015
SELECT SUM (CASE WHEN #CalMoYear BETWEEN DATEADD(m, -36, GETDATE()-day(GETDATE()-1)) AND DATEADD(m, -35, GETDATE()-day (GETDATE()-1))-1 THEN"InvenFiscPerHistTable"."QOH" * "InvenTable"."UnitCost" ELSE NULL END) AS "36"
,SUM (CASE WHEN #CalMoYear BETWEEN DATEADD(m, -35, GETDATE()-day(GETDATE()-1)) AND DATEADD(m, -34, GETDATE()-day (GETDATE()-1))-1 THEN"InvenFiscPerHistTable"."QOH" * "InvenTable"."UnitCost" ELSE NULL END) AS "35"
,SUM (CASE WHEN #CalMoYear BETWEEN DATEADD(m, -34, GETDATE()-day(GETDATE()-1)) AND DATEADD(m, -33, GETDATE()-day (GETDATE()-1))-1 THEN"InvenFiscPerHistTable"."QOH" * "InvenTable"."UnitCost" ELSE NULL END) AS "34"
--and so on until I have 36 months of history up through last month
FROM "InvenTable"
INNER JOIN "SKUTable" ON "InvenTable"."SKUKey" = "SKUTable"."SKUKey"
INNER JOIN "InvenFiscPerHistTable" ON ("InvenTable"."SKUKey" = "InvenFiscPerHistTable"."SKUKey")
AND ("InvenTable"."WarehouseKey" = "InvenFiscPerHistTable"."WarehouseKey")
INNER JOIN "SKUClassTable" "SKUClassTable" ON "SKUTable"."ICKey" = "SKUClassTable"."ICKey"
WHERE "SKUClassTable"."ItemClassName" IN (
'105-03'
,'105-04'
,'105-05'
,'105-06'
,'150-01'
)
I played around a bit with your query and think it can be rewritten and simplified (in my opinion) to the query below. The only thing you need to do is add more items in the pivot part at the end to get more columns.
The logic is that it calculates the difference in months between the first day of the current month and the date that is created from the FiscalYear and FiscalPeriod (according to your rules).
I believe it should work, unless I missed something vital, but without any test data there's of course a fair amount of guess work involved ;-)
Please give it a try and if it doesn't work I'll remove my answer.
I formatted it a bit less compact than it could be to make it easier to follow.
;WITH CTE AS
(
SELECT
SKUKey, WarehouseKey, FiscalPeriod, FiscalYear, QOH,
diff = DATEDIFF
(
month,
CASE WHEN FiscalPeriod IN (1,2,3)
THEN
CAST(FiscalYear + 1 AS CHAR(4))
+ '-' + CAST(FiscalPeriod + 9 AS VARCHAR(2))
+ '-' + CAST(1 AS CHAR(1))
ELSE
CAST(FiscalYear AS CHAR(4))
+ '-' + CAST(FiscalPeriod - 3 AS VARCHAR(2))
+ '-' + CAST(1 AS CHAR(1))
END,
DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
)
FROM InvenFiscPerHistTable
)
SELECT * FROM
(
SELECT diff, QOH * UnitCost as TotalCost
FROM InvenTable i
INNER JOIN SKUTable s ON i.SKUKey = s.SKUKey
INNER JOIN cte ON (i.SKUKey = cte.SKUKey) AND (i.WarehouseKey = cte.WarehouseKey)
INNER JOIN SKUClassTable sc ON s.ICKey = sc.ICKey
WHERE sc.ItemClassName IN ('105-03','105-04','105-05','105-06','150-01')
) A
PIVOT ( SUM(TotalCost) FOR diff IN ([36],[35],[2],[1]) ) p -- add more columns here

Sorting data by YEAR

I am completely new to SQL and have really only run statements with minimal modifications. I am currently trying to modify this specific query:
SELECT DISTINCT (ROUND (windspeed * 2, -1) / 2) AS wndspd,
SUM (CASE WHEN month = 1 THEN 1 ELSE NULL END) AS January,
SUM (CASE WHEN month = 2 THEN 1 ELSE NULL END) AS February,
SUM (CASE WHEN month = 3 THEN 1 ELSE NULL END) AS March,
SUM (CASE WHEN month = 4 THEN 1 ELSE NULL END) AS April,
SUM (CASE WHEN month = 5 THEN 1 ELSE NULL END) AS May,
SUM (CASE WHEN month = 6 THEN 1 ELSE NULL END) AS June,
SUM (CASE WHEN month = 7 THEN 1 ELSE NULL END) AS July,
SUM (CASE WHEN month = 8 THEN 1 ELSE NULL END) AS August,
SUM (CASE WHEN month = 9 THEN 1 ELSE NULL END) AS September,
SUM (CASE WHEN month = 10 THEN 1 ELSE NULL END) AS October,
SUM (CASE WHEN month = 11 THEN 1 ELSE NULL END) AS November,
SUM (CASE WHEN month = 12 THEN 1 ELSE NULL END) AS December
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
GROUP BY ROUND (windspeed * 2, -1) / 2
ORDER BY ROUND (windspeed * 2, -1) / 2;
What I want from the query is to instead of sorting by months, sort by all the years that are available for the specific location (platformid). So far I have just modified the script so that it looks like this:
SELECT DISTINCT
(ROUND (windspeed * 2, -1) / 2) AS wndspd,
SUM (
CASE
WHEN TO_CHAR (observationtime, 'YYYY') = 1957 THEN 1
ELSE NULL
END)
AS given_year,
SUM (
CASE
WHEN TO_CHAR (observationtime, 'YYYY') = 1958 THEN 1
ELSE NULL
END)
AS GIVEN_YEAR2
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
GROUP BY ROUND (windspeed * 2, -1) / 2
ORDER BY ROUND (windspeed * 2, -1) / 2;
The problem is that I know that the years go from 1957-2015. I'm pretty sure that there is a more efficient way to list out the information that I want without creating a specific SUM string for every single year. I have no idea how to do that however. Please help!
You can try creating a PIVOT query
SELECT wndspd, [1957], [1958], [1959],...etc....[2013], [2014], [2015] FROM
(
SELECT (ROUND (windspeed * 2, -1) / 2) AS wndspd,
YEAR(observationtime) yr
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
) src
PIVOT (
COUNT(yr)
FOR yr IN ([1957],[1958], [1959],...etc....[2013], [2014], [2015])
) pvt
you'll have to fill in all of the other years between 1959 and 2013.
here's an easier way to create all of the columns from 1957 to the current year.
DECLARE #PivotCols VARCHAR(MAX),
#MinYear INT = 1957,
#CurYear INT = YEAR(GETDATE())
WHILE #MinYear < #CurYear
BEGIN
SET #PivotCols = COALESCE( #PivotCols + '],[', '[') + CONVERT(VARCHAR, #MinYear)
SET #MinYear = #MinYear + 1
END
SET #PivotCols = CONCAT(#PivotCols,'],[',CONVERT(VARCHAR, #MinYear),']')
DECLARE #Sql VARCHAR(MAX) = '
SELECT wndspd, ' + #PivotCols + ' FROM
(
SELECT (ROUND (windspeed * 2, -1) / 2) AS wndspd,
YEAR(observationtime) yr
FROM table1
WHERE platformid = ''coollocation''
AND networktype = ''typeofcoollocation''
AND (windspeedqc <> ''2'' OR windspeedqc IS NULL)
) src
PIVOT (
COUNT(yr)
FOR yr IN (' + #PivotCols + ')
) pvt
'
EXEC (#Sql)
I think you are essentially trying to do this:
SELECT DISTINCT (ROUND (windspeed * 2, -1) / 2) AS wndspd
,TO_CHAR(observationtime, 'YYYY') as year,
,COUNT(1) as occurrences
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
GROUP BY (ROUND(windspeed * 2, -1) / 2), TO_CHAR(observationtime, 'YYYY')
ORDER BY 1, 2;
...however the count of occurrences in each year would then be represented by a row instead of a column.
Unfortunately there is no easy way to pivot the rows in this query into columns because you fundamentally need to know how many columns there are and ensure each row has the values populated properly.
At the end of the day you'll need to add a line for each year to your SELECT clause. It could look like this:
SELECT yearly.wndspd
,SUM(CASE WHEN yearly.year='1957' THEN yearly.occurrences ELSE 0 END) as 1957
,SUM(CASE WHEN yearly.year='1958' THEN yearly.occurrences ELSE 0 END) as 1958
...
,SUM(CASE WHEN yearly.year='2015' THEN yearly.occurrences ELSE 0 END) as 2015
FROM
(SELECT DISTINCT (ROUND (windspeed * 2, -1) / 2) AS wndspd
,TO_CHAR(observationtime, 'YYYY') as year,
,COUNT(1) as occurrences
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
GROUP BY (ROUND(windspeed * 2, -1) / 2), TO_CHAR(observationtime, 'YYYY')) yearly
GROUP BY yearly.wndspd
ORDER BY 1, 2;