Query to get the common values - mysql

I have a table like
userId classId
1 1
2 1
3 1
1 2
2 2
1 3
2 4
3 5
In the above, [1, 2, 3] share class 1. [1, 2] share class 2, and so on.
If I give userId 1, 2, 3 in a query that should return classId 1.
If I give 1 and 2, the query should return 2.
If I give 1, the query should return 3.
How can I write a MySQL query for that?

The relational operator you require is division, popularly known as "the supplier who supplies all parts".
Because you don't consider class = 1 to be a valid result when given user = {1, 2}, you should be looking at exact division (i.e. without a remainder). You also need to consider what the result should be given an empty divisor.

This was tested with Postgresql, but I'm pretty sure it should work without much modifications under mysql.
Table setup
drop table classes;
create table classes (
user_id integer,
class_id integer
);
insert into classes values(1, 1);
insert into classes values(2, 1);
insert into classes values(3, 1);
insert into classes values(1, 2);
insert into classes values(2, 2);
insert into classes values(1, 3);
insert into classes values(2, 4);
insert into classes values(3, 5);
1, 2, 3
select max(class_id) from
(
select class_id
from classes
where user_id = 1
intersect
select class_id
from classes
where user_id = 2
intersect
select class_id
from classes
where user_id = 3
) as foo;
1, 2
select max(class_id) from
(
select class_id
from classes
where user_id = 1
intersect
select class_id
from classes
where user_id = 2
) as foo;
1 (Could be simplified, but I prefer it so, for symmetry reasons)
select max(class_id) from
(
select class_id
from classes
where user_id = 1
) as foo;

You could use a query like this (for users 1,2,3) -
SELECT t1.classId FROM (SELECT * FROM users_classes WHERE userId = 1) t1 -- for userId = 1
JOIN (SELECT * FROM users_classes WHERE userId = 2) t2 ON t1.classId = t2.classId -- for userId = 2
JOIN (SELECT * FROM users_classes WHERE userId = 3) t3 ON t1.classId = t3.classId -- for userId = 3
For users 1,2 -
SELECT t1.classId FROM (SELECT * FROM users_classes WHERE userId = 1) t1 -- for userId = 1
JOIN (SELECT * FROM users_classes WHERE userId = 2) t2 ON t1.classId = t2.classId -- for userId = 2
These query will output all possible classId, to get one classId you can add LIMIT or aggregate function.

Try this
SELECT DISTINCT (
classId
)
FROM `data`
WHERE userid =1
OR userid =2 or classid = 3
LIMIT 0 , 1
SELECT DISTINCT (
classId
)
FROM `data`
WHERE userid =1
OR userid =2
LIMIT 1 , 1
SELECT DISTINCT (
classId
)
FROM `data`
WHERE userid =1
LIMIT 2 , 1

Related

Selecting according to the sum of a certain's row's column conditionally in MySQL

Assume we got a table like this:
id other_id limit
......................
1 1 4
2 1 5
3 2 3
4 2 2
Assume we got a total limit of 5 and other_id 1, is there any way I can get IDs 3,4 from the table above?
More conditions:
total limit = 3, other_id = 1 => IDs of 1 rows return
total limit = 9, other_id = 1 => IDs of 1,2 rows return
total limit = 10, other_id = 1,2 => IDs of 1,2,3 rows return
total limit = 15, other_id = 1,2 => IDs of 1,2,3,4 rows return
total limit = 3, other_id = 2 => IDs of 3 rows return
total limit = 4, other_id = 2 => IDs of 3,4 rows return
The third one is obviously unexpected, but I would be able to handle the error in a different way (total limit being more than the total of limits in the table)
You want a rolling total. MySql supports window functions since version 8.0. Limit is a reserved word, using climit for the column name
select id, climit
from (
select *, sum(climit) over(order by id) s
from mytable
) t
where s - climit < myLimitParameter;
in mysql 5.x and above you can do a correlated query
CREATE TABLE tab1( id int, `other_id` int, limits int);
INSERT INTO tab1 VALUES
(1, 1, 4),
(2, 1, 5),
(3, 2, 3),
(4, 2, 2)
SELECT GROUP_CONCAT(id)
FROM
(SELECT t.id, t.other_id
, (SELECT SUM(limits) FROM tab1 WHERE limits > t.limits OR (id < t.id AND limits = t.limits)
OR (id = t.id)) required
FROM tab1 t
HAVING required <= '12'
ORDER BY id,other_id ASC) t1
| GROUP_CONCAT(id) |
| :--------------- |
| 1,2,3 |
db<>fiddle here

MySQL - reducing number of SELECTs with one smart WHERE

I have a table with 3 columns that keeps some four-digit ids in it. Like that:
+ main_id + id_1 + id_2 + id_3 +
|---------|------|------|------|
| 1 | 1000 | 1500 | 1900 |
| 2 | 1001 | 1501 | 1901 |
| 3 | 1002 | 1502 | 1902 |
+---------+------+------+------+
The idea is the values can't repeat themselves via other combinations. I mean, if the table already has 1001 - 1501 - 1901, combinations like 1001 - 1901 - 1501 or 1501 - 1001 - 1901 can't appear in the table anymore and should point the initial combination with main_id and should return 2 in any case.
For this, I got a stored function to which I pass all three ids and get the main_id on exit, like this one:
SET temp_id = (SELECT `main_id` FROM `tableName` WHERE (`id_1` = id1 AND `id_2` = id2 AND `id_3` = id3) LIMIT 1);
IF (temp_id IS NULL) THEN SET temp_id = (SELECT `main_id` FROM `tableName` WHERE (`id_1` = id1 AND `id_2` = id3 AND `id_3` = id2) LIMIT 1);
IF(temp_id IS NULL) THEN SET temp_id = (SELECT `main_id` FROM `tableName` WHERE (`id_1` = id2 AND `id_2` = id1 AND `id_3` = id3) LIMIT 1);
IF(temp_id IS NULL) THEN SET temp_id = (SELECT `main_id` FROM `tableName` WHERE (`id_1` = id2 AND `id_2` = id3 AND `id_3` = id1) LIMIT 1);
IF(temp_id IS NULL) THEN SET temp_id = (SELECT `main_id` FROM `tableName` WHERE (`id_1` = id3 AND `id_2` = id1 AND `id_3` = id2) LIMIT 1);
IF(temp_id IS NULL) THEN SET temp_id = (SELECT `main_id` FROM `tableName` WHERE (`id_1` = id3 AND `id_2` = id2 AND `id_3` = id1) LIMIT 1);
END IF;
END IF;
END IF;
END IF;
END IF;
RETURN temp_id;
I thought about using all of possible combinations in WHERE clauses in one SELECT with OR operator, like this one:
SELECT `main_id` FROM `tableName`
WHERE (((`id_1` = id1 AND `id_2` = id2 AND `id_3` = id3)
OR (`id_1` = id2 AND `id_2` = id1 AND `id_3` - id3)
...
)) LIMIT 1
but it would run through all possible solutions not stopping when one found. The previous example would at least stop going deeper if the combination is found.
For only three columns I get 6 combinations in total. I plan to do the same thing for 4 and 5 columns, giving me a huge amount of select queries in total.
The question is, is there a way to speed this function up? Reduce the amount of SELECT queries I send? I use InnoDB, maybe something quicker would come in handy?
UPDATE
I need to pull out one main id for a set of three values. So far, the endless if statements are the fastest solution. And I'm looking for an optimization.
I would suggest concatenating the ids together to form a unique representation, and then using that to remove duplicates:
select t.*, m.cnt as NumDups
from (select min(main_id) as main_id, count(*) as cnt
concat(least(id_1, id_2, id_3),
(id_1 + id_2 + id_3) - least(id_1, id_2, id_3) - greatest(id_1, id_2, id_3),
greatest(id_1, id_2, id_3)
) as ids
from tablename t
group by ids
) m join
tablename t
on m.main_id = t.main_id;
EDIT:
If you are looking for one match and the variables #id1, #id2, and #id3 contain the values, then you can do:
select t.*
from tablename t
where least(#id1, #id2, #id3) = least(id_1, id_2, id_3) and
((#id1 + #id2 + #id3 - least(#id1, #id2, #id3) - greatest(#id1, #id2, #id3)) =
(id_1 + id_2 + id_3) - least(id_1, id_2, id_3) - greatest(id_1, id_2, id_3)
) and
greatest(#id1, #id2, #id3) = greatest(id_1, id_2, id_3);
SQL Fiddle appears to be unstable right now but here's a simple example demonstrating the technique in my comment.
Suppose you are looking for the main_id that corresponds to the values 1002, 1502 and 1902:
select main_id
from ids main
where exists (select 1 from ids where main.main_id = ids.main_id and id_1 in (select 1502 union select 1002 union select 1902))
and exists (select 1 from ids where main.main_id = ids.main_id and id_2 in (select 1502 union select 1002 union select 1902))
and exists (select 1 from ids where main.main_id = ids.main_id and id_3 in (select 1502 union select 1002 union select 1902))
In your case you would refactor this into your stored procedure and use a temporary table to store your input values instead of the union.
Normalize you original table using union, then you could solve this problem by trivial SQL.
select main_id,group_concat(id)
from
(
select main_id,id_1 as id from tablename
union all
select main_id,id_2 as id from tablename
union all
select main_id,id_3 as id from tablename
) as ids
where
id in (1001,1501,1901)
group by
main_id
having count(*) = 3

SQL select to eliminate duplicate value that has 2 other values in next column

I have constructed a junction table which goes like this.
Table Name: myTable
p_id | c_id
-----------
1 1
1 2
1 3
2 2
2 3
3 2
3 3
3 4
I wanted to SELECT p_id that doesn't have both c_id 3 and 4. In this case only p_id 3 has both c_id 3 and 4 so after the select statement the query should return both p_id 1 and 2.
The thing is that I try different kind of method but still it wouldn't work. I really need help.
my query
1.) SELECT DISTINCT p_id FROM myTable WHERE c_id != 3 AND course_id != 4;
Problem: It still returns 3 as one of the result since 3 has c_id of 2
Something like this:
SELECT DISTINCT p_id
FROM mytable
WHERE p_id NOT IN (SELECT p_id
FROM mytable
WHERE c_id IN ( 3, 4 )
GROUP BY p_id
HAVING Count(DISTINCT c_id) = 2)
SQLFiddle demo
Try this:
SELECT DISTINCT p_id
FROM myTable
WHERE c_id IN (3,4)
GROUP BY p_id HAVING COUNT(DISTINCT c_id)<2
The straightforward solution is to use exists:
select
distinct p_Id
from myTable t
where not (exists (select 1
from myTable
where (c_id = 3) and
(p_id = t.p_id)) and
exists (select 1
from myTable
where (c_id = 4) and
(p_id = t.p_id)))
Try this:
SELECT mytable.p_id
FROM mytable
LEFT OUTER JOIN (SELECT v1.p_id
FROM (SELECT p_id
FROM mytable
WHERE c_id = 3) v1
INNER JOIN (SELECT p_id
FROM mytable
WHERE c_id = 4) v2
ON v1.p_id = v2.p_id) v
ON mytable.p_id = v.p_id
WHERE v.p_id IS NULL
GROUP BY mytable.p_id
Try this:
select distinct mytable.p_id from mytable where c_id not in (3,4) and p_id <>3
This will give result which does not have 3 and 4

MySQL: fetching an id from an associative table, with specific associations

I've got the following associative table between packages and products (simplified):
package_id product_id count
1 1 6
1 2 1
1 3 1
2 1 6
2 2 1
3 1 6
4 1 8
4 2 1
I'm trying to work out how to create an query which is able to select specific package_id's which contain exactly the products and their counts I supply. So if I'd be trying to find the package that contains: (product_id = 1 AND count = 6) AND (product_id = 2 AND count = 1), it should only return package_id 2 and not the others, because those contain other products and / or other counts.
I'd be happy to work this out in my code (PHP) instead of SQL, but since I'm trying to get to the bottom of queries, I'd like to know how this is done.
This is called Relational Division
SELECT a.package_ID
FROM tableName a
WHERE (a.product_ID = 1 AND a.count = 6) OR
(a.product_ID = 2 AND a.count = 1)
GROUP BY a.package_ID
HAVING COUNT(*) = 2 AND
COUNT(*) = (SELECT COUNT(*) FROM tableName WHERE package_ID = a.package_ID)
SQLFiddle Demo
OR
SELECT package_ID
FROM tableName
WHERE (product_ID, `count`) in ((1, 6), (2, 1))
GROUP BY package_ID
HAVING COUNT(DISTINCT product_ID, `count`) = 2 AND
COUNT(*) = (SELECT COUNT(*) FROM tableName WHERE package_ID = a.package_ID)
SQLFiddle Demo

UPDATE FROM SELECT with foreign key on parent with one query

How to update (change from first select table value second) second_table.first_table_id if first_table.email match in both select.
If it even possible. With one query!
----------------------------------------- UPDATE -----------------------------------------
EXAMPLE:
I need to update foreign key of second table if email field match in first table. I need to compare two query results with different parent_id (parents are in in same table with children)
table_1
-------------------------
| id | parent_id | email |
-------------------------
1 NULL NULL
2 NULL NULL
3 1 joe#m.ru
4 2 bob#f.ly
5 1 bob#f.ly
6 2 kira#.us
table_2
----------------
| id | first_id |
----------------
1 3
2 4
3 5
4 6
I have two parents with ids 1 and 2 and some children (ids: 3,4,5,6).
Also, keep in mind: 1 - old, 2 - new
Task: change foreign key in second table if children email with parent_id = 1 and chilren email with parent_id = 2 match (are the same).
In our example in second table row with id = 3 its foreign key field - first_id has to change from 5 to 4.
Following might get you started
UPDATE Table_2 t2u
SET first_id = (
SELECT t2.first_id
FROM Table_2 t2
INNER JOIN Table_1 t1 ON t1.id = t2.first_id
INNER JOIN (
SELECT parent_id = MAX(parent_id), email
FROM Table_1
GROUP BY
email
) t1p ON t1p.email = t1.email
INNER JOIN Table_1 t1i ON t1i.email = t1p.email
AND t1i.parent_id = t1p.parent_id
WHERE t2u.first_id <> t1i.id)
Test script (SQL Server)
;WITH Table_1 (id, parent_id, email) AS (
SELECT 1, NULL, NULL
UNION ALL SELECT 2, NULL, NULL
UNION ALL SELECT 3, 1, 'joe#m.ru'
UNION ALL SELECT 4, 2, 'bob#f.ly'
UNION ALL SELECT 5, 1, 'bob#f.ly'
UNION ALL SELECT 6, 2, 'kira#.us'
)
, Table_2 (id, first_id) AS (
SELECT 1, 3
UNION ALL SELECT 2, 4
UNION ALL SELECT 3, 5
UNION ALL SELECT 4, 6
)
SELECT t2.*, t1i.id as [update with]
FROM Table_2 t2
INNER JOIN Table_1 t1 ON t1.id = t2.first_id
INNER JOIN (
SELECT parent_id = MAX(parent_id), email
FROM Table_1
GROUP BY
email
) t1p ON t1p.email = t1.email
INNER JOIN Table_1 t1i ON t1i.email = t1p.email
AND t1i.parent_id = t1p.parent_id
WHERE t2.first_id <> t1i.id
Output
id first_id update with
----------- ----------- -----------
3 5 4