MYSQL Multiple Select For Same Category? - sql

I have 3 tables (scenes, categories, scenes_categories ) in a many to many relationship.
scenes ( id, title, description )
categories ( id, title )
scenes_categories ( scene_id, category_id )
I'm having problems making a query to select scenes that must match multiple categories. For example, I might want to select scenes that match category 3 AND category 5 AND category 8, but I can't figure out how to get this to work.
So far I've got something like
SELECT scenes.id, scenes.title, scenes.description
FROM scenes
LEFT JOIN scenes_categories ON scenes.id = scenes_categories.scene_id
LEFT JOIN categories ON scenes_categories.category_id = categories.id
WHERE scenes_categories.category_id = '3'
AND scenes_categories.category_id = '5'
AND scenes_categories.category_id = '8'
AND scenes.id = '1'
How can I select for records that must match all the category ID's specified?

You need to require that a row exists in your many-to-many table for that sceneId, for each categoryId you are requiring:
So try this:
SELECT s.id, s.title, s.description
FROM scenes s
WHERE s.id = '1'
And Exists (Select * From scenes_categories
Where scene_id = s.Id
And category_id = '3')
And Exists (Select * From scenes_categories
Where scene_id = s.Id
And category_id = '5')
And Exists (Select * From scenes_categories
Where scene_id = s.Id
And category_id = '8')
another option that should work is to do three inner joins instead:
SELECT s.id, s.title, s.description
FROM scenes s
Join scenes_categories c3
On c3.scene_id = s.Id
And c3.category_id ='3'
Join scenes_categories c5
On c5.scene_id = s.Id
And c5.category_id ='5'
Join scenes_categories c8
On c8.scene_id = s.Id
And c8.category_id ='8'
WHERE s.id = '1'

Charles Bretana's answer will work, but might want to check the performance of that against this to see which works better for you.
SELECT * FROM scenes
INNER JOIN (
SELECT scene_id
FROM scenes_categories
WHERE category_id IN (3,5,8)
GROUP BY scene_id
HAVING count(*) = 3
) valid ON scenes.id = valid.scene_id
Assuming your SQL is dynamic, this will probably be a bit easier to implement.

Related

List of users who do not have transactions after 'x' date

I have a query I need to create that pulls data from three different tables. Essentially the end result is to pull data for users that have not had any activity on a users' account since 07/01/2018, but they have to have an account plan as "x." Is there a way to manipulate this query I've created to get it to display what I need it to? Maybe somehow correlate it to a count of 0 on the TRANDATE column? Very lost here and could use some help!
select p.ID as ID, p.LAST as LastName, p.FIRST as FirstName
From gl
inner join p
on p.ID = gl.PID
left join psp
on psp.PLANNUM = gl.ACCNUM
where gl.ACCNUM = 'x'
and psp.ACTIVE = 1
and gl.TRANDATE <= to_date('07/01/2018', 'MM/DD/YYYY')
;
commit;
Thank you all very much and please let me know if there's anything else I can provide here.
You don't really specify your table structure, so I'm kinda guessing here. Does your "gl" table have more than one record per PID+ACCNUM? What are the primary keys? There are a lot of extra details you could provide.
select p.ID as ID, p.LAST as LastName, p.FIRST as FirstName, g.maxdate
From (select gl.PID, gl.ACCNUM, max(gl.TRANDATE) as maxdate
from gl
where gl.ACCNUM = 'x'
group by gl.PID, gl.ACCNUM) g
inner join p
on p.ID = g.PID
inner join psp
on psp.PLANNUM = g.ACCNUM
and psp.ACTIVE = 1
where g.maxdate <= to_date('07/01/2018', 'MM/DD/YYYY')
;
Also, (a) you don't need a commit on a select, and (b) a left/outer join is an inner join when you have its column in the WHERE clause (psp.ACTIVE = 1).
I would simply use aggregation and max() in the having clause:
select p.ID as ID, p.LAST as LastName, p.FIRST as FirstName
from gl inner join
p
on p.ID = gl.PID inner join
psp
on psp.PLANNUM = gl.ACCNUM
where gl.ACCNUM = 'x' and psp.ACTIVE = 1
group by p.ID, p.LAST, p.FIRST
having max(gl.TRANDATE) <= date '2018-07-01';
Note that the where condition on psp.ACTIVE turns the outer join into an inner join, so I changed the join type for readability.
Since all your display fields are from p, you can do grouping in inner queryfor the p.id which occur more than once. All the rest of ids(not in) are of your interest.
select p.ID as ID, p.LAST as LastName, p.FIRST as FirstName
From p where p.ID not in (
select p.ID From gl
inner join p on p.ID = gl.PID
left join psp on psp.PLANNUM = gl.ACCNUM
where gl.ACCNUM = 'x'
and psp.ACTIVE = 1
and gl.TRANDATE <= to_date('07/01/2018', 'MM/DD/YYYY')
group by p.ID
having count(1) >= 1)

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);

Oracle SQL Correlated subquery - Returning count(*) in some columns

I have my initial statement which is :
SELECT TEAM.ID PKEY_SRC_OBJECT,
TEAM.MODF_DAT UPDATE_DATE,
TEAM.MODF_USR UPDATED_BY,
PERSO.FIRST_NAM FISRT_NAME
FROM TEAM
LEFT OUTER JOIN PERSO ON (TEAM.ID=PERSO.TEAM_ID)
I want to calculate some "flags" and return them in my initial statement.
There are 3 flags which can be calculated like this :
1) Flag ISMASTER:
SELECT Count(*)
FROM TEAM_TEAM_REL A, TEAM B
WHERE B.PARTY_PTY_ID = A.RLTD_TEAM_ID
AND CODE = 'Double';
2) Flag ISAGENT:
SELECT Count(*)
FROM TEAM_ROL_REL A, TEAM B
WHERE B.PARTY_PTY_ID = A.TEAM_ID;
3) Flag NUMPACTS:
SELECT Count(*)
FROM TEAM_ROL_REL A,
TEAM_ROL_POL_REL B,
PERSO_POL_STA_REL C,
TEAM D
WHERE A.ROL_CD IN ('1','2')
AND A.T_ROL_REL_ID = B.P_ROL_REL_ID
AND B.P_POL_ID = C.P_POL_ID
AND C.STA_CD = 'A'
AND D.PARTY_PTY_ID = A.TEAM_ID;
To try to achieve this, I've updated my initial statement like this :
WITH ABC AS (
SELECT TEAM.ID PKEY_SRC_OBJECT,
TEAM.MODF_DAT UPDATE_DATE,
TEAM.MODF_USR UPDATED_BY,
PERSO.FIRST_NAM FISRT_NAME
FROM TEAM
LEFT OUTER JOIN PERSO ON (TEAM.ID=PERSO.TEAM_ID)
)
SELECT ABC.*, MAST.ISMASTER, AGENT.ISAGENT, PACTS.NUMPACTS FROM ABC
LEFT OUTER JOIN (
select
RLTD_TEAM_ID,
Count(RLTD_TEAM_ID) OVER (PARTITION BY RLTD_TEAM_ID) as ISMASTER
FROM TEAM_TEAM_REL
WHERE CODE = 'Double'
) MAST
ON ABC.PKEY_SRC_OBJECT = MAST.RLTD_TEAM_ID
LEFT OUTER JOIN (
select
TEAM_ID,
Count(TEAM_ID) OVER (PARTITION BY TEAM_ID) as ISAGENT
FROM TEAM_ROL_REL
) AGENT
ON ABC.PKEY_SRC_OBJECT = AGENT.TEAM_ID
LEFT OUTER JOIN (
select
TEAM_ID,
Count(TEAM_ID) OVER (PARTITION BY TEAM_ID) as NUMPACTS
FROM TEAM_ROL_REL, TEAM_ROL_POL_REL, PERSO_POL_STA_REL
WHERE TEAM_ROL_REL.ROL_CD IN ('1','2')
AND TEAM_ROL_REL.T_ROL_REL_ID = TEAM_ROL_POL_REL.P_ROL_REL_ID
AND TEAM_ROL_POL_REL.P_POL_ID = PERSO_POL_STA_REL.P_POL_ID
AND PERSO_POL_STA_REL.STA_CD = 'A'
) PACTS
ON ABC.PKEY_SRC_OBJECT = PACTS.TEAM_ID;
For the two first flags (ISMASTER and ISAGENT) I get the result in less than 1min, but for the last flag (NUMPACTS) it runs few minutes without provide any result.
I think my statement is too heavy, maybe I should do it in a totally different way.
I think you have perhaps over complicated things.
You could do this (assuming I have understood your requirements correctly) like so:
WITH ttr AS (SELECT rltd_team_id,
COUNT(*) is_master
FROM team_team_rel
AND CODE = 'Double'
GROUP BY rltd_team_id),
trr AS (SELECT team_id,
COUNT(*) is_agent
FROM team_rol_rel
GROUP BY team_id)
pacts AS (SELECT trr1.team_id,
COUNT(*) num_pacts
FROM team_rol_rel trr1
INNER JOIN team_rol_pol_rel trpr ON (trr1.t_rol_rel_id = trpr.p_rol_rel_id)
INNER JOIN perso_pol_sta_rel ppsr ON (trpr.p_pol_id = ppsr.p_pol_id
WHERE trr1.rol_cd IN ('1', '2')
AND ppsr.st_cd = 'A'
GROUP BY trr1.team_id)
SELECT t.id pkey_src_object,
t.modf_dat update_date,
t.modf_usr updated_by,
p.first_nam first_name,
ttr.is_master,
trr.is_agent,
pacts.num_pacts
FROM team t
LEFT OUTER JOIN perso p ON t.id = p.team_id
LEFT OUTER JOIN ttr ON t.party_pty_id = ttr.rltd_team_id
LEFT OUTER JOIN trr ON t.party_pty_id = trr.team_id
LEFT OUTER JOIN pacts ON t.pkey_src_object = pacts.team_id;
N.B. untested, since you didn't provide any test data.

SQL - left join generate duplicates

I have code to select some applications but LEFT JOIN is creating duplicates.
Question: How to get rid of duplicates generated by
LEFT JOIN
attachments as att
ON
(a.ssn = att.ssn AND att.doc_type = 'id_copy' AND att.source = 'cpt3')
My Work:
I am considering SELECT DISTINCT or GROUP BY but, can't figure out where to put them.
This is the query:
SELECT
a.*, c.code, l.who, l.locked_time, att.id AS attnew,
( SELECT
COUNT(*)
FROM
applications as a2
WHERE
a2.approved = 'Y'
AND
a2.paid = 'Y'
AND
a2.paid_back = 'Y'
AND
a2.ssn = a.ssn
) AS previous_apps
FROM
applications as a
LEFT JOIN
locked_by as l
USING(id)
LEFT JOIN
campaign_codes as c
ON
c.id = a.campaign
LEFT JOIN
attachments as att
ON
(a.ssn = att.ssn AND att.doc_type = 'id_copy' AND att.source = 'cpt3')
WHERE
a.closed='N'
AND
a.paid = 'N'
ORDER BY
a.arrived_date
DESC
Use GROUP BY
to avoid duplicates. In your case:
...
WHERE
a.closed='N'
AND
a.paid = 'N'
GROUP BY
a.id
ORDER BY
a.arrived_date
DESC
If you want to make an one to one relation to avoid duplicates and att.id is the same for each att.ssn or you need only one (MAX,MIN,..ANY?) att.id. Try this:
LEFT JOIN
(SELECT ssn,
MAX(id) as id,
FROM attachments
WHERE doc_type = 'id_copy' AND source = 'cpt3'
GROUP BY ssn
)as att
ON
(a.ssn = att.ssn)

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')