ROW_NUMBER () ใน MySQL


281

มีวิธีที่ดีใน MySQL เพื่อทำซ้ำฟังก์ชัน SQL Server ROW_NUMBER()หรือไม่

ตัวอย่างเช่น:

SELECT 
    col1, col2, 
    ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1

ยกตัวอย่างเช่นฉันสามารถเพิ่มเงื่อนไขที่จะ จำกัดintRowไว้ที่ 1 เพื่อรับแถวเดียวที่มีค่าสูงสุดcol3สำหรับแต่ละ(col1, col2)คู่


9
ติดแท็กด้วยgreatest-n-per-groupเพื่อแนะนำคุณถึงคำถามที่คล้ายกัน
Bill Karwin

1
Sql-Serverแท็กที่ถูกลบออกเนื่องจากเป็นรายการที่โหวตสูงสุดในการค้นหาแท็กแบบรวมแต่ไม่เกี่ยวข้องกับ SQL Server จริงๆ
Martin Smith

สำหรับฟังก์ชั่นหมายเลขแถว mysql ง่าย ๆ ลองดูdatamakessense.com/mysql-rownum-row-number-function
AdrianBR

สำหรับ MySQL, การแก้ปัญหาที่มีประสิทธิภาพเท่านั้นที่จะกล่าวถึงในmysql.rjweb.org/doc.php/groupwise_max โซลูชันที่ได้รับการเผยแพร่จำนวนมากต้องการการสแกนแบบเต็มตาราง (หรือแย่กว่านั้น)
Rick James

2
โซลูชันการส่งเสริมตนเองอย่างไร้ยางอายสำหรับฟังก์ชันวิเคราะห์จริงROW_NUMBER, RANK, DESNSE_RANKใน MySQL
Kenneth Xu

คำตอบ:


102

ฉันต้องการแถวที่มี col3 สูงสุดเดียวสำหรับแต่ละคู่ (col1, col2)

นั่นคือจำนวนสูงสุดของคำถามหนึ่งในคำถาม SQL ที่พบบ่อยที่สุด (เนื่องจากดูเหมือนว่ามันควรจะง่าย แต่จริงๆแล้วมันไม่ใช่แบบนั้น)

ฉันมักจะอวบอ้วนสำหรับการเข้าร่วมด้วยตนเองที่ไม่มีค่า:

SELECT t0.col3
FROM table AS t0
LEFT JOIN table AS t1 ON t0.col1=t1.col1 AND t0.col2=t1.col2 AND t1.col3>t0.col3
WHERE t1.col1 IS NULL;

“ รับแถวในตารางที่ไม่มีแถวอื่นที่มี col1 ตรงกัน col2 มี col3 ที่สูงกว่า” (คุณจะสังเกตเห็นสิ่งนี้และวิธีแก้ปัญหาสูงสุดตามลำดับอื่น ๆ ของกลุ่มส่วนใหญ่จะส่งคืนหลายแถวหากมีมากกว่าหนึ่งแถวที่มี col1, col2, col3 เดียวกันหากเป็นปัญหาคุณอาจต้องใช้การประมวลผลภายหลัง)


2
แต่จะเกิดอะไรขึ้นถ้ามีค่าสูงสุดสองค่าของ col3 สำหรับคู่ (col1, col2) คุณจะจบลงด้วยสองแถว
Paul

@ พอล: ใช่! เพิ่งเพิ่มบันทึกเกี่ยวกับสิ่งนั้นในคำตอบ tic ที่ผ่านมา โดยปกติคุณสามารถวางแถวพิเศษที่ไม่ต้องการในเลเยอร์แอปพลิเคชันหลังจากนั้นแบบสุ่มได้ แต่ถ้าคุณมีแถวจำนวนมากทั้งหมดที่มี col3 เดียวกันอาจเป็นปัญหาได้
bobince

1
Bobince การแก้ปัญหาค่อนข้างเป็นที่นิยมใน SO แต่ฉันมีคำถาม การแก้ปัญหานั้นเหมือนกับว่ามีใครบางคนพยายามที่จะหา id ที่ใหญ่ที่สุดด้วยคำสั่งต่อไปนี้: SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;มันไม่จำเป็นต้องมีn*n/2 + n/2การเปรียบเทียบเป็นศูนย์เพื่อหาแถวเดียว? มีการเพิ่มประสิทธิภาพใด ๆ ที่ฉันไม่เห็นหรือไม่ ฉันพยายามถามคำถามที่คล้ายกันกับ Bill ในเธรดอื่น แต่ดูเหมือนว่าเขาจะไม่สนใจ
newtover

2
@Paul - เพื่อระบุกรณีที่มีหลายแถวที่ตรงกับจำนวนสูงสุดต่อกลุ่มและคุณต้องการที่จะคว้าเพียงหนึ่งคุณสามารถเพิ่มคีย์หลักในตรรกะส่วนคำสั่ง ON เพื่อเลือกเสมอ ... SELECT t0.col3 จากตาราง ในฐานะที่เป็นตาราง t0 ซ้ายเข้าร่วมเป็น t1 บน t0.col1 = t1.col1 และ t0.col2 = t1.col2 AND (t1.col3, t1.pk)> (t0.col3, t0.pk) ซึ่งเป็น t1.col1 IS NULL;
Jon Armstrong - Xgc

2
นี่จะสามารถอ่านได้มากขึ้นเมื่อSELECT t0.col3 FROM table AS t0 WHERE NOT EXISTS (select 1 from table AS t1 ON t0.col1=t1.col1 AND t0.col2=t1.col2 AND t1.col3>t0.col3)
wrschneider

204

ไม่มีฟังก์ชั่นการจัดอันดับใน MySQL สิ่งที่ใกล้เคียงที่สุดที่คุณจะได้รับคือใช้ตัวแปร:

SELECT t.*, 
       @rownum := @rownum + 1 AS rank
  FROM YOUR_TABLE t, 
       (SELECT @rownum := 0) r

แล้วมันจะทำงานอย่างไรในกรณีของฉัน? ฉันต้องการตัวแปรสองตัวหนึ่งตัวสำหรับแต่ละตัวของ col1 และ col2? Col2 จะต้องรีเซ็ตอย่างใดเมื่อ col1 เปลี่ยน ..

ใช่. ถ้าเป็น Oracle คุณสามารถใช้ฟังก์ชัน LEAD เพื่อจุดสูงสุดที่ค่าถัดไป โชคดีที่ Quassnoi ครอบคลุมตรรกะสำหรับสิ่งที่คุณจำเป็นต้องใช้ใน MySQL


1
อืม .... แล้วมันจะทำงานอย่างไรในกรณีของฉัน? ฉันต้องการตัวแปรสองตัวหนึ่งตัวสำหรับแต่ละตัวของ col1 และ col2? Col2 จะต้องรีเซ็ตอย่างใดเมื่อ col1 เปลี่ยน ..
Paul

ขอบคุณ ... ที่ผมกล่าวข้างต้นคำตอบนี้ได้รับการยอมรับอย่างเท่าเทียมกัน bobince แต่ฉันเท่านั้นที่สามารถติ๊กหนึ่ง :-)
พอล

9
การกำหนดและการอ่านจากตัวแปรที่ผู้ใช้กำหนดในคำสั่งเดียวกันนั้นไม่น่าเชื่อถือ นี่คือเอกสารที่นี่: dev.mysql.com/doc/refman/5.0/en/user-variables.html : "ตามกฎทั่วไปคุณไม่ควรกำหนดค่าให้กับตัวแปรผู้ใช้และอ่านค่าภายในคำสั่งเดียวกัน คุณอาจได้รับผลลัพธ์ตามที่คาดหวัง แต่ไม่รับประกันลำดับของการประเมินผลสำหรับนิพจน์ที่เกี่ยวข้องกับตัวแปรผู้ใช้นั้นไม่ได้กำหนดและอาจเปลี่ยนแปลงตามองค์ประกอบที่มีอยู่ในคำสั่งที่ระบุ "
Roland Bouman

1
@Roland: ฉันได้ทดสอบกับชุดข้อมูลขนาดเล็กเท่านั้นไม่มีปัญหาใด ๆ MySQL แย่เกินไปที่ยังไม่ได้ใช้งานฟังก์ชั่น - คำขอได้รับในตั้งแต่ปี 2008
OMG Ponies

2
นี่ดูเหมือนว่าพฤติกรรมที่ไม่ได้กำหนดไว้เป็นบันทึกของ Roland เช่นนี้จะช่วยให้ผลที่ไม่ถูกต้องโดยสิ้นเชิงสำหรับตารางฉันพยายาม:SELECT @row_num:=@row_num+1 AS row_number, t.id FROM (SELECT * FROM table1 WHERE col = 264 ORDER BY id) t, (SELECT @row_num:=0) var;
jberryman

81

ฉันมักจะทำตามรูปแบบนี้เสมอ รับตารางนี้:

+------+------+
|    i |    j |
+------+------+
|    1 |   11 |
|    1 |   12 |
|    1 |   13 |
|    2 |   21 |
|    2 |   22 |
|    2 |   23 |
|    3 |   31 |
|    3 |   32 |
|    3 |   33 |
|    4 |   14 |
+------+------+

คุณสามารถรับผลลัพธ์นี้:

+------+------+------------+
|    i |    j | row_number |
+------+------+------------+
|    1 |   11 |          1 |
|    1 |   12 |          2 |
|    1 |   13 |          3 |
|    2 |   21 |          1 |
|    2 |   22 |          2 |
|    2 |   23 |          3 |
|    3 |   31 |          1 |
|    3 |   32 |          2 |
|    3 |   33 |          3 |
|    4 |   14 |          1 |
+------+------+------------+

โดยเรียกใช้แบบสอบถามนี้ซึ่งไม่จำเป็นต้องมีตัวแปรใด ๆ ที่กำหนดไว้:

SELECT a.i, a.j, count(*) as row_number FROM test a
JOIN test b ON a.i = b.i AND a.j >= b.j
GROUP BY a.i, a.j

หวังว่าจะช่วย!


1
หากคอลัมน์เป็น VARCHAR หรือ CHAR คุณจะจัดการกับโครงสร้างนี้ได้อย่างไร
Tushar

3
คุณเจ๋งมาก Mosty ฉันกำลังมองหาสิ่งนี้
luckykrrish

เพิ่งให้คำตอบนี้โดยใช้ตรรกะของคุณสำหรับ row_number ขอบคุณ
Utsav

@Tushar ผู้ประกอบการ<, >, <=, >=จับ CHAR และชนิดข้อมูล VARCHAR ในลำดับตามตัวอักษร; ฉันคาดหวังว่าเป็นสิ่งที่คุณกำลังมองหา
alex

1
@AlmazVildanov คุณควรจะสามารถใช้แบบสอบถามนี้เพียงแค่เป็นแบบสอบถามย่อยสำหรับกรองrow_numbers <= 2 และขอบคุณมากสำหรับคำตอบนี้ Mosty มันสมบูรณ์แบบ!
Zax

61
SELECT 
    @i:=@i+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo

1
ครั้งแรก: = ดูเหมือนว่าจะหายไปจากคำตอบ @OMG Ponies ขอบคุณสำหรับการโพสต์ปีเตอร์จอห์นสันนี้
sholsinger

ฉันเดา (SELECT @i: = 0) ตามที่ foo ควรเป็นตารางแรกในคำสั่ง FROM โดยเฉพาะถ้าตารางอื่นใช้ตัวเลือกย่อย
andig

ทำไมคุณถึงต้องใช้ '.. foo' ด้วย?
Tom Chiverton

@ TomChiverton ถ้ามันหายไปคุณจะได้รับ: "รหัสข้อผิดพลาด: 1248. ทุกตารางที่ได้รับจะต้องมีนามแฝงของตัวเอง"
ExStackChanger

1
การจัดอันดับที่นี่ไม่ได้กำหนดอย่างสมบูรณ์และสิ่งนี้ไม่ได้ตอบคำถาม
jberryman

27

ตรวจสอบบทความนี้มันแสดงให้เห็นถึงวิธีการเลียนแบบ SQL ROW_NUMBER () กับพาร์ทิชันโดยใน MySQL ฉันวิ่งเข้าไปในสถานการณ์เดียวกันนี้ในการใช้ WordPress ฉันต้องการ ROW_NUMBER () และไม่ได้อยู่ที่นั่น

http://www.explodybits.com/2011/11/mysql-row-number/

ตัวอย่างในบทความใช้พาร์ติชันเดียวโดยเขตข้อมูล หากต้องการแบ่งพาร์ติชันตามฟิลด์เพิ่มเติมคุณสามารถทำสิ่งนี้:

  SELECT  @row_num := IF(@prev_value=concat_ws('',t.col1,t.col2),@row_num+1,1) AS RowNumber
         ,t.col1 
         ,t.col2
         ,t.Col3
         ,t.col4
         ,@prev_value := concat_ws('',t.col1,t.col2)
    FROM table1 t,
         (SELECT @row_num := 1) x,
         (SELECT @prev_value := '') y
   ORDER BY t.col1,t.col2,t.col3,t.col4 

การใช้ concat_ws จัดการกับค่า Null ฉันทดสอบสิ่งนี้กับ 3 ฟิลด์โดยใช้ int, date, และ varchar หวังว่านี่จะช่วยได้ ตรวจสอบบทความในขณะที่มันแบ่งแบบสอบถามนี้ลงและอธิบาย


1
น่ากลัว นี่คือการแบ่งพาร์ติชัน มีประโยชน์มาก
Stuart Watt

1
เมื่อเปรียบเทียบกับการเข้าร่วมด้วยตนเองนี้จะมีประสิทธิภาพมากขึ้น แต่มีปัญหากับตรรกะคำสั่งจะต้องเกิดขึ้นก่อนที่จะคำนวณ row_num, concat ก็ไม่จำเป็นเช่นกัน `` `เลือก @row_num: = IF (@ prev_col1 = t.col1 และ @ prev_col2 = t.col2), @ row_num + 1, 1) เป็น RowNumber, t.col1, t.col3, t.col3 , @ prev_col1: = t.col1, @ prev_col2: = t.col2 FROM (SELECT * จากตารางที่ 1 ตามลำดับโดย col1, col2, col3) t, (SELECT @row_num: = 1, @ prev_col1: = '', @ prev_col2: = '') var `` `
Kenneth Xu

หากคุณต้องการ tu ใส่ลงในแบบสอบถามย่อยแล้วเพิ่มlimit 18446744073709551615เพื่อบังคับorder byข้อ
xmedeko

concat_wsสตริงว่างเปล่า''มีอันตราย: concat_ws('',12,3) = concat_ws('',1,23). ดีกว่าที่จะใช้ตัวคั่นบางตัว'_'หรือใช้โซลูชัน @Kenneth Xu
xmedeko

ลิงก์ของ op นั้นตายไปแล้ว ลิงค์เก็บถาวรของที่นี่
user2426679

25

จากMySQL 8.0.0ด้านบนคุณสามารถใช้ฟังก์ชันแบบเรียงซ้อนได้

1.4 มีอะไรใหม่ใน MySQL 8.0 :

ฟังก์ชั่นหน้าต่าง

ขณะนี้ MySQL รองรับฟังก์ชั่นหน้าต่างที่แต่ละแถวจากแบบสอบถามทำการคำนวณโดยใช้แถวที่เกี่ยวข้องกับแถวนั้น เหล่านี้รวมถึงฟังก์ชั่นเช่น RANK (), LAG () และ NTILE () นอกจากนี้ฟังก์ชั่นรวมที่มีอยู่หลายตอนนี้สามารถใช้เป็นฟังก์ชั่นหน้าต่าง; ตัวอย่างเช่น SUM () และ AVG ()

ROW_NUMBER () มากกว่า _clause :

ส่งคืนจำนวนแถวปัจจุบันภายในพาร์ติชัน แถวจำนวนอยู่ในช่วง 1 ถึงจำนวนของพาร์ติชันแถว

เรียงลำดับโดยมีผลต่อลำดับในการเรียงลำดับแถว หากไม่มี ORDER BY หมายเลขแถวจะไม่แน่นอน

การสาธิต:

CREATE TABLE Table1(
  id INT AUTO_INCREMENT PRIMARY KEY, col1 INT,col2 INT, col3 TEXT);

INSERT INTO Table1(col1, col2, col3)
VALUES (1,1,'a'),(1,1,'b'),(1,1,'c'),
       (2,1,'x'),(2,1,'y'),(2,2,'z');

SELECT 
    col1, col2,col3,
    ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1;

การสาธิต DBFiddle


1
ถอนหายใจ ... ในที่สุด!
Used_By_Already

15

ฉันจะลงคะแนนให้ทางออกของ Mosty Mostacho ด้วยการแก้ไขเล็กน้อยในรหัสการค้นหาของเขา:

SELECT a.i, a.j, (
    SELECT count(*) from test b where a.j >= b.j AND a.i = b.i
) AS row_number FROM test a

ซึ่งจะให้ผลลัพธ์เดียวกัน:

+------+------+------------+
|    i |    j | row_number |
+------+------+------------+
|    1 |   11 |          1 |
|    1 |   12 |          2 |
|    1 |   13 |          3 |
|    2 |   21 |          1 |
|    2 |   22 |          2 |
|    2 |   23 |          3 |
|    3 |   31 |          1 |
|    3 |   32 |          2 |
|    3 |   33 |          3 |
|    4 |   14 |          1 |
+------+------+------------+

สำหรับตาราง:

+------+------+
|    i |    j |
+------+------+
|    1 |   11 |
|    1 |   12 |
|    1 |   13 |
|    2 |   21 |
|    2 |   22 |
|    2 |   23 |
|    3 |   31 |
|    3 |   32 |
|    3 |   33 |
|    4 |   14 |
+------+------+

ด้วยความแตกต่างเพียงอย่างเดียวที่แบบสอบถามไม่ได้ใช้เข้าร่วมและจัดกลุ่มตามอาศัยการเลือกแบบซ้อนกันแทน


มันควรจะดีกว่าไหม? ดูเหมือนว่าพวกเขาทั้งสองจะมีกำลังสอง แต่ฉันไม่แน่ใจว่าจะ interprate เอาท์พุทอธิบาย
jberryman

ในความเป็นจริงแล้วการเลือกแบบซ้อนกันนั้นไม่ได้รับการปรับให้เหมาะสมใน MySQL ดังนั้น anwser นี้เป็นเพียงการสาธิตเทคนิคการสืบค้น ตัวอย่างที่ใช้ตัวแปรด้านบนทำงานได้ดีขึ้นสำหรับกรณีที่ใช้งานได้จริงฉันคิดว่า
abcdn

1
ฉันไม่เชื่อใด ๆ ของคำตอบตัวแปรตามเป็นจริงโดยใช้พฤติกรรมที่กำหนดไว้ ...
jberryman

ฉันขอโทษฉันไม่แน่ใจว่าฉันได้รับสิ่งที่คุณหมายถึงโดย "พฤติกรรมที่กำหนด" คุณหมายความว่ามันใช้งานไม่ได้หรือคุณกังวลว่ามันไม่ได้มีการบันทึกไว้หรือไม่?
abcdn

1
"พฤติกรรมที่ไม่ได้กำหนด" หมายถึงมันไม่ได้มีการบันทึกไว้ในการทำงานและ / หรือเอกสารที่ไม่สามารถรับประกันว่าจะทำงาน ดูราคาเอกสารและลิงค์ในความคิดเห็นในหน้านี้ มันอาจคืนสิ่งที่ต้องการ (เดา) / คาดเดา / ตั้งสมมติฐาน / เพ้อฝัน สำหรับบางรุ่นของการนำไปใช้งานนิพจน์เคียวรีบางอย่างที่ใช้การเพิ่ม & การใช้ CASE จะแสดงให้ทำงานโดยโปรแกรมเมอร์ที่ Percona โดยดูที่รหัส ที่สามารถเปลี่ยนแปลงได้ด้วยการเปิดตัวใด ๆ
philipxy

12

ฉันจะกำหนดฟังก์ชั่น:

delimiter $$
DROP FUNCTION IF EXISTS `getFakeId`$$
CREATE FUNCTION `getFakeId`() RETURNS int(11)
    DETERMINISTIC
begin
return if(@fakeId, @fakeId:=@fakeId+1, @fakeId:=1);
end$$

จากนั้นฉันสามารถทำได้:

select getFakeId() as id, t.* from table t, (select @fakeId:=0) as t2;

ตอนนี้คุณไม่มีคิวรีย่อยซึ่งคุณไม่สามารถดูได้


ใช้งานได้ด้วยข้อ จำกัด เดียว: หากคุณเรียกใช้คิวรีหลายครั้งคุณจะได้รับ fakeIds ที่เพิ่มมากขึ้นสำหรับชุดผลลัพธ์เดียวกัน
Stephan Richter

คุณสามารถส่ง set @fakeId = 0; ทุกครั้งที่คุณต้องการเรียกใช้แบบสอบถามไม่ใช่วิธีที่ดีที่สุด แต่ใช้ได้
jmpeace

มีปัญหาแปลก ๆ เกิดขึ้นถ้าคุณลบ DETERMINISTIC จากนั้นรหัสปลอมไม่ถูกต้องเมื่อใช้คำสั่งซื้อโดย ทำไมนี้
Chris Muench

8

ค้นหา row_number ใน mysql

set @row_number=0;
select (@row_number := @row_number +1) as num,id,name from sbs

สามารถใช้กับแบบสอบถามแบบ UPDATE ได้หรือไม่ ฉันพยายาม แต่ได้รับข้อผิดพลาด "ข้อมูลถูกตัดทอนสำหรับคอลัมน์ ... "
Diego

1
หากใครสนใจที่จะใช้มันใน UPDATE จะต้องใช้มันเป็นแบบสอบถามย่อยเพื่อที่จะทำงาน UPDATE <table> SET <field> = (SELECT \ @row_number: = \ @row_number +1) เรียงตาม <คอลัมน์คำสั่งซื้อของคุณ>; คอลัมน์คำสั่งจะกำหนดค่าการเรียงลำดับของแถว
Diego

8

ไม่มีฟังก์ชั่rownumrow_num()ใน MySQL แต่มีวิธีการดังนี้:

select 
      @s:=@s+1 serial_no, 
      tbl.* 
from my_table tbl, (select @s:=0) as s;

4

วิธีแก้ปัญหาที่ฉันพบว่าทำงานได้ดีที่สุดคือการใช้แบบสอบถามย่อยดังนี้:

SELECT 
    col1, col2, 
    (
        SELECT COUNT(*) 
        FROM Table1
        WHERE col1 = t1.col1
        AND col2 = t1.col2
        AND col3 > t1.col3
    ) AS intRow
FROM Table1 t1

คอลัมน์พาร์ทิชันตามเพิ่งได้รับการเปรียบเทียบกับ '=' และคั่นด้วยและ คอลัมน์ ORDER BY จะถูกเปรียบเทียบกับ '<' หรือ '>' และคั่นด้วย OR

ฉันพบว่าสิ่งนี้มีความยืดหยุ่นสูงแม้ว่าจะมีราคาแพงนิดหน่อย


4

ฟังก์ชัน rownumber ไม่สามารถเลียนแบบได้ คุณอาจได้รับผลลัพธ์ตามที่คาดหวัง แต่คุณจะผิดหวังในบางช่วง นี่คือสิ่งที่เอกสาร mysql พูดว่า:

สำหรับข้อความอื่น ๆ เช่น SELECT คุณอาจได้รับผลลัพธ์ตามที่คาดหวัง แต่ไม่รับประกัน ในคำสั่งต่อไปนี้คุณอาจคิดว่า MySQL จะประเมิน @a ก่อนแล้วจึงทำการมอบหมายที่สอง: SELECT @ a, @a: = @ a + 1, ... ; อย่างไรก็ตามลำดับของการประเมินผลสำหรับนิพจน์ที่เกี่ยวข้องกับตัวแปรผู้ใช้นั้นไม่ได้กำหนดไว้

ขอแสดงความนับถือ Georgi


ฉันไม่ทำตาม "@i: = @i + 1 เป็นตำแหน่ง" ไม่ใช่การแทนที่โดยตรงสำหรับ "ROW_NUMBER () เหนือ (เรียงตามผลรวม (คะแนน) เรียง) เป็นตำแหน่ง" ได้อย่างไร
Tom Chiverton

1
@TomChiverton เพราะมันไม่ได้กำหนดพฤติกรรมตามที่ระบุไว้ในคู่มือ
philipxy

4

MariaDB 10.2 กำลังใช้งาน "ฟังก์ชั่นหน้าต่าง" รวมถึง RANK (), ROW_NUMBER () และสิ่งอื่น ๆ อีกมากมาย:

https://mariadb.com/kb/en/mariadb/window-functions/

จากการพูดคุยที่ Percona Live ในเดือนนี้พวกเขาได้รับการปรับปรุงให้เหมาะสม

ไวยากรณ์เหมือนกับรหัสในคำถาม


2

ฉันไม่เห็นคำตอบง่ายๆที่ครอบคลุมส่วน "PARTITION BY" ดังนั้นนี่คือของฉัน:

SELECT
    *
FROM (
    select
        CASE WHEN @partitionBy_1 = l THEN @row_number:=@row_number+1 ELSE @row_number:=1 END AS i
        , @partitionBy_1:=l AS p
        , t.*
    from (
        select @row_number:=0,@partitionBy_1:=null
    ) as x
    cross join (
        select 1 as n, 'a' as l
        union all
        select 1 as n, 'b' as l    
        union all
        select 2 as n, 'b' as l    
        union all
        select 2 as n, 'a' as l
        union all
        select 3 as n, 'a' as l    
        union all    
        select 3 as n, 'b' as l    
    ) as t
    ORDER BY l, n
) AS X
where i > 1
  • ส่วนคำสั่ง ORDER BY จะต้องสะท้อนถึงความต้องการ ROW_NUMBER ของคุณ ดังนั้นจึงมีข้อ จำกัด ที่ชัดเจนอยู่แล้ว: คุณไม่สามารถมี "การจำลอง" ROW_NUMBER หลายแบบในเวลาเดียวกัน
  • การสั่งซื้อของคอลัมน์ "คำนวณ" เรื่อง หากคุณมี mysql คำนวณคอลัมน์เหล่านั้นในลำดับอื่นอาจไม่ทำงาน
  • ในตัวอย่างง่ายๆนี้ฉันใส่เพียงชิ้นเดียว แต่คุณสามารถมีส่วน "แบ่งตาม"

        CASE WHEN @partitionBy_1 = part1 AND @partitionBy_2 = part2 [...] THEN @row_number:=@row_number+1 ELSE @row_number:=1 END AS i
        , @partitionBy_1:=part1 AS P1
        , @partitionBy_2:=part2 AS P2
        [...] 
    FROM (
        SELECT @row_number:=0,@partitionBy_1:=null,@partitionBy_2:=null[...]
    ) as x

1

สายไปบ้าง แต่อาจช่วยคนที่มองหาคำตอบ ...

ระหว่างตัวอย่าง row / row_number - เคียวรีแบบเรียกซ้ำที่อาจใช้ใน SQL ใด ๆ :

WITH data(row_num, some_val) AS 
(
 SELECT 1 row_num, 1 some_val FROM any_table --dual in Oracle
  UNION ALL
 SELECT row_num+1, some_val+row_num FROM data WHERE row_num < 20 -- any number
)
SELECT * FROM data
 WHERE row_num BETWEEN 5 AND 10
/

ROW_NUM    SOME_VAL
-------------------
5           11
6           16
7           22
8           29
9           37
10          46

2
ขออภัย แต่เท่าที่ฉันรู้ MySQL ไม่สนับสนุนการแสดงออกตารางที่พบบ่อย
ÁlvaroGonzález

มันเป็นตอนนี้ ... @ ÁlvaroGonzález MySQL 8 รองรับเฉพาะฟังก์ชัน CTE และหน้าต่างดังนั้นคำตอบนี้ไม่สมเหตุสมผลที่จะใช้ใน MySQL รุ่นเก่า ..
Raymond Nijland

1

ฟังก์ชันนี้อนุญาตให้ใช้ฟังก์ชันเดียวกันกับที่ ROW_NUMBER () และ PARTITION BY จัดเตรียมไว้ให้ใน MySQL

SELECT  @row_num := IF(@prev_value=GENDER,@row_num+1,1) AS RowNumber
       FirstName, 
       Age,
       Gender,
       @prev_value := GENDER
  FROM Person,
      (SELECT @row_num := 1) x,
      (SELECT @prev_value := '') y
  ORDER BY Gender, Age DESC

1

ยังช้าไปหน่อย แต่วันนี้ฉันมีความต้องการแบบเดียวกันดังนั้นฉันจึงค้นหาใน Google และในที่สุดก็มีวิธีการทั่วไปอย่างง่าย ๆ ที่นี่ในบทความของ Pinal Dave http://blog.sqlauthority.com/2014/03/09/mysql-reset-row -number สำหรับแต่ละกลุ่มพาร์ทิชันโดยแถวจำนวน /

ฉันต้องการที่จะมุ่งเน้นไปที่คำถามดั้งเดิมของ Paul (นั่นคือปัญหาของฉันเช่นกัน) ดังนั้นฉันจึงสรุปวิธีแก้ปัญหาของฉันเป็นตัวอย่างการทำงาน

เนื่องจากเราต้องการแบ่งพาร์ติชันมากกว่าสองคอลัมน์ฉันจะสร้างตัวแปร SET ในระหว่างการวนซ้ำเพื่อระบุว่ากลุ่มใหม่เริ่มขึ้นหรือไม่

SELECT col1, col2, col3 FROM (
  SELECT col1, col2, col3,
         @n := CASE WHEN @v = MAKE_SET(3, col1, col2)
                    THEN @n + 1 -- if we are in the same group
                    ELSE 1 -- next group starts so we reset the counter
                END AS row_number,
         @v := MAKE_SET(3, col1, col2) -- we store the current value for next iteration
    FROM Table1, (SELECT @n := 0, @v := NULL) r -- helper table for iteration with startup values
   ORDER BY col1, col2, col3 DESC -- because we want the row with maximum value
) x WHERE row_number = 1 -- and here we select exactly the wanted row from each group

3 หมายถึงพารามิเตอร์แรกของ MAKE_SET ที่ฉันต้องการค่าทั้งสองในตลาดหลักทรัพย์ (3 = 1 | 2) แน่นอนถ้าเราไม่มีคอลัมน์สองคอลัมน์ขึ้นไปที่สร้างกลุ่มเราสามารถกำจัดการดำเนินการ MAKE_SET การก่อสร้างเหมือนกันทุกประการ สิ่งนี้ใช้ได้สำหรับฉันตามที่ต้องการ ขอบคุณ Pinal Dave มากสำหรับการสาธิตที่ชัดเจนของเขา


1
โปรดทราบว่าORDER BYในแบบสอบถามย่อยนั้นอาจถูกละเว้น (ดูmariadb.com/kb/en/mariadb/ ...... ) ทางออกที่แนะนำคือการเพิ่มLIMIT 18446744073709551615ไปยังคิวรีย่อยซึ่งบังคับให้เรียงลำดับ อย่างไรก็ตามเรื่องนี้อาจก่อให้เกิดปัญหาด้านประสิทธิภาพและไม่ถูกต้องสำหรับจริงๆเลวตารางขนาดใหญ่ :)
pnomolos

1

นี่อาจเป็นทางออก:

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, firstName, lastName
FROM
    employees

มันไม่ได้ทำการแบ่งพาร์ติชัน แต่อย่างใดและก็ไม่แตกต่างจากคำตอบที่ถูกอ้างถึงมากขึ้น
Caius Jard

1

MySQL ได้ให้การสนับสนุน ROW_NUMBER ()ตั้งแต่รุ่น8.0 ขึ้นไป

หากคุณใช้ MySQL 8.0 หรือใหม่กว่าให้ตรวจสอบฟังก์ชัน ROW_NUMBER () มิฉะนั้นคุณต้องจำลองฟังก์ชัน ROW_NUMBER ()

row_number () เป็นฟังก์ชันการจัดอันดับที่ส่งคืนหมายเลขลำดับของแถวโดยเริ่มจาก 1 สำหรับแถวแรก

สำหรับรุ่นเก่ากว่า

SELECT t.*, 
       @rowid := @rowid + 1 AS ROWID
  FROM TABLE t, 
       (SELECT @rowid := 0) dummy;

1

สำคัญ: โปรดพิจารณาอัปเกรดเป็น MySQL 8+ และใช้ฟังก์ชั่น ROW_NUMBER () ที่กำหนดและมีการบันทึกไว้และคัดแฮ็กแฮ็กเก่าที่เชื่อมโยงกับฟีเจอร์ จำกัด รุ่นโบราณของ MySQL

ตอนนี้นี่เป็นหนึ่งในแฮ็กเหล่านั้น:

คำตอบที่นี่ที่ใช้ตัวแปรในแบบสอบถามส่วนใหญ่ / ทั้งหมดดูเหมือนจะไม่สนใจข้อเท็จจริงที่ว่าเอกสารอธิบาย (ถอดความ)

อย่าพึ่งพารายการในรายการ SELECT ที่ได้รับการประเมินตามลำดับจากบนลงล่าง อย่ากำหนดตัวแปรในรายการ SELECT หนึ่งรายการและใช้ในอีกรายการหนึ่ง

เช่นนี้มีความเสี่ยงที่พวกเขาจะปั่นป่วนคำตอบที่ผิดเพราะพวกเขามักจะทำ

select
  (row number variable that uses partition variable),
  (assign partition variable)

ถ้าสิ่งเหล่านี้ถูกประเมินจากล่างขึ้นบนหมายเลขแถวจะหยุดทำงาน (ไม่มีพาร์ติชัน)

ดังนั้นเราต้องใช้สิ่งที่มีคำสั่งรับประกันการดำเนินการ ใส่เคสเมื่อ:

SELECT
  t.*, 
  @r := CASE 
    WHEN col = @prevcol THEN @r + 1 
    WHEN (@prevcol := col) = null THEN null
    ELSE 1 END AS rn
FROM
  t, 
  (SELECT @r := 0, @prevcol := null) x
ORDER BY col

ในฐานะที่เป็นเค้าร่าง ld ลำดับการกำหนดของ prevcol มีความสำคัญ - prevcol จะต้องเปรียบเทียบกับค่าของแถวปัจจุบันก่อนที่เราจะกำหนดค่าจากแถวปัจจุบัน (มิฉะนั้นจะเป็นค่า col แถวปัจจุบันไม่ใช่ค่า col ของแถวก่อนหน้า) .

นี่คือวิธีการรวมกัน:

  • เมื่อมีการประเมินครั้งแรก หาก col ของแถวนี้เหมือนกับ col ของแถวก่อนหน้านี้ @r จะเพิ่มขึ้นและส่งคืนจาก CASE ค่านำที่ส่งคืนนี้ถูกเก็บไว้ใน @r มันเป็นคุณสมบัติของ MySQL ที่การมอบหมายจะส่งกลับค่าใหม่ของสิ่งที่ถูกกำหนดลงใน @r ลงในแถวผลลัพธ์

  • สำหรับแถวแรกของชุดผลลัพธ์ @prevcol เป็นโมฆะ (มันถูกกำหนดค่าเริ่มต้นเป็นโมฆะในเคียวรีย่อย) ดังนั้นเพรดิเคตนี้จึงเป็นเท็จ เพรดิเคตแรกนี้จะส่งกลับค่า false ทุกครั้งที่มีการเปลี่ยนแปลง col (แถวปัจจุบันแตกต่างจากแถวก่อนหน้า) นี่เป็นสาเหตุที่ทำให้ WHEN ที่สองถูกประเมิน

  • เมื่อเพรดิเคตที่สองเป็นเท็จเสมอและมีอยู่จริงเพื่อกำหนดค่าใหม่ให้กับ @prevcol เนื่องจากคอลัมน์ของแถวนี้แตกต่างจากคอลัมน์ของแถวก่อนหน้า (เรารู้สิ่งนี้เพราะหากเหมือนกันคอลัมน์แรกที่จะถูกใช้) เราจึงต้องกำหนดค่าใหม่เพื่อใช้ในการทดสอบครั้งต่อไป เนื่องจากการมอบหมายนั้นเกิดขึ้นและผลลัพธ์ของการมอบหมายนั้นถูกเปรียบเทียบกับค่าว่างและสิ่งใดก็ตามที่มีค่าเท่ากับ null นั้นเป็นเท็จภาคแสดงนี้จึงเป็นเท็จเสมอ แต่อย่างน้อยก็ประเมินว่ามันทำงานเพื่อรักษาคุณค่าของ col จากแถวนี้ดังนั้นจึงสามารถประเมินกับค่า col ของแถวถัดไป

  • เนื่องจาก WHEN ที่สองเป็นเท็จมันหมายถึงในสถานการณ์ที่คอลัมน์ที่เราแบ่งพาร์ติชันโดย (col) มีการเปลี่ยนแปลงมันเป็น ELSE ที่ให้ค่าใหม่สำหรับ @r เริ่มหมายเลขจาก 1

พวกเราไปถึงสถานการณ์ที่สิ่งนี้:

SELECT
  t.*, 
  ROW_NUMBER() OVER(PARTITION BY pcol1, pcol2, ... pcolX ORDER BY ocol1, ocol2, ... ocolX) rn
FROM
  t

มีรูปแบบทั่วไป:

SELECT
  t.*, 
  @r := CASE 
    WHEN col1 = @pcol1 AND col2 = @pcol2 AND ... AND colX = @pcolX THEN @r + 1 
    WHEN (@pcol1 := pcol1) = null OR (@pcol2 := col2) = null OR ... OR (@pcolX := colX) = null THEN null
    ELSE 1 
  END AS rn
FROM
  t, 
  (SELECT @r := 0, @pcol1 := null, @pcol2 := null, ..., @pcolX := null) x
ORDER BY pcol1, pcol2, ..., pcolX, ocol1, ocol2, ..., ocolX

เชิงอรรถ:

  • p ใน pcol หมายถึง "พาร์ติชัน", o ใน ocol หมายถึง "คำสั่ง" - ในรูปแบบทั่วไปฉันลด "prev" จากชื่อตัวแปรเพื่อลดความยุ่งเหยิงที่มองเห็น

  • วงเล็บที่(@pcolX := colX) = nullมีความสำคัญ หากไม่มีพวกเขาคุณจะกำหนด null ให้กับ @pcolX และสิ่งต่างๆจะหยุดทำงาน

  • มันเป็นเรื่องประนีประนอมที่จะต้องมีการสั่งซื้อชุดผลลัพธ์โดยคอลัมน์พาร์ติชันด้วยเช่นกันสำหรับคอลัมน์ก่อนหน้านี้เปรียบเทียบกับการทำงาน คุณไม่สามารถสั่งให้ rownumber ของคุณตามคอลัมน์หนึ่งได้ แต่ชุดผลลัพธ์ของคุณสั่งให้ไปที่อื่นคุณอาจแก้ไขปัญหานี้ได้ด้วยแบบสอบถามย่อย แต่ฉันเชื่อว่าเอกสารยังระบุว่าการสั่งซื้อแบบสอบถามย่อยอาจถูกละเว้นเว้นแต่จะใช้ LIMIT และอาจส่งผลกระทบ ประสิทธิภาพ

  • ฉันไม่ได้เจาะลึกเกินกว่าการทดสอบว่าวิธีนี้ใช้ได้ผล แต่ถ้ามีความเสี่ยงที่เพรดิเคตในวินาทีที่จะเพิ่มประสิทธิภาพ (สิ่งใดเทียบกับโมฆะนั้นเป็นโมฆะ / เท็จดังนั้นทำไมต้องทำงานมอบหมาย) และไม่ทำงาน มันก็หยุด สิ่งนี้ดูเหมือนจะไม่เกิดขึ้นในประสบการณ์ของฉัน แต่ฉันยินดีรับความคิดเห็นและเสนอวิธีการแก้ปัญหาหากมันเกิดขึ้นอย่างสมเหตุสมผล

  • อาจเป็นการดีที่จะส่งค่า null ที่สร้าง @pcolX ไปยังประเภทคอลัมน์จริงของคุณในแบบสอบถามย่อยที่สร้างตัวแปร @pcolX ได้แก่ : select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)


ไม่มีเหตุผลสำหรับสิ่งนี้ เช่นเดียวกับคำตอบอื่น ๆ ที่กำหนดให้ & อ่านจากตัวแปรเดียวกัน
philipxy

คุณช่วยหารายละเอียดเพิ่มเติมได้ไหม?
Caius Jard

ดูความคิดเห็นอื่น ๆ ของฉันในหน้านี้ Googling 'site: stackoverflow.com "philipxy" ตัวแปร mysql (ตั้งค่าหรือกำหนดหรือมอบหมายหรือเขียน) อ่าน': คำตอบโดยฉัน & รายงานข้อผิดพลาดที่เชื่อมโยงในความคิดเห็นโดยฉันที่คำถามนี้ที่คำตอบที่ยอมรับคำพูดคู่มือทันที ในการอ้างสิทธิ์มันก็โอเคที่จะทำสิ่งที่ขัดแย้งกับมัน อ่านตัวแปรอีกครั้งด้วยตนเอง & การกำหนดใหม่
philipxy


ฉันเข้าใจความกังวลของคุณ
Caius Jard

0

นี่ไม่ใช่วิธีการแก้ปัญหาที่แข็งแกร่งที่สุด - แต่ถ้าคุณเพียงแค่ต้องการสร้างอันดับพาร์ติชันบนเขตข้อมูลที่มีค่าที่แตกต่างกันเพียงไม่กี่อย่างก็อาจไม่เหมาะที่จะใช้บางกรณีเมื่อตรรกะที่มีตัวแปรมากเท่าที่คุณต้องการ

บางอย่างเช่นนี้ได้ผลสำหรับฉันในอดีต:

SELECT t.*, 
   CASE WHEN <partition_field> = @rownum1 := @rownum1 + 1 
     WHEN <partition_field> = @rownum2 := @rownum2 + 1 
     ...
     END AS rank
FROM YOUR_TABLE t, 
   (SELECT @rownum1 := 0) r1, (SELECT @rownum2 := 0) r2
ORDER BY <rank_order_by_field>
;

หวังว่าจะเหมาะสม / ช่วย!


-1

งานนี้สมบูรณ์แบบสำหรับฉันในการสร้าง RowNumber เมื่อเรามีคอลัมน์มากกว่าหนึ่งคอลัมน์ ในกรณีนี้สองคอลัมน์

SELECT @row_num := IF(@prev_value= concat(`Fk_Business_Unit_Code`,`NetIQ_Job_Code`), @row_num+1, 1) AS RowNumber, 
    `Fk_Business_Unit_Code`,   
    `NetIQ_Job_Code`,  
    `Supervisor_Name`,  
    @prev_value := concat(`Fk_Business_Unit_Code`,`NetIQ_Job_Code`)  
FROM (SELECT DISTINCT `Fk_Business_Unit_Code`,`NetIQ_Job_Code`,`Supervisor_Name`         
      FROM Employee    
      ORDER BY `Fk_Business_Unit_Code`, `NetIQ_Job_Code`, `Supervisor_Name` DESC) z,  
(SELECT @row_num := 1) x,  
(SELECT @prev_value := '') y  
ORDER BY `Fk_Business_Unit_Code`, `NetIQ_Job_Code`,`Supervisor_Name` DESC

-3
set @i = 1;  
INSERT INTO ARG_VALUE_LOOKUP(ARG_VALUE_LOOKUP_ID,ARGUMENT_NAME,VALUE,DESCRIPTION,UPDATE_TIMESTAMP,UPDATE_USER,VER_NBR,OBJ_ID) 
select @i:= @i+1 as ARG_VALUE_LOOKUP_ID,ARGUMENT_NAME,VALUE,DESCRIPTION,CURRENT_TIMESTAMP,'admin',1,UUID() 
FROM TEMP_ARG_VALUE_LOOKUP 
order by ARGUMENT_NAME;

1
โปรดลองจัดรูปแบบคำตอบใด ๆ และให้บริบทเพิ่มเติมเกี่ยวกับสิ่งที่คุณพยายามทำ ในขณะนี้ไม่มีอะไรเพิ่มเติม แต่มีข้อความที่จัดรูปแบบไม่ดี
Yannick Meeus

2
สิ่งนี้ดูเหมือนจะไม่มีความสัมพันธ์กับคำถามต้นฉบับ หากคุณมีคำถามของคุณเองโปรดถามแยกต่างหาก
Jeroen Mostert

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