Find purchase if same item on different days - sql

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>

Related

get item with GREATEST amount of purchases show all customers

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

How to get the max count of an attribute with 3 tables?

I need to query which author sold the most books and how many books the author sold.
select a.firstname ||''|| a.lastname as fullname,
max(count(datesold))
from author a,
transaction t,
book b
where a.authorid = b.authorid
and b.bookid = t.bookid
group by
a.firstname,
a.lastname;
It gave me an error of not a single-group group function.
Any idea what is the issue here?
With some sample data
SQL> with
2 author (authorid, firstname, lastname) as
3 (select 1, 'Stephen', 'King' from dual union all
4 select 2, 'Jo' , 'Nesbo' from dual),
5 book (bookid, authorid) as
6 (select 100, 1 from dual union all
7 select 200, 1 from dual union all
8 select 300, 2 from dual
9 ),
10 transaction (trans_id, bookid) as
11 (select 1, 100 from dual union all
12 select 2, 100 from dual union all
13 select 3, 100 from dual union all
14 select 4, 300 from dual
15 ),
query uses the RANK analytic function which ranks rows by number of rows in the transaction table (it says how many books were sold). Finally, fetch row(s) that rank as highest:
16 temp as
17 (select a.firstname || ' ' || a.lastname AS fullname,
18 count(t.bookid) cnt,
19 rank() over (order by count(t.bookid) desc) rnk
20 from author a join book b on a.authorid = b.authorid
21 join transaction t on t.bookid = b.bookid
22 group by a.firstname, a.lastname
23 )
24 select fullname, cnt
25 from temp
26 where rnk = 1;
FULLNAME CNT
------------- ----------
Stephen King 3
SQL>
You can use:
select MAX(a.firstname ||' '|| a.lastname) as fullname,
COUNT(datesold)
from author a
INNER JOIN book b
ON (a.authorid = b.authorid)
INNER JOIN transaction t
ON (b.bookid = t.bookid)
GROUP BY
a.authorid
ORDER BY
COUNT(datesold) DESC
FETCH FIRST ROW ONLY;
Do not aggregate by firstname and lastname as there are many people in the world with identical names and you do not want to count everyone with the same name as a single person.
Which, for the sample data:
CREATE TABLE author (authorid, firstname, lastname, dateofbirth) AS
SELECT 1, 'Alice', 'Adams', DATE '1900-01-01' FROM DUAL UNION ALL
SELECT 2, 'Alice', 'Adams', DATE '1910-01-01' FROM DUAL UNION ALL
SELECT 3, 'Betty', 'Baron', DATE '1920-01-01' FROM DUAL UNION ALL
SELECT 4, 'Carol', 'Corrs', DATE '1930-01-01' FROM DUAL UNION ALL
SELECT 5, 'Carol', 'Corrs', DATE '1940-01-01' FROM DUAL;
CREATE TABLE book (bookid, authorid) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 3 FROM DUAL UNION ALL
SELECT 4, 4 FROM DUAL UNION ALL
SELECT 5, 5 FROM DUAL;
CREATE TABLE transaction (bookid, datesold) AS
SELECT 1, DATE '1970-01-01' FROM DUAL UNION ALL
SELECT 1, DATE '1970-01-02' FROM DUAL UNION ALL
SELECT 1, DATE '1970-01-03' FROM DUAL UNION ALL
SELECT 1, DATE '1970-01-04' FROM DUAL UNION ALL
SELECT 3, DATE '1970-01-01' FROM DUAL UNION ALL
SELECT 4, DATE '1970-01-01' FROM DUAL UNION ALL
SELECT 4, DATE '1970-01-02' FROM DUAL UNION ALL
SELECT 5, DATE '1970-01-01' FROM DUAL UNION ALL
SELECT 5, DATE '1970-01-02' FROM DUAL UNION ALL
SELECT 5, DATE '1970-01-03' FROM DUAL;
Outputs:
FULLNAME
COUNT(DATESOLD)
Alice Adams
4
db<>fiddle here

Selecting the company with the highest number of sales of the most costly product

I have a table of all the sales records of many products, by different companies. Every record includes the product sold, the price it was sold for, the name of the company who sold it (note that the same product can be sold for any possible price). Example table structure:
id | product | sales_price | company
---+---------+-------------+---------
1 | prod122 | 123376456 | abcSales
2 | prod123 | 345676433 | xyzSales
3 | prod122 | 346876543 | xyzSales
4 | prod124 | 124688533 | wpwSales
I am trying to find out which company has the highest number of sales of the most pricey product. In other words, I want to write a query that finds the product with the highest average price, call it PRODUCTX, then finds the company with the highest number of sales of PRODUCTX, call it COMPANYX.
The result should be one row showing COMPANYX in one column and the number of sales of PRODUCTX in the other column.
Your help is extremely appreciated.
You can try this:
WITH DATAA (id,product,sales_price,company)
AS
(
SELECT 1, 'prod122', 123376456, 'abcSales' FROM DUAL UNION ALL
SELECT 2, 'prod123', 345676433, 'xyzSales' FROM DUAL UNION ALL
SELECT 3, 'prod122', 346876543, 'xyzSales' FROM DUAL UNION ALL
SELECT 4, 'prod124', 124688533, 'wpwSales' FROM DUAL
)
SELECT
D.COMPANY,
D.PRODUCT,
T.AVG_PRICE,
COUNT(1) NO_OF_PRODUCT
FROM
DATAA D
JOIN (
SELECT
AVG(SALES_PRICE) AVG_PRICE,
PRODUCT
FROM
DATAA
GROUP BY
PRODUCT
ORDER BY
AVG_PRICE DESC
FETCH FIRST ROWS ONLY
) T ON ( T.PRODUCT = D.PRODUCT )
GROUP BY
D.COMPANY,
D.PRODUCT,
T.AVG_PRICE
ORDER BY
NO_OF_PRODUCT DESC
FETCH FIRST ROWS ONLY;
Output
What if multiple products tie for the highest average sale price?
How about if multiple companies tie for the most sales of the product(s) with the highest average sales price?
The query below accounts for these scenarios. The sales_price in the sample data can be adjusted to examine different scenarios.
WITH sample (id,product,sales_price,company)
AS
(
SELECT 1, 'prod100', 10, 'Company 1' FROM DUAL UNION ALL
SELECT 2, 'prod122', 10, 'Company 1' FROM DUAL UNION ALL
SELECT 3, 'prod123', 10, 'Company 2' FROM DUAL UNION ALL
SELECT 4, 'prod123', 10, 'Company 2' FROM DUAL UNION ALL
SELECT 5, 'prod123', 10, 'Company 3' FROM DUAL UNION ALL
SELECT 6, 'prod123', 10, 'Company 3' FROM DUAL
),
-- Top product(s) by average sales price
top_product AS (
SELECT *
FROM (-- Rank the average sales price (ASP) by product
SELECT product,
AVG(sales_price) avg_sales_price,
RANK() OVER (ORDER BY AVG(sales_price) DESC) avg_sales_price_rank
FROM sample
GROUP BY product
)
WHERE avg_sales_price_rank = 1
),
-- Sales count by company, product
comp_prod_sales AS (
SELECT company,
product,
COUNT(*) sales_count
FROM sample
GROUP BY company,
product
)
-- Top sales of top product(s)
SELECT company,
product,
avg_sales_price,
sales_count
FROM (SELECT comp_prod_sales.*,
top_product.avg_sales_price,
RANK() OVER (PARTITION BY top_product.product ORDER BY sales_count DESC) sales_count_rank
FROM top_product,
comp_prod_sales
WHERE comp_prod_sales.product = top_product.product)
WHERE sales_count_rank = 1;

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;

Order by with a condition

I have two tables, Products and Category. each product is related to a specific Category, and has an Expiry_Date which may be NULL value.
I want to query all productsordered by the soonest Expiry_Date first. Null-Expiry_date products are ordered last for a Category with specific name, like Food.
UPDATED (sample data below):
Product table:
Category Table:
Results:
This isn't just about ordering, you want to exclude some rows with null dates but include others based on the category name; and then order what's left:
select p.prod_id, p.name, p.expiry_date, c.cat_id
from product p
join category c on c.cat_id = p.cat_id
where (c.name = 'Food' or p.expiry_date is not null)
order by p.expiry_date desc nulls last;
The where clause excludes products will null expiry dates, unless they are in the category called 'Food'. The order-by is then straightforward, though as you want it in descending date order you need to specify nulls last to get those... er... last.
Demo with your sample data in CTEs:
with product (prod_id, name, expiry_date, cat_id) as (
select 1, 'NAME1', date '2018-01-10', 1 from dual
union all select 2, 'NAME2', date '2018-01-11', 2 from dual
union all select 3, 'NAME3', date '2018-01-12', 3 from dual
union all select 4, 'NAME4', null, 1 from dual
union all select 5, 'NAME5', null, 2 from dual
union all select 6, 'NAME6', date '2018-01-13', 2 from dual
union all select 7, 'NAME7', date '2018-01-14', 2 from dual
union all select 8, 'NAME8', null, 3 from dual
),
category (cat_id, name) as (
select 1, 'Food' from dual
union all select 2, 'Food1' from dual
union all select 3, 'Food2' from dual
)
select p.prod_id, p.name, p.expiry_date, c.cat_id
from product p
join category c on c.cat_id = p.cat_id
where (c.name = 'Food' or p.expiry_date is not null)
order by p.expiry_date desc nulls last;
PROD_ID NAME EXPIRY_DAT CAT_ID
---------- ----- ---------- ----------
7 NAME7 2018-01-14 2
6 NAME6 2018-01-13 2
3 NAME3 2018-01-12 3
2 NAME2 2018-01-11 2
1 NAME1 2018-01-10 1
4 NAME4 1
If I understand correctly, just use nulls last in the order by:
select c.category, p.*
from products p join
category c
on p.? = c.? -- whatever the join keys are
order by category, expiry_date nulls last