MySQL - reducing number of SELECTs with one smart WHERE - mysql

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

Related

Select duplicates while concatenating every one except the first

I am trying to write a query that will select all of the numbers in my table, but those numbers with duplicates i want to append something on the end that shows it as a duplicate. However I am not sure how to do this.
Here is an example of the table
TableA
ID Number
1 1
2 2
3 2
4 3
5 4
SELECT statement output would be like this.
Number
1
2
2-dup
3
4
Any insight on this would be appreciated.
if you mysql version didn't support window function. you can try to write a subquery to make row_number then use CASE WHEN to judgement rn > 1 then mark dup.
create table T (ID int, Number int);
INSERT INTO T VALUES (1,1);
INSERT INTO T VALUES (2,2);
INSERT INTO T VALUES (3,2);
INSERT INTO T VALUES (4,3);
INSERT INTO T VALUES (5,4);
Query 1:
select t1.id,
(CASE WHEN rn > 1 then CONCAT(Number,'-dup') ELSE Number END) Number
from (
SELECT *,(SELECT COUNT(*)
FROM T tt
where tt.Number = t1.Number and tt.id <= t1.id
) rn
FROM T t1
)t1
Results:
| id | Number |
|----|--------|
| 1 | 1 |
| 2 | 2 |
| 3 | 2-dup |
| 4 | 3 |
| 5 | 4 |
If you can use window function you can use row_number with window function to make rownumber by Number.
select t1.id,
(CASE WHEN rn > 1 then CONCAT(Number,'-dup') ELSE Number END) Number
from (
SELECT *,row_number() over(partition by Number order by id) rn
FROM T t1
)t1
sqlfiddle
I made a list of all the IDs that weren't dups (left join select) and then compared them to the entire list(case when):
select
case when a.id <> b.min_id then cast(a.Number as varchar(6)) + '-dup' else cast(a.Number as varchar(6)) end as Number
from table_a
left join (select MIN(b.id) min_id, Number from table_a b group by b.number)b on b.number = a.number
I did this in MS SQL 2016, hope it works for you.
This creates the table used:
insert into table_a (ID, Number)
select 1,1
union all
select 2,2
union all
select 3,2
union all
select 4,3
union all
select 5,4

MySQL - Select results with specified ID or with null

I have one table:
| ID | ADV_ID | USER_ID |
| 1 | 22 | NULL |
| 2 | 22 | 3 |
| 5 | 44 | NULL |
and now, I want to select row where adv_id = 22 and user_id = 3. If that row doesn't exist, I want to get row where adv_id = 22 and user_id is null.
I tried in that way:
SELECT * FROM `table` WHERE adv_id = 22 AND (user_id = 3 OR user_id is null)
but this query return two rows - with user_id = NULL and with user_id = 3. I want to get one row - with user_id = 3 or (if not exist), with user_id = NULL.
How I can do it in one query?
Thanks.
Use conditional aggregation:
SELECT t1.*
FROM yourTable t1
INNER JOIN
(
SELECT
ADV_ID,
CASE WHEN COUNT(CASE WHEN USER_ID = 3 THEN 1 END) > 0 THEN 3 END USER_ID
FROM yourTable
) t2
ON t1.ADV_ID = t2.ADV_ID AND
((t1.USER_ID IS NULL AND t2.USER_ID IS NULL) OR (t1.USER_ID = t2.USER_ID))
WHERE
t1.ADV_ID = 22;
Demo
For an explanation, the subquery I have aliased as t2 aggregates over the ADV_ID, and outputs the value 3 if that value occurs in one or more records, otherwise it outputs NULL. Then, we join this subquery back to your original table on the condition that both USER_ID values are NULL, or, if not, that the two USER_ID values match.
You may modify the demo to see that it generates the output you want for other inputs.
SELECT *
FROM test
WHERE ADV_ID IS NOT NULL AND USER_ID IS NOT NULL
UNION ALL
SELECT *
FROM test
WHERE USER_ID IS NULL AND NOT EXISTS (
SELECT 1
FROM test
WHERE ADV_ID IS NOT NULL AND USER_ID IS NOT NULL
)
Select all rows with the first condition: ADV_ID IS NOT NULL AND USER_ID IS NOT NULL
and then UNION ALL with the same table if the first condition is NOT EXISTS.
So we only get results if the first condition is not returned any rows.
The MySQL UNION ALL operator is used to combine the result sets of 2 or more SELECT statements.
try like that:
SELECT * FROM `table` t1 WHERE (t1.adv_id = 44)
AND ((t1.user_id = 3) OR
(NOT EXISTS (select * from `table` t2 where t2.adv_id=t1.adv_id and t2.user_id = 3) AND t1.user_id is null ))
DEMO

Simplification mysql query by subquery for finding pair in one table

I have following table in mysql:
id | pairType
1 | 2
2 | 2
3 | 1
4 | 1
I need get second value to pair by id. I`am able doing this by 2 separated mysql queries, but it would be more comfortable to have it in one.
For example:
first query:
SELECT `pairType`
FROM `tmp`
WHERE `id` = 1
//return 2
second query:
SELECT `id`
FROM `tmp`
WHERE `id` != 1 AND `pairType` = 2
Thank's a lot
Something like this should work:
SELECT id
FROM tmp
WHERE pairtype = (SELECT pairtype FROM tmp WHERE id = 1)
AND id <> 1
That is absolutely unclear what are you asking for.
But here are some random guesses:
http://sqlfiddle.com/#!9/ee0096/4
SELECT t.id
FROM tmp t
INNER JOIN tmp t_
ON t_.pairtype = t.id
t_.id = 1 ;
SELECT t.id
FROM tmp t
INNER JOIN tmp t_
ON t_.pairtype = t.pairtype
AND t_.id = 1
WHERE t.id != 1

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

Query to get the common values

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