SQL query for Month and Year - sql

Looking for an Oracle SQL query to show Month and Year starting from the current year- 1y and current year+1y.
Eg: December 2019, January 2020, February 2020,......December 2021

You can use the hierarchy query as follows:
SQL> SELECT trunc(ADD_MONTHS(ADD_MONTHS(sysdate,-12), LEVEL-1), 'Mon') as month_year
2 FROM DUAL CONNECT BY LEVEL <= 24 + 1;
MONTH_YEAR
--------------
December 2019
January 2020
February 2020
March 2020
April 2020
May 2020
June 2020
July 2020
August 2020
September 2020
October 2020
November 2020
December 2020
January 2021
February 2021
March 2021
April 2021
May 2021
June 2021
July 2021
August 2021
September 2021
October 2021
November 2021
December 2021
25 rows selected.
SQL>

There are multiple methods for doing this. I think simple examples like this are a good opportunity to learn about recursive CTEs:
with dates(yyyymm, n) as (
select trunc(sysdate, 'Mon') as yyyymm, 1 as n
from dual
union all
select add_months(yyyymm, -1), n + 1
from dates
where n <= 12
)
select yyyymm
from dates;

WITH d AS (
SELECT
'JAN' m,
2021 y
FROM
dual
), d1 AS (
SELECT
to_date(m || y, 'MONYYYY') first_day,
last_day(to_date(m || y, 'MONYYYY')) last_day1,
last_day(to_date(m || y, 'MONYYYY')) - to_date(m || y, 'MONYYYY') no_of_days
FROM
d
)
SELECT
level - 1 + first_day dates
FROM
d1
CONNECT BY
level <= no_of_days + 1;

Related

Get the last 4 weeks prior to current week of and the same 4 weeks of last year

I have a list of date, fiscal week, and fiscal year:
DATE_VALUE FISCAL_WEEK FISCAL_YEAR_VALUE
14-Dec-20 51 2020
15-Dec-20 51 2020
16-Dec-20 51 2020
17-Dec-20 51 2020
18-Dec-20 51 2020
19-Dec-20 51 2020
20-Dec-20 51 2020
21-Dec-20 52 2020
22-Dec-20 52 2020
23-Dec-20 52 2020
24-Dec-20 52 2020
25-Dec-20 52 2020
26-Dec-20 52 2020
27-Dec-20 52 2020
28-Dec-20 1 2021
29-Dec-20 1 2021
30-Dec-20 1 2021
31-Dec-20 1 2021
1-Jan-21 1 2021
2-Jan-21 1 2021
3-Jan-21 1 2021
4-Jan-21 2 2021
5-Jan-21 2 2021
6-Jan-21 2 2021
7-Jan-21 2 2021
8-Jan-21 2 2021
9-Jan-21 2 2021
10-Jan-21 2 2021
11-Jan-21 3 2021
12-Jan-21 3 2021
13-Jan-21 3 2021
14-Jan-21 3 2021
15-Jan-21 3 2021
16-Jan-21 3 2021
17-Jan-21 3 2021
18-Jan-21 4 2021
19-Jan-21 4 2021
20-Jan-21 4 2021
21-Jan-21 4 2021
22-Jan-21 4 2021
23-Jan-21 4 2021
24-Jan-21 4 2021
20-Dec-21 52 2021
21-Dec-21 52 2021
22-Dec-21 52 2021
23-Dec-21 52 2021
24-Dec-21 52 2021
25-Dec-21 52 2021
26-Dec-21 52 2021
27-Dec-21 53 2021
28-Dec-21 53 2021
29-Dec-21 53 2021
30-Dec-21 53 2021
31-Dec-21 53 2021
1-Jan-22 53 2021
2-Jan-22 53 2021
3-Jan-22 1 2022
4-Jan-22 1 2022
5-Jan-22 1 2022
6-Jan-22 1 2022
7-Jan-22 1 2022
8-Jan-22 1 2022
9-Jan-22 1 2022
10-Jan-22 2 2022
11-Jan-22 2 2022
12-Jan-22 2 2022
13-Jan-22 2 2022
14-Jan-22 2 2022
15-Jan-22 2 2022
16-Jan-22 2 2022
17-Jan-22 3 2022
18-Jan-22 3 2022
19-Jan-22 3 2022
20-Jan-22 3 2022
21-Jan-22 3 2022
22-Jan-22 3 2022
23-Jan-22 3 2022
24-Jan-22 4 2022
25-Jan-22 4 2022
26-Jan-22 4 2022
27-Jan-22 4 2022
28-Jan-22 4 2022
29-Jan-22 4 2022
30-Jan-22 4 2022
I want to pull the last 4 weeks prior to the current week AND the same 4 weeks of the year before. Please see example 1. This works fine when all 4 weeks are within the same year. But when it comes to the beginning of a year when 1 or more weeks are in the current year but the other are in the previous year, I am not able to get the desired output below:
FISCAL_YEAR_VALUE FISCAL_WEEK
2020 51
2020 52
2021 2
2021 1
2021 52
2021 53
2022 1
2022 2
The code I have is below. I am using the date of 21-JAN-22 as an example:
SELECT
FISCAL_YEAR_VALUE,
FISCAL_WEEK
FROM TABLE_NAME
WHERE FISCAL_YEAR_VALUE IN (SELECT *
FROM (WITH T AS (
SELECT DISTINCT FISCAL_YEAR_VALUE
FROM TABLE_NAME
WHERE TRUNC(DATE_VALUE) <= TRUNC(TO_DATE('21-JAN-22'))--TEST DATE
ORDER BY FISCAL_YEAR_VALUE DESC
FETCH NEXT 2 ROWS ONLY
)
SELECT FISCAL_YEAR_VALUE
FROM T ORDER BY FISCAL_YEAR_VALUE
)
)
AND FISCAL_WEEK IN (SELECT *
FROM (WITH T AS (
SELECT DISTINCT FISCAL_WEEK, FISCAL_YEAR_VALUE
FROM TABLE_NAME
WHERE TRUNC(DATE_VALUE) <= TRUNC(TO_DATE('21-JAN-22'))--TEST DATE
ORDER BY FISCAL_YEAR_VALUE DESC, FISCAL_WEEK DESC
OFFSET 1 ROWS
FETCH NEXT 4 ROWS ONLY
)
SELECT FISCAL_WEEK
FROM T ORDER BY FISCAL_YEAR_VALUE, FISCAL_WEEK
)
)
GROUP BY FISCAL_YEAR_VALUE, FISCAL_WEEK
ORDER BY FISCAL_YEAR_VALUE, FISCAL_WEEK
Output of the code is:
FISCAL_YEAR_VALUE FISCAL_WEEK
2021 2
2021 1
2021 52
2021 53
2022 1
2022 2
As you can see, the last 2 weeks of year 2020 are not included. Please see example 2. How can I also include this exception in the code to make it dynamic? Any help would be greatly appreciated!
To find the values this year, you can use:
SELECT DISTINCT fiscal_year_value, fiscal_week
FROM table_name
WHERE date_value < TRUNC(SYSDATE, 'IW')
AND date_value >= TRUNC(SYSDATE, 'IW') - INTERVAL '28' DAY
To find the values from the previous year, you can find the maximum fiscal week from this year and subtract 1 from the year and then use that to find the upper bound of the date_value for last fiscal year and, given that can use a similar range for last year:
WITH this_year (fiscal_year_value, fiscal_week) AS (
SELECT fiscal_year_value, fiscal_week
FROM table_name
WHERE date_value < TRUNC(SYSDATE, 'IW')
AND date_value >= TRUNC(SYSDATE, 'IW') - INTERVAL '28' DAY
),
max_last_year (max_date_value) AS (
SELECT MAX(date_value) + INTERVAL '1' DAY
FROM table_name
WHERE (fiscal_year_value, fiscal_week) IN (
SELECT fiscal_year_value - 1, fiscal_week
FROM this_year
ORDER BY fiscal_year_value DESC, fiscal_week DESC
FETCH FIRST ROW ONLY
)
)
SELECT fiscal_year_value, fiscal_week
FROM this_year
UNION
SELECT t.fiscal_year_value, t.fiscal_week
FROM table_name t
INNER JOIN max_last_year m
ON ( t.date_value < m.max_date_value
AND t.date_value >= m.max_date_value - INTERVAL '28' DAY);
Which, for the sample data:
Create Table table_name(DATE_VALUE DATE, FISCAL_WEEK INT, FISCAL_YEAR_VALUE INT);
INSERT INTO table_name (date_value, fiscal_week, fiscal_year_value)
SELECT DATE '2019-12-30' + LEVEL - 1, CEIL(LEVEL/7), 2020
FROM DUAL
CONNECT BY LEVEL <= 7 * 52
UNION ALL
SELECT DATE '2020-12-28' + LEVEL - 1, CEIL(LEVEL/7), 2021
FROM DUAL
CONNECT BY LEVEL <= 7 * 53
UNION ALL
SELECT DATE '2022-01-03' + LEVEL - 1, CEIL(LEVEL/7), 2022
FROM DUAL
CONNECT BY LEVEL <= 7 * 52;
Outputs:
FISCAL_YEAR_VALUE
FISCAL_WEEK
2022
38
2022
39
2022
40
2022
41
2021
38
2021
39
2021
40
2021
41
And if today's date was 2022-01-01, would output:
FISCAL_YEAR_VALUE
FISCAL_WEEK
2021
52
2021
53
2022
1
2022
2
2020
51
2020
52
2021
1
2021
2
There may be a simpler method but without any knowledge of how you calculate a fiscal year that is not immediately possible.
fiddle

SQL Custom unique Ordering with repeated sequence

I have a datetime column (data type of timestamp without time zone) named time. I can best explain my issue with a example:
Example I've the following data in this column (pretifying timestamp for this example)
ID TIME
1 1 Mar 2022 - 1PM
2 1 Mar 2022 - 2PM
3 1 Mar 2022 - 1PM
4 1 Mar 2022 - 3PM
5 1 Mar 2022 - 2PM
6 2 Mar 2022 - 2PM
7 2 Mar 2022 - 1PM
8 2 Mar 2022 - 3PM
9 2 Mar 2022 - 1PM
10 1 Mar 2022 - 3PM
11 2 Mar 2022 - 2PM
12 2 Mar 2022 - 3PM
13 3 Mar 2022 - 4PM
14 3 Mar 2022 - 3PM
15 3 Mar 2022 - 3PM
16 3 Mar 2022 - 4PM
If i do ORDER BY time, i get the following result:
ID TIME
1 1 Mar 2022 - 1PM
3 1 Mar 2022 - 1PM
2 1 Mar 2022 - 2PM
5 1 Mar 2022 - 2PM
4 1 Mar 2022 - 3PM
10 1 Mar 2022 - 3PM
7 2 Mar 2022 - 1PM
9 2 Mar 2022 - 1PM
6 2 Mar 2022 - 2PM
11 2 Mar 2022 - 2PM
8 2 Mar 2022 - 3PM
12 2 Mar 2022 - 3PM
14 3 Mar 2022 - 3PM
15 3 Mar 2022 - 3PM
13 3 Mar 2022 - 4PM
16 3 Mar 2022 - 4PM
But i want the result in this way:
ID TIME
1 1 Mar 2022 - 1PM
2 1 Mar 2022 - 2PM
4 1 Mar 2022 - 3PM
13 3 Mar 2022 - 4PM
3 1 Mar 2022 - 1PM
5 1 Mar 2022 - 2PM
10 1 Mar 2022 - 3PM
16 3 Mar 2022 - 4PM
7 2 Mar 2022 - 1PM
6 2 Mar 2022 - 2PM
8 2 Mar 2022 - 3PM
9 2 Mar 2022 - 1PM
11 2 Mar 2022 - 2PM
12 2 Mar 2022 - 3PM
14 3 Mar 2022 - 3PM
13 3 Mar 2022 - 4PM
As you can see first 4 rows have unique timestamp and the sequence should repeat based on Time (1PM, 2PM, 3PM).
How can we do this in SQL? I'm using postresql as my DB. I'm using Rails for my Backend.
EDIT:
Have added more context to example to explain my scenario.
One way you can try to use ROW_NUMBER window function with REPLACE function
SELECT time
FROM (
SELECT *,REPLACE(time,'PM','') val,
ROW_NUMBER() OVER(PARTITION BY REPLACE(time,'PM','')) rn
FROM T
) t1
ORDER BY rn,val
For example, sequence of the col a
with tbl(a, othercol) as
(
SELECT 1,1 UNION ALL
SELECT 1,2 UNION ALL
SELECT 1,3 UNION ALL
SELECT 2,4 UNION ALL
SELECT 2,5 UNION ALL
SELECT 2,6 UNION ALL
SELECT 3,7 UNION ALL
SELECT 3,8 UNION ALL
SELECT 3,9
),
cte as (
SELECT *, row_number() over(partition by a order by a) rn
from tbl
)
select a, othercol
from cte
order by rn, a
The problem you have at hand is a direct result of not choosing the correct data type for the values you store.
To get the sorting correct, you need to convert the string to a proper time value. There is no to_time() function in Postgres, but you can convert it to a timestamp then cast it to a time:
order by to_timestamp("time", 'hham')::time
You should fix your database design and convert that column to a proper time type. Which will also prevent storing invalid values ('3 in the afternoon' or '128foo') in that column

Error building a SQL query while trying to get order by a day for a period of week

I have an ASP.NET Core application, through controller endpoint I pass #by and #period string values to the SQL query.
#by takes one of the following values: day, week
#period takes one of the following values: week, month, year
When the #period is month or year, then #by is a week, else it's a day.
I have the following working query when the #period is a month or a year:
SELECT
l.region_id AS region_id,
'Region ' + r.region_desc AS region_name,
MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)) AS date_pos,
CONVERT(VARCHAR(20), MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)), 107) AS display_date_pos
FROM
incent_summary s
INNER JOIN
location l ON s.store_num = l.store_num
INNER JOIN
region r ON l.region_id = r.region_id
WHERE
s.pos_date >= DATEADD(day, #period , CONVERT(date, GETDATE()))
AND s.pos_date <= GETDATE()
GROUP BY
DATEPART (#by, s.pos_date),
l.region_id, r.region_desc
ORDER BY
DATEPART (#by, pos_date),
l.region_id, r.region_desc
The issue is when the #period is a week, #by is day, and the statement
MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)) AS date_pos
returns the same day for all the 7 days.
Sample output when #period = year and #by = week:
region_id region_name date_pos display_date_pos
---------------------------------------------------------------------
34 Region 43 2019-12-29 00:00:00.000 Dec 29, 2019
50 Region 22 2019-12-29 00:00:00.000 Dec 29, 2019
34 Region 43 2020-01-05 00:00:00.000 Jan 05, 2020
50 Region 22 2020-01-05 00:00:00.000 Jan 05, 2020
34 Region 43 2020-01-12 00:00:00.000 Jan 12, 2020
50 Region 22 2020-01-12 00:00:00.000 Jan 12, 2020
34 Region 43 2020-01-19 00:00:00.000 Jan 19, 2020
50 Region 22 2020-01-19 00:00:00.000 Jan 19, 2020
34 Region 43 2020-01-26 00:00:00.000 Jan 26, 2020
50 Region 22 2020-01-26 00:00:00.000 Jan 26, 2020
Sample output when #period = week and #by = day:
region_id region_name date_pos display_date_pos
--------------------------------------------------------------------
34 Region 43 2020-07-12 00:00:00.000 Jul 12, 2020
50 Region 22 2020-07-12 00:00:00.000 Jul 12, 2020
34 Region 43 2020-07-12 00:00:00.000 Jul 12, 2020
50 Region 22 2020-07-12 00:00:00.000 Jul 12, 2020
34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020
50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020
34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020
50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020
34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020
50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020
How can I fix this?
SELECT
DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)
Will always return the first day of the week because the logic is: "subtract from my date the number of days from sunday and add 1."
Sunday: 1 - 1 + 1 = 1 = Sunday
Monday: 2 - 2 + 1 = 1 = Sunday
.
.
.
Saturday: 7 - 7 + 1 = Sunday
That's fine when you want the first Sunday of the year/month/whatever. But the first sunday of every week is always... sunday. But in this case you really just need to take the MIN(s.pos_date) if #period is week.
There's probably some crazy way to do this in a single statement using quaternions or something else super mathy, but it's easiest to just use a case statement:
MIN
(
CASE
WHEN '#by' = 'day' THEN s.pos_date
ELSE DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)
END
)
I'm not a C# programmer so I can't tell you the exact way to make sure the string DAY is passed to the query as "DAY" but I'm sure you can handle that part.
ALSO IMPORTANT The datepart "day" is day of month, so if you're going to possibly have a span greater than one month (but under a year), use dayofyear.

ordering a column based on calculated value

I have below values in a column
Q1 2018
Q2 2018
Q3 2018
Q4 2018
feb 2018
mar 2018
Q1 2019
Q2 2019
Q3 2019
jan 2018
sep 2018
dec 2018
jan 2019
feb 2019
mar 2019
I have above values which gets calculated on some parameters. for some data this value comes with month and for some this comes as quarter.
Is there any way to order them using order by when all the values are on the same column, means monthly values and quarterly value both should be sorted.
output should be like
Q1 2018
Q2 2018
Q3 2018
Q4 2018
Q1 2019
Q2 2019
Q3 2019
jan 2018
feb 2018
mar 2018
sep 2018
dec 2018
jan 2019
feb 2019
mar 2019
I think this does what you want:
order by len(col), -- put the quarters first
substr(col, 4), -- order by year
(case when col not like 'Q%'
then to_date(col, 'MON YYYY')
end), -- order months by date
col -- order quarters by quarter

Changing date format for PostgreSQL query result

I have following query
select substring(listDate from '............$') as v_end_date,
substring(listDate from '^...............') as v_start_date
Now listDate value can be like
select substring('06 Jan 2014 to 12 Jan 2014,
13 Jan 2014 to 19 Jan 2014,
20 Jan 2014 to 26 Jan 2014
' from '............$') as v_end_date,
substring('06 Jan 2014 to 12 Jan 2014,
13 Jan 2014 to 19 Jan 2014,
20 Jan 2014 to 26 Jan 2014
' from '^............') as v_start_date
Above query results in
V_END_DATE V_START_DATE
26 Jan 2014 06 Jan 2014
Now I need to have v_end_date and v_start_date format like yyyy-mm-dd and like
Mon 06 Jan 2014.
Convert your string to an actual date with to_date() and use to_char() to get pretty much any format you like.
Demo:
SELECT to_char(day, 'YYYY-MM-DD') AS format1
, to_char(day, 'Dy DD Mon YYYY') AS format2
FROM (SELECT to_date('26 Jan 2014', 'DD Mon YYYY') AS day) sub