SQL Oracle Add 'case when' if value in Join not available - sql

I have below two tables
~ What I am looking to do: I want to append the Price from Table 2 to Table 1. If the exact Quantity in Table 1 is not there in Table 2, take the closest max Quantity from Table 2 [i.e. 12 Quantity is not there in Table 2, the closest max is 20, so I want the price of that].
BUT in some cases there are no max Quantity values, which leads to some entries to fall out of my output. In this case, I am thinking to take the closest min Quantity from Table 2, to avoid some values to fall out of my output
So the result should look like:
I have tried below query but this does not account for taking the closest min Quantity when the closest max does not exist. Product_NR '20765' therefore falls out of my output. I'm thinking of 'case when' but not sure how to incorporate it below.
select
Product_NR,
Customer,
Quantity,
min(price)
from
( select distinct
t1.Product_NR,
t1.Customer,
t1.Quantity,
t2.price,
min(t2.quantity) over (partition by t1.product_NR) as Quantity_min
from Table_1 t1
left join Table_2 t2 on t1.Product_NR = t2.Product_NR
and t1.Quantity <= t2.Quantity
)
where t2.Quantity = Quantity_min
group by
Product_NR,
Customer,
Quantity

From Oracle 12, you can use a LATERAL join and order the correlated sub-query to get the greater quantity rows before the lower quantities and then FETCH FIRST ROW ONLY:
SELECT *
FROM table1 t1
LEFT OUTER JOIN LATERAL(
SELECT price
FROM table2 t2
WHERE t1.product_nr = t2.product_nr
ORDER BY
CASE WHEN t1.quantity <= t2.quantity THEN t2.quantity END
ASC NULLS LAST,
t2.quantity DESC
FETCH FIRST ROW ONLY
)
ON (1 = 1);
Which, for the sample data:
CREATE TABLE table1 (product_nr, customer, quantity) AS
SELECT 10023, 'X', 12 FROM DUAL UNION ALL
SELECT 10023, 'Y', 10 FROM DUAL UNION ALL
SELECT 10334, 'X', 1 FROM DUAL UNION ALL
SELECT 20765, 'Z', 5 FROM DUAL;
CREATE TABLE table2 (product_nr, quantity, price) AS
SELECT 10023, 1, 100 FROM DUAL UNION ALL
SELECT 10023, 10, 120 FROM DUAL UNION ALL
SELECT 10023, 20, 250 FROM DUAL UNION ALL
SELECT 10023, 100, 400 FROM DUAL UNION ALL
SELECT 10334, 0, 250 FROM DUAL UNION ALL
SELECT 10334, 200, 600 FROM DUAL UNION ALL
SELECT 20765, 1, 40 FROM DUAL UNION ALL
SELECT 20765, 2, 50 FROM DUAL;
Outputs:
PRODUCT_NR
CUSTOMER
QUANTITY
PRICE
10023
X
12
250
10023
Y
10
120
10334
X
1
600
20765
Z
5
50
db<>fiddle here

Related

Find purchase if same item on different days

I'm trying to find customers that bought the same item more than once in different days. I got it partially working. I can't get the customer first/last name and item_name without adding it to the group by clause. In addition, I want to include a count if how many times the same uten was purchased on different days.
I suspect that group by is probably not the best solution. Would this be better solved using a self JOIN or perhaps a lead?
CREATE TABLE customers
(CUSTOMER_ID, FIRST_NAME, LAST_NAME) AS
SELECT 1, 'Abby', 'Katz' FROM DUAL UNION ALL
SELECT 2, 'Lisa', 'Saladino' FROM DUAL UNION ALL
SELECT 3, 'Jerry', 'Torchiano' FROM DUAL;
CREATE TABLE items
(PRODUCT_ID, PRODUCT_NAME) AS
SELECT 100, 'Black Shoes' FROM DUAL UNION ALL
SELECT 101, 'Brown Shoes' FROM DUAL UNION ALL
SELECT 102, 'White Shoes' FROM DUAL;
CREATE TABLE purchases
(CUSTOMER_ID, PRODUCT_ID, QUANTITY, PURCHASE_DATE) AS
SELECT 1, 100, 1, TIMESTAMP'2022-10-11 09:54:48' FROM DUAL UNION ALL
SELECT 1, 100, 1, TIMESTAMP '2022-10-11 19:04:18' FROM DUAL UNION ALL
SELECT 2, 101,1, TIMESTAMP '2022-10-11 09:54:48' FROM DUAL UNION ALL
SELECT 2,101,1, TIMESTAMP '2022-10-17 19:04:18' FROM DUAL UNION ALL
SELECT 3, 101,1, TIMESTAMP '2022-10-11 09:54:48' FROM DUAL UNION ALL
SELECT 3,102,1, TIMESTAMP '2022-10-17 19:04:18' FROM DUAL;
With CTE as (
SELECT customer_id
,product_id
,trunc(purchase_date)
FROM purchases
GROUP BY customer_id
,product_id
,trunc(purchase_date)
)
SELECT customer_id, product_id
FROM CTE
GROUP BY customer_id ,product_id
HAVING COUNT(1)>1
I would use exists logic here:
SELECT DISTINCT c.first_name, c.last_name
FROM customers c
INNER JOIN purchases p
ON p.customer_id = c.customer_id
WHERE EXISTS (
SELECT 1
FROM purchases p2
WHERE p2.customer_id = p.customer_id AND
p2.product_id = p.product_id AND
TRUNC(p2.purchase_date) <> TRUNC(p.purchase_date)
);
In plain English, the above query says to find all customers who bought the same product but on different dates.
This might be one option: use count function in its analytic form and the fetch rows where that count is larger than 1; according to data you posted, it is Lisa who bought brown shoes on two different dates.
SQL> WITH
2 temp
3 AS
4 ( SELECT c.first_name,
5 i.product_name,
6 TRUNC (p.purchase_date),
7 COUNT (*) OVER (PARTITION BY c.first_name, i.product_name) cnt
8 FROM purchases p
9 JOIN customers c ON c.customer_id = p.customer_id
10 JOIN items i ON i.product_id = p.product_id
11 GROUP BY c.first_name, i.product_name, TRUNC (p.purchase_date))
12 SELECT DISTINCT first_name, product_name, cnt
13 FROM temp
14 WHERE cnt > 1;
FIRST PRODUCT_NAM CNT
----- ----------- ----------
Lisa Brown Shoes 2
SQL>

Change string to date and joining on date range in Oracle SQL

I have below two tables:
~ What I am looking to do: I want to append the Price from Table 2 (t2) to Table 1 (t1), by joining on Quantity and YYYY_MM. Each t2.price was active in a certain time range (t2.Price_Active_Date_From and t2.Price_Active_Date_To), and the t1.Order_YYYYMM should fall within this range. Year 9999 indicates that the price has no end date yet, and is therefore still active. I also want to find which price was active 1 year prior to the order date
So the result should look like:
What I have tried below so far, which works to get the Price_Active_At_Order, but I am not sure how to get Price_Active_PY when my date values are strings:
select distinct
t1.Product_NR,
t1.Customer,
t1.Quantity,
t2.Price as Price_Active_At_Order,
t1.Order_YYYYMM as Order_Date
from Table_1 t1
join Table_2 t2 on t1.Product_NR = t2.Product_NR
and t1.Quantity = t2.Quantity
and t1.Order_YYYYMM between t2.Price_Active_Date_From and t2.Price_Active_Date_To
Two things to point out:
Line 6 : te.Order_YYYYMM as Order_Date change to t1.Order_YYYYMM as Order_Date
Line 13,14,15 : where clause is incomplete and unnecessary, remove it
Your code should work fine with the changes above.
select distinct
t1.Product_NR,
t1.Customer,
t1.Quantity,
t2.Price as Price_Active_At_Order,
t1.Order_YYYYMM as Order_Date
from Table_1 t1
join Table_2 t2 on t1.Product_NR = t2.Product_NR
and t1.Quantity = t2.Quantity
and t1.Order_YYYYMM between t2.Price_Active_Date_From and t2.Price_Active_Date_To
If you want to find a single row in table2 that can supply the complete quantity corresponding to a table1 row at the minimum price then, from Oracle 12, you can use a LATERAL join:
SELECT *
FROM table1 t1
LEFT OUTER JOIN LATERAL (
SELECT price,
price_active_date_from,
price_active_date_to
FROM table2 t2
WHERE t1.product_nr = t2.product_nr
AND t1.quantity <= t2.quantity
AND t1.order_yyyymm BETWEEN t2.price_active_date_from
AND t2.price_active_date_to
ORDER BY t2.price ASC
FETCH FIRST ROW ONLY
) t2
ON (1 = 1)
Which, for the sample data:
CREATE TABLE table1 (product_nr, customer, quantity, order_yyyymm) AS
SELECT 10023, 'X', 20, 202104 FROM DUAL UNION ALL
SELECT 10023, 'Y', 10, 202203 FROM DUAL UNION ALL
SELECT 10334, 'X', 1, 202204 FROM DUAL;
CREATE TABLE table2 (product_nr, quantity, price, price_active_date_from, price_active_date_to) AS
SELECT 10023, 1, 100, 202101, 999912 FROM DUAL UNION ALL
SELECT 10023, 10, 80, 201605, 202012 FROM DUAL UNION ALL
SELECT 10023, 10, 120, 202101, 202205 FROM DUAL UNION ALL
SELECT 10023, 20, 250, 202101, 999912 FROM DUAL UNION ALL
SELECT 10023, 100, 400, 202101, 202111 FROM DUAL UNION ALL
SELECT 10334, 1, 25, 202101, 202111 FROM DUAL UNION ALL
SELECT 10334, 1, 30, 202112, 202205 FROM DUAL;
Outputs:
PRODUCT_NR
CUSTOMER
QUANTITY
ORDER_YYYYMM
PRICE
PRICE_ACTIVE_DATE_FROM
PRICE_ACTIVE_DATE_TO
10023
X
20
202104
250
202101
999912
10023
Y
10
202203
120
202101
202205
10334
X
1
202204
30
202112
202205
If you want to find the row in table2 that can supply the quantity from the cumulative quantities between that row and the preceding rows within the date range ordered by ascending price then you can use:
SELECT *
FROM table1 t1
LEFT OUTER JOIN LATERAL (
SELECT price,
price_active_date_from,
price_active_date_to
FROM (
SELECT price,
price_active_date_from,
price_active_date_to,
SUM(quantity) OVER (ORDER BY price ASC) AS quantity
FROM table2 t2
WHERE t1.product_nr = t2.product_nr
AND t1.order_yyyymm BETWEEN t2.price_active_date_from
AND t2.price_active_date_to
) t2
WHERE t1.quantity <= t2.quantity
ORDER BY t2.price ASC
FETCH FIRST ROW ONLY
) t2
ON (1 = 1)
Which, for the sample data, has the same output.
db<>fiddle here

How to group items by rows

I wanted to group the number of shop but i am not sure what is the syntax to create a group that is not exist in the table. I wanted the output to be like this
Group | Number of items
1 | XXX
2 | XXX
Group 1 would have number of items that is less than 10 while group 2 would have item that is more than 10.I have the data for the number of items, but I need to create the group number and I am not sure how. Thank you in advance.
Way I have tried:
SELECT
case when b.item_stock < 10 then count(a.shopid) else null end as Group_1,
case when b.item_stock >= 10 or b.item_stock < 100 then count(a.shopid) else null end as Group_2
FROM `table_a` a
left join `table_b` b
on a.id= b.id
where registration_time between "2017-01-01" and "2017-05-31"
group by b.item_stock
LIMIT 1000
Below is the BigQuery way of doing this
select 'group_' || range_bucket(item_stock, [0, 10]) as group_id,
count(*) as number_of_items
from your_table
group by group_id
if apply to dummy data like
with your_table as (
select 'ID001' shop_id, 40 item_stock union all
select 'ID002', 20 union all
select 'ID003', 30 union all
select 'ID004', 9 union all
select 'ID005', 44 union all
select 'ID006', 22 union all
select 'ID007', 28 union all
select 'ID008', 35 union all
select 'ID009', 20 union all
select 'ID010', 4 union all
select 'ID011', 5 union all
select 'ID012', 45 union all
select 'ID013', 29 union all
select 'ID014', 8 union all
select 'ID015', 40 union all
select 'ID016', 26 union all
select 'ID017', 31 union all
select 'ID018', 48 union all
select 'ID019', 45 union all
select 'ID020', 13
)
output is
Benefit of this solution is that it is easily extended to any number of ranges just by adding those into range_bucket function -
for example : range_bucket(item_stock, [0, 10, 50, 100, 1000])
From the example you've shared you were close to solving this one, just need to tweak your case statement.
The case statement in your query is splitting the groups into two separate columns, whereas you need these groups in one column with the totals to the right.
Consider the below change to your select statement.
case when b.item_stock < 10 then "Group_1"
when b.item_stock >= 10 then "Group_2" else null end as Groups,
count(a.shop_id) as total
Schema (MySQL v5.7)
CREATE TABLE id (
`shop_id` VARCHAR(5),
`item_stock` INTEGER
);
INSERT INTO id
(`shop_id`, `item_stock`)
VALUES
('ID001', '40'),
('ID002', '20'),
('ID003', '30'),
('ID004', '9'),
('ID005', '44'),
('ID006', '22'),
('ID007', '28'),
('ID008', '35'),
('ID009', '20'),
('ID010', '4'),
('ID011', '5'),
('ID012', '45'),
('ID013', '29'),
('ID014', '8'),
('ID015', '40'),
('ID016', '26'),
('ID017', '31'),
('ID018', '48'),
('ID019', '45'),
('ID020', '13');
Query #1
SELECT
case when item_stock < 10 then "Group_1"
when item_stock >= 10 then "Group_2" else null end as Groups,
count(shop_id) as total
FROM id group by 1;
Groups
total
Group_1
4
Group_2
16
View on DB Fiddle
Tom

SQL Left Outer Join not returning all rows from left table (no where clause filter)

I have a left outer join that doesn't return all rows from the "left" table. I have no where clause, so no filtering should be applied after the join.
I am expecting:
Product 1
AT
100
Product 2
AT
25
Product 4
AT
57
Product 1
GR
45
Product 2
GR
22
Product 3
GR
5
Product 4
GR
4
Product 3
null
null
But I'm missing the last row. Any light you could shed into this is very appreciated.
To reproduce it:
-- Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
drop table t1;
drop table t2;
create table t1
(ov_product varchar2(18 byte)
,product varchar2(18 byte)
)
/
create table t2
(reporting_month number
,product varchar2(18 byte)
,sender varchar2(2 byte)
,items number
)
/
insert into t1
(
select 'Product 1' ov_product, 'P1' product from dual
union
select 'Product 2' ov_product, 'P2' product from dual
union
select 'Product 3' ov_product, 'P3' product from dual
union
select 'Product 4' ov_product, 'P4' product from dual
);
insert into t2
(
select 202108, 'P1', 'AT', 100 from dual
union
select 202108, 'P2', 'AT', 25 from dual
union
-- no P3 for AT
select 202108, 'P4', 'AT', 57 from dual
union
select 202108, 'P1', 'GR', 45 from dual
union
select 202108, 'P2', 'GR', 22 from dual
union
select 202108, 'P3', 'GR', 5 from dual
union
select 202108, 'P4', 'GR', 4 from dual
)
;
commit;
select t1.ov_product
,t2.sender
,t2.items
from t1
left outer join t2
on t1.product = t2.product
order by 2, 1
;
Your outer join works fine.
You probably mean partitioned outer join.
See the additional query_partition_clause in the join
PARTITION BY (sender) only this join will fill the gaps in sender as you expects.
select t1.ov_product
,t2.sender
,t2.items
from t1
left outer join t2
PARTITION BY (sender)
on t1.product = t2.product
order by 2, 1
OV_PRODUCT SE ITEMS
------------------ -- ----------
Product 1 AT 100
Product 2 AT 25
Product 3 AT
Product 4 AT 57
Product 1 GR 45
Product 2 GR 22
Product 3 GR 5
Product 4 GR 4
Since there is a line in t2 with product 3 there is no a null-value record to be produced with the left-join.
In order to get such a line you need either to join a table of senders if you have one. Or, if the table does not exist, you may use a CTE like this
with senders(sender) as (select distinct sender from t2)
with senders(sender) as (select distinct sender from t2)
select t1.ov_product
,t2.sender
,t2.items
from t1
cross join senders
left outer join t2
on t1.product = t2.product
and t2.sender = senders.sender
order by 2, 1;

SQL: Earliest Date After Latest Null If Exists

Using T-Sql I am looking to return the min date after the latest null if one exists and simply the min date on any products where there are no nulls.
Table:
DateSold Product
12/31/2012 A
1/31/2013
2/28/2013 A
3/31/2013 A
4/30/2013 A
5/31/2013
6/30/2013 A
7/31/2013 A
8/31/2013 A
9/30/2013 A
12/31/2012 B
1/31/2013 B
2/28/2013 B
3/31/2013 B
4/30/2013 B
5/31/2013 B
6/30/2013 B
7/31/2013 B
8/31/2013 B
9/30/2013 B
For product “A” 6/30/2013 is the desired return while for product “B” 12/31/2012 is desired.
Result:
MinDateSold Product
6/30/2013 A
12/31/2012 B
Any solutions will greatly be appreciated. Thank you.
This does it for me, if there's a GROUP involved, otherwise how do you know whether the NULLs are in the run of A or B products? I realise this may not be exactly what you're after, but I hope it helps anyway.
WITH DATA_IN AS (
SELECT 1 as grp,
convert(DateTime,'12/31/2012') as d_Date,
'A' AS d_ch
UNION ALL
SELECT 1, '1/31/2013', NULL UNION ALL
SELECT 1, '2/28/2013', 'A' UNION ALL
SELECT 1, '3/31/2013', 'A' UNION ALL
SELECT 1, '4/30/2013', 'A' UNION ALL
SELECT 1, '5/31/2013', NULL UNION ALL
SELECT 1, '6/30/2013', 'A' UNION ALL
SELECT 1, '7/31/2013', 'A' UNION ALL
SELECT 1, '8/31/2013', 'A' UNION ALL
SELECT 1, '9/30/2013', 'A' UNION ALL
SELECT 2, '12/31/2012', 'B' UNION ALL
SELECT 2, '1/31/2013', 'B' UNION ALL
SELECT 2, '2/28/2013', 'B' UNION ALL
SELECT 2, '3/31/2013', 'B' UNION ALL
SELECT 2, '4/30/2013', 'B' UNION ALL
SELECT 2, '5/31/2013', 'B' UNION ALL
SELECT 2, '6/30/2013', 'B' UNION ALL
SELECT 2, '7/31/2013', 'B' UNION ALL
SELECT 2, '8/31/2013', 'B' UNION ALL
SELECT 2, '9/30/2013', 'B'
)
SELECT
grp as YourGroup,
(SELECT Min(d_date) -- first date after...
FROM DATA_IN
WHERE d_date>
Coalesce( -- either the latest NULL
(SELECT max(d_Date)
FROM DATA_IN d2
WHERE d2.grp=d1.grp AND d2.d_ch IS NULL
)
, '1/1/1901' -- or a base date if no NULLs
)
) as MinDateSold
FROM DATA_IN d1
GROUP BY grp
Results :
1 2013-06-30 00:00:00.000
2 2012-12-31 00:00:00.000
One approach to this is to count the number of NULL values that appear before a given row for a given value. This divides the ranges into groups. For each group, take the minimum date. And, find the largest minimum date for each product:
select product, minDate
from (select product, NumNulls, min(DateSold) as minDate,
row_number() over (partition by product order by min(DateSold) desc
) as seqnum
from (select t.*,
(select count(*)
from table t2
where t2.product is null and t2.DateSold <= t.DateSold
) as NumNulls
from table t
) t
group by Product, NumNUlls
) t
where seqnum = 1;
In your data, there is no mixing of different products in a range, so this query sort of assumes that is true as well.