I have a table abs_details that give data like follows -
PERSON_NUMBER ABS_DATE ABS_TYPE_NAME ABS_DAYS
1010 01-01-2022 PTO 1
1010 06-01-2022 PTO 0.52
1010 02-02-2022 VACATION 1
1010 03-02-2022 VACATION 0.2
1010 01-12-2021 PTO 1
1010 02-12-2021 sick 1
1010 30-12-2021 sick 1
1010 30-01-2022 SICK 1
I want this data to be displayed in the following way:
PERSON_NUMBER ABS_TYPE_NAME 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
1010 PTO 2 0.52
1010 VACATION 1 0.2
1010 SICK 1 2
For the days, 1-31 should should come in the header, if there is any absence taken on say 01st of the month or quarter passed then the value should go under 1 , if there is no value for date of the month, say no value is there from 07th-11th in the above case, then output should display the numbers but no value should be provided under it.
Is this feasible in SQL? I have an idea we can use pivot, but how to fix 1-31 header and give values underneath each day.
Any suggestions?
If I pass multiple quarter that is Q1(JAN-MAR), Q2(APR-JUN) it should sum up the values between the dates between those two quarters. if Just q1 then only q1 result
If I pass multiple month then it should display the sum of the values for an absence type in those multiple months.
I will be passing the year in the parameter and the above two should consider the year I pass.
Create a column which has all the dates, and pivot up using pivot function in oracle.
SELECT *
FROM
(
SELECT PERSON_NUMBER,
EXTRACT(DAY FROM TO_DATE(ABS_DATE)) AS DAY_X,
ABS_TYPE_NAME,
ABS_DAYS
FROM TABLE
-- Add additional filter here which you want
)
PIVOT(SUM(ABS_DAYS)
FOR DAY_X IN (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31))
Db fiddle - https://dbfiddle.uk/?rdbms=oracle_21&fiddle=ad3af639235f7a6db415ec714a3ee0d9
I have a table where for some dates a certain number of entries are placed. Here is the table structure :
ID EntryName Entries DateOfEntry
1 A 20 2016-01-17
2 B 22 2016-01-29
3 C 23 2016-02-17
4 D 19 2016-02-17
5 E 29 2016-03-17
6 F 30 2016-03-17
7 G 43 2016-04-17
8 H 10 2016-04-17
9 I 5 2016-05-17
10 J 120 2016-05-17
11 K 220 2016-06-17
12 L 210 2016-06-17
13 M 10 2016-07-17
14 N 20 2016-07-17
15 O 15 2016-08-17
16 P 17 2016-08-17
17 Q 19 2016-09-17
18 R 23 2016-09-17
19 S 43 2016-10-17
20 T 56 2016-10-17
21 U 65 2016-11-17
22 V 78 2016-11-17
23 W 12 2016-12-17
24 X 23 2016-12-17
25 Y 43 2016-02-17
26 Z 67 2016-03-17
27 AA 35 2015-01-17
28 AB 23 2015-01-29
29 AC 43 2015-02-17
30 AD 35 2015-02-17
31 AE 45 2015-03-17
32 AF 23 2015-03-17
33 AG 43 2015-04-17
34 AH 19 2015-04-17
35 AI 21 2015-05-17
36 AJ 13 2015-05-17
37 AK 22 2015-06-17
38 AL 45 2015-06-17
39 AM 66 2015-07-17
40 AN 77 2015-07-17
41 AO 89 2015-08-17
42 AP 127 2015-08-17
43 AQ 19 2015-09-17
44 AR 223 2015-09-17
45 AS 143 2015-10-17
46 AT 36 2015-10-17
47 AU 45 2015-11-17
48 AV 28 2015-11-17
49 AW 72 2015-12-17
50 AX 24 2015-12-17
51 AY 46 2015-02-17
52 AZ 62 2015-03-17
The column EntryName is the entry identifier, the column Entries has the total number of entries for the date specified in the column DateOfEntry.
I am trying to formulate a query where the total number of entries are displayed on a month-wise basis. I currently have this query :
SELECT DateName(MONTH, e.DateOfEntry) AS MonthOfEntry,
MONTH(e.DateOfEntry) AS MonthNumber,
SUM(e.Entries) AS TotalEntries
FROM #entry e
GROUP BY MONTH(e.DateOfEntry), DateName(MONTH,e.DateOfEntry)
ORDER BY MONTH(e.DateOfEntry) ASC
which works fine as far as displaying the results are concerned. However, my issue here is that I need to sort the results on a month-wise basis where the starting month would be dynamic i.e. arising from a parameter (supplied by the user).
This means that if the user selects May of 2015 the results should be sorted from May 2015 to April 2016. Similarly, if the user selects October 2015, the results would be displayed from October 2015 to September 2016.
How would I go about getting this condition within the ORDER BY clause ?
You can put an offset into the ORDER BY using modulo arithmetic. For April:
ORDER BY (MONTH(e.DateOfEntry) + 12 - 4) % 12
--------------------------------------^ month number to start with
(The + 12 is simply so I don't have to remember if % returns negative numbers with negative operands.)
If you want the results chronologically, you can instead do:
ORDER BY MIN(e.DateOfEntry)
You could use the belosw in order by
ORDER BY YEAR(e.DATEOFENTRY),
DATEPART(MM,e.DAREOFENTRY)
This will sort the result first for Year and next for month.
Here you need to specify these same columns in Select.
If I understood you correctly
"This means that if the user selects May of 2015 the results should be sorted from May 2015 to April 2016. Similarly, if the user selects October 2015, the results would be displayed from October 2015 to September 2016."
this should work:
SAMPLE DATA:
IF OBJECT_ID('tempdb..#entry') IS NOT NULL
DROP TABLE #entry;
CREATE TABLE #entry(ID INT ,EntryName VARCHAR(10) , Entries INT , DateOfEntry DATE);
INSERT INTO #entry (ID ,EntryName ,Entries ,DateOfEntry)
VALUES
(1 ,'A', 20 ,'2016-01-17'),
(2 ,'B', 22 ,'2016-01-29'),
(3 ,'C', 23 ,'2016-02-17'),
(4 ,'D', 19 ,'2016-02-17'),
(5 ,'E', 29 ,'2016-03-17'),
(6 ,'F', 30 ,'2016-03-17'),
(7 ,'G', 43 ,'2016-04-17'),
(8 ,'H', 10 ,'2016-04-17'),
(9 ,'I', 5 ,'2016-05-17'),
(10,'J', 120 ,'2016-05-17'),
(11,'K', 220 ,'2016-06-17'),
(12,'L', 210 ,'2016-06-17'),
(13,'M', 10 ,'2016-07-17'),
(14,'N', 20 ,'2016-07-17'),
(15,'O', 15 ,'2016-08-17'),
(16,'P', 17 ,'2016-08-17'),
(17,'Q', 19 ,'2016-09-17'),
(18,'R', 23 ,'2016-09-17'),
(19,'S', 43 ,'2016-10-17'),
(20,'T', 56 ,'2016-10-17'),
(21,'U', 65 ,'2016-11-17'),
(22,'V', 78 ,'2016-11-17'),
(23,'W', 12 ,'2016-12-17'),
(24,'X', 23 ,'2016-12-17'),
(25,'Y', 43 ,'2016-02-17'),
(26,'Z', 67 ,'2016-03-17'),
(27,'AA',35 ,'2015-01-17'),
(28,'AB',23 ,'2015-01-29'),
(29,'AC',43 ,'2015-02-17'),
(30,'AD',35 ,'2015-02-17'),
(31,'AE',45 ,'2015-03-17'),
(32,'AF',23 ,'2015-03-17'),
(33,'AG',43 ,'2015-04-17'),
(34,'AH',19 ,'2015-04-17'),
(35,'AI',21 ,'2015-05-17'),
(36,'AJ',13 ,'2015-05-17'),
(37,'AK',22 ,'2015-06-17'),
(38,'AL',45 ,'2015-06-17'),
(39,'AM',66 ,'2015-07-17'),
(40,'AN',77 ,'2015-07-17'),
(41,'AO',89 ,'2015-08-17'),
(42,'AP',127 ,'2015-08-17'),
(43,'AQ',19 ,'2015-09-17'),
(44,'AR',223 ,'2015-09-17'),
(45,'AS',143 ,'2015-10-17'),
(46,'AT',36 ,'2015-10-17'),
(47,'AU',45 ,'2015-11-17'),
(48,'AV',28 ,'2015-11-17'),
(49,'AW',72 ,'2015-12-17'),
(50,'AX',24 ,'2015-12-17'),
(51,'AY',46 ,'2015-02-17'),
(52,'AZ',62 ,'2015-03-17')
QUERY WITH PARAMS:
DECLARE #Month VARCHAR(2) = '05', #Year VARCHAR(4) = '2015'
SELECT DateName(MONTH, e.DateOfEntry) AS MonthOfEntry,
MONTH(e.DateOfEntry) AS MonthNumber,
SUM(e.Entries) AS TotalEntries
FROM #entry e
WHERE CAST(e.DateOfEntry AS DATE) >= CAST( #Year+#Month+'01' AS DATE)
GROUP BY MONTH(e.DateOfEntry), DateName(MONTH,e.DateOfEntry)
ORDER BY MONTH(e.DateOfEntry) ASC
RESULTS:
add a where clause for the query
WHERE MONTH(e.DateOfEntry) < User.Month AND YEAR(e.DateOfEntry) < User.Year AND MONTH(e.DateOfEntry) > (User.Month-1) AND YEAR(e.DateOfEntry) > (User.Year+1)
Assuming your parameter is an Integer called #FirstMonth, you could get the proper month order using:
Case
WHEN MONTH(e.DateOfEntry) < #FirstMonth then MONTH(e.DateOfEntry) + 12
ELSE MONTH(e.DateOfEntry)
END AS MonthNumber
Of all the answers and suggestions I have come across here, I find this way (suggested by xQbert in the question's comments) to be the simplest one :
SELECT DateName(MONTH, e.DateOfEntry) + ' ' + CONVERT(NVARCHAR(100), YEAR(e.DateOfEntry)) AS MonthOfEntry,
MONTH(e.DateOfEntry) AS MonthNumber,
SUM(e.Entries) AS TotalEntries
FROM Entry e
WHERE e.DateOfEntry BETWEEN #StartDate AND (DATEADD(YEAR, 1, #StartDate))
GROUP BY MONTH(e.DateOfEntry), DateName(MONTH,e.DateOfEntry), YEAR(e.DateOfEntry)
ORDER BY YEAR(e.DateOfEntry) ASC, MONTH(e.DateOfEntry) ASC
A fiddle to demonstrate this : http://rextester.com/CJFFP5640
Initially, I was using the following query :
SELECT sortingList.MonthOfEntry,
sortingList.TotalEntries,
sortingList.MonthNumber
FROM (
SELECT DateName(MONTH, DATEADD(MONTH, MONTH(e.DateOfEntry), 0) - 1) + ' ' + CONVERT(nvarchar(20),YEAR(e.DateOfEntry)) AS MonthOfEntry,
SUM(e.Entries) as TotalEntries,
CASE
WHEN ((MONTH(e.DateOfEntry) - MONTH(#StartDate)) > 0)
THEN (MONTH(e.DateOfEntry) - MONTH(#StartDate)) + 1
WHEN ((MONTH(e.DateOfEntry) - MONTH(#StartDate)) = 0)
THEN 1
ELSE
((12 - MONTH(#StartDate)) + (MONTH(e.DateOfEntry))) + 1
END
AS MonthNumber
FROM Entry e
WHERE e.DateOfEntry >= #StartDate AND e.DateOfEntry < DATEADD(YEAR, 1, #StartDate)
GROUP BY DateName(MONTH, DATEADD(MONTH, MONTH(e.DateOfEntry), 0) - 1), YEAR(e.DateOfEntry), MONTH(e.DateOfEntry) - MONTH(#StartDate), MONTH(e.DateOfEntry)
) sortingList
ORDER BY sortingList.MonthNumber ASC
Here's an Fiddle to demonstrate this : http://rextester.com/LEVD30653
Explanation (non TL;DR)
You can see that it's essentially the same WHERE clause. However, the query at the top uses much simpler logic for sorting and is more fluent and readable.
Do note that the second solution (using the CASE statement) sorts the month numbers as per the user-provided month number i.e. if the user provides December 2015, then the second solution will number Dec 2015 as 1, January 2016 as 2, February 2016 as 3 and so on and so forth. This might be more beneficial in cases where you want to work on top of this data.
As far as my use-case is concerned, this makes more sense. However, as far as the scope of the question is concerned, the query at the top is the best one.
This is from a heavily manually maintained report that I am trying to automate a bit view an SSAS cube.
The report contains daily sales, and, among other things, a measure called "last 4 's". E.g., For Friday, October 16 the measure was the average sales over the last 4 Fridays.
Is there a way to construct this in MDX in way that can be placed in a calculated measure in an SSAS cube?
ps--In response to whytheq's question, yes, the date dimension includes day of week, which is an integer in which Sun = 1, Mon = 2 and so on to Sat = 7.
I see that I was slightly ambiguous above. By "last 4 Fridays" above I meant the 4 Fridays immediately preceding October 16, not the most recent 4 Fridays.
Agreed with whytheq, that more information may help us to create optimal solution. Anyway:
Solved if you have only flat days hierarchy:
+All
-2015/01/01
-2015/01/02
...
-2015/12/31
...
Logic could be like this:
rank all days
split by weeks
calculate last 4 for every day type
show result for every selected member
Example of flat hierarchy [Report Date].[Report Date].[Day] calculation:
with
member [Measures].[AllDaysRank] as Rank([Report Date].[Report Date].CurrentMember,[Report Date].[Report Date].[Day].Members)
member [Measures].[WeekDay] as ([Measures].[AllDaysRank]-(Int([Measures].[AllDaysRank]/7)*7))
set [Last4Set0] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=0),4,[Measures].[AllDaysRank])
set [Last4Set1] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=1),4,[Measures].[AllDaysRank])
set [Last4Set2] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=2),4,[Measures].[AllDaysRank])
set [Last4Set3] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=3),4,[Measures].[AllDaysRank])
set [Last4Set4] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=4),4,[Measures].[AllDaysRank])
set [Last4Set5] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=5),4,[Measures].[AllDaysRank])
set [Last4Set6] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=6),4,[Measures].[AllDaysRank])
member [Measures].[Last4Measure] as
case [Measures].[WeekDay]
when 0 then sum([Last4Set0],[Measures].[Count])
when 1 then sum([Last4Set1],[Measures].[Count])
when 2 then sum([Last4Set2],[Measures].[Count])
when 3 then sum([Last4Set3],[Measures].[Count])
when 4 then sum([Last4Set4],[Measures].[Count])
when 5 then sum([Last4Set5],[Measures].[Count])
when 6 then sum([Last4Set6],[Measures].[Count])
end
select {[Measures].[Count],[Measures].[AllDaysRank],[Measures].[WeekDay],[Measures].[Last4Measure]} on 0
,[Report Date].[Report Date].[Day].Members on 1
from [DATA]
Result (Count, AllDaysRank, WeekDay, Last4Measure):
20151001 10 740 5 35
20151002 10 741 6 39
20151003 8 742 0 37
20151004 12 743 1 42
20151005 13 744 2 42
20151006 12 745 3 39
20151007 10 746 4 36
20151008 8 747 5 35
20151009 6 748 6 39
20151010 11 749 0 37
20151011 10 750 1 42
20151012 7 751 2 42
20151013 8 752 3 39
20151014 6 753 4 36
20151015 9 754 5 35
20151016 11 755 6 39
20151017 11 756 0 37
20151018 10 757 1 42
20151019 14 758 2 42
20151020 8 759 3 39
20151021 11 760 4 36
20151022 4 761 5 35
20151023 16 762 6 39
20151024 5 763 0 37
20151025 10 764 1 42
20151026 8 765 2 42
20151027 11 766 3 39
20151028 9 767 4 36
20151029 14 768 5 35
20151030 6 769 6 39
20151031 10 770 0 37
If you have week hierarchy or some properties (not to calculate day numbers), it would be easier.
UPDATE (weekly attribute is present):
Here is script for weeks, but please create weekday->day hierarchy first, e.g:
All
+1
-2015/01/01
-2015/01/08
...
+2
-2015/01/02
-2015/01/09
...
Code has code tricks I'll explain further:
with
member [Measures].[Week Day INFO] as [Report Date].[Week Day].Properties( "Report Date Week Day" )
member [Measures].[Last4Measure] as
/* if there are no empty 4 weeks for the first dates with data, take smaller size */
iif([Report Date].[Week Day].CurrentMember.Lag(3).Parent.Member_Key <> [Report Date].[Week Day].CurrentMember.Parent.Member_Key
,iif([Report Date].[Week Day].CurrentMember.Lag(2).Parent.Member_Key <> [Report Date].[Week Day].CurrentMember.Parent.Member_Key
,iif([Report Date].[Week Day].CurrentMember.Lag(1).Parent.Member_Key <> [Report Date].[Week Day].CurrentMember.Parent.Member_Key
,sum({[Report Date].[Week Day].CurrentMember},[Measures].[Count])
,sum({[Report Date].[Week Day].CurrentMember.Lag(1):[Report Date].[Week Day].CurrentMember},[Measures].[Count]))
,sum({[Report Date].[Week Day].CurrentMember.Lag(2):[Report Date].[Week Day].CurrentMember},[Measures].[Count]))
/* end of fixing, which could be necessary */
/* calculation part */
,sum({[Report Date].[Week Day].CurrentMember.Lag(3):[Report Date].[Week Day].CurrentMember},[Measures].[Count]))
select
{[Measures].[Count],[Measures].[Week Day INFO],[Measures].[Last4Measure]} on 0
,[Report Date].[Report Date].[Day].members on 1
from [DATA]
Week Day INFO measure is for result viewing only, not necessary in calculations.
Exact code to calculate is really simple: sum({[Report Date].[Week Day].CurrentMember.Lag(3):[Report Date].[Week Day].CurrentMember},[Measures].[Count]))
But it's possible, that you don't have empty or senseless days, which is necessary for this calculation! Since it uses .lag(3), and once we try to calculate for the VERY first, second or third weeks, it will take last members from the previous weekday, e.g. to calculate .lag(3) for the Second Wednesday ever (at the beginning of your Date dimension), it will take 2nd Wed, 1st Wed, Last Tue, Pre-last Tue, which is unacceptable, so I've added decreasing lag-level by checking Parent name (since Parent is WeekDay number from the hierarchy we've already created).
Sure thing, you'll use AVG instead of SUM. I used SUM to simplify checking an answer. It's dynamical, not for the very last member only. It depends on current member.
If you have a hierarchy within the date dimension that indicates Day of the Week then you should use it in the sort of circumstance of this question.
Via AdvWrks I wrote the following:
WITH
MEMBER [Measures].[Avg4wkDays] AS
Sum
(
Tail
( --<<find just the past 4
Exists
( --<< find all the days from the past 25 days which have the same day of the week as the current date
Tail
( --<< find the last 25 days prior to each date
NULL : [Date].[Calendar].CurrentMember
,25
)
,Exists
( --<< find the Day of the Week for the currentmember
[Date].[Day of Week].[Day of Week].MEMBERS
,[Date].[Calendar].CurrentMember
)
)
,4
)
,[Measures].[Internet Sales Amount]
)
SELECT
{
[Measures].[Internet Sales Amount]
,[Measures].[Avg4wkDays]
} ON 0
,
[Date].[Calendar].[Date] * [Date].[Day of Week].[Day of Week] ON 1
FROM [Adventure Works]
WHERE
[Date].[Calendar Year].&[2007];
It gives the following which agrees with the requirements: