get item with GREATEST amount of purchases show all customers - sql

I have a query, which finds the GREATEST amount of which item was purchased. It appears to be working fine (see below).
I have two questions, first is there a better way to rewrite this query without using 2 CTE and not displaying the value of rnk in the output.
Secondly, in another query
how can I show the product_id, product_name, price,
customer_id, first_name, last_name, total_qty, total_amt for this item. I am expecting to see 3 rows based on the data provided.
CREATE TABLE customers
(CUSTOMER_ID, FIRST_NAME, LAST_NAME) AS
SELECT 1, 'Faith', 'Mazzarone' FROM DUAL UNION ALL
SELECT 2, 'Lisa', 'Saladino' FROM DUAL UNION ALL
SELECT 3, 'Micheal', 'Palmice' FROM DUAL UNION ALL
SELECT 4, 'Jerry', 'Torchiano' FROM DUAL;
CREATE TABLE items
(PRODUCT_ID, PRODUCT_NAME, PRICE) AS
SELECT 100, 'Black Shoes', 79.99 FROM DUAL UNION ALL
SELECT 101, 'Brown Pants', 111.99 FROM DUAL UNION ALL
SELECT 102, 'White Shirt', 10.99 FROM DUAL;
CREATE TABLE purchases(
ORDER_ID NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
CUSTOMER_ID NUMBER,
PRODUCT_ID NUMBER,
QUANTITY NUMBER,
PURCHASE_DATE TIMESTAMP
);
INSERT INTO purchases
(CUSTOMER_ID, PRODUCT_ID, QUANTITY, PURCHASE_DATE)
SELECT 1, 101, 3, TIMESTAMP'2022-10-11 09:54:48' FROM DUAL UNION ALL
SELECT 1, 100, 1, TIMESTAMP '2022-10-12 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, 3, TIMESTAMP '2022-10-17 19:34:58' FROM DUAL UNION ALL
SELECT 2, 102, 3,TIMESTAMP '2022-12-06 11:41:25' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual CONNECT BY LEVEL <= 6 UNION ALL
SELECT 2, 102, 3,TIMESTAMP '2022-12-26 11:41:25' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual CONNECT BY LEVEL <= 6 UNION ALL
SELECT 3, 101,1, TIMESTAMP '2022-12-21 09:54:48' FROM DUAL UNION ALL
SELECT 3, 102,1, TIMESTAMP '2022-12-27 19:04:18' FROM DUAL UNION ALL
SELECT 3, 102, 4,TIMESTAMP '2022-12-22 21:44:35' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual
CONNECT BY LEVEL <= 15 UNION ALL
SELECT 3, 101,1, TIMESTAMP '2022-12-11 09:54:48' FROM DUAL UNION ALL
SELECT 3, 102,1, TIMESTAMP '2022-12-17 19:04:18' FROM DUAL UNION ALL
SELECT 3, 102, 4,TIMESTAMP '2022-12-12 21:44:35' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual
CONNECT BY LEVEL <= 5;
ALTER TABLE customers
ADD CONSTRAINT customers_pk PRIMARY KEY (customer_id);
ALTER TABLE items
ADD CONSTRAINT items_pk PRIMARY KEY (product_id);
ALTER TABLE purchases
ADD CONSTRAINT order_pk PRIMARY KEY (order_id);
ALTER TABLE purchases ADD CONSTRAINT customers_fk FOREIGN KEY (customer_id) REFERENCES customers(customer_id);
ALTER TABLE purchases ADD CONSTRAINT items_fk FOREIGN KEY (PRODUCT_ID) REFERENCES items(product_id);
/*
get item with GREATEST amount of purchases
*/
with cte as
(SELECT
i.product_id,
i.product_name,
i.price,
SUM (p.quantity) AS total_qty,
SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY
i.product_id,
i.product_name,
i.price, p.quantity),
cte2 as
(SELECT product_id,
product_name,
price,
total_qty,
total_amt,
RANK() OVER (ORDER BY total_amt DESC) rnk
FROM cte)
SELECT *
FROM cte2
WHERE rnk = 1;
PRODUCT_ID PRODUCT_NAME PRICE TOTAL_QTY TOTAL_AMT RNK
102 White Shirt 10.99 80 879.2 1

You could create a cte (or a subquery) that will give you all the data you need to get either greatest amount of all products or greatest per product:
SELECT
i.PRODUCT_ID, i.PRODUCT_NAME, i.PRICE,
c.CUSTOMER_ID, c.FIRST_NAME, c.LAST_NAME,
Sum(p.QUANTITY) "TOTAL_QTY", Sum(p.QUANTITY * i.PRICE) "TOTAL_AMT",
RANK() OVER (PARTITION BY i.PRODUCT_ID ORDER BY i.PRODUCT_ID, Sum(p.QUANTITY * i.PRICE) DESC) "PRODUCT_RNK",
RANK() OVER (ORDER BY Sum(p.QUANTITY * i.PRICE) DESC) "TOTAL_RNK"
FROM
items i
INNER JOIN
purchases p ON(p.PRODUCT_ID = i.PRODUCT_ID)
INNER JOIN
customers c ON(c.CUSTOMER_ID = p.CUSTOMER_ID)
GROUP BY
i.PRODUCT_ID, i.PRODUCT_NAME, i.PRICE,
c.CUSTOMER_ID, c.FIRST_NAME, c.LAST_NAME
ORDER BY
i.PRODUCT_ID, Sum(p.QUANTITY * i.PRICE) DESC
SubQuery or CTE Result:
PRODUCT_ID PRODUCT_NAME PRICE CUSTOMER_ID FIRST_NAME LAST_NAME TOTAL_QTY TOTAL_AMT PRODUCT_RNK TOTAL_RNK
---------- ------------ ---------- ----------- ---------- --------- ---------- ---------- ----------- ----------
100 Black Shoes 79.99 1 Faith Mazzarone 1 79.99 1 6
101 Brown Pants 111.99 2 Lisa Saladino 4 447.96 1 2
101 Brown Pants 111.99 1 Faith Mazzarone 3 335.97 2 4
101 Brown Pants 111.99 3 Micheal Palmice 2 223.98 3 5
102 White Shirt 10.99 3 Micheal Palmice 82 901.18 1 1
102 White Shirt 10.99 2 Lisa Saladino 36 395.64 2 3
with this dataset you can get the results like below:
-- if it is a CTE named cte:
SELECT * FROM cte WHERE PRODUCT_RNK = 1
PRODUCT_ID PRODUCT_NAME PRICE CUSTOMER_ID FIRST_NAME LAST_NAME TOTAL_QTY TOTAL_AMT PRODUCT_RNK TOTAL_RNK
---------- ------------ ---------- ----------- ---------- --------- ---------- ---------- ----------- ----------
100 Black Shoes 79.99 1 Faith Mazzarone 1 79.99 1 6
101 Brown Pants 111.99 2 Lisa Saladino 4 447.96 1 2
102 White Shirt 10.99 3 Micheal Palmice 82 901.18 1 1
OR
SELECT * FROM cte WHERE TOTAL_RNK = 1
PRODUCT_ID PRODUCT_NAME PRICE CUSTOMER_ID FIRST_NAME LAST_NAME TOTAL_QTY TOTAL_AMT PRODUCT_RNK TOTAL_RNK
---------- ------------ ---------- ----------- ---------- --------- ---------- ---------- ----------- ----------
102 White Shirt 10.99 3 Micheal Palmice 82 901.18 1 1

Related

Oracle SQL Developer, whose name (PRODUCT_NAME) does not contain the symbols '_' and '<'

I have table product_information with columns: product_id, product_name and product_description.I want to make a query (PRODUCT_NAME) does not contain the symbols '_' and '<'.
Can you help me ?
I try:
select * from product_information
WHERE product_name NOT LIKE '%[_[<]%'
One option is to use the instr function:
SQL> with product_information (product_id, product_name) as
2 (select 1, 'Toyota_Aygo' from dual union all
3 select 2, 'BMW < Audi' from dual union all
4 select 3, 'Mercedes' from dual union all
5 select 4, 'Up < Polo < Golf' from dual union all
6 select 5, 'Citroen_C3_C4' from dual
7 )
8 select *
9 from product_information
10 where instr(product_name, '_') = 0
11 and instr(product_name, '<') = 0;
PRODUCT_ID PRODUCT_NAME
---------- ----------------
3 Mercedes
SQL>
Or, regular expressions:
8 select *
9 from product_information
10 where not regexp_like(product_name, '_|<');
PRODUCT_ID PRODUCT_NAME
---------- ----------------
3 Mercedes
SQL>
select * from product_information
WHERE product_name NOT LIKE '%_%' AND product_name NOT LIKE '%<%'

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>

how to get value from multiple table in oracle including date

I have three tables customers, orders and orderitems
Customer table
CUSTOMER# LASTNAME FIRSTNAME
1001 MORALES BONITA
1002 THOMPSON RYAN
1003 SMITH LEILA
1004 PIERSON THOMAS
1005 GIRARD CINDY
1006 CRUZ MESHIA
1007 GIANA TAMMY
Orders table
ORDER# CUSTOMER# ORDERDATE
1000 1005 31/MAR/09
1001 1010 31/MAR/09
1002 1011 31/MAR/09
1003 1001 01/APR/09
1004 1020 01/APR/09
Orderitems table
ORDER# ITEM# ISBN QUANTITY
1000 1 3437212490 1
1001 1 9247381001 1
1001 2 2491748320 1
1002 1 8843172113 2
1003 1 8843172113 1
1003 2 1059831198 1
I want to print the customer name and the total number of orders the customer has placed for all customer who have placed at least one order in march.
And i tried with the following query. i stucked on this problem. i am getting problem on adding the quantity for customer who have placed at least one order on march.
select c.firstname
, c.lastname
, or.orderitems#
from customers c
, orderitems or
where customer# in
(
select customer#
from orders
where order# in
(
select order#
from orderitems
where /* (query truncated) */
I want to print the customer name and the total number of orders the customer has placed for all customer who have placed at least one order in march.
You do not need the OrderItems table for this. So you can do:
select c.lastname, c.firstname, count(*)
from customers c join
orders o
on c.CUSTOMER# = o.CUSTOMER#
group by c.lastname, c.firstname
having sum(case when orderdate >= date '2009-03-01' and orderdate < date '2009-04-01'
then 1 else 0
end) > 0;
You can also filter before the aggregation, using exists (or in):
select c.lastname, c.firstname, count(*)
from customers c join
orders o
on c.CUSTOMER# = o.CUSTOMER#
where exists (select 1
from orders o2
where c.CUSTOMER# = o2.CUSTOMER# and
o2.orderdate >= date '2009-03-01' and
o2.orderdate < date '2009-04-01'
)
group by c.lastname, c.firstname
Although I like the having method, filtering before aggregation often performs better.
If I understand well, you may need something like this (assuming your date values have no time informations):
select LASTNAME, FIRSTNAME, sum(QUANTITY)
from customers
inner join Orders using(CUSTOMER#)
inner join Orderitems using(ORDER#)
group by LASTNAME, FIRSTNAME
having count(
case when ORDERDATE between date '2009-03-01'
and date '2009-03-31'
then 1
end
) > 0
Here I apply an having condition to only get the customers having at least one order in march.
For example:
SQL> with customers(CUSTOMER#, LASTNAME, FIRSTNAME) as
2 (
3 select 1001, 'MORALES' , 'BONITA' from dual union all
4 select 1002, 'THOMPSON', 'RYAN' from dual union all
5 select 1003, 'SMITH' , 'LEILA' from dual union all
6 select 1004, 'PIERSON' , 'THOMAS' from dual union all
7 select 1005, 'GIRARD' , 'CINDY' from dual union all
8 select 1006, 'CRUZ' , 'MESHIA' from dual union all
9 select 1007, 'GIANA' , 'TAMMY' from dual
10 ),
11 Orders(ORDER#, CUSTOMER#, ORDERDATE) as
12 (
13 select 1000, 1005, to_date('31/MAR/09', 'DD/MON/YY') from dual union all
14 select 1001, 1010, to_date('31/MAR/09', 'DD/MON/YY') from dual union all
15 select 1002, 1011, to_date('31/MAR/09', 'DD/MON/YY') from dual union all
16 select 1003, 1001, to_date('01/APR/09', 'DD/MON/YY') from dual union all
17 select 1004, 1020, to_date('01/APR/09', 'DD/MON/YY') from dual
18 ),
19 Orderitems(ORDER#, ITEM#, ISBN, QUANTITY) as
20 (
21 select 1000, 1, 3437212490, 1 from dual union all
22 select 1001, 1, 9247381001, 1 from dual union all
23 select 1001, 2, 2491748320, 1 from dual union all
24 select 1002, 1, 8843172113, 2 from dual union all
25 select 1003, 1, 8843172113, 1 from dual union all
26 select 1003, 2, 1059831198, 1 from dual
27 )
28 select LASTNAME, FIRSTNAME, sum(QUANTITY)
29 from customers
30 inner join Orders using(CUSTOMER#)
31 inner join Orderitems using(ORDER#)
32 group by LASTNAME, FIRSTNAME
33 having count(case when ORDERDATE between date '2009-03-01' and date '2009-03-31' then 1 end) > 0;
LASTNAME FIRSTN SUM(QUANTITY)
-------- ------ -------------
GIRARD CINDY 1
SQL>
try this
select c.FIRSTNAME , c.LASTNAME , count(i.quantity)
from Customer c , Orders o , Orderitems i
where c.CUSTOMER# = o.CUSTOMER#
and o.ORDER# = i.ORDER#
and o.ORDERDATE between '01-mar-2018' and '30-mar-2018'
group by c.FIRSTNAME , c.LASTNAME;

Oracle SQL - order table

I am new to Oracle SQL and have a table below.
TABLE NAME : ORDERS
CNUM AMT SNUM
1001 1000 2001
1002 2000 2002
1001 1500 2001
1001 500 2001
need to get only those data where cnum (customer number) is serviced by more than 3 snum(sales person) from this above table
thank you
Asit
Something like that?
select cnum, count(*), sum(amount)
from orders
group by cnum
having count(*) > 3
Not sure what you need - aggregate result or every single row. If you need every row then try this one:
select * from (
select a.*, count(*) over(partition by cnum) cnt
from orders a
)
where cnt > 3
You could use the analytic function ROW_NUMBER.
For example,
SQL> WITH DATA AS(
2 SELECT t.*, row_number() OVER(PARTITION BY cnum ORDER BY snum) rn FROM t
3 )
4 SELECT cnum, amt, snum FROM DATA
5 WHERE rn >=3;
CNUM AMT SNUM
---------- ---------- ----------
1001 1500 2001
SQL>
Based on your sample data, the above gives the result including 3 snum for cnum, if you want more than 3, then replace >= with >.
You can group and count distinct SNUMs:
select CNUM
from ORDERS
group by CNUM
having count(distinct SNUM) > 3
WITH tab
AS (SELECT 1001 CNUM, 1000 AMT, 2001 SNUM FROM DUAL
UNION ALL
SELECT 1002, 2000, 2002 FROM DUAL
UNION ALL
SELECT 1001, 1500, 2001 FROM DUAL
UNION ALL
SELECT 1001, 500, 2001 FROM DUAL)
SELECT cnum
FROM tab
GROUP BY cnum
HAVING COUNT (*) >= 3;
If you only need the cnum and the snum you could do the following witch is partially taken from Rusty's answer:
WITH tab
AS (SELECT 1001 CNUM, 1000 AMT, 2001 SNUM FROM DUAL
UNION ALL
SELECT 1002, 2000, 2002 FROM DUAL
UNION ALL
SELECT 1001, 1500, 2001 FROM DUAL
UNION ALL
SELECT 1001, 500, 2001 FROM DUAL)
SELECT DISTINCT cnum, snum
FROM (SELECT cnum, snum, COUNT (*) OVER (PARTITION BY cnum) RANK FROM tab)
WHERE RANK >= 3;

HAVING COUNT() not giving out the expected result

Attempting to get duplicate values of produce.prod_id using distinct, having clauses.but not giving out the expected result.
Here's my data:
PRODUCE
--------- -----------
PROD_ID PROD_NAME
--------- -----------
1 APPLES
2 PEARS
3 ORANGES
4 BANANAS
5 PEACHES
BUYERS
---------- ------------
BUYER_ID BUYER_NAME
---------- --------------
1 ADAM BARR
2 SEAN CHAI
3 EVA CORETS
4 ERIN O`MELIA
SALES
---------- --------- ------
BUYER_ID PROD_ID QTY
---------- --------- ------
1 2 15
1 3 5
4 1 37
3 5 11
4 3 1005
and here's my code:
select produce.prod_name,
buyer.BUYER_NAME,
SALES.PROD_ID
from produce
inner join SALES on produce.PROD_ID = SALES.PROD_ID
inner join buyer on SALES.BUYER_ID = buyer.BUYER_ID
group by produce.prod_name,
buyer.BUYER_NAME,
SALES.PROD_ID
having count(SALES.PROD_ID) > 1;
Expected result:
PROD_Name Buyer_Name
----------- --------------
Oranges ADAM BARR
Oranges ERIN O`MELIA
You need to use analytical function for your requirement
WITH PRODUCE(PROD_ID, PROD_NAME) AS (
SELECT 1, 'APPLES' from dual union all
select 2, 'PEARS' from dual union all
select 3, 'ORANGES' from dual union all
select 4, 'BANANAS' from dual union all
select 5, 'PEACHES' from dual),
BUYERS (BUYER_ID, BUYER_NAME) as (
select 1, 'ADAM BARR' from dual union all
select 2, 'SEAN CHAI' from dual union all
select 3, 'EVA CORETS' from dual union all
select 4, 'ERIN O`MELIA' from dual),
SALES(BUYER_ID, PROD_ID, QTY) as (
select 1, 2, 15 from dual union all
select 1, 3, 5 from dual union all
select 4, 1, 37 from dual union all
select 3, 5, 11 from dual union all
select 4, 3, 1005 from dual),
-- End of data preparation
TABLE_ AS (
SELECT produce.prod_name,
buyers.buyer_name,
sales.prod_id,
COUNT(1) OVER (PARTITION BY sales.prod_id) p_count
FROM produce
INNER JOIN sales
ON produce.prod_id = sales.prod_id
INNER JOIN buyers
ON sales.buyer_id = buyers.buyer_id)
SELECT prod_name, buyer_name, prod_id
FROM table_
WHERE p_count > 1;
Output:
| PROD_NAME | BUYER_NAME | PROD_ID |
|-----------|--------------|---------|
| ORANGES | ERIN O`MELIA | 3 |
| ORANGES | ADAM BARR | 3 |
Update: You simplified query would be:
With TABLE_ AS (
SELECT produce.prod_name,
buyers.buyer_name,
sales.prod_id,
COUNT(1) OVER (PARTITION BY sales.prod_id) p_count
FROM produce
INNER JOIN sales
ON produce.prod_id = sales.prod_id
INNER JOIN buyers
ON sales.buyer_id = buyers.buyer_id)
SELECT prod_name, buyer_name, prod_id
FROM table_
WHERE p_count > 1;
you don't need distinct. you only need group by and having. you also cant group by buyer_name to get a count > 1. that is where the issue lies, you can to do this as a set of nested queries
select dupes.prod_name,
buyers.buyer_name
from (
select produce.prod_name,
SALES.PROD_ID,
count(SALES.PROD_ID)
from produce
inner join SALES on produce.PROD_ID=SALES.PROD_ID
group by produce.prod_name,
SALES.PROD_ID
having count(*)>1
) as dupes
inner join sales on sales.prod_id = dupes.prod_id
inner join buyers on buyers.buyer_id = sales.buyer_id
Here's my, hopefully, simple answer.
Here's my SQL Fiddle scratch pad as well. http://sqlfiddle.com/#!9/d80f6/3
select P.Prod_Name, Buyer_Name
from Sales S
join
(select prod_id, count(prod_id) as ActSales
from Sales group by prod_id having count(prod_id) > 1) SQ
on S.Prod_Id = SQ.Prod_Id
join Produce P
on S.Prod_Id = P.Prod_Id
join Buyers B
on S.Buyer_Id = B.Buyer_Id