SELECT WHERE IN with logical AND statement - mysql

Im trying to select only objects which are in all three groups (1,2,3).
With the "WHERE gid IN (1,2,3)" Select i get an OR selection. What i need is an AND selection.
OBJECT_TABLE AS o
id | field1 | field2 | ...
VALUES
1, a, b
2, c, d
3, e, f
...
GROUP_XREF_TABLE AS gx
oid | gid
VALUES
1, 1
1, 2
1, 3
2, 2
3, 1
3, 2
...
SELECT DISTINCT o.id, gx.gid FROM `OBJECT_TABLE` AS o
LEFT JOIN `GROUP_XREF_TABLE` AS gx ON o.id = gx.oid
WHERE gx.gid IN (1,2,3)
This outputs all rows. I need an WHERE clause which outputs only rows with the object id 1, because only that object is in all three groups.
Its part of a larger select, so its important that this should be done only within the where statement (subselects should be fine if needed).

The subselect below will find those oid:s that have 3 rows in gx:
SELECT o.id, gx.gid FROM `OBJECT_TABLE` AS o
LEFT JOIN `GROUP_XREF_TABLE` AS gx ON o.id = gx.oid
WHERE gx.oid IN (
SELECT oid FROM GROUP_XREF_TABLE as gx2
GROUP BY oid
HAVING count(*) = 3)

try this
SELECT o.id, gx.gid FROM `OBJECT_TABLE` AS o
LEFT JOIN `GROUP_XREF_TABLE` AS gx ON o.id = gx.oid
WHERE gx.gid IN (1,2,3)
group by gx.gid
DEMO HERE

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

How to convert a NOT IN query into a JOIN query

I have this following query that needs to be optimized
Select
1 As status,
e.entity_id,
e.attribute_set_id,
e.type_id,
e.created_at,
e.updated_at,
e.sku,
e.name,
e.short_description,
e.image,
e.small_image,
e.thumbnail,
e.url_key,
e.free,
e.number_of_downloads,
e.sentence1,
e.url_path
From
catalog_product_flat_1 As e
Inner Join catalog_category_product_index_store1 As cat_index
On cat_index.product_id = e.entity_id And
cat_index.store_id = 1 And
cat_index.visibility In (3, 2, 4) And
cat_index.category_id = '2'
Where
e.entity_id Not In (13863, 14096, 13856, 13924, 15875, 15869, 13788, 15977, 15873, 17141, 22214, 16900, 14485,
15628, 15656, 14220, 14259, 14284, 13875, 13216, 14168, 13892, 16540, 19389, 17286, 16591, 30178, 31517, 31734,
31621, 2487, 2486, 2485, 2484, 2483, 2482, 2481, 2480, 2479, 2478, 2477, 2475, 2474, 2473, 13402, 13427, 13694,
13774, 13804, 13837, 13849, 13864, 30299, 30300) And
e.free = 1
Order By
e.number_of_downloads Desc;
Here The ids passed in NOT IN() are the "product_id" column values from a table named "mcsdownloads"
So my goal here is to replace NOT IN with a JOIN operation on table "mcsdownloads".
please help !
You are looking for LEFT JOIN ... WHERE ... IS NULL:
SELECT ...
From catalog_product_flat_1 As e
Inner Join catalog_category_product_index_store1 As cat_index
ON cat_index.product_id = e.entity_id
LEFT JOIN mcsdownloads AS m ON m.entity_id = e.entity_id
WHERE cat_index.store_id = 1
And cat_index.visibility In (3, 2, 4)
And cat_index.category_id = '2'
AND e.free = 1
AND m.entity_id IS NULL
(Note: I moved the filtering criteria from ON to WHERE. ON is used for saying how tables relate; WHERE is for filtering.)
Indexes needed:
cat_index: INDEX(store_id, category_id, visibility, product_id)
e: INDEX(free, number_of_downloads)
m: INDEX(entity_id)
I don't think there is a way to perform a join instead of not in, but you can arrange your code in the following way so that it works correctly without manually writing down all the product_id from the mcsdownloads table
SELECT
1 AS status, e.entity_id, e.attribute_set_id,
e.type_id, e.created_at, e.updated_at, e.sku,
e.name, e.short_description, e.image, e.small_image,
e.thumbnail, e.url_key, e.free, e.number_of_downloads,
e.sentence1, e.url_path
FROM catalog_product_flat_1 AS e
INNER JOIN catalog_category_product_index_store1 AS cat_index ON cat_index.product_id=e.entity_id
AND cat_index.store_id=1
AND cat_index.visibility IN (3,2,4)
AND cat_index.category_id='2'
WHERE (e.entity_id NOT IN (SELECT product_id FROM mcsdownloads))
AND (e.free = 1)
ORDER BY e.number_of_downloads DESC;
This is the significant part that I changed
e.entity_id NOT IN (SELECT product_id FROM mcsdownloads)

Django Intersection on Mysql database

My table with this values:
id, product, category
1, A, 1
2, B, 1
3, B, 2
4, C, 1
5, C, 2
6, D, 2
7, E, 2
8, E, 3
9, F, 3
10, F, 4
I need to select only products matching category (1 OR 2) AND category (3 OR 4) in Django. Expected result is only product: E
I have error "intersections are not supported in mysql" How i can achieve this using Q() ?
This query return no result but work.
main_query = Q()
main_query &= Q(category__in=[1,2])
main_query &= Q(category__in=[3,4])
models.Table.objects.filter(main_query)
SELECT product
FROM yourTable
GROUP BY product
HAVING COUNT(CASE WHEN category IN (1,2) THEN 1 END) > 0
AND COUNT(CASE WHEN category IN (3,4) THEN 1 END) > 0
Here is one option with inline tables.
SELECT a.* FROM
(SELECT * FROM tab WHERE category IN (1,2)) AS a,
(SELECT * FROM tab WHERE category IN (3,4)) AS b
WHERE a.product = b.product ;
Demo here
Another option using WITH Clause as following
WITH cat_1_2 AS (
SELECT * FROM tab WHERE category IN (1,2)
),
cat_3_4 AS (
SELECT * FROM tab WHERE category IN (3,4)
)
SELECT a.* FROM cat_1_2 AS a, cat_3_4 AS b
WHERE a.product = b.product ;
Demo here
The AND condition is applied to each single row so you have not the result you expected ..but for select the rows associated to the category you have in IN clause.
You could try using a couple of inner join on subquery
SELECT t.*
FROM table t
INNER JOIN (SELECT 1 cat
UNION
SELECT 2) t1 ON t1.cat = t.category
INNER JOIN (SELECT 3 cat
UNION
SELECT 4) t2 ON t2.cat = t.category
I learned from #RajmondNijland that INTERSECT doesn't exist in MySQL. Thanks him. So, You can use the following query with IN operator to link the subquery with the main:
SELECT product
FROM table1
WHERE category IN (1,2)
AND product IN ( SELECT product FROM table1 WHERE category IN (3,4) );
product
-------
E

MySQL how to join on same table including missing rows

I have a table with text in various language. Its defined like this:
Id|Language|Text
EXAMPLE DATA
0, ENU, a
0, DAN, b
1, ENU, c
2, ENU, d
2, DAN, e
3, ESP, f
3, ENU, g
Language and Id form the key.
Now I want to extract all texts in a langauge (lets say english) and have the coorosponding text in another language (lets say danish) shown in the column next to. So the result should be:
0, a, b
1, c,
2, d, e
3, g
I know I can do a join like this:
SELECT t1.Id, t1.Text AS "ENU", t2.Text AS "DAN" table as t1
JOIN table as t2 ON (t1.Id= t2.Id)
WHERE t1.Langauge = "ENU" AND t2.Language = "DAN";
But this does not include the missing rows (ie row id=1 and id=3). How to do this?
* UPDATE ****
I get suggestion to use LEFT JOIN but I cant get it working. Maybe because my table layout is a bit different than in the simplified question above. My table is defined as this:
Language|MPageId|MFieldId|MParagraph|MText
Where Language,MPageId,MFieldId,MParagraph forms the key
I tried this:
SELECT t1.MPageId, t1.MFieldId, t1.MParagraphId, t1.MText, t2.MText
FROM main as t1 LEFT JOIN main as t2 ON (t1.MPageId = t2.MPageId AND
t1.MFieldId = t2.MFieldId AND t1.MParagraphId = t2.MParagraphId) WHERE
t1.MLanguage = 'ENU' AND t2.MLanguage = 'DAN'
SELECT t1.Id, t1.Text AS "ENU", t2.Text AS "DAN" FROM table as t1
LEFT JOIN table as t2 ON (t1.Id= t2.Id AND t2.Language = "DAN")
WHERE t1.Langauge = "ENU"
You do need the left join... but the "AND" clause for "DAN"ish would be applied AT the LEFT JOIN, and not in the WHERE clause... The where clause implies an INNER JOIN
SELECT
t1.Id,
t1.Text AS "ENU",
t2.Text AS "DAN"
from
YourTable t1
LEFT JOIN YourTable t2
ON t1.Id= t2.Id
AND t2.Language = "DAN"
where
t1.Langauge = "ENU"
You want a Left Join: http://www.tizag.com/mysqlTutorial/mysqlleftjoin.php
select Id,
MAX(case when LanguageS='ENU' then Text else null end ) as A,
MAX( case when LanguageS<>'ENU' then Text else null end ) as B
from LAN
GROUP BY 1
Id A B
0 a b
1 c ?
2 d e
3 g f

How can I do this with MySQL?

I am using MySQL and have two database tables as follows:
Users
id username
--------------
1 Bill
2 Steve
Objects
user_id key value
----------------------
1 A X
1 B Y
1 C Z
2 A S
2 C T
What query is required to produce the following result?
username A B C
-------------------
Bill X Y Z
Steve S T
I have tried this with an INNER JOIN, but end up with 5 rows (one for each corresponding object row).
Any help much appreciated.
If 'A', 'B', and 'C' are known beforehand, you can do this:
SELECT users.username,
( SELECT objects.value
FROM objects
WHERE objects.user_id = users.id
AND objects.`key` = 'A'
) AS a,
( SELECT objects.value
FROM objects
WHERE objects.user_id = users.id
AND objects.`key` = 'B'
) AS b,
( SELECT objects.value
FROM objects
WHERE objects.user_id = users.id
AND objects.`key` = 'C'
) AS c
FROM users
ORDER
BY users.username
;
select u.username
, oA.value A
, oB.value B
, oC.value C
from users u
left
join objects oA
on u.id = oA.user_id
and oA.key = 'A'
left
join objects oB
on u.id = oB.user_id
and oB.key = 'B'
left
join objects oC
on u.id = oC.user_id
and oC.key = 'C'
perhaps this is not the query that you are asking for, but this is a clean and sipmle query that I have used in your situation:
select objects.*, matrix.*
from
(select users.id, o.key
from users, (select distinct key from objects) as o
)
as matrix left join objects on matrix.id = objects.user_id
and matrix.key = objets.key
order by matrix.id, matrix.key
This query "fills" the empty spaces. So you can consume the resultset with two nested foreach (or whatever similar) and draw the desired table.