Order by with a condition - sql

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

Related

Query don't return the right result

I have a POST table, a ACTION table and ACTION_TYPE table, I explain the ACTION table contains all the actions that were made by users, and the table ACTION_TYPE contains the actions details for example the ACTION whose ID = 4 has ACTION_TYPE_ID = 1 for POST_ID 6, which mean an action was made for post number 50, we can have many actions for one post_id
the POST table
id title content user_id
---------- ---------- ---------- ----------
1 title1 Text... 1
2 title2 Text... 1
3 title3 Text... 1
4 title4 Text... 5
5 title5 Text... 2
6 title6 Text... 1
the ACTION_TYPE table
id name
---------- ----------
1 updated
2 deleted
3 restored
4 hided
the ACTION table
id post_id action_type_id date
---------- ---------- -------------- -----
1 1 1 2017-01-01
2 1 1 2017-02-15
3 1 3 2018-06-10
4 6 1 2019-08-01
5 5 2 2019-12-09
6 2 3 2020-04-27
7 2 1 2020-07-29
8 3 2 2021-03-13
So i would like to know the last action are made for each post sometimes i would like to get teh last action made by specific action_type and user for each post.
here is my query
select actions, count(*) as cnt
from(
select ac.post_id as action_post_id, max(ac.date) as max_date,
case
when ac.action_type_id is not null then act.name
end as actions,
case
when p.user_id is not null then u.name
end as user_name
from action ac
left join post p on ac.post_id = p.id
left join user u on p.user_id = u.id
left join action_type act on ac.action_type_id = act.id
where p.user_id = 1
and act.name in ('restored','deleted','updated')
group by ac.post_id, case when ac.action_type_id is not null then act.name end , case when p.user_id is not null then u.full_name end
)
group by actions
;
so here is one probleme i want to groupe by post_id but it ask me to add cases too so i get the following error:
/ORA-00979. 00000 - "not a group by expression/
when i delete the cases from group by
but when i use this query i get false result
here the result i get
actions user_name cnt
---------- ---------- -----------
updated ERIC 2
deleted ERIC 2
restored ERIC 2
so here the result expected to be
actions user_name cnt
---------- ---------- -----------
updated ERIC 2
deleted ERIC 1
restored ERIC 1
the sum must equal 4 but i'm geting 6 it's like if it brings more than one action per post
Important ! when i use simple query to check manualy actions the sum equal 4
Best regards
You can use:
SELECT MAX(t.name) AS action_name,
MAX(u.name) AS user_name,
COUNT(*) AS number_posts
FROM users u
INNER JOIN post p
ON (u.id = p.user_id)
INNER JOIN (
SELECT post_id,
MAX(action_type_id) KEEP (DENSE_RANK LAST ORDER BY "DATE", id)
AS action_type_id
FROM action
GROUP BY post_id
) a
ON (p.id = a.post_id)
INNER JOIN action_type t
ON (t.id = a.action_type_id)
GROUP BY
u.id,
t.id
Which, for the sample data:
CREATE TABLE users (id, name) AS
SELECT 1, 'Eric' FROM DUAL;
CREATE TABLE POST ( id, title, content, user_id) AS
SELECT 1, 'title1', 'Text...', 1 FROM DUAL UNION ALL
SELECT 2, 'title2', 'Text...', 1 FROM DUAL UNION ALL
SELECT 3, 'title3', 'Text...', 1 FROM DUAL UNION ALL
SELECT 4, 'title4', 'Text...', 5 FROM DUAL UNION ALL
SELECT 5, 'title5', 'Text...', 2 FROM DUAL UNION ALL
SELECT 6, 'title6', 'Text...', 1 FROM DUAL;
CREATE TABLE ACTION_TYPE ( id, name ) AS
SELECT 1, 'updated' FROM DUAL UNION ALL
SELECT 2, 'deleted' FROM DUAL UNION ALL
SELECT 3, 'restored' FROM DUAL UNION ALL
SELECT 4, 'hided' FROM DUAL;
CREATE TABLE ACTION ( id, post_id, action_type_id, "DATE") AS
SELECT 1, 1, 1, DATE '2017-01-01' FROM DUAL UNION ALL
SELECT 2, 1, 1, DATE '2017-02-15' FROM DUAL UNION ALL
SELECT 3, 1, 3, DATE '2018-06-10' FROM DUAL UNION ALL
SELECT 4, 6, 1, DATE '2019-08-01' FROM DUAL UNION ALL
SELECT 5, 5, 2, DATE '2019-12-09' FROM DUAL UNION ALL
SELECT 6, 2, 3, DATE '2020-04-27' FROM DUAL UNION ALL
SELECT 7, 2, 1, DATE '2020-07-29' FROM DUAL UNION ALL
SELECT 8, 3, 2, DATE '2021-03-13' FROM DUAL;
Outputs:
ACTION_NAME
USER_NAME
NUMBER_POSTS
restored
Eric
1
updated
Eric
2
deleted
Eric
1
fiddle

Join a table that depends on another table

I have a POST table, a CATEGORY table, a TAG table and a MIGTATION_TAG table, I explain the MIGTATION_TAG table contains the movement of the tags between the categories, for example the tag whose ID = 1 belongs to the category whose l 'ID = 10 if I change its category to 12 a line will be added to the MIGTATION_TAG table as follows:
ID 1 TAG_ID 1 CATEGOTY_ID 12
the POST table
id title content tag_id
---------- ---------- ---------- ----------
1 title1 Text... 1
2 title2 Text... 3
3 title3 Text... 1
4 title4 Text... 2
5 title5 Text... 5
6 title6 Text... 4
the CATEGORY table
id name
---------- ----------
1 category_1
2 category_2
3 category_3
the TAG table
id name fist_category_id
---------- ---------- ----------------
1 tag_1 1
2 tag_2 1
3 tag_3 3
4 tag_4 1
5 tag_5 2
the MIGTATION_TAG table
id tag_id category_id
---------- ---------- ----------------
9 1 3
8 5 1
7 1 2
5 3 1
4 2 2
3 5 3
2 3 3
1 1 3
so i would like to know how many posts are registered for each category.
in some cases if there has been no change of category for a tag then it keeps its first category,
I manage to join the TAG table to the POST table via LEFT JOIN but the problem is that the join must depend on the MIGTATION_TAG table which must check if there has been a migration, if so then it must bring me back the last MAX (tag_id ) for each tag ,
here is my query
select category, COUNT(*) AS numer_of_posts
from(
select CATEGORY.name,
case
when POST.tag_id is not null then CATEGORY.name
end as category
from POST
left join TAG ON POST.tag_id = TAG.id
left join (
select id, MAX(tag_id) tag_id
from MIGTATION_TAG
group by id, tag_id
) MIGTATION_TAG
ON TAG.id = MIGTATION_TAG.tag_id
left join CATEGORY on MIGTATION_TAG.category_id = CATEGORY.id
)
GROUP BY category
;
here is the result i want to display with my query
Important ! for the post with id = 6 the tag_id = 4 whish was not changed so it will be using the fist_category_id in TAG table
category numer_of_posts
---------- --------------
category_1 3
category_2 1
category_3 2
Best regards
You can use:
SELECT MAX(c.name) AS category,
COUNT(*)
FROM post p
INNER JOIN tag t
ON (p.tag_id = t.id)
LEFT OUTER JOIN (
SELECT tag_id,
MAX(category_id) KEEP (DENSE_RANK LAST ORDER BY id) AS category_id
FROM migration_tag
GROUP BY tag_id
) m
ON (t.id = m.tag_id)
INNER JOIN category c
ON ( COALESCE(m.category_id, t.first_category_id) = c.id )
GROUP BY c.id
ORDER BY category
Which, for the sample data:
CREATE TABLE POST ( id, title, content, tag_id ) AS
SELECT 1, 'title1', 'Text...', 1 FROM DUAL UNION ALL
SELECT 2, 'title2', 'Text...', 3 FROM DUAL UNION ALL
SELECT 3, 'title3', 'Text...', 1 FROM DUAL UNION ALL
SELECT 4, 'title4', 'Text...', 2 FROM DUAL UNION ALL
SELECT 5, 'title5', 'Text...', 5 FROM DUAL UNION ALL
SELECT 6, 'title6', 'Text...', 4 FROM DUAL;
CREATE TABLE CATEGORY ( id, name ) AS
SELECT 1, 'category_1' FROM DUAL UNION ALL
SELECT 2, 'category_2' FROM DUAL UNION ALL
SELECT 3, 'category_3' FROM DUAL;
CREATE TABLE TAG (id, name, first_category_id) AS
SELECT 1, 'tag_1', 1 FROM DUAL UNION ALL
SELECT 2, 'tag_2', 1 FROM DUAL UNION ALL
SELECT 3, 'tag_3', 3 FROM DUAL UNION ALL
SELECT 4, 'tag_4', 1 FROM DUAL UNION ALL
SELECT 5, 'tag_5', 2 FROM DUAL;
CREATE TABLE MIGRATION_TAG ( id, tag_id, category_id ) AS
SELECT 9, 1, 3 FROM DUAL UNION ALL
SELECT 8, 5, 1 FROM DUAL UNION ALL
SELECT 7, 1, 2 FROM DUAL UNION ALL
SELECT 5, 3, 1 FROM DUAL UNION ALL
SELECT 4, 2, 2 FROM DUAL UNION ALL
SELECT 3, 5, 3 FROM DUAL UNION ALL
SELECT 2, 3, 3 FROM DUAL UNION ALL
SELECT 1, 1, 3 FROM DUAL;
Outputs:
CATEGORY
COUNT(*)
category_1
3
category_2
1
category_3
2
fiddle
One option uses a left join to bring the tag table, and the a lateral join to lookup the latest migration, ifi any. We can then use conditional logic:
select coalesce(t2.category_id, t.first_category_id) category, count(*) number_of_posts
from post p
inner join tag t on t.id = p.tag_id
outer apply (
select mt.category_id
from migration_tag mt
where mt.tag_id = p.tag_id
order by mt.id desc fetch first row only
) t2
group by coalesce(t2.category_id, t.first_category_id)

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>

Counting one field of table in other table

I wrote a script in oracle. But it does not give me the result that i want.
I need this one, imagine i have two table. Order_table and book table.
My order table is like this
ORDER_TABLE Table
ID TYPE_ID VALUE_ID
1 11 null
2 11 null
3 11 null
4 12 null
5 11 null
Book Table
ID ORDER_TYPE DELETED
1 1 F
2 null F
3 5 F
4 5 F
5 4 F
6 4 F
7 3 T
My script is like this
Select *
From (
Select Newtable.Counter As Value_id,
o.Id As Id,
o.Type_id As Type_id
From (
Select (Count B.Order_Type) As Counter,
B.Order_Type As Id
From Book B
Where B.Deleted = 'F'
Group By B.Order_Type
Order By Count(B.Order_Type) Desc
) newtable,
order_table o
where o.id = newtable.id
and o.type_id = 11
)
order by id asc;
Result is like this.
Value_ID TYPE_ID ID
2 11 5
2 11 4
1 11 1
It is not showing that second and third id has 0 count, Have can i show 0 count too ?
Result should be like this.
Value_ID TYPE_ID ID
2 11 5
2 11 4
1 11 1
0 11 2
0 11 3
First, do not use implicit JOIN syntax(comma separated), that's one of the reason this mistakes are hard to catch! Use the proper JOIN syntax.
Second, your problem is that you need a left join, not an inner join , so try this:
Select *
From (Select coalesce(Newtable.Counter,0) As Value_id,
o.Id As Id,
o.Type_id As Type_id
From order_table o
LEFT JOIN (Select Count(B.Order_Type) As Counter, B.Order_Type As Id
From Book B
Where B.Deleted = 'F'
Group By B.Order_Type
Order By Count(B.Order_Type) Desc) newtable
ON(o.id = newtable.id)
WHERE o.type_id = 11)
order by id asc;
Oracle Setup:
CREATE TABLE order_table ( id, type_id, value_id ) AS
SELECT 1, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 2, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 3, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 4, 12, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 5, 11, CAST( NULL AS INT ) FROM DUAL;
CREATE TABLE book ( id, order_type, deleted ) AS
SELECT 1, 1, 'F' FROM DUAL UNION ALL
SELECT 2, NULL, 'F' FROM DUAL UNION ALL
SELECT 3, 5, 'F' FROM DUAL UNION ALL
SELECT 4, 5, 'F' FROM DUAL UNION ALL
SELECT 5, 4, 'F' FROM DUAL UNION ALL
SELECT 6, 4, 'F' FROM DUAL UNION ALL
SELECT 7, 3, 'T' FROM DUAL;
Query:
SELECT COUNT( b.order_type ) AS value_id,
o.id,
o.order_type
FROM order_table o
LEFT OUTER JOIN
book b
ON ( o.id = b.order_type AND b.deleted = 'F' )
WHERE o.type_id = 11
GROUP BY o.id, o.type_id
ORDER BY value_id DESC, id DESC;
Output:
VALUE_ID ID TYPE_ID
-------- -- -------
2 5 11
1 1 11
0 3 11
0 2 11
However, if you did want to use the legacy Oracle comma-join syntax then you can get the same result with:
SELECT COUNT( b.order_type ) AS value_id,
o.id,
o.order_type
FROM order_table o,
book b
WHERE o.type_id = 11
AND b.order_type (+) = o.id
AND b.deleted (+) = 'F'
GROUP BY o.id, o.type_id
ORDER BY value_id DESC, id DESC;
But please don't as the ANSI/ISO joins are much easier to comprehend the join conditions.
You could also do this with a scalar subquery, which may or may not be more performant than the left join versions described in the other answers. (Quite possibly, the optimizer may rewrite it to be a left join anyway!):
with order_table ( id, type_id, value_id ) as (select 1, 11, cast( null as int ) from dual union all
select 2, 11, cast( null as int ) from dual union all
select 3, 11, cast( null as int ) from dual union all
select 4, 12, cast( null as int ) from dual union all
select 5, 11, cast( null as int ) from dual),
book ( id, order_type, deleted ) as (select 1, 1, 'F' from dual union all
select 2, null, 'F' from dual union all
select 3, 5, 'F' from dual union all
select 4, 5, 'F' from dual union all
select 5, 4, 'F' from dual union all
select 6, 4, 'F' from dual union all
select 7, 3, 'T' from dual)
-- end of mimicking your tables; you wouldn't need the above subqueries as you already have the tables.
-- See SQL below:
select (select count(*) from book bk where bk.deleted = 'F' and bk.order_type = ot.id) value_id,
ot.type_id,
ot.id
from order_table ot
order by value_id desc,
id desc;
VALUE_ID TYPE_ID ID
---------- ---------- ----------
2 11 5
2 12 4
1 11 1
0 11 3
0 11 2

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.