How to get all month names and need to show month data - sql

Pnum Fdate description
==== ========== ===========
1024 2018-02-17 A
1024 2018-05-17 B
1024 2018-05-17 C
1024 2018-09-17 D
MY table PW have fields looks like this.
--> I want to show the result as
**Month Name Description**
January -
February A
March -
April -
May B
June -
July -
August C
September D
October -
November -
December -
Please help me how to achive this.

Join with a list of month names, there is only twelve of them:
SELECT monthname, description
FROM (VALUES
(1, 'January'),
(2, 'February'),
(3, 'March'),
(4, 'April'),
(5, 'May'),
(6, 'June'),
(7, 'July'),
(8, 'August'),
(9, 'September'),
(10, 'October'),
(11, 'November'),
(12, 'December')
) AS va(monthnumber, monthname)
LEFT JOIN yourdata ON DATEPART(MONTH, fdate) = va.monthnumber
ORDER BY monthnumber

Try this
;WITH CTE(Pnum, Fdate,description)
AS
(
SELect 1024,'2018-02-17','A' union all
SELect 1024,'2018-05-17','B' union all
SELect 1024,'2018-08-17','C' union all
SELect 1024,'2018-09-17','D'
)
SELECT MonthNames,ISNULL([Description],'-') AS [Description]
FROM CTE RIGHT JOIN
(
SELECT DATENAME(MONTH,DATEADD(MONTH,number-datepart(month,GETDATE()),GETDATE())) as MonthNames
FROM MASTER.DBO.spt_values
WHERE TYPE ='P'
AND number BETWEEN 1 AND 12
) dt
ON dt.MonthNames=DATENAME(MONTH,Fdate)
Result
MonthNames Description
--------------------------
January -
February A
March -
April -
May B
June -
July -
August C
September D
October -
November -
December -

You can try below
DEMO
with cte1 as (
select cast('2018-01-01' as date) dt
union all
select dateadd(month, 1, dt)
from cte1
where dateadd(month, 1, dt) < cast('2018-12-31' as date)
)
select DateName(month,dt),coalesce(Description,'-') as Description from cte1 a left join yourtable b
on month(a.dt)=month(b.Fdate)

This solution will allow an index on Fdate to be used (MONTH(column) will force a scan every time).
DECLARE #year int = 2018;
;WITH m AS
(
SELECT m = 1 UNION ALL SELECT m + 1 FROM m WHERE m < 12
),
months(b,e) AS
(
SELECT b = DATEFROMPARTS(#year, m, 1)
FROM m
)
SELECT DATENAME(MONTH, m.b), PW.Description
FROM months AS m
LEFT OUTER JOIN dbo.PW
ON PW.Fdate >= m.b AND PW.Fdate < DATEADD(MONTH, 1, m.b)
ORDER BY m.b;

Try the following query:
SELECT MONTHNAME(fdate), description FROM table
For more reference go through
https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_month

Related

Create a year/quarter table in SQL without a loop

I have a start year and an end year, say 2017 and 2019 for example.
I'd like to create a table with columns year and quarter (eg, 1, 2, 3, 4) between my stated startYear and endYear, and have quarter for the final, endYear, to stop at 2 (it's always forward looking).
Sample desired output below.
year quarter
2017 1
2017 2
2017 3
2017 4
2018 1
2018 2
2018 3
2018 4
2019 1
2019 2
Seems like it should be simple, nothing occurs to me except somewhat clunky methods relying on a loop or UNION or simply inserting values manually into the table.
Just another option... an ad-hoc tally table in concert with a Cross Join
Example
Declare #Y1 int = 2017
Declare #Y2 int = 2019
Select *
From ( Select Top (#Y2-#Y1+1) Year=#Y1-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1 ) A
Cross Join (values (1),(2),(3),(4)) B([Quarter])
Returns
Year Quarter
2017 1
2017 2
2017 3
2017 4
2018 1
2018 2
2018 3
2018 4
2019 1
2019 2
2019 3
2019 4
Use a recursive CTE:
with yq as (
select 2017 as yyyy, 1 as qq
union all
select (case when qq = 4 then yyyy + 1 else yyyy end),
(case when qq = 4 then 1 else qq + 1 end)
from yq
where yyyy < 2019 or yyyy = 2019 and qq < 2
)
select *
from yq;
If the table will have more than 100 rows, you will also need option (maxrecursion 0).
Here is a db<>fiddle.
This solution is very similar to the one by John, but it doesn't depend on a system table.
Declare #Y1 int = 2017;
Declare #Y2 int = 2019;
WITH
E(n) AS(
SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
SELECT a.n FROM E a, E b
),
E4(n) AS(
SELECT a.n FROM E2 a, E2 b
),
cteYears([Year]) AS(
SELECT TOP (#Y2-#Y1+1)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + #Y1 - 1 AS [Year]
FROM E4
)
SELECT [Year], [Quarter]
FROM cteYears
CROSS JOIN (VALUES (1),(2),(3),(4)) Q([Quarter]);
Let me to propose a recursve query for you:
WITH prepare AS
(
SELECT tbl.year
FROM (VALUES (2017) ) AS tbl(year) -- for example, start year is 2k17
UNION ALL
SELECT year + 1
FROM prepare
WHERE year < 2030 -- and last year is 2030
)
SELECT
year, quarter
FROM prepare
CROSS JOIN ( VALUES (1), (2), (3), (4) ) AS tbl (quarter)

recursive common table expression in SQL Server

I have a table that contains two pieces of information BusinessDate and DailyPerf where DailyPerf shows percentage change in the value of a price I am tracking. The first few rows of the data looks like this
BusinessDate DailyPerf
Jan 3, 2017 -0.0356%
Jan 4, 2017 -0.4325%
Jan 5, 2017 -0.3953%
Jan 6, 2017 -0.8469%
Jan 9, 2017 -0.5050%
What I am trying to do is to calculate another column YearFac that shows the current percentage of my initial value I have left
Done in Excel the data would look like this:
BusinessDate DailyPerf YearFac
Jan 2, 2017 NULL 100%
Jan 3, 2017 -0.0356% 99.9644%
Jan 4, 2017 -0.4325% 99.5321%
Jan 5, 2017 -0.3953% 99.1386%
Jan 6, 2017 -0.8469% 98.2989%
Jan 9, 2017 -0.5050% 97.8025%
So, I need to prime my query with a YearFac of 100% and then recursively calculate the factor. The one caveat is that the dates are not necessarily sequential (there are no entries over weekends or on holidays) - so I cannot assume that next day = this day + 1 just that the next day is the next larger date
I tried the following
WITH cte
AS (
SELECT cast(1 as int) RowCnt,
cast('3 jan 2017' as date) BusinessDate,
cast(1.0 as float) YearFac -- anchor member
UNION ALL
select cast(ROW_NUMBER() OVER(ORDER BY p.BusinessDate ASC) + 1 as int) as RowCnt,
p.BusinessDate, -- recursive member
cast(cte.YearFac*(1.0 + p.DailyPerc) as float) YearFac
from cte
inner join dbo.MsfsDailyPnl p
on
cte.RowCnt = ROW_NUMBER() OVER(ORDER BY p.BusinessDate ASC) + 1
where p.BusinessDate < sysutcdatetime()
)
SELECT RowCnt,BusinessDate, YearFac
FROM cte
But, of course, this fails because I cannot reference the row number in the join. Could anyone suggest a modification that will get this query to work?
Use a cumulative sum:
select BusinessDate, DailyPerf,
1 - exp(sum(log(1 + DailyPerf)) over (order by BusinessDate desc)
from (select cast('2017-01-03' as date) as BusinessDate, cast(0 as float) as DailyPerf
union all
select BusinessDate, DailyPerf from dbo.MsfsDailyPnl
) p;
Use window function
select *, sum(DailyPerf) over (order by BusinessDate) + 100 YearFac
from (
select dateadd(day, -1, min(BusinessDate)) BusinessDate, 0 DailyPerf from data
union all
select * from data) t
dbfiddle demo
RESULT
BusinessDate DailyPerf YearFac
-------------------------------------------
02/01/2017 00:00:00 0.0000 100.0000
03/01/2017 00:00:00 -0.0356 99.9644
04/01/2017 00:00:00 -0.4325 99.5319
05/01/2017 00:00:00 -0.3953 99.1366
06/01/2017 00:00:00 -0.8469 98.2897
09/01/2017 00:00:00 -0.5050 97.7847
Here's what you might want to try. I attached also definition of table, so you know how it looks like and what can be done easily to accomplish your task.
declare #x table(BusinessDate date, DailyPerf float)
insert into #x values
('Jan 2, 2017', NULL),
('Jan 3, 2017', -0.0356),
('Jan 4, 2017', -0.4325),
('Jan 5, 2017', -0.3953),
('Jan 6, 2017', -0.8469),
('Jan 9, 2017', -0.5050)
select *,
100 + SUM(isnull(DailyPerf,0)) over (partition by (select null) order by
BusinessDate rows between unbounded preceding and current row)
from #x

sum of the average

working with this subquery and I am doing something wrong were I am not getting the right average in the LastSixMosAvg.
JOIN
(SELECT Avg(LastSix) as LastSixMosAvg,id
FROM (SELECT t.id,Sum(t.total) as LastSix
FROM table t
WHERE t.Month Between #6mosStart and #enddate
group by t.id)t
Group by id) d
ON d.ID=a.ID
that query yields this results in a particular ID.
Month Total LastSixMosAvg
Month 1 325 1,367
Month 2 30 1,367
Month 3 330 1,367
Month 4 336 1,367
Month 5 220 1,367
Month 6 126 1,367
Instead in the LasSixMos Avg I should see $228
Your query is averaging the sum of the Total column. The sum is 1,367, and it is a constant for each ID, so averaging it correctly returns 1,367.
Try selecting the average of the Total column itself:
INNER JOIN
(
SELECT t.ID, Avg(t.Total) as LastSixMosAvg
FROM MyTable t
WHERE t.Month Between #6mosStart and #enddate
GROUP BY t.ID
) d
ON d.ID = a.ID
Example:
declare #t table
(
[ID] int,
[Month] int,
Total int,
primary key clustered([ID], [Month])
)
insert into #t values
(1, 1, 325),
(1, 2, 30),
(1, 3, 330),
(1, 4, 336),
(1, 5, 220),
(1, 6, 127)
declare #6mosStart int = 1, #enddate int = 6
SELECT t.ID, Avg(t.Total) as LastSixMosAvg
FROM #t t
WHERE t.Month Between #6mosStart and #enddate
GROUP BY t.ID
-- Results:
-- ID LastSixMosAvg
-- 1 228

How to write this query in SQL with category and date sum

I have a table in MySQL with these data belows:
DATE Category AMOUNT
2016-1-1 A 12
2016-1-1 B 10
2016-1-2 A 5
2016-1-3 C 1
2016-2-1 A 5
2016-2-1 B 6
2016-2-2 A 7
2016-2-3 C 3
How can I get the result as below:
MONTH TOTAL Category-A Category-B Category-C
2016 Jan 28 17 10 1
2016 Feb 21 12 6 3
If you're using MySQL this would work:
SELECT
DATE_FORMAT(DATE, '%Y %b') AS MONTH,
SUM(AMOUNT) AS TOTAL,
SUM(IF(CATEGORY='A', AMOUNT, 0)) AS `Category-A`,
SUM(IF(CATEGORY='B', AMOUNT, 0)) AS `Category-B`,
SUM(IF(CATEGORY='C', AMOUNT, 0)) AS `Category-C`
FROM your_table
GROUP BY MONTH;
For other database engines you might have to change that a little.
DECLARE #Table1 TABLE
(DATE varchar(8), CAT varchar(1), AMOUNT int)
;
INSERT INTO #Table1
(DATE, CAT, AMOUNT)
VALUES
('2016-1-1', 'A', 12),
('2016-1-1', 'B', 10),
('2016-1-2', 'A', 5),
('2016-1-3', 'C', 1),
('2016-2-1', 'A', 5),
('2016-2-1', 'B', 6),
('2016-2-2', 'A', 7),
('2016-2-3', 'C', 3)
;
Select Months,
[A][Category-A],
[B][Category-B],
[C][Category-C],
SUM([A]+[B]+[C])TOTAL
from (
select CAST(year(DATE) AS VARCHAR)+' '+CONVERT(CHAR(3), DATENAME(MONTH, date))Months,
CAT,
AMOUNT
from #Table1)T
PIVOT (SUM(AMOUNT) FOR cat IN ([A],[B],[C]))P
GROUP BY Months,[A],[B],[C]
ORDER BY CONVERT(CHAR(3), DATENAME(MONTH, Months)) desc

Table with dates, table with week numbers, join together?

I have two tables. Table 1:
StuAp_Id StuAp_StaffID StuAp_Date StuAp_Attended
16 77000002659366 2011-09-07 Yes
17 77000002659366 2011-09-14 Yes
18 77000002659366 2011-09-14 Yes
19 77000002659366 2011-09-14 No
20 77000001171783 2011-09-19 Yes
Table 2:
Year Week Start
2011 1 2011-09-05 00:00:00.000
2011 2 2011-09-12 00:00:00.000
2011 3 2011-09-19 00:00:00.000
2011 4 2011-09-26 00:00:00.000
2011 5 2011-10-03 00:00:00.000
2011 6 2011-10-10 00:00:00.000
2011 7 2011-10-17 00:00:00.000
2011 8 2011-10-24 00:00:00.000
2011 9 2011-10-31 00:00:00.000
How would I join these two tables to make something like this:
StuAp_Id StuAp_StaffID StuAp_Date StuAp_Attended Week
16 77000002659366 2011-09-07 Yes 1
17 77000002659366 2011-09-14 Yes 2
18 77000002659366 2011-09-14 Yes 2
19 77000002659366 2011-09-14 No 2
20 77000001171783 2011-09-19 Yes 3
Thanks in advance
You can write simple INNER JOIN using GROUP BY clause.
SELECT Table1.*
,MAX(WEEK) AS WEEK
FROM Table1
INNER JOIN Table2 ON STUAP_DATE >= START
GROUP BY STUAP_ID,STUAP_STAFFID,STUAP_DATE,STUAP_ATTENDED
don't know about specifics on sql2k5 (don't have one around to test) but I would use a sub select eg.
select table_1.*,
[week] = (select isnull(max([week]), 0)
from table_2
where table_1.StuAp_Date >= table_2.start)
from table_1
CTEs to the rescue!
create table StuAp (
StuAp_Id int,
StuAp_StaffID bigint,
StuAp_Date datetime,
StuAp_Attended varchar(3)
)
create table Weeks (
Year int,
Week int,
Start datetime
)
insert into StuAp
values (16, 77000002659366, {d '2011-09-07'}, 'Yes'),
(17, 77000002659366, {d '2011-09-14'}, 'Yes'),
(18, 77000002659366, {d '2011-09-14'}, 'Yes'),
(19, 77000002659366, {d '2011-09-14'}, 'No'),
(20, 77000001171783, {d '2011-09-19'}, 'Yes')
insert into Weeks
values (2011, 1, {d '2011-09-05'}),
(2011, 2, {d '2011-09-12'}),
(2011, 3, {d '2011-09-19'}),
(2011, 4, {d '2011-09-26'}),
(2011, 5, {d '2011-10-03'}),
(2011, 6, {d '2011-10-10'}),
(2011, 7, {d '2011-10-17'}),
(2011, 8, {d '2011-10-24'}),
(2011, 9, {d '2011-10-31'})
;with OrderedWeeks as (
select ROW_NUMBER() OVER (ORDER BY year, week) as row, w.*
from Weeks w
), Ranges as (
select w1.*, w2.Start as Finish
from OrderedWeeks w1 inner join
OrderedWeeks w2 on w1.row = w2.row - 1
)
select s.StuAp_Id, s.StuAp_StaffID, s.StuAp_Date, s.StuAp_Attended, r.Week
from StuAp s inner join
Ranges r on s.StuAp_Date >= r.Start and s.StuAp_Date < r.Finish
This should scale quite well too.
Honestly though, if you find yourself doing queries like this often, you should really consider changing the stucture of your Weeks table to include a finish date. You could even make it an indexed view, or (assuming that the data changes rarely), you could keep your original table and use triggers or a SQL Agent job to keep a copy that contains Finish up to date.
SET ANSI_WARNINGS ON;
GO
DECLARE #Table1 TABLE
(
StuAp_Id INT PRIMARY KEY
,StuAp_StaffID NUMERIC(14,0) NOT NULL
,StuAp_Date DATETIME NOT NULL
,StuAp_Attended VARCHAR(3) NOT NULL
,StuAp_DateOnly AS DATEADD(DAY, DATEDIFF(DAY,0,StuAp_Date), 0) PERSISTED
);
INSERT #Table1
SELECT 16,77000002659366 ,'2011-09-07','Yes'
UNION ALL
SELECT 17,77000002659366 ,'2011-09-14','Yes'
UNION ALL
SELECT 18,77000002659366 ,'2011-09-14','Yes'
UNION ALL
SELECT 19,77000002659366 ,'2011-09-14','No'
UNION ALL
SELECT 20,77000001171783 ,'2011-09-19','Yes';
DECLARE #Table2 TABLE
(
Year INT NOT NULL
,Week INT NOT NULL
,Start DATETIME NOT NULL
,[End] AS DATEADD(DAY,6,Start) PERSISTED
,PRIMARY KEY(Year, Week)
,UNIQUE(Start)
);
INSERT #Table2
SELECT 2011,1 ,'2011-09-05 00:00:00.000'
UNION ALL
SELECT 2011,2 ,'2011-09-12 00:00:00.000'
UNION ALL
SELECT 2011,3 ,'2011-09-19 00:00:00.000'
UNION ALL
SELECT 2011,4 ,'2011-09-26 00:00:00.000'
UNION ALL
SELECT 2011,5 ,'2011-10-03 00:00:00.000'
UNION ALL
SELECT 2011,6 ,'2011-10-10 00:00:00.000'
UNION ALL
SELECT 2011,7 ,'2011-10-17 00:00:00.000'
UNION ALL
SELECT 2011,8 ,'2011-10-24 00:00:00.000'
UNION ALL
SELECT 2011,9 ,'2011-10-31 00:00:00.000';
--Solution 1 : if StuAp_Date has only date part
SELECT a.*, b.Week
FROM #Table1 a
INNER JOIN #Table2 b ON a.StuAp_Date BETWEEN b.Start AND b.[End]
--Solution 2 : if StuAp_Date has only date part
SELECT a.*, b.Week
FROM #Table1 a
INNER JOIN #Table2 b ON a.StuAp_Date BETWEEN b.Start AND DATEADD(DAY,6,b.Start)
--Solution 3 : if StuAp_Date has date & time
SELECT a.*, b.Week
FROM #Table1 a
INNER JOIN #Table2 b ON a.StuAp_DateOnly BETWEEN b.Start AND b.[End]
--Solution 4 : if StuAp_Date has date & time
SELECT a.*, b.Week
FROM #Table1 a
INNER JOIN #Table2 b ON DATEADD(DAY, DATEDIFF(DAY,0,a.StuAp_Date), 0) BETWEEN b.Start AND DATEADD(DAY,6,b.Start)