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
ไม่แน่ใจว่านี้อยู่ในเมตาหรือใน dba.stackexchange.com หรืออาจจะในเคล็ดลับสำหรับการเล่นกอล์ฟในหัวข้อ SQL
BradC

8
วิธีปิดผู้ลงคะแนน: นี่คือความท้าทายในหัวข้อ คำถามที่ไม่ใช่ความท้าทายที่เกี่ยวข้องกับรหัสการเล่นกอล์ฟจะถือเป็นคำถามเคล็ดลับในหัวข้อ
HyperNeutrino

3
ฉัน kinda เช่นคำตอบนี้จาก SO แฮ็กที่ดีที่สุด แต่คุณขอวิธีการเล่นกอล์ฟหลังจากทั้งหมด
Arnauld

@Arnauld ที่น่าทึ่ง!
Dimgold

2
หาก“SQL ใด ๆ” รวมถึง PostgreSQL generate_series()ดู เรามีตัวอย่างการใช้งานที่นี่สองตัวอย่าง
จัดการ

คำตอบ:


9

สำหรับภาษา SQL ที่สนับสนุนCTE แบบเรียกซ้ำเช่น sqlite คุณสามารถทำสิ่งต่อไปนี้:

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

สิ่งนี้ไม่ได้ขึ้นอยู่กับตารางใด ๆ ที่มีอยู่และคุณสามารถเปลี่ยนข้อ จำกัด ได้ตามต้องการ ฉันเห็นความแตกต่างของสิ่งนี้ใน StackOverflow


2
ยอดเยี่ยม นี่คือเวอร์ชั่น golfed ที่ทำงานใน 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

เพื่อที่คล้ายกัน@ วิธี

ฉันใช้ 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

ทำไมคุณถึงแตกต่าง ดูเหมือนจะซ้ำซ้อน
Magic Octopus Urn

1
@MagicOctopusUrn เนื่องจากมีตัวเลขที่ซ้ำกันในตารางนั้น
Oliver

1
ใช่คุณต้องใช้ DISTINCT หรือใช้ WHERE type = 'P' ความแตกต่างจะสั้นกว่าเล็กน้อย
BradC

1
@BradC หรือSELECT DISTINCT(number+2)... WHERE number<19
Peter Taylor

5

PostgreSQL 35 ไบต์

PostgreSQL นั้นง่ายมาก:

SELECT * FROM generate_series(1,5)

หากคุณต้องการชื่อ:

SELECT num FROM generate_series(1,5)AS a(num)

คุณสามารถทำได้ด้วยการประทับเวลา https://www.postgresql.org/docs/9.5/static/functions-srf.html


2
ฉันเดาว่าฉันกำลังจะย้ายไปอยู่ที่ระดับสูง
Dimgold

4

ตัวเลือกที่ยอดเยี่ยมจากโพสต์นี้ (ค้นพบโดย @Arnauld):

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

สำหรับฉัน - มันค่อนข้างแก้ความท้าทาย


สิ่งนี้ดูเหมือนว่าจะอาศัยตารางที่มีอยู่แล้วที่มีidเขตข้อมูลอยู่ในค่าที่มีขนาดใหญ่มาก เฉพาะฐานข้อมูลที่สวยมากและคุณอาจพลาดได้ถ้าพูดว่ามีคนลบ ID ผลิตภัณฑ์ = 4021
BradC

ใช่ แต่มันก็ดีสำหรับช่วงที่ค่อนข้างเล็ก (1-7 สำหรับวัน, 1-12 ต่อเดือนและอื่น ๆ ... )
Dimgold

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
สิ่งนี้จะทำงานกับ a limit Yเพื่อสร้างช่วงโดยพลการหรือไม่?
Rod

1
@Rod ใน MS-SQL คุณต้องใช้SELECT TOP 250 ...
BradC

โอ้ฉันไม่เห็นส่วนหัว MSSQL = X
Rod

ไม่ทำงานบน 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 เท่านั้น

นี่เป็นคำตอบของ langelgjmเวอร์ชัน T-SQL ที่ดี เลขชี้กำลังเป็นกลอุบายที่ดีเช่นกัน
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;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.