How do I make a row generator in MySQL? - sql

Is there a way to generate an arbitrary number of rows that can be used in a JOIN similar to the Oracle syntax:
SELECT LEVEL FROM DUAL CONNECT BY LEVEL<=10

Hate to say this, but MySQL is the only RDBMS of the big four that doesn't have this feature.
In Oracle:
SELECT *
FROM dual
CONNECT BY
level < n
In MS SQL (up to 100 rows):
WITH hier(row) AS
(
SELECT 1
UNION ALL
SELECT row + 1
FROM hier
WHERE row < n
)
SELECT *
FROM hier
or using hint up to 32768
WITH hier(row) AS
(
SELECT 1
UNION ALL
SELECT row + 1
FROM hier
WHERE row < 32768
)
SELECT *
FROM hier
OPTION (MAXRECURSION 32767) -- 32767 is the maximum value of the hint
In PostgreSQL:
SELECT *
FROM generate_series (1, n)
In MySQL, nothing.

In MySql, it is my understand that you can get more than one row with a SELECT with no table (or DUAL).
Therefore, to get multiple rows, you do need a real or temporary table with at least the required number of rows.
However, you do not need to build a temporary table as you can use ANY existing table which has at least the number of rows required. So, if you have a table with at least the required number of rows, use:
SELECT #curRow := #curRow + 1 AS row_number
FROM sometable
JOIN (SELECT #curRow := 0) r
WHERE #curRow<100;
Just replace "sometable" with the name of any table of yours with at least the required number of rows.
PS: The "r" is a table "alias": I could have used "AS r". Any subquery in a FROM or JOIN clause creates a "derived table" which, as with all tables, must have a name or alias. (See MySql manual: 13.2.9.8. Subqueries in the FROM Clause)

Since this is currently one of the first results in Google for "mysql row generator", I'll add an update.
If your flavor of MySQL happens to be MariaDB, they have this feature. It's called the "Sequence Storage engine" and it's used like this:
select * from seq_1_to_10;
With the results:
+-----+
| seq |
+-----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+-----+
10 rows in set (0.00 sec)
Until version 10.0 it was a separate plugin that needed to be explicitly installed, but from 10.0 onwards it's built in. Enjoy!

MySQL 8.0
With MySQL 8.0, MariaDB 10.2, and later versions, you can use recursive CTEs:
WITH RECURSIVE sequence AS (
SELECT 1 AS level
UNION ALL
SELECT level + 1 AS value
FROM sequence
WHERE sequence.level < 10
)
SELECT level
FROM sequence;
Note that CTEs are limited by cte_max_recursion_depth (default 1000, max 4,294,967,295 (2³²−1)) in MySQL and by max_recursive_iterations (default 4,294,967,295) in MariaDB.
You can increase the limit by executing:
SET cte_max_recursion_depth = 4294967295;
It will only affect your current session and won't be persisted.
MySQL 5.7, 5.6 and less
For MySQL versions prior to 8.0, you can use the clever trick of Markus Winand below:
CREATE OR REPLACE VIEW generator_16
AS SELECT 0 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 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL
SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL
SELECT 15;
CREATE OR REPLACE VIEW generator_256
AS SELECT ( ( hi.n << 4 ) | lo.n ) AS n
FROM generator_16 lo, generator_16 hi;
CREATE OR REPLACE VIEW generator_4k
AS SELECT ( ( hi.n << 8 ) | lo.n ) AS n
FROM generator_256 lo, generator_16 hi;
CREATE OR REPLACE VIEW generator_64k
AS SELECT ( ( hi.n << 8 ) | lo.n ) AS n
FROM generator_256 lo, generator_256 hi;
CREATE OR REPLACE VIEW generator_1m
AS SELECT ( ( hi.n << 16 ) | lo.n ) AS n
FROM generator_64k lo, generator_16 hi;
CREATE OR REPLACE VIEW generator_16m
AS SELECT ( ( hi.n << 16 ) | lo.n ) AS n
FROM generator_64k lo, generator_256 hi;
CREATE OR REPLACE VIEW generator_4b
AS SELECT ( ( hi.n << 16 ) | lo.n ) AS n
FROM generator_64k lo, generator_64k hi;
and then:
SELECT n FROM generator_4b limit 10;
It takes only about 20 ms on my laptop to create even generator_4b, which contains more than 4 billion rows. And all of the generator views above combined take only 28 KB of storage.
If you want to know how it works, you can find a detailed explanation in his blog post.

I had a table with a column (c5) that contained a number x, I needed a SQL expression that repeated the same row x numbers of times.
My table A contained:
c1 c2 c3 c4 c5
16 1 2 16 3
16 1 2 17 2
16 1 2 18 1
And I needed:
c1 c2 c3 c4 c5 n
16 1 2 16 3 1
16 1 2 16 3 2
16 1 2 16 3 3
16 1 2 17 2 1
16 1 2 17 2 2
16 1 2 18 1 1
I solved that with the expression:
SELECT
c1, c2, c3, c4, c5, row_number AS n
FROM
(
SELECT
#curRow := #curRow + 1 AS row_number
FROM
tablea
JOIN (SELECT #curRow := 0) r
WHERE
#curRow < (
SELECT
max(field1)
FROM
tablea
)
) AS vwtable2
LEFT JOIN tablea d ON vwtable2.row_number <= tablea.field1;

If I'm understanding you, you want a list of consequtive numbers?
Just make the list:
create table artificial_range (id int not null primary key auto_increment, idn int);
insert into artificial_range (idn) values (0); --first row
insert into artificial_range(idn) select idn from artificial_range; --2nd
insert into artificial_range(idn) select idn from artificial_range; -- now 4 rows
insert into artificial_range(idn) select idn from artificial_range; --8
insert into artificial_range(idn) select idn from artificial_range; --16
insert into artificial_range(idn) select idn from artificial_range; --32
insert into artificial_range(idn) select idn from artificial_range; --64
insert into artificial_range(idn) select idn from artificial_range; --128
... etc, until you have, say, 1024.
update artificial_range set idn = id - 1 ;
-- now you have a series staring at 1 (id) and a series starting at 0
Now join to it, or join to transformations of it:
create view days_this_century as
select date_add('2000-01-01', interval a.idn day) as cdate
from artificial_range;

I don't know if this helps but you can number the rows from each select statement with sth. like:
SET #NUM = 0;
SELECT #NUM:=#NUM+1 rowNumber, * FROM
...
And later join them on this one.
At large databases this can be very slow.

To generate 10 rows:
SELECT a AS 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 from dual
You can generate 100 rows making a join with another 10 rows:
select t2.a*10 + t1.a from
(SELECT 0 AS 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) t1,
(SELECT 0 AS 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) t2
And then 1000 rows with another join, ...

You can, but it's a little bit tricky,
here it is:
mysql> create table t(inc bigint primary key auto_increment);
Query OK, 0 rows affected (0.03 sec)
mysql> insert into t values(0);
Query OK, 1 row affected (0.01 sec)
mysql> insert into t select 0 from t;
Query OK, 1 row affected (0.02 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> insert into t select 0 from t;
Query OK, 2 rows affected (0.02 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> insert into t select 0 from t;
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> insert into t select 0 from t;
Query OK, 8 rows affected (0.01 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> select count(inc), max(inc) from t;
+------------+----------+
| count(inc) | max(inc) |
+------------+----------+
| 16 | 20 |
+------------+----------+
1 row in set (0.00 sec)
mysql> select row_number() over w as inc from t window w as (order by inc);
+-----+
| inc |
+-----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| 11 |
| 12 |
| 13 |
| 14 |
| 15 |
| 16 |
+-----+
16 rows in set (0.00 sec)
You can double the count of generated rows by issuing the statement
insert into t select 0 from t;
as much as you need.
Also note that auto_increment by default generate some gaps, that's why row_number() is used.

Related

How to combine two tables in Oracle SQL on a quantitative base

beacause of a really old db design I need some help. This might be quite simple I'm just not seeing the wood for the trees at the moment.
TABLE A:
ID
1
2
3
4
5
TABLE B:
ID
VALUE B
1
10
1
20
2
10
2
20
3
10
3
20
3
30
4
10
TABLE C:
ID
VALUE C
1
11
1
21
2
11
2
21
2
31
3
11
5
11
Expected result:
where ID = 1
ID
VALUE B
VALUE C
1
10
11
1
20
21
where ID = 2
ID
VALUE B
VALUE C
2
10
11
2
20
21
2
null
31
where ID = 3
ID
VALUE B
VALUE C
3
10
11
3
20
null
3
30
null
where ID = 4
ID
VALUE B
VALUE C
4
10
null
where ID = 5
ID
VALUE B
VALUE C
5
null
11
The entries in table B and C are optional and could be unlimited, the ID from table A is the connection.
B and C are not directly connected. I need a quantitative comparision to find gaps in the database. The number of entries of table B and C should be the same (but not the value), usually entries are missing in either B or C.
I tried it with outer joins but I'm getting two much rows, because I need B or C join only one time per single row.
I hope anybody understand my problem and can help me.
It looks like, for each distinct ID, you want the nth row (ordered by VALUE) from TABLE_A to match with the nth row from TABLE_B. And if one table - A or B - has more values, you want those to match to null.
Your solution will have two parts. First, use row_number() over ( partition by id order by value) to order the rows in both tables. Then, use FULL OUTER JOIN to join on (id, rownumber).
Here is a full example:
-- WITH clauses are just test data...+
with table_a (id) as (
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL ),
table_b (id, value) as (
SELECT 1,10 FROM DUAL UNION ALL
SELECT 1,20 FROM DUAL UNION ALL
SELECT 2,10 FROM DUAL UNION ALL
SELECT 2,20 FROM DUAL UNION ALL
SELECT 3,10 FROM DUAL UNION ALL
SELECT 3,20 FROM DUAL UNION ALL
SELECT 3,30 FROM DUAL UNION ALL
SELECT 4,10 FROM DUAL ),
table_c (id, value) as (
SELECT 1,11 FROM DUAL UNION ALL
SELECT 1,21 FROM DUAL UNION ALL
SELECT 2,11 FROM DUAL UNION ALL
SELECT 2,21 FROM DUAL UNION ALL
SELECT 2,31 FROM DUAL UNION ALL
SELECT 3,11 FROM DUAL UNION ALL
SELECT 5,11 FROM DUAL )
-- Solution begins here
SELECT id, b.value b_value, c.value c_value
FROM ( SELECT b.*,
row_number() OVER ( PARTITION BY b.id ORDER BY b.value ) rn
FROM table_b b ) b
FULL OUTER JOIN ( SELECT c.*,
row_number() OVER ( PARTITION BY c.id ORDER BY c.value ) rn
FROM table_c c ) c USING (id, rn)
ORDER BY id, b_value, c_value;
+----+---------+---------+
| ID | B_VALUE | C_VALUE |
+----+---------+---------+
| 1 | 10 | 11 |
| 1 | 20 | 21 |
| 2 | 10 | 11 |
| 2 | 20 | 21 |
| 2 | | 31 |
| 3 | 10 | 11 |
| 3 | 20 | |
| 3 | 30 | |
| 4 | 10 | |
| 5 | | 11 |
+----+---------+---------+

How to select a single row multiple times in PostgreSql

I want to print 4 times the same row in PostgreSQL, how to achieve that ?
Table : mytable
Id | name
------------
1 | foo
2 | bar
3 | zzz
I want something like
Select 4x mytable.* from mytable where id=1
And the result should be
Id | name
------------
1 | foo
1 | foo
1 | foo
1 | foo
You can cross join against generate_series(1,4), which will return a table containing the numbers 1 to 4:
SELECT mytable.*
FROM mytable
CROSS JOIN generate_series(1,4) as x
WHERE id=1
For each row in your original result set, there will be one copy with 1 next to it, one with 2, and so on.
you can use generate_series.
sample:
t=# create table so48 (i int,n text);
CREATE TABLE
t=# insert into so48 select 1,'a';
INSERT 0 1
t=# insert into so48 select 2,'b';
INSERT 0 1
select:
t=# with s as (select generate_series(1,4,1) g) select so48.* from so48 join s on true where i = 1;
i | n
---+---
1 | a
1 | a
1 | a
1 | a
(4 rows)
use union all
Select mytable.* from mytable where id=1
union all Select mytable.* from mytable where id=1
union all Select mytable.* from mytable where id=1
union all Select mytable.* from mytable where id=1
Cross join should do the job
Select 4x mytable.* from mytable where id=1
cross join
(select 1 from dual union all
select 1 from dual union all
select 1 from dual union all
select 1 from dual )

SQL recursive id nodes

I have a table structure like so
Id Desc Node
---------------------
1 A
2 Aa 1
3 Ab 1
4 B
5 Bb 4
6 Bb1 5
these Desc values are presented in a listview to the user, if the user chooses Bb, I want the ID 5 and also the ID 4 becuase thats the root node of that entry, simular to that if the user chooses Bb1, I need ID 6, 5 and 4
I am only able to query one level up, but there could be n levels, so my query at the moment looks like this
SELECT Id
FROM tbl
WHERE Desc = 'Bb1'
OR Id = (SELECT Node FROM tbl WHERE Desc = 'Bb1');
You can do this with Recursive CTE like below
Schema:
CREATE TABLE #TAB (ID INT, DESCS VARCHAR(10), NODE INT)
INSERT INTO #TAB
SELECT 1 AS ID, 'A' DESCS, NULL NODE
UNION ALL
SELECT 2 , 'AA', 1
UNION ALL
SELECT 3, 'AB', 1
UNION ALL
SELECT 4, 'B', NULL
UNION ALL
SELECT 5, 'BB', 4
UNION ALL
SELECT 6, 'BB1', 5
Now do recursive CTE for picking node value and apply it again on #TAB with a Join.
;WITH CTE AS(
SELECT ID, DESCS, NODE FROM #TAB WHERE ID=6
UNION ALL
SELECT T.ID, T.DESCS, T.NODE FROM #TAB T
INNER JOIN CTE C ON T.ID = C.NODE
)
SELECT * FROM CTE
When you pass 6 to the first query in CTE, the result will be
+----+-------+------+
| ID | DESCS | NODE |
+----+-------+------+
| 6 | BB1 | 5 |
| 5 | BB | 4 |
| 4 | B | NULL |
+----+-------+------+

Oracle: enumerate groups of similar rows

I have the following table:
ID | X
1 | 1
2 | 2
3 | 5
4 | 6
5 | 7
6 | 9
I need to enumerate groups of rows in such way that if row i and i-1 differ in column X by less than 2 they should have the same group number N. See example below.
ID | X | N
1 | 1 | 1
2 | 2 | 1
3 | 5 | 2
4 | 6 | 2
5 | 7 | 2
6 | 9 | 3
Note that rows X(2)-X(1)=1 so they are grouped in the first group. Than X(3)-X(2)=3 so the 3rd row goes to 2nd group with 3rd and 4th row. X(6)-X(5)=2 so 6th row is in the 3rd group.
Can anybody help me with writing SQL query that will return the second table?
This should do it:
select id, x, sum(new_group) over (order by id) as group_no
from
( select id, x, case when x-prev_x = 1 then 0 else 1 end new_group
from
( select id, x, lag(x) over (order by id) prev_x
from mytable
)
);
I get the correct answer for your data with that query.
SQL> create table mytable (id,x)
2 as
3 select 1, 1 from dual union all
4 select 2, 2 from dual union all
5 select 3, 5 from dual union all
6 select 4, 6 from dual union all
7 select 5, 7 from dual union all
8 select 6, 9 from dual
9 /
Table created.
SQL> select id
2 , x
3 , sum(y) over (order by id) n
4 from ( select id
5 , x
6 , case x - lag(x) over (order by id)
7 when 1 then 0
8 else 1
9 end y
10 from mytable
11 )
12 order by id
13 /
ID X N
---------- ---------- ----------
1 1 1
2 2 1
3 5 2
4 6 2
5 7 2
6 9 3
6 rows selected.
Which is essentially the same as Tony's answer, only one inline view less.
Regards,
Rob.
Using basic operations only:
create table test(id int, x int);
insert into test values(1, 1), (2, 2), (3, 5), (4, 6), (5, 7), (6, 9);
create table temp as
select rownum() r, 0 min, x max
from test t
where not exists(select * from test t2 where t2.x = t.x + 1);
update temp t set min = select max + 1 from temp t2 where t2.r = t.r - 1;
update temp t set min = 0 where min is null;
select * from temp order by r;
select t.id, t.x, x.r from test t, temp x where t.x between x.min and x.max;
drop table test;
drop table temp;

sql range operator

Is there a SQL construct or a trick to do something like:
SELECT i WHERE i BETWEEN 0 AND 10;
?
My first idea is to create a temporary table such as:
CREATE TABLE _range ( i INT PRIMARY KEY );
and fill it
INSERT INTO _range VALUES 0;
INSERT INTO _range VALUES 1;
etc.
Is there a better way?
UPDATE:
I use sqlite in this particular case but i am interested in general answer.
What DB are you using? The between operator generally is legal syntax for SQL. We use it all the time for date ranges.
from http://www.w3schools.com/Sql/sql_between.asp
SELECT column_name(s)
FROM table_name
WHERE column_name
BETWEEN value1 AND value2
Very interesting question. Here's an ugly hack that probably is useless unless your ranges are really that small...
select * from (
select 1 as num UNION
select 2 as num UNION
select 3 as num UNION
select 4 as num UNION
select 5 as num UNION
select 6 as num UNION
select 7 as num UNION
select 8 as num UNION
select 9 as num UNION
select 10 as num
) t ;
+-----+
| num |
+-----+
| 1 |
| 2 |
....
| 9 |
| 10 |
+-----+
10 rows in set (0.00 sec)
Edit: Ok, so I got to thinking why not use cross joins. So, here is another hack and this one will quickly net you pretty large in memory tables and is perhaps reasonably good.
select POW(2,0)*t0.num + POW(2,1)*t1.num + POW(2,2)*t2.num + POW(2,3)*t3.num
as num
from (
select 0 as num UNION
select 1 as num
) t0, (
select 0 as num UNION
select 1 as num
) t1, (
select 0 as num UNION
select 1 as num
) t2, (
select 0 as num UNION
select 1 as num
) t3
order by num ;
+------+
| num |
+------+
| 0 |
| 1 |
....
| 14 |
| 15 |
+------+
16 rows in set (0.00 sec)
Will easily go to any power of 2 and should be fast enough.
with MS SQL you could use CTE :
;with numbs as
(
select 1 as col
union all
select col + 1 from numbs
where col < 10
)
select * from numbs
An SQL syntax that multiply records is CROSS JOIN
I am not sure from your request and comments, but I guess you are trying to do that.
I have done a similar thing to populate a table with a datestamp as string "YYYYMM" for a period of 20 years.
You did not specify the dbms you use, but with Oracle you could use CONNECT BY:
SELECT ROWNUM-1 i
FROM dual
CONNECT BY ROWNUM <= 10 + 1