SQL выбрать диапазоны номеров


19

Мне было довольно сложно достичь диапазона чисел в виде строк MySQL.

Например, диапазон 1-5 достигается путем:

SELECT 1 
UNION
SELECT 2
UNION
SELECT 3
UNION
SELECT 4
UNION
SELECT 5

приведет к:

1
2
3
4
5

для 0-99 я могу соединить две таблицы 0-9:

CREATE TABLE nums as
SELECT 0 as num
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
;

Select n.num*10+nums.num v 
From nums n cross join nums

Я устал писать все это UNIONи искать способ уменьшить код.

Любые идеи, как играть в гольф (например, диапазон 0-1 000 000) в MySQL или любой синтаксис SQL?

Дополнительные баллы начисляются за:

  • одно заявление
  • нет процедур
  • без переменных
  • нет заявлений DDL
  • только операторы DQL

2
Не уверен, что это относится к meta, или к dba.stackexchange.com, или, может быть, к советам по игре в гольф в потоке SQL .
BradC

8
Чтобы закрыть избирателей: это проблема по теме; вопросы, которые не являются проблемами, связанными с кодом игры в гольф, считаются вопросами советов по теме.
HyperNeutrino

3
Мне нравится этот ответ от SO . В лучшем случае хакш, но ты все-таки попросил решение для игры в гольф.
Арно

@Arnauld, это потрясающе!
Димголд

2
Если «любой SQL» включает в себя PostgreSQL, см generate_series(). У нас есть несколько примеров использования здесь.
manatwork

Ответы:


9

Для диалектов SQL, которые поддерживают рекурсивные CTE, такие как sqlite, вы можете сделать что-то вроде следующего:

WITH RECURSIVE f(x) AS
(
  SELECT 1 UNION ALL SELECT x + 1 FROM f LIMIT 1000000
)
SELECT x
FROM f;

Это не зависит от какой-либо существующей таблицы, и вы можете изменить предложение LIMIT по желанию. Первоначально я видел вариант этого на StackOverflow.


2
Отлично. Вот версия для гольфа, которая работает в MS SQL: WITH t AS(SELECT 1n UNION ALL SELECT n+1FROM t WHERE n<36)SELECT n FROM t Для разных конечных точек просто измените 1и 36на то, что вы хотите.
BradC

1
К сожалению, если вам нужно более 100 строк в MS SQL, вам может потребоваться добавить option (maxrecursion 0)в конец вышеприведенного оператора, в противном случае произойдет ошибка при рекурсии свыше 100. (Либо установите maxrecursionконкретное значение, либо 0, чтобы разрешить бесконечность) ,
BradC

6

Похоже на метод @ BradC .

Я использовал MS SQL, в котором есть таблица [master]с диапазоном чисел от -1 до 2048. Вы можете использовать BETWEENоператор для создания своего диапазона.

SELECT DISTINCT(number)
FROM master..[spt_values] 
WHERE number BETWEEN 1 AND 5

Если вы хотите играть в гольф, вы можете сделать:

SELECT TOP 5 ROW_NUMBER()OVER(ORDER BY number)FROM master..spt_values

1
Для игры в гольф вы экономите 2 байта сWHERE number>0AND number<21
BradC

Почему вы используете разные? Кажется излишним.
Волшебная Урна Осьминога

1
@MagicOctopusUrn Потому что в этой таблице есть повторяющиеся числа.
Оливер

1
Да, вам нужно либо использовать DISTINCT, либо использовать WHERE type = 'P'. Отличается немного короче.
BradC

1
@BradC, илиSELECT DISTINCT(number+2)... WHERE number<19
Питер Тейлор


4

Отличный вариант из этого поста (найден @Arnauld):

SELECT id%1000001 as num
FROM <any_large_table>
GROUP BY num

Для меня - это в значительной степени решает проблему.


Кажется, это зависит от существующей таблицы, в которой уже есть idполе, заполненное очень большими значениями. Это довольно специфично для базы данных, и вы можете пропустить строку, если, скажем, кто-то удалит ID продукта = 4021.
BradC

Да, но это действительно хорошо для относительно небольших диапазонов (1-7 дней, 1-12 месяцев и т. Д.)
Димголд

4

Специфичный для PostgreSQL

generate_series()генерирует набор, так что вы можете использовать его не только в fromпредложении, но и везде, где может происходить набор:

psql=# select generate_series(10, 20, 3);
 generate_series 
-----------------
              10
              13
              16
              19
(4 rows)

Вы также можете выполнять операции непосредственно на съемочной площадке:

psql=# select 2000 + generate_series(10, 20, 3) * 2;
 ?column? 
----------
     2020
     2026
     2032
     2038
(4 rows)

Если несколько наборов имеют одинаковую длину, вы можете пройти их параллельно:

psql=# select generate_series(1, 3), generate_series(4, 6);
 generate_series | generate_series 
-----------------+-----------------
               1 |               4
               2 |               5
               3 |               6
(3 rows)

Для наборов различной длины генерируется декартово произведение:

psql=# select generate_series(1, 3), generate_series(4, 5);
 generate_series | generate_series 
-----------------+-----------------
               1 |               4
               2 |               5
               3 |               4
               1 |               5
               2 |               4
               3 |               5
(6 rows)

Но если вы используете их в fromпредложении, вы получите декартово произведение для наборов одинаковой длины:

psql=# select * from generate_series(1, 2), generate_series(3, 4) second;
 generate_series | second 
-----------------+--------
               1 |      3
               1 |      4
               2 |      3
               2 |      4
(4 rows)

Он также может генерировать множество временных меток. Например, вы родились 2000-06-30 и хотите знать, в какие годы вы отмечали свой день рождения на выходных:

psql=# select to_char(generate_series, 'YYYY - Day') from generate_series('2000-06-30', current_date, interval '1 year') where to_char(generate_series, 'D') in ('1', '7');
     to_char      
------------------
 2001 - Saturday 
 2002 - Sunday   
 2007 - Saturday 
 2012 - Saturday 
 2013 - Sunday   
(5 rows)

3

MS SQL имеет недокументированную системную таблицу в masterбазе данных spt_values. Среди прочего, он содержит диапазон чисел от 0 до 2047:

--returns 0 to 2,047
SELECT number n 
FROM master..spt_values
WHERE TYPE='P'

Полезно в качестве таблицы чисел само по себе, но в CTE вы можете довольно быстро получить несколько больших чисел:

--returns 0 to 4,194,304
WITH x AS(SELECT number n FROM master..spt_values WHERE TYPE='P')
SELECT 2048*x.a+*y.a
FROM x,x y
ORDER BY 1

3

(Они работают в MS-SQL, не уверен, работают ли они для mySQL или других платформ.)

Для небольших наборов (упорядоченных или не упорядоченных) используйте VALUESконструктор:

--Generates 0-9
SELECT a 
FROM(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9))x(a)

(Это работает для чего угодно, хотя строки могут быть довольно длинными со всеми повторяющимися одинарными кавычками.)

Затем вы можете кросс-умножить, используя именованное CTE (общее табличное выражение), так что вам не нужно его повторять:

--Generates 0-999
WITH x AS(SELECT a FROM(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9))x(a))
SELECT 100*x.a+10*y.a+z.a 
FROM x,x y,x z
ORDER BY 1

Есть множество других методов, ищите «SQL, генерирующий таблицу чисел», хотя большинство не оптимизировано для игры в гольф.


1
Будет ли это работать с, limit Yчтобы сделать произвольные диапазоны?
Род

1
@Rod В MS-SQL вы должны будете использоватьSELECT TOP 250 ...
BradC

О, я не видел заголовок MSSQL = X
Род

не работает на MySQL, но все еще полезно :)
Dimgold

2

Еще одна опция, специфичная для MS SQL 2016 и выше:

SELECT value v
FROM STRING_SPLIT('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16', ',')

Я, вероятно, нахожу это более удобным для списков строк, но я вижу способы, которые это будет полезно и с числами.


2

T-SQL, 98 байт

WITH H AS(SELECT 0i UNION ALL SELECT i+1FROM H WHERE i<99)SELECT H.i+1e4*A.i+B.i*1e2FROM H,H A,H B
  • ✓ разовое заявление
  • ✓ нет процедур
  • ✓ без переменных
  • ✓ нет заявлений DDL
  • ✓ только операторы DQL

Это хорошая аккуратная T-SQL версия ответа langelgjm . Экспоненты - это тоже хитрый трюк.
BradC

1

Еще один для SQL Server ...

WITH 
    cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)),   -- 10
    cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b),                             -- 100
    cte_Tally (n) AS (
        SELECT TOP (<how many ROWS do you want?>)
            ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
        FROM
            cte_n2 a CROSS JOIN cte_n2 b                                                    -- 10,000
        )
SELECT 
    t.n
FROM
    cte_Tally t;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.