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

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.

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;

mysql alternative for key value query with multiple self joins

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;

One row per group with multiple column sorting

Would like to return one row per group, where the one is selected by multiple sort columns. Treading lightly here in the land of greatest-n-per-group to avoid a duplicate question.
SCHEMA:
CREATE TABLE logs (
id INT NOT NULL,
ip_address INT NOT NULL,
status INT NOT NULL,
PRIMARY KEY id
);
DATA:
INSERT INTO logs (id, ip_address, status)
VALUES ('1', 19216800, 1),
('2', 19216801, 2),
('3', 19216800, 2),
('4', 19216803, 0),
('5', 19216804, 0),
('6', 19216803, 0),
('7', 19216804, 1);
CURRENT QUERY:
SELECT *
FROM logs
ORDER BY ip_address, status=1 DESC, id DESC
Note: sorting by status=1 effectively turns the status column into a boolean. The tie breaker after status=1 is id. This query currently returns the correct row for each ip_address first and then a bunch of other rows I don't want for that ip_address.
CURRENT OUTPUT:
1, 19216800, 1
3, 19216800, 2
2, 19216801, 2
6, 19216803, 0
4, 19216803, 0
7, 19216804, 1
5, 19216804, 0
WANTED OUTPUT:
1, 19216800, 1
2, 19216801, 2
6, 19216803, 0
7, 19216804, 1
Today my workaround is to filter in PHP with if ($lastIP == $row['ip_address']) continue;. But I would like to move this logic to MySQL.
Try this -
SELECT MIN(id), ip_address, status
FROM logs
GROUP BY ip_address, status
Since there are already hundreds of solutions for greatest-n-per-group problems in MySQL, I'm going to start answering these questions with CTE syntax with window functions, since that is now available in MySQL 8.0.3.
WITH sorted AS (
SELECT id, ip_address, status,
ROW_NUMBER() OVER (PARTITION BY ip_address ORDER BY status) AS rn
FROM logs
)
SELECT * FROM sorted WHERE rn = 1;
Here is different way to think about the problem. You want to find the "best" row for each id_address. Or in other words, you want to select rows where no better row exists.
This solution works for MySQL versions before 8.0. In other words, it works with the version you already have installed with RHEL 7. You can extend this technique easily for an arbitrary number of sort columns.
SELECT a.*
FROM (SELECT * FROM logs) a
LEFT JOIN (SELECT * FROM logs) b
ON (b.ip_address = a.ip_address AND (b.stat=1) > (a.stat=1))
OR (b.ip_address = a.ip_address AND (b.stat=1) = (a.stat=1) AND b.id > a.id)
WHERE b.id IS NULL
ORDER BY a.ip_address
If you have more columns to sort by then keeping adding OR clauses to handle tie breaks and select the "best" row for each ip_address. Regardless how complicated your subquery is or how many "SORT BY~ conditions you have, you will only need one LEFT JOIN with this technique.
Try this:
SELECT
l.`ip_address` , l.`status`
FROM
`logs` l
GROUP BY l.`ip_address`
ORDER BY l.`status` = 1 DESC

SELECT from external data in MySQL

Let's consider a made up example
SELECT id, name, score.score FROM
someTable,
(select someTableId, count(*) as score FROM SecondTable GROUP BY someTableId) as score
WHERE score.someTableId == id
ORDER BY score.score DESC
Let's now assume that I have a backend computing my scoring, and that I would like to remove the subquery and insert my own data instead. I would like to know how to do this.
I would like to do something like (this is the question, because what's below doesn't work):
SELECT id, name, score.score FROM
someTable,
((12,324), (1, 342)) as score(id, score)
WHERE score.someTableId == id
ORDER BY score.score DESC
Here is an example of external data substitution to a subquery:
SELECT * FROM users WHERE id IN (SELECT user_id FROM posts WHERE thread_id = 12 GROUP BY user_id);
Without a subquery and with external data:
SELECT * FROM users WHERE id IN (1,2,3);
If I understood you correctly :
SELECT id, name, score.score FROM
someTable,
(SELECT 12 as someTableId,324 as score UNION ALL SELECT 1, 342 <UNION ALL....>) as score(id, score)
WHERE score.someTableId == id
ORDER BY score.score DESC
Thats the only way you can do it, it doesn't actually replace the the subquery, but it replace the select from the table and can improve performance if thats what you are looking for.
In MySQL you don't need to specify a from clause like a dummy table when you are just looking to fetch dummy data.
Other DBMS require a dummy table name (typically DUAL) but in MySQL it's rather straightforward:
SELECT 12 AS id, 324 AS score
UNION ALL SELECT 2, 65
UNION ALL SELECT 3, 598
UNION ALL SELECT 4, 244
You can use this as any other result-set.