SQL seleziona intervalli di numeri


19

Ho trovato abbastanza difficile ottenere un intervallo di numeri come righe dentro MySQL.

Ad esempio, l'intervallo 1-5 è raggiunto da:

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

comporterà:

1
2
3
4
5

per 0-99 posso incrociare unire due tabelle 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

Sono stanco di scrivere tutti questi messaggi UNIONe cercare un modo per ridurre il codice.

Qualche idea su come giocare a golf (ad esempio 0-1.000.000 di range) in MySQL o qualsiasi sintassi SQL?

Vengono assegnati punti extra per:

  • singola dichiarazione
  • nessuna procedura
  • nessuna variabile
  • nessuna istruzione DDL
  • solo istruzioni DQL

2
Non sono sicuro se questo appartiene a meta, o in dba.stackexchange.com o forse nei suggerimenti per giocare a golf nel thread SQL .
BradC,

8
Per chiudere gli elettori: questa è una sfida sul tema; le domande che non rappresentano una sfida legata al codice del golf sono considerate domande di suggerimenti sull'argomento.
HyperNeutrino,

3
Mi piace un po ' questa risposta di SO . Hackish nella migliore delle ipotesi, ma dopo tutto hai chiesto una soluzione per il golf.
Arnauld,

@Arnauld è fantastico!
Dimgold,

2
Se "qualsiasi SQL" include PostgreSQL, vedere generate_series(). Abbiamo un paio di esempi di utilizzo qui.
arte

Risposte:


9

Per i dialetti SQL che supportano CTE ricorsivi come sqlite, è possibile eseguire le operazioni seguenti:

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

Ciò non dipende da alcuna tabella esistente e puoi modificare la clausola LIMIT come desiderato. Inizialmente ho visto una variante di questo su StackOverflow.


2
Eccellente. Ecco una versione golfata che funziona in MS SQL: WITH t AS(SELECT 1n UNION ALL SELECT n+1FROM t WHERE n<36)SELECT n FROM t per diversi punti finali, basta cambiare 1e 36con quello che vuoi.
BradC,

1
Oops, se si desidera più di 100 righe in MS SQL, potrebbe essere necessario aggiungere option (maxrecursion 0)alla fine della mia precedente dichiarazione, altrimenti si verifica un errore per la ricorsione su 100. (Impostare maxrecursionsu un valore specifico o su 0 per consentire infinito) .
BradC,

6

Simile al metodo di @ BradC .

Ho usato MS SQL, che ha una tabella [master]con un intervallo di numeri compreso tra -1 e 2048. È possibile utilizzare l' BETWEENoperatore per creare l'intervallo.

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

Se vuoi giocare a golf, puoi fare:

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

1
Per giocare a golf risparmi 2 byte conWHERE number>0AND number<21
BradC il

Perché usi distinti? Sembra ridondante.
Magic Octopus Urn,

1
@MagicOctopusUrn Perché ci sono numeri duplicati in quella tabella.
Oliver,

1
Sì, è necessario utilizzare DISTINCT o utilizzare WHERE type = 'P'. Distinto è leggermente più corto.
BradC,

1
@ Brad, oppureSELECT DISTINCT(number+2)... WHERE number<19
Peter Taylor,


4

Ottima opzione da questo post (trovato da @Arnauld):

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

Per me - risolve praticamente la sfida.


Questo sembra dipendere da una tabella esistente che ha già un idcampo popolato attraverso valori molto grandi. Così piuttosto specifico per il database e potresti perdere una riga se, per esempio, qualcuno ha eliminato l'ID prodotto = 4021.
BradC

Sì, ma è davvero buono per intervalli relativamente piccoli (1-7 per giorni, 1-12 per mesi ecc ...)
Dimgold

4

PostgreSQL specifico

generate_series()genera un set, quindi è possibile utilizzarlo non solo nella fromclausola, ma ovunque si possa verificare un set:

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

Puoi anche eseguire operazioni direttamente sul set:

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

Se più insiemi hanno la stessa lunghezza, puoi attraversarli in parallelo:

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

Per set con lunghezze diverse viene generato un prodotto cartesiano:

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)

Ma se li usi nella fromclausola, ottieni anche il prodotto cartesiano per gli stessi set di lunghezza:

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)

Può anche generare un set di timestamp. Ad esempio sei nato il 2000-06-30 e vuoi sapere in quali anni hai festeggiato il tuo compleanno in un fine settimana:

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 ha una tabella di sistema non documentata nel masterdatabase chiamata spt_values. Tra le altre cose, contiene un intervallo di numeri da 0 a 2047:

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

Utile come tabella dei numeri da solo, ma in un CTE puoi ottenere alcuni numeri grandi abbastanza rapidamente:

--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

(Funzionano in MS-SQL, non sono sicuro che funzionino per mySQL o altre piattaforme.)

Per set più piccoli (ordinati o non ordinati), utilizzare il VALUEScostruttore:

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

(Funziona per qualsiasi cosa, anche se le stringhe possono diventare piuttosto lunghe con tutte le virgolette singole ripetute.)

Quindi puoi moltiplicare in modo incrociato utilizzando un CTE (espressione di tabella comune) denominato in modo da non doverlo ripetere:

--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

Ci sono tonnellate di altre tecniche là fuori, cerca "SQL che genera una tabella numerica", sebbene la maggior parte non sia ottimizzata per il golf.


1
Funzionerebbe con un limit Yper creare intervalli arbitrari?
Rod,

1
@Rod In MS-SQL dovresti usareSELECT TOP 250 ...
BradC

Oh, non ho visto l'intestazione MSSQL = X
Rod

non funziona su MySQL, ma è comunque utile :)
Dimgold

2

Un'altra opzione, questa specifica per MS SQL 2016 e versioni successive:

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

Probabilmente lo troverò più utile per gli elenchi di stringhe, ma vedo come sarà utile anche con i numeri.


2

T-SQL, 98 byte

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
  • ✓ singola istruzione
  • ✓ nessuna procedura
  • ✓ nessuna variabile
  • ✓ nessuna istruzione DDL
  • ✓ solo istruzioni DQL

Questa è una bella versione T-SQL della risposta di langelgjm . Gli esponenti sono anche un trucco.
BradC,

1

Un altro per 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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.