IN Clause selects all variables - mysql

When I run this procedure:
SELECT * FROM photo
LEFT JOIN photo_selectedTags
ON photo.COUNTER = photo_selectedTags.PHOTO_COUNTER
WHERE photo_selectedTags.TAGS_COUNTER IN (6,192)
It retrieves rows where TAGS_COUNTER = 6 OR 192
How do I retrieve the rows from 'photo' where TAGS_COUNTER = 6 AND 192?
Corrected: the IN for ALL

The basic idea is this:
SELECT PHOTO_COUNTER
FROM photo_selectedTags
WHERE TAGS_COUNTER in (6, 192)
group by PHOTO_COUNTER
having count(distinct TAGS_COUNTER) = 2 --2 matches # of items in IN clause
You can then do this to get the rest of the columns:
SELECT *
from PHOTO_COUNTER
LEFT JOIN photo_selectedTags
ON photo.COUNTER = photo_selectedTags.PHOTO_COUNTER
where photo.COUNTER in (
SELECT PHOTO_COUNTER
FROM photo_selectedTags
WHERE TAGS_COUNTER in (6,192)
group by PHOTO_COUNTER
having count(distinct TAGS_COUNTER) = 2 --2 matches # of items in IN clause
) a

Edit
Now that i understand what you want and DB structure, try with this:
SELECT * FROM photo
LEFT JOIN photo_selectedTags
ON photo.COUNTER = photo_selectedTags.PHOTO_COUNTER
WHERE photo_selectedTags.TAGS_COUNTER = 6 AND photo_id IN
(SELECT photo_id FROM photoSELECT * FROM photo
LEFT JOIN photo_selectedTags
ON photo.COUNTER = photo_selectedTags.PHOTO_COUNTER
WHERE photo_selectedTags.TAGS_COUNTER = 192)
I don't know if photo_id is an actual field of your table, but try to adapt it to your structure
Obviously in the first SELECT don't insert PHOTO_COUNTER because i'll be always the same value and it haven't much sense.

I would propose to use 2 joins
SELECT *
FROM photo
JOIN photo_selectedTags as photo_selectedTags6 -- this join restricts to 'photo.COUNTER' whic have TAGS_COUNTER = 6
ON photo.COUNTER = photo_selectedTags6.PHOTO_COUNTER
AND photo_selectedTags6.TAGS_COUNTER = 6
JOIN photo_selectedTags as photo_selectedTags192 -- this join restricts to 'photo.COUNTER' whic have TAGS_COUNTER = 192
ON photo.COUNTER = photo_selectedTags192.PHOTO_COUNTER
AND photo_selectedTags192.TAGS_COUNTER = 192
Also would be possible to achive it with analytical functions (if supported by your DB)
-- This one works on teradata. Something similar should work on oracle. Don't know about others
SELECT *
FROM photo
LEFT JOIN photo_selectedTags
ON photo.COUNTER = photo_selectedTags.PHOTO_COUNTER
QUALIFY max(case when photo_selectedTags.TAGS_COUNTER = 6 then 1 end) over (partition by photo.COUNTER) = 1
AND max(case when photo_selectedTags.TAGS_COUNTER = 192 then 1 end) over (partition by photo.COUNTER) = 1
If you have many values in the list (in addition to 192,6), then this might be possible solution
SELECT *
FROM photo
JOIN
(
SELECT PHOTO_COUNTER, count(distinct TAGS_COUNTER) cnt
FROM photo_selectedTags
WHERE TAGS_COUNTER in (192,6)
HAVING cnt = 2 -- adjust this according to the number of different values
) as pht
ON photo.COUNTER = pht.PHOTO_COUNTER
In subquery only PHOTO_COUNTERs are left which have both (192 and 6), then this is joined

Related

Matching Exactly all values in IN clause

I am searching for the solution for this problem for hours now with no luck. I have a Workouts table as below. Each item in the workout table can have multiple target muscles, which are listed in the Target Muscles table.
Workouts table:
id
1
2
Target Muscles table:
id
muscle_key
workout_id
1
a
1
2
b
1
3
c
1
4
a
2
5
b
2
I need to fetch all items in the workouts table which match EXACTLY ALL target muscles keys in the given set, not less and not more. For example, given the set of muscle keys:
(a,b)
The desired output would be:
id
2
The row for workout id = 1 should NOT be selected since it contains an extra muscle key (c).
I am using the following query:
SELECT id
FROM workouts
LEFT JOIN target_muscles ON workouts.id = target_muscles.workout_id
WHERE target_muscles.muscle_key IN (a,b)
GROUP BY workouts.id
HAVING COUNT(DISTINCT target_muscles.muscle_key) = 2
The above query is also returning the workout id = 1, instead of only 2. How can I achieve this?
Any help is appreciated.
Skip the WHERE clause. Use HAVING to make sure exactly a and b are there.
SELECT workouts.id
FROM workouts
JOIN target_muscles ON workouts.id = target_muscles.workout_id
GROUP BY workouts.id
HAVING COUNT(DISTINCT target_muscles.muscle_key) =
COUNT(DISTINCT CASE WHEN target_muscles.muscle_key IN (a,b)
THEN target_muscles.muscle_key END)
AND COUNT(DISTINCT target_muscles.muscle_key) = 2
Can also be done as:
SELECT workouts.id
FROM workouts
JOIN target_muscles ON workouts.id = target_muscles.workout_id
GROUP BY workouts.id
HAVING MIN(target_muscles.muscle_key) = 'a'
AND MAX(target_muscles.muscle_key) = 'b'
AND COUNT(DISTINCT target_muscles.muscle_key) = 2
Or, perhaps less performant:
SELECT workouts.id
FROM workouts
JOIN (SELECT workout_id FROM target_muscles WHERE muscle_key = 'a'
INTERSECT
SELECT workout_id FROM target_muscles WHERE muscle_key = 'b'
EXCEPT
SELECT workout_id FROM target_muscles WHERE muscle_key NOT IN ('a', 'b')) dt
ON workouts.id = dt.workout_id
You can remove the filtering clause and use two conditions:
count of non-(a,b) muscle_keys = 0
distinct count of (a,b) muscle_keys = 2
SELECT w.id
FROM workouts w
LEFT JOIN target_muscles ts ON w.id = ts.workout_id
GROUP BY w.id
HAVING COUNT(CASE WHEN ts.muscle_key NOT IN ('a', 'b') THEN w.id END) = 0
AND COUNT(DISTINCT CASE WHEN ts.muscle_key IN ('a', 'b') THEN ts.muscle_key END) = 2
Check the demo here.
This is an other way to do it using inner join
select distinct s.id
from (
select w.id
from workouts w
inner join targets t on t.workout_id = w.id
group by workout_id
having count(1) = 2
) as s
inner join targets t on t.workout_id = s.id and t.muscle_key in ('a', 'b');
You can Try it from here : https://dbfiddle.uk/nPTQlhqT

Combine the result of 3 SQL queries?

I've had troubles when trying to combine the result of 3 queries:
It's the result of 3 queries joined with union, they all pick datas in the same tables except for the last column and I'd like to have the result in one row, which would give me the result 17 in this case but I can't make it works...Any ideas?
thanks
edit: here is the code of the 3 queries used with union from the result above:
select distinct SSN_ID,
TME_ID,
TME_LIBELLE,
convert(varchar,ssn_date_debut,103) as 'Debut',
CONVERT (varchar,ssn_date_fin,103) as 'Fin',
SSN_NB_JOURS,
COUNT (atr_id) as 'Total'
from SESSION
join INSCRIPTION on INS_SSN_ID = SSN_ID
join ACTEUR on INS_ATR_ID = ATR_ID
join theme on tme_id = ssn_tme_id
join ETAT_SESSION on esn_id = ssn_esn_id
join LIEU_SESSION on LSN_SSN_ID =ssn_id
join LIEU on LEU_ID = LSN_LEU_ID
where INS_DT_CONVOCATION is not null
and SSN_ESN_ID = 15
and SSN_JOURNEE_ETUDE = 1
and LEU_NOM is not null
and year(ssn_date_debut) = YEAR(GETDATE())
group by SSN_ID,TME_ID,TME_LIBELLE,ssn_date_debut,ssn_date_fin,SSN_NB_JOURS,ins_ssn_id
union
select distinct SSN_ID,
TME_ID,
TME_LIBELLE,
convert(varchar,ssn_date_debut,103) as 'Debut',
CONVERT (varchar,ssn_date_fin,103) as 'Fin',
SSN_NB_JOURS,
COUNT (atr_id) as 'Total'
from SESSION
join SESSION_FORMATEUR on ASN_SSN_ID = SSN_ID
join ACTEUR anim on anim.ATR_ID = asn_atr_id
join theme on tme_id = ssn_tme_id
join ETAT_SESSION on esn_id = ssn_esn_id
join LIEU_SESSION on LSN_SSN_ID =ssn_id
join LIEU on LEU_ID = LSN_LEU_ID
where SSN_ESN_ID = 15
and SSN_JOURNEE_ETUDE = 1
and LEU_NOM is not null
and year(ssn_date_debut) = YEAR(GETDATE())
group by SSN_ID, TME_ID,
TME_LIBELLE,ssn_date_debut,ssn_date_fin,SSN_NB_JOURS,asn_ssn_id
union
select distinct SSN_ID,
TME_ID,
TME_LIBELLE,
convert(varchar,ssn_date_debut,103) as 'Debut',
CONVERT (varchar,ssn_date_fin,103) as 'Fin',
SSN_NB_JOURS,
COUNT (atr_id) as 'Total'
from SESSION
left join INTERVENANT on ITV_SSN_ID = SSN_ID
left join ACTEUR on ITV_ATR_ID = ATR_ID
join theme on tme_id = ssn_tme_id
join ETAT_SESSION on esn_id = ssn_esn_id
join LIEU_SESSION on LSN_SSN_ID =ssn_id
join LIEU on LEU_ID = LSN_LEU_ID
where SSN_ESN_ID = 15
and SSN_JOURNEE_ETUDE = 1
and LEU_NOM is not null
and year(ssn_date_debut) = YEAR(GETDATE())
group by SSN_ID, TME_ID,
TME_LIBELLE,ssn_date_debut,ssn_date_fin,SSN_NB_JOURS,ITV_SSN_ID
A derived query approach to get SUM on a total column:
SELECT SSN_ID, TIME_ID, TIME_LIBELLE, DEBUT, FIN, SSN_NB_JOURS, SUM(Total) as Total
FROM
(
-- Your original SELECT with UNION
SELECT .. FROM ..
UNION ALL
SELECT .. FROM ..
UNION ALL
SELECT .. FROM ..
) d
GROUP BY SSN_ID, TIME_id, TIME_LIBELLE, DEBUT, FIN, SSN_NB_JOURS
Such way should be a valid syntax on both: mysql and sql server
Use as
select(
select distinct (all columns except the last one) from table) , (select sum(total) from table)

MySQL select with group and one to many relations condition

For example have such structure:
CREATE TABLE clicks
(`date` varchar(50), `sum` int, `id` int)
;
CREATE TABLE marks
(`click_id` int, `name` varchar(50), `value` varchar(50))
;
where click can have many marks
So example data:
INSERT INTO clicks
(`sum`, `id`, `date`)
VALUES
(100, 1, '2017-01-01'),
(200, 2, '2017-01-01')
;
INSERT INTO marks
(`click_id`, `name`, `value`)
VALUES
(1, 'utm_source', 'test_source1'),
(1, 'utm_medium', 'test_medium1'),
(1, 'utm_term', 'test_term1'),
(2, 'utm_source', 'test_source1'),
(2, 'utm_medium', 'test_medium1')
;
I need to get agregated values of click grouped by date which contains all of selected values.
I make request:
select
c.date,
sum(c.sum)
from clicks as c
left join marks as m ON m.click_id = c.id
where
(m.name = 'utm_source' AND m.value='test_source1') OR
(m.name = 'utm_medium' AND m.value='test_medium1') OR
(m.name = 'utm_term' AND m.value='test_term1')
group by date
and get 2017-01-01 = 700, but I want to get 100 which means that only click 1 has all of marks.
Or if condition will be
(m.name = 'utm_source' AND m.value='test_source1') OR
(m.name = 'utm_medium' AND m.value='test_medium1')
I need to get 300 instead of 600
I found answer in getting distinct click_id by first query and then sum and group by date with condition whereIn, but on real database which is very large and has id as uuid this request executes extrimely slow. Any advices how to get it work propely?
You can achieve it using below queries:
When there are the three conditions then you have to pass the HAVING count(*) >= 3
SELECT cc.DATE
,sum(cc.sum)
FROM clicks AS cc
INNER JOIN (
SELECT id
FROM clicks AS c
LEFT JOIN marks AS m ON m.click_id = c.id
WHERE (
m.NAME = 'utm_source'
AND m.value = 'test_source1'
)
OR (
m.NAME = 'utm_medium'
AND m.value = 'test_medium1'
)
OR (
m.NAME = 'utm_term'
AND m.value = 'test_term1'
)
GROUP BY id
HAVING count(*) >= 3
) AS t ON cc.id = t.id
GROUP BY cc.DATE
When there are the three conditions then you have to pass the HAVING count(*) >= 2
SELECT cc.DATE
,sum(cc.sum)
FROM clicks AS cc
INNER JOIN (
SELECT id
FROM clicks AS c
LEFT JOIN marks AS m ON m.click_id = c.id
WHERE (
m.NAME = 'utm_source'
AND m.value = 'test_source1'
)
OR (
m.NAME = 'utm_medium'
AND m.value = 'test_medium1'
)
GROUP BY id
HAVING count(*) >= 2
) AS t ON cc.id = t.id
GROUP BY cc.DATE
Demo: http://sqlfiddle.com/#!9/fe571a/35
Hope this works for you...
You're getting 700 because the join generates multiple rows for the different IDs. There are 3 rows in the mark table with ID=1 and sum=100 and there are two rows with ID=2 and sum=200. On doing the join where shall have 3 rows with sum=100 and 2 rows with sum=200, so adding these sum gives 700. To fix this you have to aggregate on the click_id too as illustrated below:
select
c.date,
sum(c.sum)
from clicks as c
inner join (select * from marks where (name = 'utm_source' AND
value='test_source1') OR (name = 'utm_medium' AND value='test_medium1')
OR (name = 'utm_term' AND value='test_term1')
group by click_id) as m
ON m.click_id = c.id
group by c.date;
DEMO SQL FIDDLE
I found the right way myself, which works on large amounts of data
The main goal is to make request generate one table with subqueries(conditions) which do not depend on amount of data in results, so the best way is:
select
c.date,
sum(c.sum)
from clicks as c
join marks as m1 ON m1.click_id = c.id
join marks as m2 ON m2.click_id = c.id
join marks as m3 ON m3.click_id = c.id
where
(m1.name = 'utm_source' AND m1.value='test_source1') AND
(m2.name = 'utm_medium' AND m2.value='test_medium1') AND
(m3.name = 'utm_term' AND m3.value='test_term1')
group by date
So we need to make as many joins as many conditions we have

(My)SQL JOIN - get teams with exactly specified members

Assume tables
team: id, title
team_user: id_team, id_user
I'd like to select teams with just and only specified members. In this example I want team(s) where the only users are those with id 1 and 5, noone else. I came up with this SQL, but it seems to be a little overkill for such simple task.
SELECT team.*, COUNT(`team_user`.id_user) AS cnt
FROM `team`
JOIN `team_user` user0 ON `user0`.id_team = `team`.id AND `user0`.id_user = 1
JOIN `team_user` user1 ON `user1`.id_team = `team`.id AND `user1`.id_user = 5
JOIN `team_user` ON `team_user`.id_team = `team`.id
GROUP BY `team`.id
HAVING cnt = 2
EDIT: Thank you all for your help. If you want to actually try your ideas, you can use example database structure and data found here: http://down.lipe.cz/team_members.sql
How about
SELECT *
FROM team t
JOIN team_user tu ON (tu.id_team = t.id)
GROUP BY t.id
HAVING (SUM(tu.id_user IN (1,5)) = 2) AND (SUM(tu.id_user NOT IN (1,5)) = 0)
I'm assuming a unique index on team_user(id_team, id_user).
You can use
SELECT
DISTINCT id,
COUNT(tu.id_user) as cnt
FROM
team t
JOIN team_user tu ON ( tu.id_team = t.id )
GROUP BY
t.id
HAVING
count(tu.user_id) = count( CASE WHEN tu.user_id = 1 or tu.user_id = 5 THEN 1 ELSE 0 END )
AND cnt = 2
Not sure why you'd need the cnt = 2 condition, the query would get only those teams where all of users having the ID of either 1 or 5
Try This
SELECT team.*, COUNT(`team_user`.id_user) AS cnt FROM `team`
JOIN `team_user` ON `team_user`.id_team = `team`.id
where `team_user`.id_user IN (1,5)
GROUP BY `team`.id
HAVING cnt = 2

Select Most Recent Items with dup values

Given the following information, how can I select the most recent line items (based on time_entered) on unique params and cron_action_id pairs that haven't been executed?
cron_schedule
For example, ids 1, 2, and 4 have the same params and cron_action_id, so I need not select all 3, just id 4. Same principle for id 3/5 and 7/8.
I can only get so far as
SELECT *
FROM cron_schedule cs
INNER JOIN cron_actions ca
ON cs.cron_action_id = ca.cron_action_id
WHERE time_executed = 0
-- GROUP BY (params, cron_action_id) ?
This should return rows with id 4, 5, 6, and 8
Thanks
SELECT t1.*
FROM cron_schedule t1
INNER JOIN (SELECT params,
cs.cron_action_id,
Max(time_entered) AS time_entered
FROM cron_schedule cs
INNER JOIN cron_actions ca
ON cs.cron_action_id = ca.cron_action_id
WHERE time_executed = 0
GROUP BY params, cron_action_id) AS t2
ON t1.params = t2.params
AND t1.cron_action_id = t2.cron_action_id
AND t1.time_entered = t2.time_entered
INNER JOIN cron_actions ca2
ON t1.cron_action_id = ca2.cron_action_id
WHERE t1.time_executed = 0
Not tested, but this basic idea should work:
SELECT *
FROM
cron_schedule outer_cron_schedule JOIN cron_actions
WHERE
time_entered = (
SELECT MAX(time_entered)
FROM cron_schedule inner_cron_schedule
WHERE
outer_cron_schedule.id = inner_cron_schedule.id
GROUP BY
params, cron_action_id
)
AND time_executed = 0
Give it a try