select between two many-to-many relations - sql

I have four tables Level, Tag, Level_Tag and Tag_hierarchy. How can select all tags of a level which have this condition id_tag = id_parent which means the Tag is the root. I can select from join table (Maybe not a good performance?) but I don't know how to add the other self join here.
SELECT level.name, tag.id, tag.name
FROM level INNER JOIN
tag_level ON level.id = tag_level.id_level INNER JOIN
tag ON tag_level.id_tag = tag.id
WHERE (level.Id = #id)
Tag Table contains thousands of rows and I'm really worry about memory and performance issues.
Could you please help me on this? Here is the schema

Try this:
;with cte as
(select id_tag
from tag_hierarchy where id_tag = id_parent)
select l.name, t.id, t.name
from cte c
inner join tag t on t.id = c.id_tag
inner join tag_level tl on t.id = tl.id_tag
inner join level l on tl.id_level = l.id
where l.lid = #id

Maybe you can add another exists. Like this:
SELECT
level.name,
tag.id,
tag.name
FROM
level
INNER JOIN tag_level
ON level.id = tag_level.id_level
INNER JOIN tag
ON tag_level.id_tag = tag.id
WHERE
(level.Id = #id)
AND EXISTS
(
SELECT NULL
FROM Tag_hierarchy
WHERE Tag_hierarchy.id_tag=tag.id
AND Tag_hierarchy.id_tag=Tag_hierarchy.id_parent
)

Related

Filter on two many to many relations

I'm trying to apply multiples filter on a table join to two tables
My tables
main
Id
Name
tags
Id
Name
main_primary_tags
mainId
tagId
main_secondary_tags
mainId
tagId
I want to select some items in main table which have some primary and secondary tags.
The difficulity is I need to have an exact result.
Exemple if I want to select items which have "Tag1 and "Tag2" tags, I want to have items which have both of these tags not just one of them (Like IN operator)
And I can filter on primary AND secondary..
Thanks for your help!
I would aggregate the tags into arrays and compare the arrays:
select main.id as main_id,
mt.tags as main_tags,
st.tags as secondary_tags
from main
left join lateral (
select array_agg(mpt.tag order by t.tag) as tags
from main_primary_tags mpt
join tags t1 on t1.id = mpt.tagid
where mpt.mainid = main.id
) mt on true
left join lateral (
select array_agg(mst.tag order by t.tag) as tags
from main_secondary_tags mst
join tags t2 on t2.id = mst.tagid
where mst.mainid = main.id
) st on true
where mt.tags = array['Tag1', 'Tag2']
and st.tags = array['Tag1', 'Tag2']
Note that the = operator for arrays depends on the order of the elements, so it's important to list them in the same way order by sorts them.
You can also do it like this
WITH tag_filter AS (
SELECT 'Tag1' AS primary_tag, 'Tag2' AS secondary_tag
UNION ALL
SELECT 'Tag1', 'Tag2'
)
SELECT m.*
FROM "main" m
INNER JOIN main_primary_tags mpt ON mpt.mainId = m.Id
INNER JOIN main_secondary_tags mst ON mst.mainId = m.Id
INNER JOIN tags primary_t ON primary_t.Id = mpt.tagId
INNER JOIN tags secondary_t ON secondary_t.Id = mst.tagId
INNER JOIN tag_filter tf
ON tf.primary_tag = primary_t."Name"
AND tf.secondary_tag = secondary_t."Name"
Or if you want something more compact in the tag_filter CTE
WITH tag_filter AS (
SELECT primary_tag, secondary_tag
FROM (VALUES ('Tag1', 'Tag2'), ('Tag1', 'Tag2')) t(primary_tag, secondary_tag)
)
SELECT m.*
FROM "main" m
INNER JOIN main_primary_tags mpt ON mpt.mainId = m.Id
INNER JOIN main_secondary_tags mst ON mst.mainId = m.Id
INNER JOIN tags primary_t ON primary_t.Id = mpt.tagId
INNER JOIN tags secondary_t ON secondary_t.Id = mst.tagId
INNER JOIN tag_filter tf
ON tf.primary_tag = primary_t."Name"
AND tf.secondary_tag = secondary_t."Name"
And in the case the tag_filter is much smaller than the main table, then you can also spool the tagId in the CTE, and then join with that instead.
WITH tag_name_filter AS (
SELECT primary_tag, secondary_tag
FROM (VALUES ('Tag1', 'Tag2'), ('Tag1', 'Tag2')) t(primary_tag, secondary_tag)
),
tag_filter AS (
SELECT primary_t.Id AS primary_tag_id, secondary_t.Id AS secondary_tag_id
FROM tag_name_filter tmf
INNER JOIN tags primary_t ON primary_t."Name" = tmf.primary_tag
INNER JOIN tags secondary_t ON secondary_t."Name" = tmf.secondary_tag
)
SELECT m.*
FROM "main" m
INNER JOIN main_primary_tags mpt ON mpt.mainId = m.Id
INNER JOIN main_secondary_tags mst ON mst.mainId = m.Id
INNER JOIN tag_filter tf
ON tf.primary_tag_id = mpt.tagId
AND tf.secondary_tag_id = mst.tagId

combine two query results into one with conditions in SQL Server

I have two query to combine two results into one. However; my challenge is to get the second query look into the first query if it doesn't exist in the first query.
I changed my post to the actual query
SELECT Name.CO_ID, Name.FULL_NAME, Name.ID, rpt.date AS StartDate,
vw_Coords.TARGET_ID AS CoordID, vw_RegDirs.TARGET_ID AS
RDID
FROM Name INNER JOIN
Tops_Profile ON dbo.Name.ID = Tops_Profile.ID left
outer JOIN
vw_mz_rpt_leader_log rpt ON Name.CO_ID = rpt.ID LEFT
OUTER JOIN
vw_RegDirs ON Name.CO_ID = vw_RegDirs.CHAPTER LEFT
OUTER JOIN
vw_Coords ON Name.CO_ID = vw_Coords.CHAPTER LEFT OUTER
JOIN
Tops_Chapter ON Tops_Chapter.ID = Name.CO_ID
WHERE (Name.MEMBER_TYPE = 'm') AND (Tops_Profile.LDR = '1') and
LOG_TEXT like '%LEADER Change%'
union
SELECT Name.CO_ID, Name.FULL_NAME, Name.ID,
YEAR(dbo.Tops_Chapter.PST_DATE_LEAD) AS StartDate,
vw_Coords.TARGET_ID AS CoordID, vw_RegDirs.TARGET_ID AS
RDID
FROM Name INNER JOIN
Tops_Profile ON Name.ID = Tops_Profile.ID left outer
JOIN
vw_mz_rpt_leader_log rpt ON Name.CO_ID = rpt.ID LEFT
OUTER JOIN
vw_RegDirs ON Name.CO_ID = vw_RegDirs.CHAPTER LEFT
OUTER JOIN
vw_Coords ON Name.CO_ID = vw_Coords.CHAPTER LEFT OUTER
JOIN
Tops_Chapter ON Tops_Chapter.ID = Name.CO_ID
WHERE (Name.MEMBER_TYPE = 'm') AND (Tops_Profile.LDR = '1')
the scope is if the record exists in the first query don't bring it from second query.
Here's a quick and dirty way...
select *
from
(select id, Name, log.Date
from Name
inner join Log on Name.id = log.id
where log.text_log like '%Leader%'
union
select id, Name, Profile.Date
from Name
inner join profile on Name.id = profile.id
where profile.Leader = '1') d
order by row_number() over(partition by x.id order by x.Date asc)
Note, this doesn't care where John came from, it's simply finding the first occurrence based on the date which seems to be what you want.
You have altered your request. Suddenly both queries select from the same tables and a UNION (or UNION ALL for that matter) doesn't seem a good solution anymore.
There are very few differences between the two queries even. And looking at the whole it boils down to: select records for member_type = 'm' and tp.ldr = 1 and then keep only one record per name, preferredly one with log_text like '%LEADER Change%'. This is mere ranking, as already shown in my other answer. You only need one query to select all records in question and use TOP (1) WITH TIES to keep the best matches per name.
select top(1) with ties
n.co_id,
n.full_name,
n.id,
case when log_text like '%LEADER Change%' then rpt.date else year(tc.pst_date_lead) end
as startdate,
c.target_id as coordid,
rd.target_id as rdid
from name n
inner join tops_profile tp on n.id = tp.id
left outer join vw_mz_rpt_leader_log rpt on n.co_id = rpt.id
left outer join vw_regdirs rd on n.co_id = rd.chapter
left outer join vw_coords c on n.co_id = c.chapter
left outer join tops_chapter tc on tc.id = n.co_id
where n.member_type = 'm'
and tp.ldr = 1
order by row_number() over (
partition by n.id
order by case when log_text like '%LEADER Change%' then 1 else 2 end);
As you said you just want only one record per name, I am using ROW_NUMBER. If you want more, use RANK instead.
It's not clear why you are joining the tops_chapter table. Is log_text a column in that table? (You should use a table qualifier for this column in your query.) If it isn't, then the join is superfluous and you can remove it from your query.
Use row_number and select id's with least date
with cte as
(select id, Name, log.Date
from Name
inner join Log on Name.id = log.id
where log.text_log like '%Leader%'
union all
select id, Name, Profile.Date as log.date
from Name
inner join profile on Name.id = profile.id
where profile.Leader = '1'
) , ct1 as (select id,name,log.date, ROW_NUMBER() over (partition by id order by log.date) rn from cte )
select id,name,log.date from ct1 where rn = 1
where profile.Leader = '1'
and id not in ( select Name.id
from Name
inner join Log
on Name.id = log.id
where log.text_log like '%Leader%' )
You can use NOT EXISTS in the second query to filter out already existing Name records:
select id, Name, log.Date
from Name
inner join Log on Name.id = log.id
where log.text_log like '%Leader%'
union
select n1.id, n1.Name, Profile.Date
from Name as n1
inner join profile on n1.id = profile.id
where profile.Leader = '1' and
not exists (select 1
from Name as n2
inner join Log on n2.id = Log.id
where Log.text_log like '%Leader%' and
n2.id = n1.id and n2.name = n1.name)
The query below finds logdate and profiledate for each name. If there is a logdate, the logdate will be diplayed else the profile date will be displayed. If both don't exist the Name won't be displayed.
select id, Name, coalesce(log.Date,profile.date)
from Name
left join Log on Name.id = log.id and log.text_log like '%Leader%'
left join profile on Name.id = profile.id and profile.Leader = '1'
where coalesce(log.Date,profile.date) is not null
You can add a rank to your two queries. Then per ID you keep the record(s) with the better rank (using ORDER BY with TOP (1) WITH TIES).
select top(1) with ties
id, name, date
from
(
select n.id, n.name, log.date, 1 as rnk
from name n
inner join log on name.id = log.id
where log.text_log like '%Leader%'
union all
select n.id, n.name, profile.date, 2 as rnk
from name n
inner join profile on name.id = profile.id
where profile.leader = '1'
) data
order by rank() over (partition by id order by rnk);

multi level cascade inner join select query

I've following table structure
I'd like to select post_id from any available data of short_name(country name), name (state table) or region_name. Executing following query true result for region_name but not for short_name(country name), name (state table).
select *
from t_post_city
inner join t_region on t_region.region_id = t_post_city.city_id
inner join t_country on t_region.country_id = t_country.country_id
inner join t_states on t_region.province_id = t_states.state_id
where t_country.short_name like %india%
or t_states.name like %rajasthan%
or t_region.region_name like %sitapura%
Tell me please, where I'm mistaking!
select *
from t_post_city
LEFT OUTER join t_region on t_region.region_id = t_post_city.city_id
LEFT OUTER join t_country on t_region.country_id = t_country.country_id
LEFT OUTER join t_states on t_region.province_id = t_states.state_id
where t_country.short_name like '%india%'
or t_states.name like '%rajasthan%'
or t_region.region_name like '%sitapura%'
Use a distinct table expression for each of your three criteria. Write your OR logic using UNION in SQL:
select post_id
from t_post_city
inner join t_region on t_region.region_id = t_post_city.city_id
where t_country.short_name like %india%
t_region.region_name like %sitapura%
union
select post_id
from t_post_city
inner join t_region on t_region.region_id = t_post_city.city_id
inner join t_country on t_region.country_id = t_country.country_id
where t_country.short_name like %india%
union
select post_id
from t_post_city
inner join t_region on t_region.region_id = t_post_city.city_id
inner join t_states on t_region.province_id = t_states.state_id
where t_states.name like %rajasthan%;

How to select multiple many to many in relation with a single table

I'm currently working with database, but I've got stuck with a select query.
However, I'm not database expert.
The query should return the data from a table that has two relationships of many to many.
This is my tables Diagram that would shows the concept of my question
The Select Query should View three columns, which are VidTbl.Name, ActorTbl.Name and SubTitelTbl.name.
So, I've read and search in the Internet and I've given tries
First try
SELECT
VidTbl.NAME AS Video_Titel_Name,
ActorTbl.NAME AS Actor_Name
FROM ActorInVid
INNER JOIN VidTbl
ON VidTbl.Id = ActorInVid.FKVidId
INNER JOIN ActorTbl
ON ActorTbl.Id = ActorInVid.FKActorId
UNION all
SELECT
VidTbl.NAME AS Video_Titel_Name,
SubTitelTbl.NAME AS SubTitel_Langu
FROM SubTitelInVid
INNER JOIN VidTbl
ON VidTbl.Id = SubTitelInVid.FKVidId
INNER JOIN SubTitelTbl
ON SubTitelTbl.Id = SubTitelInVid.FKSTId
The Result I've got, it was wrong
Then I tried another way to solve this problem, but again I've got another error
second try
SELECT Temp1.*
From (SELECT VidTbl.Id AS Video_Id,
VidTbl.NAME AS Video_Titel_Name,
ActorTbl.NAME AS Actor_Name
FROM ActorInVid
INNER JOIN VidTbl
ON VidTbl.Id = ActorInVid.FKVidId
INNER JOIN ActorTbl
ON ActorTbl.Id = ActorInVid.FKActorId) AS Temp1
SELECT Temp2.*
FROM (SELECT VidTbl.Id AS Video_Id,
SubTitelTbl.NAME AS SubTitel_Langu
FROM SubTitelInVid
INNER JOIN VidTbl
ON VidTbl.Id = SubTitelInVid.FKVidId
INNER JOIN SubTitelTbl
ON SubTitelTbl.Id = SubTitelInVid.FKSTId) AS Temp2
SELECT *
FROM VidTbl
INNER JOIN Temp1
on Temp1.Video_Id = VidTbl.Id
INNER JOIN Temp2
on Temp2.Video_Id = VidTbl.Id
The error, I've got in the last select that was wrong
Thanks a lot for your help any ways
I wish that my question is clear and useful
Thanks again.
You are close. This should work...
SELECT
VidTbl.Name,
ActorTbl.Name,
SubTitelTbl.name
FROM VidTbl
INNER JOIN ActorInVid ON VidTbl.Id = ActorInVid.FKVidId
INNER JOIN ActorTbl ON ActorTbl.Id = ActorInVid.FKActorId
INNER JOIN SubTitelInVid ON VidTbl.Id = SubTitelInVid.FKVidId
INNER JOIN SubTitelTbl ON SubTitelTbl.Id = SubTitelInVid.FKSTId
SELECT DISTINCT vt.Name, at.Name, st.Name
FROM VidTbl vt
JOIN ActionInVid aiv ON aiv.VidId = vt.Id
JOIN SubtitleInVid siv ON siv.VidId = vt.Id
JOIN ActorTbl at ON at.Id = aiv.ActorId
JOIN SubTitleTbl st ON st.Id = siv.STId

SQL SELECT with m:n relationship

I have m:n relationship between users and tags. One user can have m tags, and one tag can belong to n users. Tables look something like this:
USER:
ID
USER_NAME
USER_HAS_TAG:
USER_ID
TAG_ID
TAG:
ID
TAG_NAME
Let's say that I need to select all users, who have tags "apple", "orange" AND "banana". What would be the most effective way to accomplish this using SQL (MySQL DB)?
SELECT u.*
FROM (
SELECT user_id
FROM tag t
JOIN user_has_tag uht
ON uht.tag_id = t.id
WHERE tag_name IN ('apple', 'orange', 'banana')
GROUP BY
user_id
HAVING COUNT(*) = 3
) q
JOIN user u
ON u.id = q.user_id
By removing HAVING COUNT(*), you get OR instead of AND (though it will not be the most efficient way)
By replacing 3 with 2, you get users that have exactly two of three tags defined.
By replacing = 3 with >= 2, you get users that have at least two of three tags defined.
In addition to the other good answers, it's also possible to check the condition in a WHERE clause:
select *
from user u
where 3 = (
select count(distinct t.id)
from user_has_tag uht
inner join tag t on t.id = uht.tag_id
where t.name in ('apple', 'orange', 'banana')
and uht.user_id = u.userid
)
The count(distinct ...) makes sure a tag is counted only once, even if the user has multiple 'banana' tags.
By the way, the site fruitoverflow.com is not yet registered :)
You can do it all with joins...
select u.*
from user u
inner join user_has_tag ut1 on u.id = ut1.user_id
inner join tag t1 on ut1.tag_id = t1.id and t1.tag_name = 'apple'
inner join user_has_tag ut2 on u.id = ut2.user_id
inner join tag t2 on ut2.tag_id = t2.id and t2.tag_name = 'orange'
inner join user_has_tag ut3 on u.id = ut3.user_id
inner join tag t3 on ut3.tag_id = t3.id and t3.tag_name = 'banana'
SELECT *
FROM USER u
INNER JOIN USER_HAS_TAG uht
ON u.id = uht.user_id
INNER JOIN TAG t
ON uht.TAG_ID = t.ID
WHERE t.TAG_NAME IN ('apple','orange','banana')