Create a product base table by date - oracle

I want to create a query that returns number of active products by colander date. I don’t have calendar dim table in database.
Current table
Product_name|prod_id|start_date|end_date
P1|1234|02/01/2020|30/05/2020
P1|2345|02/01/2020|31/12/9999
P1|3456|03/01/2020|31/12/9999
Expected Result
Calander_date|product_name|active_base
01/01/2020|P1|0
02/01/2020|P1|2
03/01/2020|P1|3
01/06/2020|P1|2

Create your own calendar, then - either in the database, as a "real" table (row generator technique helps here), or as a CTE (as I did in the following example):
SQL> with
2 test (product_name, prod_id, start_date, end_date) as
3 -- you have that table; don't type that
4 (select 'P1', 1234, date '2020-01-02', date '2020-05-30' from dual union all
5 select 'P1', 2345, date '2020-01-02', date '9999-12-31' from dual union all
6 select 'P1', 3456, date '2020-01-03', date '9999-12-31' from dual
7 ),
8 calendar (datum) as
9 -- create your own calendar table
10 (select date '2020-01-01' + level - 1
11 from dual
12 connect by level <= 10000 --> number of days you want in calendar
13 )
14 -- final query - join!
15 select c.datum,
16 t.product_name,
17 count(*) active_base
18 from calendar c join test t on c.datum between t.start_date and t.end_date
19 group by c.datum, t.product_name
20 order by c.datum;
DATUM PR ACTIVE_BASE
---------- -- -----------
02/01/2020 P1 2
03/01/2020 P1 3
04/01/2020 P1 3
05/01/2020 P1 3
06/01/2020 P1 3
<snip>
28/05/2020 P1 3
29/05/2020 P1 3
30/05/2020 P1 3
31/05/2020 P1 2
01/06/2020 P1 2
02/06/2020 P1 2
<snip>

Related

Oracle: Return the specific records based on one column date

I have a database structure as below.
period
month
start_date
1
April
2022-04-01
2
May
2022-05-07
3
June
2022-06-04
4
July
2022-07-02
5
August
2022-08-06
6
September
2022-09-03
7
October
2022-10-01
8
November
2022-11-05
9
December
2022-12-03
10
January
2023-01-01
11
February
2023-02-04
12
March
2023-03-04
End date of the year is 2023-03-31.
Based on current_date, how do I select the query to return where the current date falls under Period 6.
My current query as below.
SELECT period FROM table1 as a
WHERE
a.start_date = (SELECT MAX(start_date) FROM table1 as b WHERE
b.start_date <=current_date) and ROWNUM <= 1
Is there anyway to improve the current query which to avoid using subquery?
Today is September 22nd, so - would this do?
Some sample data:
SQL> with test (period, month, start_date) as
2 (select 1, 'april' , date '2022-04-01' from dual union all
3 select 5, 'august' , date '2022-08-06' from dual union all
4 select 6, 'september', date '2022-09-03' from dual union all
5 select 7, 'october' , date '2022-10-01' from dual union all
6 select 10, 'january' , date '2023-01-01' from dual union all
7 select 12, 'march' , date '2023-03-04' from dual
8 ),
Query begins here:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= sysdate
14 )
15 select period
16 from temp
17 where rn = 1
18 /
PERIOD
----------
6
SQL>
It still uses a subquery (or a CTE, as in my example), but - as opposed to your approach, it selects from the source table only once, so performance should be improved.
A few more tests: instead of sysdate (line #13), presume that today is September 2nd (which means that it is in period #5):
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-09-02'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Or, if today were August 7th:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-08-07'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Your rule for the start_date appears to be:
If the month is January (first month of the calendar year) or April (typically, first month of the financial year) then use the 1st of that month;
Otherwise use the 1st Saturday of the month.
If that is the case then you can calculate the start date of the next month and use the query:
SELECT *
FROM table1
WHERE start_date <= SYSDATE
AND SYSDATE < CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(start_date, 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN TRUNC(ADD_MONTHS(start_date, 1), 'MM')
ELSE NEXT_DAY(TRUNC(ADD_MONTHS(start_date, 1), 'MM') - 1, 'SATURDAY')
END
Then, for your sample data:
CREATE TABLE table1 (
period NUMBER(2,0),
month VARCHAR2(9)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(start_date, 'FXMonth', 'NLS_DATE_LANGUAGE=English')
AS VARCHAR2(9)
)
),
start_date DATE
);
INSERT INTO table1 (period, start_date)
SELECT LEVEL,
CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(DATE '2022-04-01', LEVEL - 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN ADD_MONTHS(DATE '2022-04-01', LEVEL - 1)
ELSE NEXT_DAY(ADD_MONTHS(DATE '2022-04-01', LEVEL - 1) - 1, 'SATURDAY')
END
FROM DUAL
CONNECT BY LEVEL <= 12;
Outputs:
PERIOD
MONTH
START_DATE
6
September
2022-09-03 00:00:00
fiddle

Query to select rows based on time minus time

I want to build a query on oracle 11g R2 ,I have the following table :
let's take the first row as an example: first row F11 is 1 pm and T11 is 3 pm ,, the difference between 1 pm and 3 pm is 120 minute ,I have a variable X ,let's say X=10, what I want is to create a query to select rows between 1 pm and 3 pm divided by X , so I should have 12 row.
I think it is a hierarchical query you're looking for. Those 12 rows would represent f11 (starting time) plus 120 minutes divided by the x value (which is 10) so - that's 12 minutes.
If that's so, here you go:
SQL> var x number;
SQL> exec :x := 10;
PL/SQL procedure successfully completed.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:Mi:ss';
Session altered.
SQL> with test (id, f11, t11) as
2 (select 1,
3 to_date('28.03.2022 01:00', 'dd.mm.yyyy hh24:mi'),
4 to_date('28.03.2022 03:00', 'dd.mm.yyyy hh24:mi')
5 from dual union all
6 --
7 select 2,
8 to_date('29.11.2021 01:00', 'dd.mm.yyyy hh24:mi'),
9 to_date('29.11.2021 01:30', 'dd.mm.yyyy hh24:mi')
10 from dual
11 ),
12 temp as
13 (select id, f11, (t11 - f11) * (24 * 60) diff
14 from test
15 )
16 select id,
17 f11 + (diff / :x) / (24 * 60) * (column_value - 1) val
18 from temp
19 cross join table(cast(multiset(select level from dual
20 connect by level <= diff / :x
21 ) as sys.odcinumberlist))
22 order by id, val;
ID VAL
---------- -------------------
1 28.03.2022 01:00:00
1 28.03.2022 01:12:00
1 28.03.2022 01:24:00
1 28.03.2022 01:36:00
1 28.03.2022 01:48:00
1 28.03.2022 02:00:00
1 28.03.2022 02:12:00
1 28.03.2022 02:24:00
1 28.03.2022 02:36:00
1 28.03.2022 02:48:00
1 28.03.2022 03:00:00
1 28.03.2022 03:12:00
2 29.11.2021 01:00:00
2 29.11.2021 01:03:00
2 29.11.2021 01:06:00
15 rows selected.
SQL>

How to select the first 5 dates from each group and put them in a single column separated by comma in Oracle?

I have a table like this:
Division
Region
Date of Last Visit
1
2
11/20/2021
1
2
11/18/2021
1
7
10/18/2021
1
7
11/19/2021
2
2
11/17/2021
2
3
09/20/2021
2
3
10/20/2021
I want to write a query that groups by the division and region columns and gives me the last 5 dates for each group separated by commas in a single column. Something like this:
Division
Region
Date of Last Visit
Today
Days since last visit
1
2
11/20/2021, 11/18/2021
sysdate
sysdate - max(date of last visit)
1
7
10/18/2021, 11/19/2021
sysdate
sysdate - max(date of last visit)
2
2
11/17/2021
sysdate
sysdate - max(date of last visit)
2
3
9/20/2021, 10/20/2021
sysdate
sysdate - max(date of last visit)
The last two columns are custom calculated columns that I also need for the final output table. Any help would be greatly appreciated as I have tried a lot of things but I keep getting errors about it not being grouped properly, possibly because of the two extra columns at the end. But even without that, I am not sure how to fetch only the last 5 dates per group in oracle.
Thanks!
You want to filter the greatest-n-per-group using the ROW_NUMBER analytic function and then aggregate:
SELECT division,
region,
LISTAGG(TO_CHAR(date_of_last_visit, 'DD/MM/YYYY'), ',')
WITHIN GROUP (ORDER BY date_of_last_visit DESC)
AS date_of_last_visit,
SYSDATE AS today,
TRUNC(SYSDATE - MAX(date_of_last_visit)) AS days_since_last_visit
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY division, region
ORDER BY date_of_last_visit DESC) AS rn
FROM table_name t
)
WHERE rn <= 5
GROUP BY
division,
region
Which, for the sample data:
CREATE TABLE table_name (division, region, date_of_last_visit) as
select 1, 2, date '2021-11-20' from dual union all
select 1, 2, date '2021-11-18' from dual union all
select 1, 7, date '2021-10-18' from dual union all
select 1, 7, date '2021-11-19' from dual union all
select 2, 2, date '2021-11-17' from dual union all
select 2, 3, date '2021-09-20' from dual union all
select 2, 3, date '2021-10-20' from dual;
Outputs:
DIVISION
REGION
DATE_OF_LAST_VISIT
TODAY
DAYS_SINCE_LAST_VISIT
1
2
20/11/2021,18/11/2021
30-NOV-21
10
1
7
19/11/2021,18/10/2021
30-NOV-21
11
2
2
17/11/2021
30-NOV-21
13
2
3
20/10/2021,20/09/2021
30-NOV-21
41
db<>fiddle here
Here you go; read comments within code.
SQL> with test (division, region, datum) as
2 -- sample data
3 (select 1, 2, date '2021-11-20' from dual union all
4 select 1, 2, date '2021-11-18' from dual union all
5 select 1, 7, date '2021-10-18' from dual union all
6 select 1, 7, date '2021-11-19' from dual union all
7 select 2, 2, date '2021-11-17' from dual union all
8 select 2, 3, date '2021-09-20' from dual union all
9 select 2, 3, date '2021-10-20' from dual
10 ),
11 temp as
12 -- rank rows per division/region, sorted by date column in descending order
13 (select t.*,
14 rank() over (partition by division, region order by datum desc) rnk
15 from test t
16 )
17 -- select up to 5 last rows per division/region
18 select division, region,
19 listagg(datum, ', ') within group (order by datum) dates,
20 trunc(sysdate) today,
21 --
22 (select trunc(sysdate) - a.datum
23 from temp a
24 where a.division = t.division
25 and a.region = t.region
26 and a.rnk = 1) days_since
27 from temp t
28 where rnk <= 5
29 group by division, region
30 order by division, region;
DIVISION REGION DATES TODAY DAYS_SINCE
---------- ---------- ------------------------------ ---------- ----------
1 2 11/18/2021, 11/20/2021 11/30/2021 10
1 7 10/18/2021, 11/19/2021 11/30/2021 11
2 2 11/17/2021 11/30/2021 13
2 3 09/20/2021, 10/20/2021 11/30/2021 41
SQL>

I have one requirement where I have to show the records between specific date and time every day of one week

I have one requirement where I have to show the records between specific date and time every day in one week duration.
in one week duration( 2019-04-01 till 2019-04-06) ,for instance record of 2019-04-01 at 19 PM till 8 Am of 2019-04-02 ,and record of 2019-04-02 at 19 PM till 08 AM of 2019-04-03 and ...
would you please help me!
Use recursive query to create proper periods then join with your data or do it simpler with condition like here:
select callbegin, callerno
from table4
where callerno in ('7032','750')
and callbegin between timestamp '2019-04-01 19:00:00'
and timestamp '2019-04-06 08:00:00'
and ('19' <= to_char(callbegin, 'hh24') or to_char(callbegin, 'hh24') < '08');
demo
Here's how I understood the question.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi';
Session altered.
SQL> break on period;
SQL> with
2 data (id, datum) as
3 (select 1, to_date('01.04.2019 15:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 2, to_date('01.04.2019 20:00', 'dd.mm.yyyy hh24:mi') from dual union all -- 1st
5 select 3, to_date('02.04.2019 01:15', 'dd.mm.yyyy hh24:mi') from dual union all -- 1st perios
6 select 4, to_date('02.04.2019 11:00', 'dd.mm.yyyy hh24:mi') from dual union all
7 select 5, to_date('02.04.2019 23:15', 'dd.mm.yyyy hh24:mi') from dual union all -- 2nd period
8 select 6, to_date('03.04.2019 00:10', 'dd.mm.yyyy hh24:mi') from dual union all -- 2nd
9 select 7, to_date('04.04.2019 22:20', 'dd.mm.yyyy hh24:mi') from dual -- 3rd period
10 ),
11 test as
12 (select date '2019-04-01' dstart,
13 date '2019-04-06' dend
14 from dual
15 ),
16 inter as
17 (select dstart + level - 1 datum
18 from test
19 connect by level <= dend - dstart + 1
20 ),
21 from_to as
22 (select datum + 19/24 date_from,
23 lead(datum) over (order by datum) + 8/24 date_to
24 from inter
25 )
26 select f.date_From ||' - '|| f.date_to period,
27 d.id,
28 d.datum
29 from data d join from_to f on 1 = 1
30 where d.datum between f.date_from and f.date_to
31 order by f.date_From, d.id;
PERIOD ID DATUM
----------------------------------- ---------- ----------------
01.04.2019 19:00 - 02.04.2019 08:00 2 01.04.2019 20:00
3 02.04.2019 01:15
02.04.2019 19:00 - 03.04.2019 08:00 5 02.04.2019 23:15
6 03.04.2019 00:10
04.04.2019 19:00 - 05.04.2019 08:00 7 04.04.2019 22:20
SQL>
This is how to filter data by days and time by one week:
With date_list as (
Select
to_date(to_char( (sysdate - level), 'yyyymmdd') || '19', 'yyyymmddhh24') begin_time,
to_date(to_char( ((sysdate - level)+1), 'yyyymmdd') || '08', 'yyyymmddhh24') end_time
From dual connect by level <= 7
)
Select begin_time, your_table.*
From
your_table t1,
date_list t2
Where
t1.your_date between t2.begin_time and t2.end_time;

Oracle PIVOT a select statement

I would like to pivot a select statement.
Columns "Country", "Store" and "Sales" are given.
Now I would like to have an output like:
Store1 Store2 Store3
Country1 2342 2342 5675
Country2 5753 3274 7326
Country3 1543 4367 3367
So basically I need the salescount for every Store, for every Country.
The Input comes from (example):
Country: StoreNr: ProductSold:
Belgium 23 Car
Belgium 23 House
Netherland 23 Car
Output would be:
Store23
Belgium 2
Netherlands 1
If the number of stores is finite, you could use one of these approaches:
Using count() aggregate function combined with case expression:
-- sample of data. just for the sake of demonstration
SQL> with t1(Country, StoreNr, ProductSold) as(
2 select 'Belgium' , 23, 'Car' from dual union all
3 select 'Belgium' , 23, 'House' from dual union all
4 select 'Netherland', 23, 'Car' from dual union all
5 select 'Belgium' , 25, 'House' from dual
6 )
7 select country
8 , count(case
9 when StoreNr = 23
10 then 1
11 end) as storeNr_23
12 , count(case
13 when StoreNr = 25
14 then 1
15 end) as storeNr_25
16 from t1
17 group by country
18 ;
Result:
COUNTRY STORENR_23 STORENR_25
---------- ---------- ----------
Belgium 2 1
Netherland 1 0
Starting from Oracle 11g and up, the pivot operator as follows:
select *
from (Select country as country
, country as country_cnt
, StoreNr
from t1)
pivot( -- list all store numbers here
count(country_cnt) for storenr in ( 23 as StoreNr_23
, 25 as StoreNr_25)
)
Result:
COUNTRY STORENR_23 STORENR_25
---------- ---------- ----------
Belgium 2 1
Netherland 1 0

Resources