mysql alternative for key value query with multiple self joins - mysql

I have a table with numerical filters (age, weight, salary, etc).
Since I do not know the filters in advance I have to use key value pairs:
company
id, name
1, ACME
users
id, name, company_id
1, jon doe, 1
filters
id, user_id, filter, value
1, 1, 'age', 30
2, 1, 'weight', 82
3, 2, 'salary', 50000
My queries retrieve users that belong to a specific company and match any combination of one or many filter criteria: e.g.
SELECT COUNT(*) FROM users, filters as age, filters as weight
WHERE age.user_id = users.id
AND weight.user_id = users.id
AND age.filter = 'age'
AND age.value = 30
AND weight.filter = 'weight'
AND weight.value = 100
AND users.company_id = 1
The table contains many million rows and I have tried all possible index combinations for the filter, value, user_id columns.
The queries take many seconds to minutes.
Is there a better solution for my usecase?

Is this what you want?
select f.user_id
from filters f
where (f.filter, f.value) in ( ('age', 20), ('weight', 100) )
group by f.user_id
having count(distinct f.filter) = 2;

Related

SQL- preserve order from 'IN' clause and return null for non matching clauses

Let's say I have a table Person with columns id, name, and phone. I want to fetch all records matching a list of pairs of names and phone numbers while preserving the order from the 'IN' clause and returning null or any default value for the mismatching clause.
For instance, if the Person table has the following records:
id
name
phone
1
Name1
1234
2
Name2
2345
3
Name3
4532
I want the query to return the ids of people matching pairs of names and phone numbers.
When queried with
('Name2', 2345), ('NonExistingName', 34543), ('Name1', 1234) should return a list [2, <null or a default value>, 1]
I am aware that I can use IN clause to find the matching rows,
SELECT id
FROM Person
WHERE (name, phone) in (('Name2', 2345),
('NonExistingName', 34543),
('Name1', 1234));
however, this alone doesn't fulfill what I want. The rows returned do not preserve the order and do not allow me to add a default value for nonexisting ids.
Relational databases explicitly disclaim any responsibility to ever preserve order unless you specify an ORDER BY clause. Therefore you will need to include the order information as part of the data in a way where you can reference it in the ORDER BY clause.
For example:
WITH source AS (
SELECT 'Name2' Name, 2345 Phone, 0 Ordinal
UNION
SELECT 'NonExistingName', 34543, 1
UNION
SELECT 'Name1', 1234, 2
)
SELECT p.id
FROM source s
LEFT JOIN Person p ON s.Name = p.Name and s.Phone = p.Phone
ORDER BY s.Ordinal
Or:
SELECT p.id
FROM (VALUES
ROW ('Name2', 2345, 0),
ROW ('NonExistingName', 34543, 1),
ROW ('Name1', 1234, 2)
) s
LEFT JOIN Person p ON s.column_0 = p.Name and s.column_1 = p.Phone
ORDER BY s.column_2

How to count the number of entries in the list when requesting Select?

I apologize for the possible incorrectness in the presentation, I use a translator. Let's say there is a users table in which there is an id field. And there is a list that lists the id numbers and some of them are repeated. My query
select id, count(*)
from users
where id in (3, 10, 10, 10)
group by id;
returns the following 3 - 1, 10 - 1. And I would like to get 3 - 1, 10 - 3, and so on. Is it possible to get it somehow?
UPD.
The data in the list (3, 10, 10, 10) is just an example, the exact number of digits is not known because they are returned from another question.
You would need to use a join. You can put the values in a derived table for this:
select id, count(*)
from users u join
(select 3 as id union all
select 10 as id union all
select 10 as id union all
select 10 as id union all
) i
using(id)
group by id;

Finding records that occur most commonly together in SQL

I have a table of ingredients:
ing_id, ing_name
1 , ing1
...
a table of recipes:
rec_id, rec_name
1 , rec1
...
and a table showing the connection between the two:
id, ing_id, rec_id
1, 1, 1
2, 1, 2
3, 2, 1
4, 3, 3
...
How can I find the ingredients that most commonly appear in the same recipe?
You can use a self join and group by:
select c1.ing_id, c2.ing_id, count(*)
from connections c1 join
connections c2
on c1.rec_id = c2.rec_id and c1.ing_id < c2.ing_id
group by c1.ing_id, c2.ing_id
order by count(*) desc;
If you actually want the names instead of the ids, you'll need two more joins to bring them in.

SQL question. Find the two person having same hobbies in one table

TABLE [tbl_hobby]
person_id (int) , hobby_id(int)
has many records. I want to get a SQL query to find all pairs of personid who have the same hobbies( same hobby_id ).
If A has hobby_id 1, B has too, if A doesn't have hobby_id 2, B doesn't have too, we will output A & B 's person_ids.
If A and B and C reach the limits, we output A & B , B & C, A & C.
I've finished in a very very very stupid method, multiple joins the table itself and multiple sub-queries. And of course be laughed by leader.
Is there any high performance method in a SQL for this question?
I have been thinking hard for this since 36 hrs ago......
sample data in mysql dump
CREATE TABLE `tbl_hobby` (
`person_id` int(11) NOT NULL,
`hobby_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tbl_hobby` (`person_id`, `hobby_id`) VALUES
(1, 1),(1, 2),(1, 3),(1, 4),(1, 5),(2, 2),
(2, 3),(2, 4),(3, 1),(3, 2),(3, 3),(3, 4),
(4, 1),(4, 3),(4, 4),(5, 1),(5, 5),(5, 9),
(6, 2),(6, 3),(6, 4),(7, 1),(7, 3),(7, 7),
(8, 2),(8, 3),(8, 4),(9, 1),(9, 2),(9, 3),
(9, 4),(10, 1),(10, 5),(10, 9),(10, 11);
COMMIT;
Expert result: (2 and 6 and 8 same, 3 and 9 same)
2,6
2,8
6,8
3,9
Order of result records and order of the two number in one record is not important. Result record in one column or in two columns are all accepted since it can be easily concated or seperated.
Aggregate per person to get strings of their hobbies. Then aggregate per hobby list find out which belong to more than one person.
select hobbies, group_concat(person_id order by person_id) as persons
from
(
select person_id, group_concat(hobby_id order by hobby_id) as hobbies
from tbl_hobby
group by person_id
) persons
group by hobbies
having count(*) > 1
order by hobbies;
This gives a a list of persons per hobby. Which is the easiest way to output a solution as we would otherwise have to build all possible pairs.
UPDATE: If you want pairs, you'll have to query the table twice:
select p1.person_id as person 1, p2.person_id as person2
from
(
select person_id, group_concat(hobby_id order by hobby_id) as hobbies
from tbl_hobby
group by person_id
) p1
join
(
select person_id, group_concat(hobby_id order by hobby_id) as hobbies
from tbl_hobby
group by person_id
) p2 on p2.person_id > p1.person_id and p2.hobbies = p1.hobbies
order by person1, person2;
Alternative version, without using any proprietary string handling:
select distinct t1.person_id, t2.person_id
from tbl_hobby t1
join tbl_hobby t2
on t1.person_id < t2.person_id
where 2 = all (select count(*)
from tbl_hobby
where person_id in (t1.person_id, t2.person_id)
group by hobby_id);
Perhaps less efficient, but portable!

Order results in which it was read in. Using in()

This is my query in mysql:
SELECT school_id, first_name, last_name, email, blog_username, comment_username
FROM table
WHERE user_id IN (100, 3,72) ;
The results show the two user_id's in ascending order. How can I make it so that it is ordered by in which is was received?
So instead of 3, 72, 100 I want the results to be 100, 3, 72.
Select school_id, first_name, last_name, email, blog_username, comment_username
From table
Where user_id IN ( 100, 3, 72 )
Order By Case
When user_id = 100 Then 1
When user_id = 3 Then 2
When user_id = 72 Then 3
End Asc
Addition explanation:
What is being sought is the ability to order the rows in a custom manner. Said another way, we need to add custom cardinality to a set of values that do not conform to a standard cardinality. A Case expression can be used to do just that. Another way to accomplish the same thing would be:
Select school_id, first_name, last_name, email, blog_username, comment_username
From table
Join (
Select 100 As user_id, 1 As Sort
Union All Select 3, 2
Union All Select 72, 3
) As Seq
On Seq.user_id = table.user_id
Order By Seq.Sort
MySQL has a function FIELD() which is suited for this:
ORDER BY FIELD(user_id, 100, 3, 72 )
http://dev.mysql.com/doc/refman/5.6/en/string-functions.html#function_field
and in general (method is designed for strings but it will work just fine with numbers)
ORDER BY FIELD(field_name, value_1, value_2, ... )
However
It makes your SQL less portable
Ordering like this is much less efficient
Whole code
SELECT school_id, first_name, last_name, email, blog_username, comment_username
FROM table
WHERE user_id IN (100, 3, 72)
ORDER BY FIELD(user_id, 100, 3, 72)
Although I would consider changing the rest of you code so you do not have to use this method.