Sum of qty between two date ranges - oracle

I have two tables- Tab1 and Tab2.
Tab1 contains Item and Dates
|Item|Dates |
|I1 |06-30-2022|
|I1 |07-02-2022|
|I1 |07-05-2022|
Tab2 contains Item ,Qty and ItemAvailDate
|Item|Qty|ItemAvailDate|
|I1 |10 |06-30-2022|
|I1 |20 |07-01-2022|
|I1 |40 |07-02-2022|
|I1 |30 |07-03-2022|
|I1 |40 |07-04-2022|
|I1 |50 |07-05-2022|
I want the quantities from Tab2 to be summed up in the range of the dates in Tab1, i.e,
|Item|Dates |Total(Qty)|
|I1 |06-30-2022|30 |
|I1 |07-02-2022|110 |
|I1 |07-05-2022|50 |
Row 1 has sum of qty from first date in Tabl - 06-30-2022 till next date in Tab1 - 07-02-2022(excluding 07-02)
Row 2 has sum of qty from 07-02-2022 till 07-05-2022(excluding 07-05)
Row3 has sum of qty of 07-05-2022 as no other rows after this
All qty of Tab2 from the first date in Tab1 should be summed up till the next date in Tab1(excluding that date).
The dates should be partitioned by the items available in Tab1
I do not want to use Loops. Any easy logic using Analytical function of oracle ?
Thanks in Advance!

Use LEAD and a correlated sub-query:
WITH next_dates (item, dates, next_date) AS (
SELECT item,
dates,
LEAD(dates, 1, dates + 1) OVER (PARTITION BY item ORDER BY dates)
FROM tab1
)
SELECT item,
dates,
(
SELECT SUM(qty)
FROM tab2 t2
WHERE n.item = t2.item
AND n.dates <= t2.itemavaildate
AND t2.itemavaildate < n.next_date
) AS total
FROM next_dates n
Which, for the sample data:
CREATE TABLE Tab1 (Item, Dates) AS
SELECT 'I1', DATE '2022-06-30' FROM DUAL UNION ALL
SELECT 'I1', DATE '2022-07-02' FROM DUAL UNION ALL
SELECT 'I1', DATE '2022-07-05' FROM DUAL;
CREATE TABLE Tab2 (Item, Qty, ItemAvailDate) AS
SELECT 'I1', 10, DATE '2022-06-30' FROM DUAL UNION ALL
SELECT 'I1', 20, DATE '2022-07-01' FROM DUAL UNION ALL
SELECT 'I1', 40, DATE '2022-07-02' FROM DUAL UNION ALL
SELECT 'I1', 30, DATE '2022-07-03' FROM DUAL UNION ALL
SELECT 'I1', 40, DATE '2022-07-04' FROM DUAL UNION ALL
SELECT 'I1', 50, DATE '2022-07-05' FROM DUAL;
Outputs:
ITEM
DATES
TOTAL
I1
30-JUN-22
30
I1
02-JUL-22
110
I1
05-JUL-22
50
db<>fiddle here

In Oracle 12.1 and later, match_recognize can do quick work of such assignments. First UNION ALL the rows from both tables, assigning null quantity for rows from table 1, and keeping track of which table the rows come from in an additional column tbl. Then everything is ready for a trivial application of match_recognize:
select item, dates, total_qty
from (
select item, null as qty, dates, 1 as tbl from tab1
union all
select item, qty, itemavaildate, 2 as tbl from tab2
)
match_recognize (
partition by item
order by dates, tbl
measures t1.dates as dates, sum(qty) as total_qty
pattern ( t1 t2*)
define t1 as tbl = 1, t2 as tbl = 2
);

Related

Multiply with Previous Value from One colum in Oracle SQL

I have the following result, which is easily calculated in Excel, but how to do it in Oracle, the result is the following, based on a previous select and comes from one column,
Result from select Expected result
1.62590
0.60989 0.991620151
0.83859 0.831562742
the result is based on 1.62590 * 0.60989 = 0.991620151,
1.62590 * 0.60989 * 0.83859 = 0.831562742
You can use:
SELECT id,
result,
EXP(SUM(LN(result)) OVER (ORDER BY id)) AS expected
FROM table_name;
Note: Use any other column instead of id to give the appropriate ordering or, if your rows are already ordered, use the ROWNUM pseudo-column instad of id.
Which, for the sample data:
CREATE TABLE table_name (id, Result) AS
SELECT 1, 1.62590 FROM DUAL UNION ALL
SELECT 2, 0.60989 FROM DUAL UNION ALL
SELECT 3, 0.83859 FROM DUAL;
Outputs:
ID
RESULT
EXPECTED
1
1.6259
1.62590000000000000000000000000000000001
2
.60989
.9916201510000000000000000000000000000026
3
.83859
.8315627424270900000000000000000000000085
fiddle
One option is to use a recursive CTE; it, though, expects that sample data can be sorted, somehow, so I added the ID column which starts with 1, while other values are incremented by 1:
Sample data:
SQL> with
2 test (id, col) as
3 (select 1, 1.62590 from dual union all
4 select 2, 0.60989 from dual union all
5 select 3, 0.83859 from dual
6 ),
Query begins here:
7 product (id, col, prod) as
8 (select id, col, col
9 from test
10 where id = 1
11 union all
12 select t.id, t.col, t.col * p.prod
13 from test t join product p on p.id + 1 = t.id
14 )
15 select id,
16 round(prod, 10) result
17 from product;
ID RESULT
---------- ----------
1 1,6259
2 ,991620151
3 ,831562742
SQL>
You can use a MODEL clause:
SELECT *
FROM (SELECT ROW_NUMBER() OVER (ORDER BY id) AS rn, result FROM table_name)
MODEL
DIMENSION BY (rn)
MEASURES ( result, 0 AS expected)
RULES (
expected[rn] = result[cv()] * COALESCE(expected[cv()-1], 1)
)
order by rn;
Which, for the sample data:
CREATE TABLE table_name (id, Result) AS
SELECT 1, 1.62590 FROM DUAL UNION ALL
SELECT 2, 0.60989 FROM DUAL UNION ALL
SELECT 3, 0.83859 FROM DUAL;
Outputs:
RN
RESULT
EXPECTED
1
1.6259
1.6259
2
.60989
.991620151
3
.83859
.83156274242709
fiddle

group orders based on crossing date ranges

I need to group order together with crossing their date ranges only
scenario A.
order 1, 1.3.2020-30.6.2020
order 2, 1.5.2020-31.8.2020
order 3, 31.7.2020-31.10.2020
order 4, 31.7.2020-31.12.2020
so the output should be
order 1, order 2
order 2, order 3, order 4
order1,3,4 are not grouped because their ranges don't cross at all
scenario B.
same as above plus another order
order 5, 1.1.2020-31.12.2020
so output will be
order 1, order 2, order 5
order 2, order 3, order 4, order 5
I tried Self Join to check which start date falls in that range.
so in the range of order 1 falls only the start date of order 2 -> we have one group
then in the range of order 2 fall both start dates of order 3 and 4 -> we have second group
but then for order 3 falls start date of order 4 and opposite -> that will give another 2 groups but they are invalid because order 2 is crossing their date ranges as well and shoul be included as well and becuase there will be 3 douplicates we should display it just once as in the desired output but this approach will fail.
Thanks
the result of MATCH_RECOGNIZE solution is incorrent because order 5 should be in both groups
I use some analitycal functions to solve this:
-- create table
Create table cross_dates (order_id number, start_date date , end_date date);
-- insert dates
insert into cross_dates values( 1, to_date('01.03.2020', 'dd.mm.yyyy'), to_date('30.06.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 2, to_date('01.05.2020', 'dd.mm.yyyy'), to_date( '31.08.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 3, to_date('31.07.2020', 'dd.mm.yyyy'), to_date( '31.08.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 4, to_date('31.07.2020', 'dd.mm.yyyy'), to_date( '31.10.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 5, to_date('01.01.2020', 'dd.mm.yyyy'), to_date( '31.12.2020', 'dd.mm.yyyy'));
-- SQL
select 'Order '|| min_order_id ||': ', listagg( order_id, ',') within group (order by order_id) list
from (
select distinct min_order_id, order_id from (
with dates (cur_date, end_date, order_id, start_date) as (
select start_date, end_date, order_id, start_date
from cross_Dates
union all
select cur_date + 1, end_date, order_id,start_date
from dates
where cur_date < end_date )
select d.order_id,
min(d.order_id) over(partition by greatest(d.start_date, cd.start_date)) min_order_id
from dates d, cross_Dates cd
where d.cur_date between cd.start_date and cd.end_date ))
group by min_order_id
having count(*) > 1;
Result:
Order 1: 1,2,5
Order 2: 2,3,4,5
-- add new column and update old records
alter table cross_dates add (item varchar2(1));
update cross_dates set item = 'A';
-- insert new records B
insert into cross_dates values( 1, to_date('01.01.2020', 'dd.mm.yyyy'), to_date( '30.06.2020', 'dd.mm.yyyy'), 'B');
insert into cross_dates values( 1, to_date('01.07.2020', 'dd.mm.yyyy'), to_date( '31.12.2020', 'dd.mm.yyyy'), 'B');
My assumption:
A and B are separate orders, not going in same groups even when crossing
order 1 B - has two records as a continuations - in my understanding counts like one order : order 1 B 01.01.2020 - 21.12.2020
If my assumption are correct the SQL could look like this:
select distinct min_order_id, order_id, item from (
with dates (cur_date, end_date, order_id, start_date, item) as (
select start_date, end_date, order_id, start_date, item
from cross_Dates
union all
select cur_date + 1, end_date, order_id,start_date, item
from dates
where cur_date < end_date )
select d.order_id, d.item,
min(d.order_id) over(partition by greatest(d.start_date, cd.start_date),d.item) min_order_id
from dates d, cross_Dates cd
where d.cur_date between cd.start_date and cd.end_date and d.item = cd.item )
order by item, min_order_id;
Result:
MIN_ORDER_ID ORDER_ID I
1 1 A
1 2 A
1 5 A
2 2 A
2 3 A
2 4 A
2 5 A
5 5 A
1 1 B
If my assumption are not ok please provide me what result should look like i this case.
:)
You can use MATCH_RECOGNIZE to find groups where the next value's start date is before, or equal to, the end date of all the previous values in the group. Then you can aggregate and exclude groups that would be entirely contained in another group:
WITH groups ( id, ids, start_date, end_date ) AS (
SELECT id,
LISTAGG( grp_id, ',' ) WITHIN GROUP ( ORDER BY start_date ),
MIN( start_date ),
MIN( end_date )
FROM (
SELECT t.id,
x.id AS grp_id,
x.start_date,
x.end_date
FROM table_name t
INNER JOIN table_name x
ON (
x.start_date >= t.start_date
AND x.start_date <= t.end_date
)
)
MATCH_RECOGNIZE (
PARTITION BY id
ORDER BY start_date
MEASURES
MATCH_NUMBER() AS mno
ALL ROWS PER MATCH
PATTERN ( FIRST_ROW GROUPED_ROWS* )
DEFINE GROUPED_ROWS AS (
GROUPED_ROWS.start_date <= MIN( end_date )
)
)
WHERE mno = 1
GROUP BY id
)
SELECT id,
ids
FROM groups g
WHERE NOT EXISTS (
SELECT 1
FROM groups x
WHERE g.ID <> x.ID
AND x.start_date <= g.start_date
AND g.end_date <= x.end_date
)
Which for the sample data:
CREATE TABLE table_name ( id, start_date, end_date ) AS
SELECT 'order 1', DATE '2020-03-01', DATE '2020-06-30' FROM DUAL UNION ALL
SELECT 'order 2', DATE '2020-05-01', DATE '2020-08-31' FROM DUAL UNION ALL
SELECT 'order 3', DATE '2020-07-31', DATE '2020-10-31' FROM DUAL UNION ALL
SELECT 'order 4', DATE '2020-07-31', DATE '2020-12-31' FROM DUAL;
Outputs:
ID | IDS
:------ | :----------------------
order 2 | order 2,order 3,order 4
order 1 | order 1,order 2
I you then:
INSERT INTO table_name ( id, start_date, end_date )
VALUES ( 'order 5', DATE '2020-01-01', DATE '2020-12-31' );
The output would be:
ID | IDS
:------ | :----------------------
order 2 | order 2,order 3,order 4
order 5 | order 5,order 1,order 2
db<>fiddle here

Oracle 11g - How to calculate the value of a number in range minimum or max

i need a help to get solution to my problem, Please.
I have a table like this :
ID Number
|6 |20.90 |
|7 |45.00 |
|8 |52.00 |
|9 |68.00 |
|10 |120.00 |
|11 |220.00 |
|12 |250.00 |
The first range is 0 - 20.90.
When the value is in the half, the value id is for the max range.
When i got value 20.91, i want to get "ID = 6".
If the value is 31.00, i want to get "ID = 6"
If the value is
33.95, i want to get "ID = 7".
if the value is 44.99, i want to get ID = 7
How i can do it? Is there a function that will do what I need?
If you want the record with a number that is closest to your input, then you can use this:
select *
from (
select *
from mytable
order by abs(number - my_input_number), id
)
where rownum < 2
The inner query selects all records, but orders them by the distance they have from your input number. This distance can be calculated with number - my_input_number. But that could be negative, so we take the absolute value of that. This result is not output; it is just used to order by. So records with smaller distances will come first.
Now we need just the first of those records, and that is what the outer query does with the typical Oracle reserved word rownum: it represents a sequence number for every record of the final result set (1, 2, 3, ...). The where clause will effectively filter away all records we do not want to see, leaving only one (with smallest distance).
As mathguy suggested in comments, the order by now also has a second value to order by in case the input value is right at the mid point between the two closest records. In that case the record with the lowest id value will be chosen.
This is a good illustration of the power of analytic functions:
with mytable ( id, value ) as (
select 6, 20.90 from dual union all
select 7, 45.00 from dual union all
select 8, 52.00 from dual union all
select 9, 68.00 from dual union all
select 10, 120.00 from dual union all
select 11, 220.00 from dual union all
select 12, 250.00 from dual
),
inputs ( x ) as (
select 0.00 from dual union all
select 20.91 from dual union all
select 31.00 from dual union all
select 33.95 from dual union all
select 44.99 from dual union all
select 68.00 from dual union all
select 32.95 from dual union all
select 400.11 from dual
)
-- End of test data (not part of the solution). SQL query begins BELOW THIS LINE
select val as x, new_id as closest_id
from (
select id, val,
last_value(id ignore nulls) over (order by val desc) as new_id
from (
select id, (value + lead(value) over (order by value))/2 as val
from mytable
union all
select null, x
from inputs
)
)
where id is null
order by x -- if needed
;
Output:
X CLOSEST_ID
------ ----------
0 6
20.91 6
31 6
32.95 6
33.95 7
44.99 7
68 9
400.11 12

Find highest and lowest selling item in a table

I have two tables as follows--
ORDERS
create table orders (
ono number(5) not null primary key,
cno number(5) references customers,
eno number(4) references employees,
received date,
shipped date);
ODETAILS
create table odetails (
ono number(5) not null references orders,
pno number(5) not null references parts,
qty integer check(qty > 0),
primary key (ono,pno));
ODETAILS Table
Now I'm trying to figure out the highest and lowest selling product. Logically PNO 10601 which has the highest QTY of 4 is the highest selling product. the following query returns the highest selling product.
SELECT PNO FROM
(SELECT od.PNO, SUM(od.QTY) AS TOTAL_QTY
FROM ODETAILS od
GROUP BY od.PNO
ORDER BY SUM(od.QTY) DESC)
WHERE ROWNUM =1
--Thanks to Bob Jarvis
How do I add a DATE WHERE clause to the SQL above so that I can find out the highest selling product for a given month(lets say DECEMBER) ? The DATE that I'm referring to is from ORDERS table and RECEIVED attribute. I guess I need to join the tables first as well
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table orders (
ono number(5) not null primary key,
cno number(5),
eno number(4),
received date,
shipped date
);
INSERT INTO orders
SELECT 1020, 1, 1, DATE '2015-12-21', NULL FROM DUAL UNION ALL
SELECT 1021, 1, 1, DATE '2015-12-20', DATE '2015-12-20' FROM DUAL UNION ALL
SELECT 1022, 1, 1, DATE '2015-12-18', DATE '2015-12-20' FROM DUAL UNION ALL
SELECT 1023, 1, 1, DATE '2015-12-21', NULL FROM DUAL UNION ALL
SELECT 1024, 1, 1, DATE '2015-12-20', DATE '2015-12-20' FROM DUAL;
create table odetails (
ono number(5) not null references orders(ono),
pno number(5) not null,
qty integer check(qty > 0),
primary key (ono,pno)
);
INSERT INTO odetails
SELECT 1020, 10506, 1 FROM DUAL UNION ALL
SELECT 1020, 10507, 1 FROM DUAL UNION ALL
SELECT 1020, 10508, 2 FROM DUAL UNION ALL
SELECT 1020, 10509, 3 FROM DUAL UNION ALL
SELECT 1021, 10601, 4 FROM DUAL UNION ALL
SELECT 1022, 10601, 1 FROM DUAL UNION ALL
SELECT 1022, 10701, 1 FROM DUAL UNION ALL
SELECT 1023, 10800, 1 FROM DUAL UNION ALL
SELECT 1024, 10900, 1 FROM DUAL;
Query 1 - The onoand pnos for the pno which has sold the maximum total quantity in December 2015:
SELECT ono,
pno,
TOTAL_QTY
FROM (
SELECT q.*,
RANK() OVER ( ORDER BY TOTAL_QTY DESC ) AS rnk
FROM (
SELECT od.ono,
od.PNO,
SUM( od.QTY ) OVER ( PARTITION BY od.PNO ) AS TOTAL_QTY
FROM ODETAILS od
INNER JOIN
orders o
ON ( o.ono = od.ono )
WHERE TRUNC( o.received, 'MM' ) = DATE '2015-12-01'
-- WHERE EXTRACT( MONTH FROM o.received ) = 12
) q
)
WHERE rnk = 1
Change the WHERE clause to get the results for any December rather than just December 2015.
Results:
| ONO | PNO | TOTAL_QTY |
|------|-------|-----------|
| 1021 | 10601 | 5 |
| 1022 | 10601 | 5 |
Query 2 - The ono and pnos for the items which have sold the maximum quantity in a single order in December 2015:
SELECT ono,
pno,
qty
FROM (
SELECT od.*,
RANK() OVER ( ORDER BY od.qty DESC ) AS qty_rank
FROM ODETAILS od
INNER JOIN
orders o
ON ( o.ono = od.ono )
WHERE TRUNC( o.received, 'MM' ) = DATE '2015-12-01'
-- WHERE EXTRACT( MONTH FROM o.received ) = 12
)
WHERE qty_rank = 1
Change the WHERE clause to get the results for any December rather than just December 2015.
Results:
| ONO | PNO | QTY |
|------|-------|-----|
| 1021 | 10601 | 4 |
... where received between to_date('12/01/2015','MM/DD/YYYY') and to_date('12/31/2015','MM/DD/YYYY')
I believe I have solved it!
SELECT PNO
FROM (SELECT OD.PNO, SUM(OD.QTY) AS TOTAL_QTY
FROM ODETAILS OD INNER JOIN ORDERS ON OD.ONO = ORDERS.ONO
WHERE EXTRACT(MONTH FROM ORDERS.RECEIVED) = &MONTH_NUMBER
GROUP BY OD.PNO
ORDER BY SUM(OD.QTY) DESC)
WHERE ROWNUM =1;
You can add some to_char calls to your query on the date columns to parse out year and month, or just month if you want all years divided by month (month and year seems more useful), then add that to your where clause. See my self-contained example:
with odetails as
(
select 1 as ono, 1 as pno, 4 as qty from dual
union all
select 1 as ono, 2 as pno, 1 as qty from dual
union all
select 1 as ono, 3 as pno, 2 as qty from dual
union all
select 1 as ono, 4 as pno, 1 as qty from dual
union all
select 2 as ono, 2 as pno, 1 as qty from dual
union all
select 2 as ono, 3 as pno, 2 as qty from dual
),
orders as
(
select 1 as ono, 1 as cno, 1 as eno, to_date('2015-10-12', 'YYYY-MM-DD') as received, to_date('2015-10-15', 'YYYY-MM-DD') as shipped from dual
union all
select 2 as ono, 1 as cno, 1 as eno, to_date('2015-11-12', 'YYYY-MM-DD') as received, to_date('2015-11-15', 'YYYY-MM-DD') as shipped from dual
)
select pno
from
(
select od.pno, Sum(od.qty) as total_qty, to_char(received, 'YYYY-MM') as year_month
from odetails od
join orders o
on o.ono = od.ono
group by od.pno, to_char(received, 'YYYY-MM')
order by Sum(od.qty) desc
)
where rownum = 1
and year_month = '2015-11'
;
This gives you PNO of 3, since it has the highest quantity in november of 2015.

Oracle sql retrive records based on maximum time

i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.

Resources