Django Intersection on Mysql database - mysql

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

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

Select rows with show flag but based on another table

Can someone help me about this problem.
I have 2 tables:
TASKS:(id,once)
SAVED_tasks (id, task_id)
table where i save tasks..
I need to show all tasks but NOT if the task have value is 1 and if is already inserted in SAVED_tasks table..
EXAMPLE:
tasks:
id, name, once
1, task1, 0
2, task2, 1
3, task3, 0
4, task4, 1
saved_tasks:
id, task_id
1, 1
2, 2
3, 3
4, 4
I need result:
1, task1
3, task3
SELECT TASK.id, SAVED_tasks.task_id FROM TASKS Inner join SAVED_tasks ON TASK.id = SAVED_tasks.id
AND TASK.once > 1
Try this:
SELECT t.id, t.once
FROM TASKS t
LEFT JOIN SAVED_tasks st ON t.id = st.task_id
WHERE (t.once != 1 OR (t.once = 1 AND st.id IS NULL));
OR
SELECT t.id, t.once
FROM TASKS t
LEFT JOIN SAVED_tasks st ON t.id = st.task_id
WHERE NOT(t.once = 1 AND st.id IS NULL);
This ought to do it:
Select * from TASKS
Where TASKS.id Not In (Select task_id from SAVED_TASKS)
and TASK.once != 1
Try joining two tables if you need values if you have data in your SAVED_task table and once != 1 like below:
SELECT t.id
FROM TASKS t INNER JOIN SAVED_tasks st
ON t.id = st.id
WHERE t.once != 1

SELECT WHERE IN with logical AND statement

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

SQL order by multiple condition

I have a table with 'name','status'-fail or success,'counts'- 1 to 100. I want to order it in such a way that names of only 'fail' should show on top together, then same name with fail and success together, then names with only success together.
Can we do it sql query language ?
Thank you.
Ok, this should work on most RDBMS:
SELECT A.*
FROM YourTable A
INNER JOIN (SELECT name,
COUNT(DISTINCT status) StatusCount,
MIN(status) MinStatus
FROM YourTable
GROUP BY name) B
ON A.name = B.name
ORDER BY CASE WHEN StatusCount = 1 AND MinStatus = 'fail' THEN 1
WHEN StatusCount = 2 THEN 2
ELSE 3 END, A.name, A.status
If it is MySQL
select *, 0 as o
from t
union all
select *, 1 as o
from t
order by
o = 1 and status != 'fail', o != 1, status = fail

Limiting JOIN query to 5 of each type

I have 2 tables, one has the categories within it, and the other has the distinct items in it.
What I want to do is to select
the item_id and cat_id, and limit it to 5 result for each category, for example:
cat_id item_id
1 1
1 2
1 3
1 4
2 5
2 6
3 7
etc...
The closest query i've come up with
SELECT cat.cat_id, cat.cat_name, item_id, item_author, item_name, item_pic_big
FROM item_table ipt
JOIN cat_table cat ON ipt.cat_id = cat.cat_id
GROUP BY item_id
HAVING COUNT(*) < 5
ORDER BY cat.cat_id
That's what I found on stackoverflow, however if I want to change the count to let's say... 2, it gives me the same result, and if I change the group by to cat_id, it only gives me 1 result.
Any help would be appreciated
What you actually need is something like:
SELECT
cat.cat_id, cat.cat_name, item_id, item_author, item_name, item_pic_big
FROM item_table a
JOIN cat_table b ON a.cat_id = b.cat_id
WHERE a.item_id IN (SELECT item_id
FROM item_table
WHERE cat_id = b.cat_id
LIMIT 5)
GROUP BY a.item_id
ORDER BY b.cat_id
Unfortunately, if you try to run this you will get this disappointing
error message:
This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
So you'll need a workaround. You can see a list of possible workarounds here:
http://www.artfulsoftware.com/infotree/queries.php#104
EDIT: The first solution there can be translated to your structure like this:
(I don't have your tables, so there may be minor column names issues)
SELECT temp2.cat_id, temp2.item_id,
temp2.cat_name, temp2.item_author,
temp2.item_name, temp2.item_pic_big
FROM
(SELECT
temp.cat_id,
temp.item_id,
temp.cat_name,
temp.item_author,
temp.item_name,
temp.item_pic_big,
IF( #prev <> temp.cat_id, #rownum := 1, #rownum := #rownum+1 ) AS rank,
#prev := temp.cat_id
FROM (SELECT
a.item_id,
b.cat_id,
b.cat_name,
a.item_author,
a.item_name,
a.item_pic_big
FROM item_table a
JOIN cat_table b ON a.cat_id = b.cat_id
ORDER BY cat_id, item_id) AS temp
JOIN (SELECT #rownum := NULL, #prev := 0) AS r
ORDER BY temp.cat_id, temp.item_id) as temp2
WHERE temp2.rank <= 5
ORDER BY temp2.cat_id, temp2.item_id;