ORACLE Query Get Last ID Using MIN Based On Quantity Consumed By ID - oracle

I have Incoming Stock transaction data using Oracle:
ID | DESCRIPTION | PART_NO | QUANTITY | DATEADDED
TR5 | FG | P0025 | 5 | 06-SEP-2017 08:20:33 <-- just now added
TR4 | Test | TEST1 | 8 | 05-SEP-2017 15:11:15
TR3 | FG | GSDFGSG | 10 | 31-AUG-2017 16:26:04
TR2 | FG | GSDFGSG | 2 | 31-AUG-2017 16:05:39
TR1 | FG | GSDFGSG | 2 | 30-AUG-2017 16:30:16
And now I'm grouping that data to be:
TR_ID | PART_NO | TOTAL
TR1 | GSDFGSG | 14
TR4 | TEST1 | 8
TR5 | P0025 | 5 <-- just now added
Query Code:
SELECT MIN(TRANSACTION_EQUIPMENTID) as TR_ID,
PART_NO,
SUM(T.QUANTITY) AS TOTAL
FROM WA_II_TBL_TR_EQUIPMENT T
GROUP BY T.PART_NO
As you can see on that data and query code, I'm show TR_ID using MIN to get first ID on first transaction.
And now I have Outgoing transaction data:
Assume I try to get quantity 8
ID_FK | QUANTITY
TR1 | 8
And now I want to get last ID due to quantity 8 has been consumed
ID | DESCRIPTION | PART_NO | QUANTITY
TR3| FG | GSDFGSG | 10 <-- CONSUMED 4+2+2, TOTAL 8
TR2| FG | GSDFGSG | 2 <-- CONSUMED 2+2, TOTAL 4
TR1| FG | GSDFGSG | 2 <-- CONSUMED 2
As you can see above, TR1, TR2 has been consumed. Now I want the query
SELECT MIN(TRANSACTION_EQUIPMENTID) as TR_ID,
PART_NO,
SUM(T.QUANTITY) AS TOTAL
FROM WA_II_TBL_TR_EQUIPMENT T
GROUP BY T.PART_NO
get the last id is : TR3, due to TR1 & TR2 has been consumed.
How to do that in query?

Take minimum id where growing sum is greater than 8. Use analytic sum():
select min(id) id
from (select t.*,
sum(quantity) over (partition by part_no order by id) sq
from t
where part_no = 'GSDFGSG'
)
where sq >= 8
Test data, output:
create table t(ID varchar2(3), DESCRIPTION varchar2(5),
PART_NO varchar2(8), QUANTITY number(5), DATEADDED date);
insert into t values ('TR4', 'Test', 'TEST1', 8, timestamp '2017-09-05 15:11:15');
insert into t values ('TR3', 'FG', 'GSDFGSG', 10, timestamp '2017-08-31 16:26:04');
insert into t values ('TR2', 'FG', 'GSDFGSG', 2, timestamp '2017-08-31 16:05:39');
insert into t values ('TR1', 'FG', 'GSDFGSG', 2, timestamp '2017-08-30 16:30:16');
insert into t values ('TR5', 'FG', 'GSDFGSG', 3, timestamp '2017-08-31 17:00:00');
Edit:
Add part_no and total columns and group by clause:
select min(id) id, part_no, min(sq) total
from (select t.*,
sum(quantity) over (partition by part_no order by id) sq
from t
where part_no = 'GSDFGSG'
)
where sq >= 8
group by part_no
ID PART_NO TOTAL
--- -------- ----------
TR3 GSDFGSG 14

Related

why adding order by in the query changes the aggregate value?

Following vertica example from https://www.vertica.com/docs/11.0.x/HTML/Content/Authoring/AnalyzingData/SQLAnalytics/AnalyticFunctionsVersusAggregateFunctions.htm?tocpath=Analyzing%20Data%7CSQL%20Analytics%7C_____2
CREATE TABLE employees(emp_no INT, dept_no INT);
INSERT INTO employees VALUES(1, 10);
INSERT INTO employees VALUES(2, 30);
INSERT INTO employees VALUES(3, 30);
INSERT INTO employees VALUES(4, 10);
INSERT INTO employees VALUES(5, 30);
INSERT INTO employees VALUES(6, 20);
INSERT INTO employees VALUES(7, 20);
INSERT INTO employees VALUES(8, 20);
INSERT INTO employees VALUES(9, 20);
INSERT INTO employees VALUES(10, 20);
INSERT INTO employees VALUES(11, 20);
COMMIT;
If I run this query without order by, I get same count value for all rows
dbadmin#b006bc38a718(*)=>
select
emp_no
, dept_not
, count(*) over (partition by dept_not) as emp_count
from employees;
emp_no | dept_not | emp_count
--------+----------+-----------
6 | 20 | 6
7 | 20 | 6
8 | 20 | 6
9 | 20 | 6
10 | 20 | 6
11 | 20 | 6
1 | 10 | 2
4 | 10 | 2
2 | 30 | 3
3 | 30 | 3
5 | 30 | 3
(11 rows)
But if I add order by, I get incremental value
dbadmin#b006bc38a718(*)=>
select
emp_no
, dept_not
, count(*) over (partition by dept_not order by emp_no) as emp_count
from employees;
emp_no | dept_not | emp_count
--------+----------+-----------
2 | 30 | 1
3 | 30 | 2
5 | 30 | 3
1 | 10 | 1
4 | 10 | 2
6 | 20 | 1
7 | 20 | 2
8 | 20 | 3
9 | 20 | 4
10 | 20 | 5
11 | 20 | 6
(11 rows)
Time: First fetch (11 rows): 85.075 ms. All rows formatted: 85.139 ms
What is the affect of order by ? Why do I get incremental value?
If the window clause only contains PARTITION BY, it returns the total sum of the partition - for each row of the partition the same value.
If the window clause contains both PARTITION BY and ORDER BY, it returns the running count within the partition . So, using the ORDER BY expression, how many rows have been counted so far within the partition.
That's exactly how window functions work. They give you a whole world of possibilities ...
That happens because Vertica applies a default frame-clause which is defined as:
RANGE UNBOUNDED PRECEDING AND CURRENT ROW
So to get the result you want, you may want to add the frame clause as below after you ORDER BY in the OVER() clause:
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
This behaviour is documented as:
If the OVER clause omits specifying a window frame, the function creates a default window that extends from the current row to the first row in the current partition.
Link to doc

display avg of the last column on the last row in SQL query

Hello I am having trouble trying to figure out this particular question. using oracle SQL developer.
trying to figure out how a query so that it will display exactly like the below table/picture.
the last row of this query to display word AVERAGE: and show the average (of all values in the in the sixth column) of the percentage above min selling price for all the sales made. and all the remaining column to display "--------"
Code ProductName Title ShopID SalePrice %SoldAbove Min.SellPrice
1 Martin Robot 1 $49000 15%
2
3
4
--- ------ ---- ---- AVERAGE: 16.5%
below is last row of the output i am looking for. But i have no clue on how to produce the
"--------" in the remaining columns let alone AVERAGE: and the average of all the values in the sixth column of the last row.
in summary, the last row of the output should show the average (in the sixth column) of the percentage
sold above the minimum selling price for all the sales.
Use ROLLUP:
SELECT DECODE( GROUPING( Code ), 1, '----', code ) AS code,
DECODE( GROUPING( Code ), 1, '----', MAX(col1) ) AS Col1,
DECODE( GROUPING( Code ), 1, '----', MAX(col2) ) AS Col2,
DECODE( GROUPING( Code ), 1, 'Average:', MAX(col3) ) AS Col3,
AVG( value )
FROM table_name
GROUP BY ROLLUP(Code);
Which, for the sample data:
CREATE TABLE table_name ( code, col1, col2, col3, value ) AS
SELECT 1, 'AAA', 1, 'AA1', 15.0 FROM DUAL UNION ALL
SELECT 2, 'BBB', 2, 'BB2', 17.5 FROM DUAL UNION ALL
SELECT 3, 'CCC', 3, 'CC3', 20.0 FROM DUAL;
Outputs:
CODE | COL1 | COL2 | COL3 | AVG(VALUE)
:--- | :--- | :--- | :------- | ---------:
1 | AAA | 1 | AA1 | 15
2 | BBB | 2 | BB2 | 17.5
3 | CCC | 3 | CC3 | 20
---- | ---- | ---- | Average: | 17.5
db<>fiddle here
This is a job for UNION ALL. A good way to get this sort of result:
SELECT * FROM (
SELECT COALESCE(whatever1, '------') whatever1
COALESCE(whatever2, '------') whatever2,
COALESCE(whatever3, '------') whatever3,
whatever4
FROM whatever
UNION ALL
SELECT NULL, NULL, NULL, AVG(whatever4) FROM whatever
) r
ORDER BY whatever1 NULLS LAST
The COALESCE function puts your ----- characters into the output.
You can also investigate GROUP BY WITH ROLLUP. It may do what you want.

Is there an other solution calculate data by filtered date without "with" clause?

Need to calculate value when date< first_date by name with interval 3 day in BigQuery.
Example of data:
+------+------------+------------------+
| Name | date | order_id | value |
+------+------------+----------+-------+
| JONES| 2019-01-03 | 11 | 10 |
| JONES| 2019-01-05 | 12 | 5 |
| JONES| 2019-06-03 | 13 | 3 |
| JONES| 2019-07-03 | 14 | 20 |
| John | 2019-07-23 | 15 | 10 |
+------+------------+----------+-------+
My solution is:
WITH data AS (
SELECT "JONES" name, DATE("2019-01-03") date_time, 11 order_id, 10 value
UNION ALL
SELECT "JONES", DATE("2019-01-05"), 12, 5
UNION ALL
SELECT "JONES", DATE("2019-06-03"), 13, 3
UNION ALL
SELECT "JONES", DATE("2019-07-03"), 14, 20
UNION ALL
SELECT "John", DATE("2019-07-23"), 15, 10
),
data2 AS (
SELECT *, MIN(date_time) OVER (PARTITION BY name) min_date
FROM data
)
SELECT name,
ARRAY_AGG(STRUCT(order_id as f_id, date_time as f_date) ORDER BY order_id LIMIT 1)[OFFSET(0)].*,
sum(case when date_time< date_add(min_date,interval 3 day) then value end) as total_value_day3,
SUM(value) AS total
FROM data2
GROUP BY name
Output:
+------+------+------------+----------------+------+
| name | f_id | f_date |total_value_day3| total|
+------+------+------------+----------------+------+
| JONES| 11 | 2019-01-03 | 15 | 38 |
| John | 15 | 2019-07-23 | 10 | 10 |
+------+------+------------+----------------+------+
So my question, can do the same calculated with a more effective way?
Or this solution is ok for large datasets?
The following gets the same results without using window functions or array aggregations, so BQ has to do less ordering/partitioning. For this small example, my query takes longer to run, but there is less byte shuffling. If you run this against a much larger dataset, I think mine will be more efficient.
WITH data AS (
SELECT "JONES" name, DATE("2019-01-03") date_time, "11" order_id, 10 value UNION ALL
SELECT "JONES", DATE("2019-01-05"), "12", 5 UNION ALL
SELECT "JONES", DATE("2019-06-03"), "13", 3 UNION ALL
SELECT "JONES", DATE("2019-07-03"), "14", 20 UNION ALL
SELECT "John", DATE("2019-07-23"), "15", 10
),
aggs as (
select name, min(date_time) as first_order_date, min(order_id) as first_order_id, sum(value) as total
from data
group by 1
)
select
name,
first_order_id as f_id,
first_order_date as f_date,
sum(value) as total_value_day3,
total
from aggs
inner join data using(name)
where date_time < date_add(first_order_date, interval 3 day) -- <= perhaps
group by 1,2,3,5
Note, this makes an assumption that order_id is sequential (aka order_id 11 always occurs before order_id 12) in the same manner that dates are sequential.

Selecting only distinct record from table in oracle

I have table with following records;
ID | NN | MBL | IC | OTHER
---+-----+------+----+------
1 | 123 | | | ac
2 | | 544 | | dc
3 | | | 524| df
4 |527 | | 124| ff
5 |123 | | | tr // duplicate NN of ID 1
6 | | 544 | | op // duplicate MBL of ID 2
7 | | | 124| ii // duplicate for IC ID 4
When querying with select I need just records with single entry, skipping second occurrence,
select
ID, NN, MBL, IC, OTHER
from
TABLE1 // this should return only one entry of any NN, MBL and IC
How do I get this, I cannot use distinct for multiple columns and I also need ID and OTHER column to display in select query
Expecting result like this:
1 | 123 | | | ac
2 | | 544 | | dc
3 | | | 524| df
4 |527 | | 124| ff
You can use the analytical function ROW_NUMBER() to calculate ranks over each column you want and filter only these rows with rank = 1.
Here is an example:
WITH testdata AS (
SELECT 1 AS ID, 123 AS NN, NULL AS MBL, NULL AS IC, 'ac' AS OTHER FROM DUAL UNION ALL
SELECT 2, NULL, 544 , NULL, 'dc' FROM DUAL UNION ALL
SELECT 3, NULL, NULL, 524 , 'df' FROM DUAL UNION ALL
SELECT 4, 527, NULL, 124, 'ff' FROM DUAL UNION ALL
SELECT 5, 123, NULL, NULL, 'tr' FROM DUAL UNION ALL
SELECT 6, NULL, 544, NULL, 'op' FROM DUAL UNION ALL
SELECT 7, NULL, NULL , 124, 'ii' FROM DUAL
)
SELECT *
FROM(SELECT ID,
NN,
CASE WHEN NN IS NULL THEN 1 ELSE ROW_NUMBER() OVER (PARTITION BY NN ORDER BY ID) END AS NN_RANG,
MBL,
CASE WHEN MBL IS NULL THEN 1 ELSE ROW_NUMBER() OVER (PARTITION BY MBL ORDER BY ID) END AS MBL_RANG,
IC,
CASE WHEN IC IS NULL THEN 1 ELSE ROW_NUMBER() OVER (PARTITION BY IC ORDER BY ID) END AS IC_RANG,
OTHER
FROM testdata
)
WHERE NN_RANG = 1
AND MBL_RANG = 1
AND IC_RANG = 1
;
Hope it helps.

Oracle CONNECT BY with multiple tables

I have 4 tables containing my data:
Table COMP: definition of my component data
COMPID | NAME | DESCRIPTION
--------+-----------+------------
000123 | Comp. 1 | A44.123
000277 | Comp. 2 | A96.277
000528 | Comp. 3 | 1235287
001024 | Comp. 4 | Lollipop
004711 | Comp. 5 | Yippie
Table COMPLIST: containing the sub-components of each component
COMPID | POS | SUBCOMPID | QUANTITY
--------+------+------------ +-----------
000123 | 1 | 000277 | 3
000123 | 2 | 000528 | 1
000528 | 1 | 004711 | 1
Table COMPSUPPLIER: definition of the components suppliers
COMPID | SUPPLIER | ORDERNUMBER
--------+-----------+-------------
000123 | Supp1 | A44.123
000277 | Supp1 | A96.277
000528 | Supp2 | 1235287
001024 | Supp2 | ux12v39
004711 | Supp1 | 123456
Table ASSEMBLY: definition of my assembly
ASSYID | POS | COMPID | QUANTITY
--------+------+---------+----------
5021 | 1 | 000123 | 1
5021 | 2 | 001024 | 2
I want to get all components used in an assembly with their supplier and order number (Edited: added Position):
POS | COMPID | NAME | SUPPLIER | ORDERNUMBER | QUANTITY
-------|---------+---------+----------+-------------+----------
1 | 000123 | Comp. 1 | Supp1 | A44.123 | 1
1.1 | 000277 | Comp. 2 | Supp1 | A96.277 | 3
1.2 | 000528 | Comp. 3 | Supp2 | 1235287 | 1
1.2.1 | 004711 | Comp. 5 | Supp1 | 123456 | 1
2 | 001024 | Comp. 4 | Supp2 | ux12v39 | 2
My idea was to use a SELECT in combination with CONNECT BY but I can't get it working right.
My current approach (Edited: updated with GurV's input):
SELECT c.COMPID, c.NAME, cs.SUPPLIER, cs.ORDERNUMBER
FROM COMP c
JOIN COMPSUPPLIER cs ON c.COMPID = cs.COMPID
WHERE c.COMPID in (
SELECT COMPID
FROM ASSEMBLY
WHERE ASSYID = '5021'
UNION ALL
SELECT SUBCOMPID
FROM COMPLIST
CONNECT BY NOCYCLE PRIOR SUBCOMPID = COMPID
START WITH COMPID in (
SELECT COMPID
FROM ASSEMBLY
WHERE ASSYID = '5402')
);
With this I get all my sub components but not the position. Is it possible to get also the position column somehow?
A standard hierarchical query will work for this problem. I see in your desired output that you don't have a column for assyid; if you have more than one assembly in your business, that's a flaw. Also, I thought at some point you will want to compute the total quantity of a sub-component for an assembly (say, screws are used in component a and also in component b, both part of assembly 1000, and you would need the total number of screws); but, since you want to show everything "in its proper hierarchy" (as reflected in the pos column), it seems you aren't interested in that, at least in this query. That would be harder to do with a standard hierarchical query and easier to do in a recursive query, but that doesn't seem to be the case here.
The idea is to union all between complist and assembly, adding a flag column to use in the start with clause of the hierarchical query. Everything else is pretty standard.
with
comp ( compid, name, description ) as (
select '000123', 'Comp. 1', 'A44.123' from dual union all
select '000277', 'Comp. 2', 'A96.277' from dual union all
select '000528', 'Comp. 3', '1235287' from dual union all
select '001024', 'Comp. 4', 'Lollipop' from dual union all
select '004711', 'Comp. 5', 'Yippie' from dual
),
Complist ( compid, pos, subcompid, quantity ) as (
select '000123', 1, '000277', 3 from dual union all
select '000123', 2, '000528', 1 from dual union all
select '000528', 1, '004711', 1 from dual
),
compsupplier ( compid, supplier, ordernumber ) as (
select '000123', 'Supp1', 'A44.123' from dual union all
select '000277', 'Supp1', 'A96.277' from dual union all
select '000528', 'Supp2', '1235287' from dual union all
select '001024', 'Supp2', 'ux12v39' from dual union all
select '004711', 'Supp1', '123456' from dual
),
assembly ( assyid, pos, compid, quantity ) as (
select '5021', 1, '000123', 1 from dual union all
select '5021', 2, '001024', 2 from dual
)
select h.assyid, ltrim(h.pos, '.') as pos, h.compid,
c.name, s.supplier, s.ordernumber, h.quantity
from (
select subcompid as compid, quantity,
connect_by_root compid as assyid,
sys_connect_by_path(pos, '.') as pos
from ( select complist.*, 'f' as flag from complist
union all
select assembly.*, null as flag from assembly
)
start with flag is null
connect by compid = prior subcompid
) h
left outer join comp c on h.compid = c.compid
left outer join compsupplier s on h.compid = s.compid
;
Output:
ASSYID POS COMPID NAME SUPPLIER ORDERNUMBER QUANTITY
------ -------- ------ ------- -------- ----------- ----------
5021 1 000123 Comp. 1 Supp1 A44.123 1
5021 1.1 000277 Comp. 2 Supp1 A96.277 3
5021 1.2 000528 Comp. 3 Supp2 1235287 1
5021 1.2.1 004711 Comp. 5 Supp1 123456 1
5021 2 001024 Comp. 4 Supp2 ux12v39 2
5 rows selected.
If I'm following your logic, you can use recursive subquery factoring instead of a hierarchical query, which makes cycles etc. a bit easier to cope with:
with rcte (position, compid, name, supplier, ordernumber, quantity) as (
select to_char(a.pos), a.compid, c.name, cs.supplier, cs.ordernumber, a.quantity
from assembly a
join compsupplier cs on cs.compid = a.compid
join comp c on c.compid = cs.compid
where a.assyid = 5021
union all
select rcte.position ||'.' || cl.pos, cl.subcompid, c.name,
cs.supplier, cs.ordernumber, cl.quantity
from rcte
join complist cl on cl.compid = rcte.compid
join compsupplier cs on cs.compid = cl.subcompid
join comp c on c.compid = cs.compid
)
select *
from rcte;
POSITION COMPID NAME SUPPL ORDERNU QUANTITY
---------- ------ ------- ----- ------- ----------
1 000123 Comp. 1 Supp1 A44.123 1
2 001024 Comp. 4 Supp2 ux12v39 2
1.1 000277 Comp. 2 Supp1 A96.277 3
1.2 000528 Comp. 3 Supp2 1235287 1
1.2.1 004711 Comp. 5 Supp1 123456 1
The anchor member gets the first two rows direct from the assembly data, including the position from that table - that's essentially your original (pre-Gurv) query, plus the position.
The recursive member then looks at complist for each generated row's compid existing as a subcompid, and appends its position to the parent's while getting the other relevant data from the other tables.
If you want to preserve the order as you showed it in the question, you can add additional columns to the recursive CTE that track the original position and the level you're currently at (possibly with other info to break ties, if they are possible), and exclude those from the final select list:
with rcte (position, compid, name, supplier, ordernumber, quantity,
order_by_1, order_by_2)
as (
select to_char(a.pos), a.compid, c.name, cs.supplier, cs.ordernumber, a.quantity,
a.pos, 1
from assembly a
join compsupplier cs on cs.compid = a.compid
join comp c on c.compid = cs.compid
where a.assyid = 5021
union all
select rcte.position ||'.' || cl.pos, cl.subcompid, c.name,
cs.supplier, cs.ordernumber, cl.quantity,
rcte.order_by_1, rcte.order_by_2 + 1
from rcte
join complist cl on cl.compid = rcte.compid
join compsupplier cs on cs.compid = cl.subcompid
join comp c on c.compid = cs.compid
)
select position, compid, name, supplier, ordernumber, quantity
from rcte
order by order_by_1, order_by_2;
POSITION COMPID NAME SUPPL ORDERNU QUANTITY
---------- ------ ------- ----- ------- ----------
1 000123 Comp. 1 Supp1 A44.123 1
1.1 000277 Comp. 2 Supp1 A96.277 3
1.2 000528 Comp. 3 Supp2 1235287 1
1.2.1 004711 Comp. 5 Supp1 123456 1
2 001024 Comp. 4 Supp2 ux12v39 2

Resources