Join a table that depends on another table - sql

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)

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

Query to delete duplicate records by keeping original in oracle

This is the table.
Id. Name
1 A
1 A
2 B
2 C
1 A
2 B
2 D
The output should be
Id. Name
1 A
2 B
2 C
2 D
please try
Select distinct id, name
from <name of you table>
order by name
Check this link.
Sample data:
create table demo (id, name) as
select 1, 'A' from dual union all
select 1, 'A' from dual union all
select 2, 'B' from dual union all
select 2, 'C' from dual union all
select 1, 'A' from dual union all
select 2, 'B' from dual union all
select 2, 'D' from dual;
select * from demo order by 1,2;
ID NAME
---------- ------------------------------
1 A
1 A
1 A
2 B
2 B
2 C
2 D
7 rows selected
Delete all but the first row in each (id, name) group:
delete demo where rowid in
( select lag(rowid) over (partition by id, name order by null) from demo );
3 rows deleted
select * from demo order by 1,2
ID N
---------- -
1 A
2 B
2 C
2 D
4 rows selected.

How can I get all the parent element by providing child element ID in oracle?

My Oracle table looks like this
ID | ParentID
-----------------
1 | 0
2 | 1
3 | 2
4 | 3
5 | 3
If I know only ID and need to get all parent elements in oracle, what is the query I need to use?
ex:- If I pass 5, need to get 5 > 3 > 2 > 1
For example:
SQL> with test (id, parent) as
2 (select 1, 0 from dual union
3 select 2, 1 from dual union
4 select 3, 2 from dual union
5 select 4, 3 from dual union
6 select 5, 3 from dual
7 )
8 select listagg(id, '->') within group (order by level) result
9 from test
10 start with id = &par_id
11 connect by prior parent = id;
Enter value for par_id: 5
RESULT
---------------------------------------------------------------------
5->3->2->1
SQL>
You may use a recursive CTE
WITH cte (id, parentid, p)
AS (SELECT id,
parentid,
To_char(id) AS p
FROM t
WHERE id = :p_id --enter 5
UNION ALL
SELECT t.id,
t.parentid,
c.p
|| '>'
|| t.id AS p
FROM t
JOIN cte c
ON ( c.parentid = t.id ))
SELECT p
FROM cte
WHERE parentid = 0 --Highest parent.
Demo

Using the results of a STRING_AGG function with the IN operator in a WHERE clause

I have column children_ids which contain PKs from a STRING_AGG function. I am trying to use this column within a WHERE clause with the IN operator to return the total_pets but it doesn't work. If I copy and paste the values directly into the IN operator the query returns the correct info, otherwise no reuslts are found.
Here are my data sets:
Parents
=======
id parent_name
----------------
1 Bob and Mary
2 Mick and Jo
Children
========
id child_name parent_id
-------------------------
1 Eddie 1
2 Frankie 1
3 Robbie 1
4 Duncan 2
5 Rick 2
6 Jen 2
Childrens Pets
===============
id pet_name child_id
-------------------------
1 Puppy 1
2 Piggy 2
3 Monkey 3
4 Lamb 4
5 Tiger 5
6 Bear 6
7 Zebra 6
Expected Output
===============
parent_id children_ids total_pets
-----------------------------------
1 1,2,3 3
2 4,5,6 4
Current [undesired] Output
==========================
parent_id children_ids total_pets
-----------------------------------
1 1,2,3 0
2 4,5,6 0
here is the standard sql to test for yourself
# setup data with standardSQL
WITH `parents` AS (
SELECT 1 id, 'Bob and Mary' parent_names UNION ALL
SELECT 2, 'Mick and Jo'
),
`children` AS (
SELECT 1 id, 'Eddie' child_name, 1 parent_id UNION ALL
SELECT 2, 'Frankie', 1 UNION ALL
SELECT 3, 'Robbie', 1 UNION ALL
SELECT 4, 'Duncan', 2 UNION ALL
SELECT 5, 'Rick', 2 UNION ALL
SELECT 6, 'Jen', 2
),
`childrens_pets` AS (
SELECT 1 id, 'Puppy' pet_name, 1 child_id UNION ALL
SELECT 2, 'Piggy', 2 UNION ALL
SELECT 3, 'Monkey', 3 UNION ALL
SELECT 4, 'Lamb', 4 UNION ALL
SELECT 5, 'Tiger', 5 UNION ALL
SELECT 6, 'Bear', 6 UNION ALL
SELECT 7, 'Zebra', 6
)
And the query:
#standardSQL
select
parent_id
, children_ids
-- !!! This keeps returning 0 instead of the total pets for each parent based on their children
, (
select count(p1.id)
from childrens_pets p1
where cast(p1.child_id as string) in (children_ids)
) as total_pets
from
(
SELECT
p.id as parent_id
, (
select string_agg(cast(c1.id as string))
from children as c1
where c1.parent_id = p.id
) as children_ids
FROM parents as p
join children as c
on p.id = c.parent_id
join childrens_pets as cp
on cp.child_id = c.id
)
GROUP BY
parent_id
, children_ids
... but is there a way to do it using the IN operator as my query ...
Just fix one line and it will work for you!
Replace
WHERE CAST(p1.child_id AS STRING) IN (children_ids)
with
WHERE CAST(p1.child_id AS STRING) IN (SELECT * FROM UNNEST(SPLIT(children_ids)))
Huh? This would seem to do what you want:
SELECT p.id as parent_id,
string_agg(distinct cast(c.id as string)) as children_ids
count(distinct cp.id) as num_pets
FROM parents p JOIN
children c
ON p.id = c.parent_id JOIN
children_pets cp
ON cp.child_id = c.id
GROUP BY parent_id;

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.