MySQL Binary Tree Index order - mysql

I have a query like this:
SELECT * FROM mytable WHERE
((num=8198747 AND class='A') OR
(num=1646463 AND class='B') OR
(num=4099442 AND class='C') OR
(num=1176312 AND class='A') OR
(num=2146847 AND class='B') OR
(num=7000296 AND class='F') OR
--...about 400 more clauses like this
)
SHOW INDEXES FROM mytable;
+---------+------------+----------+--------------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name |
+---------+------------+----------+--------------+---------------+
| mytable | 0 | PRIMARY | 1 | id |
| mytable | 1 | nc_idx | 1 | num |
| mytable | 1 | nc_idx | 2 | class |
+---------+------------+----------+--------------+---------------+
3 rows in set (0.00 sec)
My understanding is that for each of the ~400 clauses in the query, it will do a separate BTREE lookup on num=XXXXXXXX. Is there any value in changing the query to:
SELECT * FROM mytable WHERE
((class='A' AND num IN (8198747, 1176312, ...)) OR
(class='B' AND num IN (1247910, 1248192, ...)) OR
(class='F' AND num IN (7244626, 9084903, ...)) OR
--...for each class in the query
)
after adding a new index cn_idx on class and num, in that order?
I think it won't be much faster, since class is just one char, and thus the number of BTREE lookups will be the same. But, each subtree will be shorter. Thoughts?

In short
just stick to OR
the index on num is the only index that will help this query
an composite index on (num,class) has negligible effect, since num is already very selective
Two other ways to write it, using UNION ALL
SELECT * FROM mytable
WHERE (num=8198747 AND class='A')
UNION ALL
SELECT * FROM mytable
WHERE (num=1646463 AND class='B')
UNION ALL
SELECT * FROM mytable
WHERE (num=4099442 AND class='C')
UNION ALL
... etc ...
This may work well if you don't have too many num/class pairs, as each one will individually perform an index seek. (A composite index on num/class will work better than num and class indexes single-field indexes. class/num is less selective)
The second way uses JOIN mechanics, by making a virtual table out of the num/class pairs:
SELECT t.*
FROM mytable t
JOIN (
select 8198747 as num, 'A' as class union all
select 1646463, 'B' union all
select 4099442, 'C' union all
... etc ...
) v on v.num=t.num and v.class=t.class
Performance comparison
Create a table
create table mytable (
id int auto_increment primary key,
num int,
class char(1),
other varchar(10),
date timestamp default current_timestamp) ENGINE InnoDB;
Fill it up with 1 million records
(Note: data properties - selectivity: num ~ 1, class ~ 1/26)
insert into mytable(num, class, other)
select rand()*100000000, char(rand()*26+65), concat('',rand()*10000000)
from
(select 1 a 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 union all select 0) a,
(select 1 a 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 union all select 0) b,
(select 1 a 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 union all select 0) c,
(select 1 a 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 union all select 0) d,
(select 1 a 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 union all select 0) e,
(select 1 a 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 union all select 0) f
Create the indexes
create index nc_num on mytable(num);
create index nc_class on mytable(class);
Select using OR
select * from mytable
WHERE
(num=38142659 and class='T') OR
(num=42476845 and class='E') OR
(num=45205882 and class='B') OR
(num=84861596 and class='K') OR
..... 100 in total
Output of Show profiles: (run set profiling=1; once. then run the queries. run show profiles to see the last timings)
Duration: 0.00003025
Explain extended (add explain extended before the query)
"id";"select_type";"table";"type";"possible_keys";"key";"key_len";"ref";"rows";"Extra"
"1";"SIMPLE";"mytable";"range";"nc_num,nc_class";"nc_num";"5";NULL;"125";"Using where"
Select using UNION ALL between num/class
SELECT * FROM mytable WHERE (num=38142659 AND class='T') UNION ALL
SELECT * FROM mytable WHERE (num=42476845 AND class='E') UNION ALL
SELECT * FROM mytable WHERE (num=45205882 AND class='B') UNION ALL
SELECT * FROM mytable WHERE (num=84861596 AND class='K') UNION ALL
.... 100 in total
Show Profile
Duration: 0.00069525
Explain extended
"id";"select_type";"table";"type";"possible_keys";"key";"key_len";"ref";"rows";"Extra"
"1";"PRIMARY";"mytable";"ref";"nc_num,nc_class";"nc_num";"5";"const";"1";"Using where"
"2";"UNION";"mytable";"ref";"nc_num,nc_class";"nc_num";"5";"const";"1";"Using where"
"3";"UNION";"mytable";"ref";"nc_num,nc_class";"nc_num";"5";"const";"1";"Using where"
"4";"UNION";"mytable";"ref";"nc_num,nc_class";"nc_num";"5";"const";"1";"Using where"
... etc
Select using Union All to build up a virtual table
SELECT t.*
FROM mytable t
JOIN (
select 41805446 num, 'X' collate utf8_general_ci class union all
select 84867135, 'P' union all
select 52747446, 'R' union all
.... etc...
) v on v.num=t.num and v.class=t.class
Show profile
Duration: 0.00026100
Explain extended
"id";"select_type";"table";"type";"possible_keys";"key";"key_len";"ref";"rows";"Extra"
"1";"PRIMARY";"<derived2>";"ALL";NULL;NULL;NULL;NULL;"100";""
"1";"PRIMARY";"t";"ref";"nc_num";"nc_num";"5";"v.num";"1";"Using where"
"2";"DERIVED";NULL;NULL;NULL;NULL;NULL;NULL;NULL;"No tables used"
"3";"UNION";NULL;NULL;NULL;NULL;NULL;NULL;NULL;"No tables used"
"4";"UNION";NULL;NULL;NULL;NULL;NULL;NULL;NULL;"No tables used"
....
"101";"UNION";NULL;NULL;NULL;NULL;NULL;NULL;NULL;"No tables used"
NULL;"UNION RESULT";"<union2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,...>";"ALL";NULL;NULL;NULL;NULL;NULL;""
Select using IN
SELECT * FROM mytable WHERE
(class='A' and num in (28538065
)) or (class='B' and num in (70851926
,90457823
,94804149
)) or (class='C' and num in (74179050
,43280101
,24562525
,56859448
,38226813
,33532373
,93501613
,28634136
,8204338
,15636810
)) or (class='D' and num in (26672499
.... etc
Show profile
Duration: 0.00003125
Explain extended
"id";"select_type";"table";"type";"possible_keys";"key";"key_len";"ref";"rows";"Extra"
"1";"SIMPLE";"mytable";"range";"nc_num,nc_class";"nc_num";"5";NULL;"136";"Using where"

Related

mysql select odd number values in a column that includes delimiter separated values

My next database table will be set up more optimally. Unfortunately this one was already set up where one column [data] contains checkbox array values that were saved the following way:
value 1|~|value 1 value 2|~|value 2 value 3|~|value 3
Not optimal, I know.
What I need is a mysql query that select only the values in [data] column in front of the |~|. Basically think I need to select the only odd values.
Any help pointing me in the right direction is greatly appreciated. I tried an if statement in a query and it did not work. Of course I deleted that by mistake.
What I need is a mysql query that select only the
values in [data] column in front of the |~|.
One thing to note the numbers before |~| must be unique.
It will not show the same number twice.
Query
SELECT
DISTINCT
SUBSTRING (
record_data.column
, LOCATE('|~|', record_data.`column` , number_generator.number) - 1
, 1
) AS number
FROM (
SELECT
#row := #row + 1 AS number
FROM (
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) record_1
CROSS JOIN (
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) record_2
CROSS JOIN (
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) record_4
CROSS JOIN (
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) record_5
CROSS JOIN (
SELECT #row := 0
) AS init_user_params
) AS number_generator
CROSS JOIN (
SELECT
*
FROM (
SELECT 'value 1|~|value 1 value 2|~|value 2 value 3|~|value 3' AS `column`
) AS record
) AS record_data
WHERE
LOCATE('|~|', record_data.`column` , number_generator.number) <> 0
Result
| number |
| ------ |
| 1 |
| 2 |
| 3 |
demo

Get integer which is not in database

I have this MySQL table :
create table example (
My_Id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
My_Integer INT UNSIGNED DEFAULT 0
) ENGINE=InnoDB
Suppose this table to be populated like that :
SELECT * FROM `example`
My_Id My_Integer
1 10
2 1
3 3
4 152
5 16
6 481
7 128
My goal is to retrieve, from an user input, which integer is not in my table 'example'.
For example, if the user enter 1, 3, 10, the result should be empty.
If the user enter 1, 2, 3, 10, the result should be "2".
I can't find a way to do that, other than to do one MySQL request per integer in order to know if the number is in the table 'example'.
Do someone know a way ?
Select numberToMatch
from example as a
right outer join (
select 1 as numberToMatch union all
select 2 union all
select 3 union all
select 10
) as b
on b.numberToMatch = a.My_Integer
where a.My_Integer is null
Using right outer join and select those null record in the left table.
You could genereate tally table and use OUTER JOIN:
SELECT t.n
FROM (
SELECT a.N + b.N * 10 + c.N * 100 + 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
,(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) c
) t
LEFT JOIN example e
ON t.n = e.My_Integer
WHERE t.n IN (1,2,3,10) -- here goes numbers
AND e.My_Integer IS NULL;
LiveDemo
Output:
╔═══╗
║ n ║
╠═══╣
║ 2 ║
╚═══╝
It will work in range 1-999. You could expand it if needed.
Another way is to use UNION ALL:
SELECT s.col
FROM (SELECT 1 AS col
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 10) AS s
LEFT JOIN example e
ON s.col = e.My_Integer
WHERE e.My_Integer IS NULL;
LiveDemo2

INSERT rows multiple times based on a column value from another table

Mainly, I would like to insert a row in table 1 multiple times, based on an integer value in a column of table 2.
My situation
Table 2 contains a column 'SKU' and 'stock', and I would like to insert the 'SKU' and a timestamp into table 1. I want this row duplicated for 'stock'-value times in table 1.
I currently have the following query:
DECLARE #Count int = 1
WHILE #Count <= ....
BEGIN
INSERT INTO table1 (table1.SKU, table1.timestamp_in)
SELECT table2.SKU, "some timestamp" FROM table2
SET ...
END
I am not sure if this is the correct approach. I would like to run this loop for 'table2.stock' times.
My question is: Is this possible with just a SQL query, or should it be a better practice to build some (in my case) java code for this?
You don't need a procedure or anything like that. All you need is a table containing just numbers. I'm creating this table on the fly with this in this example:
SELECT aa.a + 10*bb.b + 100*cc.c AS numbers FROM (
SELECT 0 a 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) aa
, (SELECT 0 b 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) bb
, (SELECT 0 c 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) cc;
This creates the numbers 0 till 999.
Now you join your table2 with this numbers table in the range of stock. Your final query looks like this:
INSERT INTO table1 (table1.SKU, table1.timestamp_in)
SELECT table2.SKU, "some timestamp" FROM table2
INNER JOIN (
SELECT aa.a + 10*bb.b + 100*cc.c AS n FROM (
SELECT 0 a 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) aa
, (SELECT 0 b 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) bb
, (SELECT 0 c 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) cc
) numbers ON numbers.n BETWEEN 0 AND table2.stock /*assuming you have no negative stock*/
Just make sure, that the numbers table contains more numbers than the highest value in the stock column.

How to INSERT INTO a range of numbers into a MYSQL table [duplicate]

How do I generate a range of consecutive numbers (one per line) from a MySQL query so that I can insert them into a table?
For example:
nr
1
2
3
4
5
I would like to use only MySQL for this (not PHP or other languages).
Here is one way to do it set-based without loops. This can also be made into a view for re-use. The example shows the generation of a sequence from 0 through 999, but of course, it may be modified to suit.
INSERT INTO
myTable
(
nr
)
SELECT
SEQ.SeqValue
FROM
(
SELECT
(HUNDREDS.SeqValue + TENS.SeqValue + ONES.SeqValue) SeqValue
FROM
(
SELECT 0 SeqValue
UNION ALL
SELECT 1 SeqValue
UNION ALL
SELECT 2 SeqValue
UNION ALL
SELECT 3 SeqValue
UNION ALL
SELECT 4 SeqValue
UNION ALL
SELECT 5 SeqValue
UNION ALL
SELECT 6 SeqValue
UNION ALL
SELECT 7 SeqValue
UNION ALL
SELECT 8 SeqValue
UNION ALL
SELECT 9 SeqValue
) ONES
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 10 SeqValue
UNION ALL
SELECT 20 SeqValue
UNION ALL
SELECT 30 SeqValue
UNION ALL
SELECT 40 SeqValue
UNION ALL
SELECT 50 SeqValue
UNION ALL
SELECT 60 SeqValue
UNION ALL
SELECT 70 SeqValue
UNION ALL
SELECT 80 SeqValue
UNION ALL
SELECT 90 SeqValue
) TENS
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 100 SeqValue
UNION ALL
SELECT 200 SeqValue
UNION ALL
SELECT 300 SeqValue
UNION ALL
SELECT 400 SeqValue
UNION ALL
SELECT 500 SeqValue
UNION ALL
SELECT 600 SeqValue
UNION ALL
SELECT 700 SeqValue
UNION ALL
SELECT 800 SeqValue
UNION ALL
SELECT 900 SeqValue
) HUNDREDS
) SEQ
Here's a hardware engineer's version of Pittsburgh DBA's solution:
SELECT
(TWO_1.SeqValue + TWO_2.SeqValue + TWO_4.SeqValue + TWO_8.SeqValue + TWO_16.SeqValue) SeqValue
FROM
(SELECT 0 SeqValue UNION ALL SELECT 1 SeqValue) TWO_1
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 2 SeqValue) TWO_2
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 4 SeqValue) TWO_4
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 8 SeqValue) TWO_8
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 16 SeqValue) TWO_16;
If you need the records in a table and you want to avoid concurrency issues, here's how to do it.
First you create a table in which to store your records
CREATE TABLE `incr` (
`Id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Secondly create a stored procedure like this:
DELIMITER ;;
CREATE PROCEDURE dowhile()
BEGIN
DECLARE v1 INT DEFAULT 5;
WHILE v1 > 0 DO
INSERT incr VALUES (NULL);
SET v1 = v1 - 1;
END WHILE;
END;;
DELIMITER ;
Lastly call the SP:
CALL dowhile();
SELECT * FROM incr;
Result
Id
1
2
3
4
5
Let's say you want to insert numbers 1 through 100 into your table. As long as you have some other table that has at least that many rows (doesn't matter the content of the table), then this is my preferred method:
INSERT INTO pivot100
SELECT #ROW := #ROW + 1 AS ROW
FROM someOtherTable t
join (SELECT #ROW := 0) t2
LIMIT 100
;
Want a range that starts with something other than 1? Just change what #ROW gets set to on the join.
As you all understand, this is rather hacky so use with care
SELECT
id % 12 + 1 as one_to_twelve
FROM
any_large_table
GROUP BY
one_to_twelve
;
DECLARE i INT DEFAULT 0;
WHILE i < 6 DO
/* insert into table... */
SET i = i + 1;
END WHILE;
Very similar to the accepted response, but using the new WITH syntax for mysql >= 8.0 which makes a lot more legible and the intent is also clearer
WITH DIGITS (N) AS (
SELECT 0 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)
SELECT
UNITS.N + TENS.N*10 + HUNDREDS.N*100 + THOUSANDS.N*1000
FROM
DIGITS AS UNITS, DIGITS AS TENS, DIGITS AS HUNDREDS, DIGITS AS THOUSANDS;
In MariaDB you can do:
SELECT * FROM seq_i_to_N
For example:
SELECT * FROM seq_0_to_1000
SELECT * FROM seq_1_to_1000000
Reference: https://www.percona.com/blog/2020/07/27/generating-numeric-sequences-in-mysql/
The "shortest" way i know (in MySQL) to create a table with a long sequence is to (cross) join an existing table with itself. Since any (common) MySQL server has the information_schema.COLUMNS table i would use it:
DROP TABLE IF EXISTS seq;
CREATE TABLE seq (i MEDIUMINT AUTO_INCREMENT PRIMARY KEY)
SELECT NULL AS i
FROM information_schema.COLUMNS t1
JOIN information_schema.COLUMNS t2
JOIN information_schema.COLUMNS t3
LIMIT 100000; -- <- set your limit here
Usually one join should be enough to create over 1M rows - But one more join will not hurt :-) - Just don't forget to set a limit.
If you want to include 0, you should "remove" the AUTO_INCEMENT property.
ALTER TABLE seq ALTER i DROP DEFAULT;
ALTER TABLE seq MODIFY i MEDIUMINT;
Now you can insert 0
INSERT INTO seq (i) VALUES (0);
and negative numbers as well
INSERT INTO seq (i) SELECT -i FROM seq WHERE i <> 0;
You can validate the numbers with
SELECT MIN(i), MAX(i), COUNT(*) FROM seq;
All other answers are good, however they all have speed issues for larger ranges because they force MySQL to generate every number then filter them.
The following only makes MySQL generate the numbers that are needed, and therefore is faster:
set #amount = 55; # How many numbers from zero you want to generate
select `t0`.`i`+`t1`.`i`+`t2`.`i`+`t3`.`i` as `offset`
from
(select 0 `i` union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) `t0`,
(select 0 `i` union select 10 union select 20 union select 30 union select 40 union select 50 union select 60 union select 70 union select 80 union select 90) `t1`,
(select 0 `i` union select 100 union select 200 union select 300 union select 400 union select 500 union select 600 union select 700 union select 800 union select 900) `t2`,
(select 0 `i` union select 1000 union select 2000 union select 3000 union select 4000 union select 5000 union select 6000 union select 7000 union select 8000 union select 9000) `t3`
where `t3`.`i`<#amount
and `t2`.`i`<#amount
and `t1`.`i`<#amount
and `t0`.`i`+`t1`.`i`+`t2`.`i`+`t3`.`i`<#amount;
With the above you can generate upto 10,000 numbers (0 to 9,999) with no slower speed overhead for lower numbers, regardless how low they are.
Here's a way to do it with json_table if you have MySql 8 and above:
set #noRows = 100;
SELECT tt.rowid - 1 AS value
FROM JSON_TABLE(CONCAT('[{}', REPEAT(',{}', #noRows - 1), ']'),
"$[*]" COLUMNS(rowid FOR ORDINALITY)
) AS tt;
(h/t - https://www.percona.com/blog/2020/07/27/generating-numeric-sequences-in-mysql/)
The idea I want to share is not a precise response for the question but can be useful for some so I would like to share it.
If you frequently need only a limited set of numbers then it can be beneficial to create a table with the numbers you may need and just use that table every time. For example:
CREATE TABLE _numbers (num int);
INSERT _numbers VALUES (0), (1), (2), (3), ...;
This can be applied only if you need numbers below a certain reasonable limit, so don't use it for generating sequence 1...1 million but can be used for numbers 1...10k, for example.
If you have this list of numbers in the _numbers table then you can write queries like this, for obtaining the individual characters of a string:
SELECT number, substr(name, num, 1)
FROM users
JOIN _numbers ON num < length(name)
WHERE user_id = 1234
ORDER BY num;
If you need larger numbers than 10k then you can join the table to itself:
SELECT n1.num * 10000 + n2.num
FROM _numbers n1
JOIN _numbers n2
WHERE n1 < 100
ORDER BY n1.num * 10000 + n2.num; -- or just ORDER BY 1 meaning the first column
This is based on a previous answer (https://stackoverflow.com/a/53125278/2009581), but is compatible with MySQL 5.7. It works for replicas and read-only users:
SELECT x1.N + x10.N*10 + x100.N*100 + x1000.N*1000
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) x1,
(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) x10,
(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) x100,
(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) x1000
WHERE x1.N + x10.N*10 + x100.N*100 + x1000.N*1000 <= #max;
It generates integers in the range of [0, #max].
with recursive cte..
with recursive rnums as (
select 1 as n
union all
select n+1 as n from rnums
where n <10
)
select * from rnums
;
Result would be..
+------+
| n |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+------+
10 rows in set (0.00 sec)

SELECT on an array of constants

In MySQL, how is it possible to do a SELECT on an array of constant values? Something similar to this:
SELECT ['1', '2', '3'] AS ID;
Where the desired output is:
+-----+
| ID |
+-----+
| 1 |
| 2 |
| 3 |
You can use joins to generate a series of rows.
SELECT 1 AS ID UNION SELECT 2 UNION SELECT 3
For small amounts of data this works well. Down side is that there is no index to check these on.
A temp table is likely far better for this as at least any SQL that joins against these values can then use indexes.
Another possibility if the values are from a limited pool is to just have a table which contains all possible values and select from it:-
SELECT ID
FROM all_poss_values
WHERE ID IN (1,2,3)
or using a generated range of values (which again loses the availability of indexes):-
SELECT 1 + units.i + tens.i * 10 AS ID
FROM (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units,
(SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
WHERE 1 + units.i + tens.i * 10 IN (1,2,3)