SQL Row_Number () ฟังก์ชันใน Where Clause


91

ฉันพบหนึ่งคำถามที่ตอบด้วยRow_Number()ฟังก์ชันในประโยค where เมื่อฉันลองใช้แบบสอบถามหนึ่งรายการฉันได้รับข้อผิดพลาดต่อไปนี้:

"ข่าวสารเกี่ยวกับ 4108 ระดับ 15 สถานะ 1 บรรทัด 1 ฟังก์ชันที่มีหน้าต่างสามารถปรากฏได้เฉพาะในส่วนคำสั่ง SELECT หรือ ORDER BY"

นี่คือคำถามที่ฉันพยายาม หากใครรู้วิธีแก้ปัญหานี้โปรดแจ้งให้เราทราบ

SELECT employee_id 
FROM V_EMPLOYEE 
WHERE row_number() OVER ( ORDER BY employee_id ) > 0 
ORDER BY Employee_ID

10
ROW_NUMBER() OVER (ORDER BY employee_id) > 0จะประเมินเป็นเสมอTRUE
Quassnoi

3
ใช่ใช่แล้ว ฉันไม่กังวลเกี่ยวกับสภาพซึ่งสามารถเปลี่ยนแปลงได้ตลอดเวลา ฉันต้องการให้แบบสอบถามทำงานก่อนแล้วจึงคิดว่าจะคงหมายเลขแถวไว้ระหว่าง 500 ถึง 800 ... ขอบคุณ

2
@ โจเซฟ: ทำไมคุณถึงพยายามหลีกเลี่ยงการใช้ CTE?
OMG Ponies

1
@rexem - ฉันไม่ใช่ผู้เชี่ยวชาญใน SQL Server ฉันพยายามช่วยทีมในโครงการใหญ่ที่พวกเขาประสบปัญหามากมายเกี่ยวกับประสิทธิภาพ พวกเขากำลังใช้ UDF และ CTE ในตารางหนึ่งมีระเบียนเพียง 5,000 รายการและหากผู้ใช้ 5 คนเข้าถึงการค้นหาจะใช้เวลามากกว่าหนึ่งนาทีในการดึงข้อมูล บางครั้งก็ล้มเหลวและหมดเวลา ดังนั้นฉันพยายามหลีกเลี่ยง CTE และ UDF และพยายามสร้างแบบสอบถาม SQL ตรงไปตรงมาซึ่งสามารถแก้ปัญหาด้านประสิทธิภาพได้

1
สวัสดีทุกคนโปรดดูลิงก์ที่ฉันโพสต์ไว้ด้านล่างซึ่งคำตอบโดยใช้ row_number () ด้วยวิธีอื่น ใครสามารถเปรียบเทียบคำค้นหาเริ่มต้นของฉันกับคำถามในลิงก์ได้หรือไม่ เห็นคุณค่าความช่วยเหลือ ..

คำตอบ:


96

ในการแก้ไขปัญหานี้ให้รวมคำสั่ง select ของคุณไว้ใน CTE จากนั้นคุณสามารถสอบถามกับ CTE และใช้ผลลัพธ์ของฟังก์ชันที่มีหน้าต่างใน where clause

WITH MyCte AS 
(
    select   employee_id,
             RowNum = row_number() OVER ( order by employee_id )
    from     V_EMPLOYEE 
    ORDER BY Employee_ID
)
SELECT  employee_id
FROM    MyCte
WHERE   RowNum > 0

7
ฉันพยายามหลีกเลี่ยง CTE นั่นคือกรณีที่แย่กว่าที่ฉันกำลังมองหา ขอบคุณ

3
อาจทำงานได้เร็วขึ้นหากคุณใช้แบบสอบถามย่อยแทน CTE ฉันเห็นประสิทธิภาพที่ดีขึ้นโดยปัจจัย 1.5 ในบางกรณี
Brian Webster

3
ควรมี TOP ใน CTE SELECT มิฉะนั้น SQL 2008 Server จะไม่ดำเนินการค้นหาเนื่องจาก ORDER BY (ซึ่งไม่รองรับเว้นแต่จะใช้ TOP)
Muflix

2
ฉันกำลังใช้ SQL2005 (ugh) - ฉันสามารถหลีกเลี่ยงการใช้ "TOP" ได้โดยวาง "ORDER BY" ไว้หลัง FROM มันซ้ำซ้อนกับ (Order By) หลัง OVER แต่อย่างใด
Joe B

ฉันหวังว่าจะมีวิธีใช้ROW_NUMBER()ในWHEREข้อโดยไม่ต้อง CTE :(
Jalal

62
SELECT  employee_id
FROM    (
        SELECT  employee_id, ROW_NUMBER() OVER (ORDER BY employee_id) AS rn
        FROM    V_EMPLOYEE
        ) q
WHERE   rn > 0
ORDER BY
        Employee_ID

โปรดทราบว่าตัวกรองนี้ซ้ำซ้อน: ROW_NUMBER()เริ่มจาก1และมากกว่า0เสมอ


2
@ DavideChicco.it: ใน SQL Server ตารางที่ได้รับต้องใช้นามแฝง (ฉันควรเขียนAS qแทน แต่ก็ใช้ได้เช่นกัน)
Quassnoi

2
ความสามารถในการอ่านเป็นสิ่งสำคัญที่ฉันมีเมื่อตั้งชื่อนามแฝง คุณสามารถเขียน rn เป็น RowNumber และ q เป็น DerivedTable และประโยค where เป็น DerivedTable.RowNumber> 0 ในความคิดของฉันสิ่งนี้จะสับสนน้อยลงมากในเวลา 6 เดือนเมื่อโค้ดไม่สดในใจของคุณ
Edward Comeau

2
@EdwardComeau: rnเป็นคำย่อที่ยอมรับกันทั่วไปสำหรับหมายเลขแถวในทุกวันนี้ ลองพิมพ์ "row_number ทับเป็น ... " ลงในสตริงการค้นหาของ Google และดูสิ่งที่แนะนำคุณ
Quassnoi

3
@Quassnoi ความสามารถในการอ่านเป็นกุญแจสำคัญในการเข้ารหัสที่ดีและความพยายามในการรับรู้ในการแปล rn (หรือนามแฝงแบบย่ออื่น ๆ ) เพิ่มขึ้นสำหรับตัวคุณเองและคนที่ดูแลรหัสของคุณ NB, Microsoft ตีครั้งแรก, SELECT ROW_NUMBER () OVER (ORDER BY SalesYTD DESC) AS Row, ... ฉันยังไม่เคยเจอ rn มาก่อนดังนั้นระยะทางของคุณใน "universal" อาจแตกต่างกันไป
Edward Comeau

1
@Quassnoi และตีสองบทความ SO - stackoverflow.com/questions/961007/how-do-i-use-row-numberหลายรูปแบบและไม่ใช่ rn ;-)
Edward Comeau


19

ฉันคิดว่าคุณต้องการสิ่งนี้:

SELECT employee_id 
FROM  (SELECT employee_id, row_number() 
       OVER (order by employee_id) AS 'rownumber' 
       FROM V_EMPLOYEE) TableExpressionsMustHaveAnAliasForDumbReasons
WHERE rownumber > 0

4
สร้างนามแฝงสำหรับตารางหากแบบสอบถามด้านบนไม่ได้ผลสำหรับคุณ แก้ไขบรรทัดสุดท้ายที่สองเนื่องจากFrom V_EMPLOYEE) Aเพิ่ม A เป็นนามแฝง
Hammad Khan

7

ในการตอบสนองต่อความคิดเห็นเกี่ยวกับคำตอบของ rexem เกี่ยวกับว่ามุมมองแบบอินไลน์หรือ CTE จะเร็วขึ้นฉันเขียนข้อความค้นหาใหม่เพื่อใช้ตาราง I และทุกคนมีให้: sys.objects

WITH object_rows AS (
    SELECT object_id, 
        ROW_NUMBER() OVER ( ORDER BY object_id) RN
    FROM sys.objects)
SELECT object_id
FROM object_rows
WHERE RN > 1

SELECT object_id
FROM (SELECT object_id, 
        ROW_NUMBER() OVER ( ORDER BY object_id) RN
    FROM sys.objects) T
WHERE RN > 1

แผนการสืบค้นที่สร้างขึ้นนั้นเหมือนกันทุกประการ ฉันคาดหวังว่าในทุกกรณีเครื่องมือเพิ่มประสิทธิภาพการสืบค้นจะมาพร้อมกับแผนเดียวกันอย่างน้อยก็ในการเปลี่ยน CTE ง่ายๆด้วยมุมมองแบบอินไลน์หรือในทางกลับกัน

แน่นอนลองใช้แบบสอบถามของคุณเองในระบบของคุณเองเพื่อดูว่ามีความแตกต่างหรือไม่

นอกจากนี้row_number()ในส่วนที่เป็นข้อผิดพลาดทั่วไปในคำตอบที่ให้ไว้ใน Stack Overflow Logicaly row_number()ใช้ไม่ได้จนกว่าจะประมวลผลส่วนคำสั่งที่เลือก ผู้คนลืมสิ่งนั้นและเมื่อพวกเขาตอบโดยไม่ได้ทดสอบคำตอบบางครั้งคำตอบก็ผิด (ข้อหาที่ตัวเองมีความผิด)


1
ขอบคุณ Shannon คุณใช้ SQL Server เวอร์ชันใด
OMG Ponies

1
นั่นหมายความว่าคำตอบที่ให้ไว้ในลิงค์นั้นผิด? แต่คนที่โพสต์ถามเห็นด้วยว่ามันใช้งานได้ .. น่าแปลกใจ .. :-)

2
@ โจเซฟ แต่ถ้าคุณดูคำตอบอื่นที่ OP โพสต์ในคำถามที่เชื่อมโยงคุณจะเห็นว่าเขาเชื่อมโยงไปยังรุ่นของรหัสที่ไม่เหมือนกับในคำตอบที่ยอมรับ ฉันไม่รู้ว่าทำไมเขาถึงยอมรับคำตอบแม้ว่ามันจะไม่วิ่งตามที่ป้อนก็ตาม บางทีมันอาจถูกแก้ไขในบางจุดหลังจากได้รับการยอมรับบางทีมันก็เพียงพอที่จะทำให้เขาไปได้แม้จะไม่ถูกต้องทั้งหมด
Shannon Severance

1
@Rexem: ทั้ง SQL Server 2005 และ SQL Server 2008 เวอร์ชันก่อนหน้าไม่รองรับ CTEs หรือ ROW_NUMBER ()
Shannon Severance

6

ฉันรู้สึกว่าคำตอบทั้งหมดที่แสดงการใช้ CTE หรือ Sub Query เป็นการแก้ไขที่เพียงพอสำหรับสิ่งนี้ แต่ฉันไม่เห็นใครเข้าใจว่าทำไม OP ถึงมีปัญหา สาเหตุที่สิ่งที่ OP แนะนำใช้ไม่ได้เกิดจากคำสั่งประมวลผลการสืบค้นเชิงตรรกะที่นี่:

  1. จาก
  2. บน
  3. เข้าร่วม
  4. ที่ไหน
  5. GROUP BY
  6. ด้วย CUBE / ROLLUP
  7. มี
  8. เลือก
  9. แตกต่าง
  10. สั่งโดย
  11. TOP
  12. OFFSET / FETCH

ฉันเชื่อว่าสิ่งนี้มีส่วนช่วยในคำตอบอย่างมากเพราะมันอธิบายได้ว่าทำไมปัญหาเช่นนี้จึงเกิดขึ้น WHEREจะถูกประมวลผลเสมอก่อนที่จะSELECTสร้าง CTE หรือ Sub Query ซึ่งจำเป็นสำหรับหลายฟังก์ชัน คุณจะเห็นสิ่งนี้มากมายใน SQL Server


4

การใช้ CTE (SQL Server 2005+):

WITH employee_rows AS (
  SELECT t.employee_id,
         ROW_NUMBER() OVER ( ORDER BY t.employee_id ) 'rownum'
    FROM V_EMPLOYEE t)
SELECT er.employee_id
  FROM employee_rows er
 WHERE er.rownum > 1

การใช้มุมมองแบบอินไลน์ / ทางเลือกเทียบเท่าที่ไม่ใช่ CTE:

SELECT er.employee_id
  FROM (SELECT t.employee_id,
               ROW_NUMBER() OVER ( ORDER BY t.employee_id ) 'rownum'
          FROM V_EMPLOYEE t) er
 WHERE er.rownum > 1

1
อันไหนประสิทธิภาพดีกว่ากัน? ใช้ CTE หรือแบบสอบถามย่อย? ขอบคุณ

1
ดูคำตอบของแชนนอน - ในการทดสอบของเขาพวกเขาเท่าเทียมกัน
OMG Ponies

6
ไม่มันไม่เร็วขึ้น ในSQL Server, CTE's และมุมมองแบบอินไลน์เป็นสิ่งเดียวกันและมีประสิทธิภาพการทำงานที่เดียวกัน เมื่อใช้ฟังก์ชันที่ไม่ได้กำหนดใน a CTEระบบจะประเมินค่าใหม่ในการเรียกแต่ละครั้ง เราต้องใช้กลอุบายสกปรกเพื่อบังคับให้เป็นรูปธรรมของกCTE. ดูบทความเหล่านี้ในบล็อกของฉัน: explainextended.com/2009/07/28/... explainextended.com/2009/05/28/generating-xml-in-subqueries
Quassnoi

2

ขึ้นอยู่กับคำตอบของ OP สำหรับคำถาม:

โปรดดูที่ลิงค์นี้ มันมีวิธีแก้ปัญหาที่แตกต่างออกไปซึ่งดูเหมือนจะเหมาะกับคนที่ถามคำถาม ฉันกำลังพยายามหาวิธีแก้ปัญหาเช่นนี้

แบบสอบถามแบบแบ่งหน้าโดยใช้การเรียงลำดับในคอลัมน์ต่างๆโดยใช้ ROW_NUMBER () OVER () ใน SQL Server 2005

~ โจเซฟ

"วิธีที่ 1" เป็นเหมือนคำค้นหาของ OP จากคำถามที่เชื่อมโยงและ "วิธีที่ 2" ก็เหมือนกับการสืบค้นจากคำตอบที่เลือก คุณต้องดูรหัสที่เชื่อมโยงในคำตอบนี้เพื่อดูว่าเกิดอะไรขึ้นเนื่องจากรหัสในคำตอบที่เลือกได้รับการแก้ไขเพื่อให้ใช้งานได้ ลองสิ่งนี้:

DECLARE @YourTable table (RowID int not null primary key identity, Value1 int, Value2 int, value3 int)
SET NOCOUNT ON
INSERT INTO @YourTable VALUES (1,1,1)
INSERT INTO @YourTable VALUES (1,1,2)
INSERT INTO @YourTable VALUES (1,1,3)
INSERT INTO @YourTable VALUES (1,2,1)
INSERT INTO @YourTable VALUES (1,2,2)
INSERT INTO @YourTable VALUES (1,2,3)
INSERT INTO @YourTable VALUES (1,3,1)
INSERT INTO @YourTable VALUES (1,3,2)
INSERT INTO @YourTable VALUES (1,3,3)
INSERT INTO @YourTable VALUES (2,1,1)
INSERT INTO @YourTable VALUES (2,1,2)
INSERT INTO @YourTable VALUES (2,1,3)
INSERT INTO @YourTable VALUES (2,2,1)
INSERT INTO @YourTable VALUES (2,2,2)
INSERT INTO @YourTable VALUES (2,2,3)
INSERT INTO @YourTable VALUES (2,3,1)
INSERT INTO @YourTable VALUES (2,3,2)
INSERT INTO @YourTable VALUES (2,3,3)
INSERT INTO @YourTable VALUES (3,1,1)
INSERT INTO @YourTable VALUES (3,1,2)
INSERT INTO @YourTable VALUES (3,1,3)
INSERT INTO @YourTable VALUES (3,2,1)
INSERT INTO @YourTable VALUES (3,2,2)
INSERT INTO @YourTable VALUES (3,2,3)
INSERT INTO @YourTable VALUES (3,3,1)
INSERT INTO @YourTable VALUES (3,3,2)
INSERT INTO @YourTable VALUES (3,3,3)
SET NOCOUNT OFF

DECLARE @PageNumber     int
DECLARE @PageSize       int
DECLARE @SortBy         int

SET @PageNumber=3
SET @PageSize=5
SET @SortBy=1


--SELECT * FROM @YourTable

--Method 1
;WITH PaginatedYourTable AS (
SELECT
    RowID,Value1,Value2,Value3
        ,CASE @SortBy
             WHEN  1 THEN ROW_NUMBER() OVER (ORDER BY Value1 ASC)
             WHEN  2 THEN ROW_NUMBER() OVER (ORDER BY Value2 ASC)
             WHEN  3 THEN ROW_NUMBER() OVER (ORDER BY Value3 ASC)
             WHEN -1 THEN ROW_NUMBER() OVER (ORDER BY Value1 DESC)
             WHEN -2 THEN ROW_NUMBER() OVER (ORDER BY Value2 DESC)
             WHEN -3 THEN ROW_NUMBER() OVER (ORDER BY Value3 DESC)
         END AS RowNumber
    FROM @YourTable
    --WHERE
)
SELECT
    RowID,Value1,Value2,Value3,RowNumber
        ,@PageNumber AS PageNumber, @PageSize AS PageSize, @SortBy AS SortBy
    FROM PaginatedYourTable
    WHERE RowNumber>=(@PageNumber-1)*@PageSize AND RowNumber<=(@PageNumber*@PageSize)-1
    ORDER BY RowNumber



--------------------------------------------
--Method 2
;WITH PaginatedYourTable AS (
SELECT
    RowID,Value1,Value2,Value3
        ,ROW_NUMBER() OVER
         (
             ORDER BY
                 CASE @SortBy
                     WHEN  1 THEN Value1
                     WHEN  2 THEN Value2
                     WHEN  3 THEN Value3
                 END ASC
                ,CASE @SortBy
                     WHEN -1 THEN Value1
                     WHEN -2 THEN Value2
                     WHEN -3 THEN Value3
                 END DESC
         ) RowNumber
    FROM @YourTable
    --WHERE  more conditions here
)
SELECT
    RowID,Value1,Value2,Value3,RowNumber
        ,@PageNumber AS PageNumber, @PageSize AS PageSize, @SortBy AS SortBy
    FROM PaginatedYourTable
    WHERE 
        RowNumber>=(@PageNumber-1)*@PageSize AND RowNumber<=(@PageNumber*@PageSize)-1
        --AND more conditions here
    ORDER BY
        CASE @SortBy
            WHEN  1 THEN Value1
            WHEN  2 THEN Value2
            WHEN  3 THEN Value3
        END ASC
       ,CASE @SortBy
            WHEN -1 THEN Value1
            WHEN -2 THEN Value2
            WHEN -3 THEN Value3
        END DESC

เอาท์พุท:

RowID  Value1 Value2 Value3 RowNumber  PageNumber  PageSize    SortBy
------ ------ ------ ------ ---------- ----------- ----------- -----------
10     2      1      1      10         3           5           1
11     2      1      2      11         3           5           1
12     2      1      3      12         3           5           1
13     2      2      1      13         3           5           1
14     2      2      2      14         3           5           1

(5 row(s) affected

RowID  Value1 Value2 Value3 RowNumber  PageNumber  PageSize    SortBy
------ ------ ------ ------ ---------- ----------- ----------- -----------
10     2      1      1      10         3           5           1
11     2      1      2      11         3           5           1
12     2      1      3      12         3           5           1
13     2      2      1      13         3           5           1
14     2      2      2      14         3           5           1

(5 row(s) affected)

1
fyi เมื่อใช้SET SHOWPLAN_ALL ONวิธีที่ 1 มี TotalSubtreeCost เท่ากับ 0.08424953 ในขณะที่วิธีที่ 2 อยู่ที่ 0.02627153 วิธีที่ 2 ดีกว่าสามเท่า
กม ธ .

1
@rexem ทั้งวิธีที่ 1 และ 2 ใช้ CTE วิธีที่ใช้เลขหน้าและลำดับแถวจะแตกต่าง ฉันไม่แน่ใจว่าทำไมคำถามจริงนี้จึงแตกต่างจากคำถามที่ OP เชื่อมโยง (ในคำตอบของคำถามนี้โดย OP) แต่คำตอบของฉันสร้างโค้ดที่ใช้งานได้ตามลิงค์ที่ OP อ้างถึง
KM

1
ขอบคุณฉันกำลังพยายามเปรียบเทียบโพสต์เก่ากับคำตอบนี้ [ฉันไม่รู้ว่าจะจัดรูปแบบอย่างไร] นี่คือคำตอบโดย Tomalak stackoverflow.com/questions/230058?sort=votes#sort-top ผิดหรือเปล่า หากเขาโพสต์คำตอบเพียงครึ่งเดียวฉันจะดำเนินการตามวิธีการที่ดีกว่าของเขาในการทำแบบสอบถามได้อย่างไร โปรดให้ความรู้เพิ่มเติมเพื่อดำเนินการต่อ .. ขอบคุณ

@ โจเซฟคำตอบที่เลือกในลิงก์ที่คุณระบุ ( stackoverflow.com/questions/230058?sort=votes#sort-top ) แตกต่างจากรหัสการทำงานที่ผู้ถามคำถามระบุว่าทำงานในคำตอบ: stackoverflow.com/ คำถาม / 230058 / …หากคุณอ่านคำตอบนั้นคุณจะเห็นลิงก์ไปยังรหัสของพวกเขา: pastebin.com/f26a4b403และลิงก์ไปยังเวอร์ชันของ Tomalak: pastebin.com/f4db89a8e ในคำตอบของฉันฉันให้เวอร์ชันที่ใช้งานได้ของแต่ละเวอร์ชันโดยใช้ ตัวแปรตาราง
KM

2
WITH MyCte AS 
(
    select 
       employee_id,
       RowNum = row_number() OVER (order by employee_id)
    from V_EMPLOYEE 
)
SELECT  employee_id
FROM    MyCte
WHERE   RowNum > 0
ORDER BY employee_id

-1
 select salary from (
 select  Salary, ROW_NUMBER() over (order by Salary desc) rn from Employee 
 ) t where t.rn = 2

3
ยินดีต้อนรับสู่ Stack Overflow! แม้ว่าข้อมูลโค้ดนี้อาจเป็นวิธีแก้ปัญหา แต่การมีคำอธิบายจะช่วยปรับปรุงคุณภาพของโพสต์ของคุณได้มาก โปรดจำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตและบุคคลเหล่านั้นอาจไม่ทราบสาเหตุของการแนะนำโค้ดของคุณ
โยฮั

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