Optimizing mySQL query to scale better - mysql

I want to query a database to retrieve a list of names (the list of names is provided by the user in python). My criteria for looking up data for these names are the following: the results should appear in the order of the list of names the user provided, so if I say ...WHERE name = "Bob" OR name = "Alice" I want the results for Bob to come first followed by that of Alice. The second criteria is that if there is a search for a name twice, then the result should also contain it twice, so I want a way to write down ...WHERE name = 'Bob' OR name = 'Bob' so that the result also contains the rows for Bob twice.
I came up with the following query:
SELECT * FROM
(SELECT *, 1 order_position FROM table WHERE name = 'Alice'
UNION ALL
SELECT *, 2 order_position FROM table WHERE name = 'Bob'
UNION ALL
SELECT *, 3 order_position FROM table WHERE name = 'Charlie'
UNION ALL
SELECT *, 4 order_position FROM table WHERE name = 'Dan'
) r ORDER BY order_position
This query works well, but when the user submits hundreds of names and there are hundreds of UNION ALL sections, the query becomes extremely slow. Is there a way to improve the performance of the query while maintaining the two criteria mentioned before?

SELECT *, CASE name WHEN 'Alice' THEN 1
WHEN 'Bob' THEN 2
WHEN 'Charlie' THEN 3
WHEN 'Dan' THEN 4
END AS order_position
FROM table
WHERE name IN ('Alice', 'Bob', 'Charlie', 'Dan')
ORDER BY order_position;
or without additional column:
SELECT *
FROM table
WHERE name IN ('Alice', 'Bob', 'Charlie', 'Dan')
ORDER BY CASE name WHEN 'Alice' THEN 1
WHEN 'Bob' THEN 2
WHEN 'Charlie' THEN 3
WHEN 'Dan' THEN 4
END;
PS. For this names set ORDER BY name is enough.
How does this handle the requirement of repeating some results? –
Willem Renzema
If you need in repeating then you must convert the list to a rowset.
SELECT table.*
FROM table
JOIN ( SELECT 1 pos, 'Alice' name UNION ALL
SELECT 2 , 'Bob' UNION ALL
SELECT 3 , 'Charlie' UNION ALL
SELECT 4 , 'Bob' UNION ALL
SELECT 5 , 'Charlie' ) names USING (name)
ORDER BY names.pos

Somehow you have to construct the list of names with the order_position of each name.
You can do this in a query which uses UNION ALL to preserve duplicate names like this:
SELECT 'Alice' name, 1 order_position UNION ALL
SELECT 'Bob', 2 UNION ALL
SELECT 'Charlie', 3 UNION ALL
SELECT 'Dan', 4 UNION ALL
SELECT 'Alice', 1 UNION ALL
SELECT 'Bob', 2 UNION ALL
...............................
Then all you have to do is join it to the table:
SELECT t.*
FROM tablename t
INNER JOIN (
SELECT 'Alice' name, 1 order_position UNION ALL
SELECT 'Bob', 2 UNION ALL
SELECT 'Charlie', 3 UNION ALL
SELECT 'Dan', 4 UNION ALL
SELECT 'Alice', 1 UNION ALL
SELECT 'Bob', 2 UNION ALL
...............................
) n ON n.name = t.name
ORDER BY n.order_position;
In MySql 8.0+ you can use a CTE:
WITH cte(name, order_position) AS (VALUES
ROW('Alice', 1), ROW('Bob', 2), ROW('Charlie', 3),
ROW('Dan', 4), ROW('Alice', 1), ROW('Bob', 2),
...................................................
)
SELECT t.*
FROM tablename t INNER JOIN cte c
ON c.name = t.name
ORDER BY c.order_position;

The following will be "fast" if name is indexed:
WHERE name IN ('Alice', 'Bob', 'Charlie', 'Dan')
ORDER BY FIND_IN_SET(name, 'Alice,Bob,Charlie,Dan')
Note the syntax difference between Where and Order.
The following is likely to be somewhat slower because it cannot use any index but is simpler to code:
WHERE FIND_IN_SET(name, 'Alice,Bob,Charlie,Dan')
ORDER BY FIND_IN_SET(name, 'Alice,Bob,Charlie,Dan')
Note the restriction in FIND_IN_SET that commas cannot be used in the items.
In no case will CASE or FIND_IN_SET() use an index. (Cf "sargable")
Repeats
If, say, there are multiple "Bobs", then each of these have exactly the same effect as the above:
name IN ('Alice', 'Bob', 'Charlie', 'Bob', 'Dan')
FIND_IN_SET(name, 'Alice,Bob,Charlie,Bob,Dan')
That is, all the Bobs will be listed in the output before all the Charlies. Furthermore, no individual row is listed twice.

Related

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 query delivers wrong result when using <> (not equal) in WHERE clause

I came accross a strange problem with a MySQL Query
SELECT COUNT(id) FROM members
100
SELECT COUNT(id) FROM members WHERE lastname = 'Smith'
20
SELECT COUNT(id) FROM members WHERE lastname <> 'Smith'
0
The problem is, that the last query (Members with lastname != 'Smith') returns 0.
If there are 100 members in total and 20 members named 'Smith', the number of member with other last names should be 80, shouldn't it?
I tried different version using <>, !=, enclosing Smith with ' or ". The result when using LIKE and NOT LIKE instead is the same.
How is this possible? It seems that I am missing something quite obvious, but what...?
because others are null
try this :
SELECT COUNT(id) FROM members WHERE IFNULL(lastname ,'--')<> 'Smith'
Example :
CREATE TABLE my_table
SELECT 'ersin' name FROM dual
union all
SELECT 'ersin' name FROM dual
union all
SELECT 'ersin' name FROM dual
union all
SELECT null name FROM dual
union all
SELECT null name FROM dual
union all
SELECT null name FROM dual;
select script:
select count(*) from my_table where IFNULL(name ,'--') <> 'ersin' ;
output:
count(*)
3

mysql find numbers in query that are NOT in table

Is there a simple way to compare a list of numbers in my query to a column in a table to return the ones that are NOT in the db?
I have a comma separated list of numbers (1,57, 888, 99, 76, 490, etc etc) that I need to compare to the number column in a table in my DB. SOME of those numbers are in the table, some are not. I need the query to return those that are in my comma separated list, but are NOT in the DB...
I would put the list of numbers to be checked in a table of their own, then use WHERE NOT EXISTS to check whether they exist in the table to be queried. See this SQLFiddle demo for an example of how this might be accomplished:
If you're comfortable with this syntax, you can even avoid putting into a temp table:
SELECT * FROM (
SELECT 1 AS mycolumn
UNION
SELECT 2
UNION
SELECT 3
UNION
SELECT 4
UNION
SELECT 5
UNION
SELECT 6
UNION
SELECT 7
) a
WHERE NOT EXISTS ( SELECT 1 FROM mytable b
WHERE b.mycolumn = a.mycolumn )
UPDATE per comments from OP
If you can insert your very long list of numbers into a table, then query as follows to get the numbers that are not found in the other table:
SELECT mynumber
FROM mytableof37000numbers a
WHERE NOT EXISTS ( SELECT 1 FROM myothertable b
WHERE b.othernumber = a.mynumber)
Alternately
SELECT mynumber
FROM mytableof37000numbers a
WHERE a.mynumber NOT IN ( SELECT b.othernumber FROM myothertable b )
Hope this helps.
May be this is what you are looking for.
Convert your CSV to rows using SUBSTRING_INDEX. Use NOT IN operator to find the values which is not present in DB
Then Convert the result back to CSV using Group_Concat.
select group_concat(value) from(
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.a, ',', n.n), ',', -1) value
FROM csv t CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(t.a) - LENGTH(REPLACE(t.a, ',', '')))) ou
where value not in (select a from db)
SQLFIDDLE DEMO
CSV TO ROWS referred from this ANSWER
You could use the 'IN' clause of MySQL. Maybe check this out IN clause tutorial

SELECT WHERE IN - mySQL

let's say I have the following Table:
ID, Name
1, John
2, Jim
3, Steve
4, Tom
I run the following query
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill');
I want to get something like:
ID
1
2
NULL or 0
Is it possible?
How about this?
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill')
UNION
SELECT null;
Start by creating a subquery of names you're looking for, then left join the subquery to your table:
SELECT myTable.ID
FROM (
SELECT 'John' AS Name
UNION SELECT 'Jim'
UNION SELECT 'Bill'
) NameList
LEFT JOIN myTable ON NameList.Name = myTable.Name
This will return null for each name that isn't found. To return a zero instead, just start the query with SELECT COALESCE(myTable.ID, 0) instead of SELECT myTable.ID.
There's a SQL Fiddle here.
The question is a bit confusing. "IN" is a valid operator in SQL and it means a match with any of the values (see here ):
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill');
Is the same as:
SELECT Id FROM Table WHERE NAME = 'John' OR NAME = 'Jim' OR NAME = 'Bill';
In your answer you seem to want the replies for each of the values, in order. This is accomplished by joining the results with UNION ALL (only UNION eliminates duplicates and can change the order):
SELECT max(Id) FROM Table WHERE NAME = 'John' UNION ALL
SELECT max(Id) FROM Table WHERE NAME = 'Jim' UNION ALL
SELECT max(Id) FROM Table WHERE NAME = 'Bill';
The above will return 1 Id (the max) if there are matches and NULL if there are none (e.g. for Bill). Note that in general you can have more than one row matching some of the names in your list, I used "max" to select one, you may be better of in keeping the loop on the values outside the query or in using the (ID, Name) table in a join with other tables in your database, instead of making the list of ID and then using it.

Append to MySQL Results if 1 column in NOT IN the results

I want to create a MyQSL Query similar to
SELECT person, city FROM mytable
UNION
SELECT 'BOB', 'Chicago' IF 'BOB' NOT IN (SELECT person FROM mytable);
If 'BOB' is not returned in the results, I want to append him to the results and list him as being in Chicago. If BOB does come back in the results, no matter what his location is, I do not want to append him as being in Chicago.
I can make this work if I exactly match the columns, but I will end up getting multiple results for BOB if he is listed as being somewhere other than Chicago.
SELECT person, city FROM mytable
UNION
SELECT 'BOB', 'Chicago'
but I do not want to match on the location. Just the person's name.
This should work:
SELECT person, city FROM mytable
UNION
SELECT 'BOB', 'Chicago' from dual
where NOT exists (SELECT person FROM mytable WHERE person = 'BOB');
A more optimized version, that returns the same results
SELECT person, city FROM mytable WHERE person <> 'BOB'
UNION
SELECT 'BOB', COALESCE((select city from mytable WHERE person = 'BOB'), 'Chicago') from dual
Here's a rewrite of your original query that should work:
SELECT person, city FROM mytable
UNION
SELECT 'BOB', 'Chicago' from dual where not exists (
SELECT NULL FROM mytable where person = 'BOB'
);