Funkcja SQL Row_Number () w klauzuli Where


91

Znalazłem odpowiedź na jedno pytanie z Row_Number()funkcją w klauzuli where. Kiedy wypróbowałem jedno zapytanie, otrzymałem następujący błąd:

„Msg 4108, poziom 15, stan 1, wiersz 1 Funkcje okienkowe mogą pojawiać się tylko w klauzulach SELECT lub ORDER BY”.

Oto zapytanie, które wypróbowałem. Jeśli ktoś wie, jak to rozwiązać, daj mi znać.

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) > 0zawsze oceniTRUE
Quassnoi

3
Tak, zgadza się. Nie martwię się stanem, który w każdej chwili mogę zmienić. Chcę, aby najpierw zadziałało zapytanie, a potem myślę o utrzymaniu numeru seryjnego między 500 a 800 ... dzięki

2
@Joseph: Dlaczego próbujesz uniknąć używania CTE?
OMG Kucyki

1
@rexem - nie jestem ekspertem w SQL Server. Próbuję pomóc zespołowi w dużym projekcie, w którym borykają się z wieloma problemami z wydajnością. Używają UDF i CTE. W jednej z tabel mają tylko 5000 rekordów, a jeśli 5 użytkowników uzyskuje dostęp do wyszukiwania, ich odzyskanie zajmie więcej niż minutę. Czasami kończy się niepowodzeniem i kończy się czas. Tak więc staram się unikać CTE i UDF i próbuję wymyślić proste zapytanie SQL, które może rozwiązać problemy z wydajnością.

1
Cześć wszystkim, Zobacz link, który zamieściłem poniżej, który odpowiada za pomocą row_number () w inny sposób. Czy ktoś może porównać moje początkowe zapytanie z tym w linku? Doceń pomoc ...

Odpowiedzi:


95

Aby obejść ten problem, zawiń instrukcję select w CTE, a następnie możesz wykonać zapytanie dotyczące CTE i użyć wyników funkcji okienkowej w klauzuli where.

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
Próbuję uniknąć CTE. To najgorszy przypadek, którego szukam. dzięki

3
Może działać szybciej, jeśli zamiast CTE zostanie użyte podzapytanie. W niektórych przypadkach widziałem lepszą wydajność o współczynnik 1,5
Brian Webster

3
W CTE SELECT powinien również znajdować się TOP, w przeciwnym razie SQL 2008 Server nie wykona zapytania z powodu ORDER BY (który nie jest obsługiwany, chyba że używany jest TOP)
Muflix

2
Używam SQL2005 (ugh) - mogę uniknąć używania „TOP”, upuszczając „ORDER BY” po FROM. W każdym razie jest to zbędne z (Order By) po OVER.
Joe B

Żałowałem, że nie ma sposobu na użycie ROW_NUMBER()w WHEREklauzuli bez 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

Zauważ, że ten filtr jest nadmiarowy: ROW_NUMBER()zaczyna się od 1i jest zawsze większy niż 0.


2
@ DavideChicco.it: w SQL Server tabele pochodne wymagają aliasu ( AS qzamiast tego powinienem był napisać , ale to też zadziała).
Quassnoi

2
Podczas nadawania nazw aliasom najważniejsza jest czytelność. Możesz zapisać rn jako RowNumber i q jako DerivedTable, a klauzulę where jako DerivedTable.RowNumber> 0. Moim zdaniem będzie to znacznie mniej zagmatwane za 6 miesięcy, gdy kod nie będzie świeży w twojej głowie.
Edward Comeau

2
@EdwardComeau: rnjest obecnie powszechnie akceptowanym akronimem dla numeru wiersza. Spróbuj wpisać „numer_wiersza na jako ...” w ciągu wyszukiwania Google i zobacz, co sugeruje.
Quassnoi

3
@Quassnoi, czytelność jest kluczem do dobrego kodowania, a wysiłek poznawczy związany z tłumaczeniem rn (lub innych skróconych aliasów) sumuje się dla ciebie i osób utrzymujących twój kod. NB, pierwsze trafienie firmy Microsoft, SELECT ROW_NUMBER () OVER (ORDER BY SalesYTD DESC) AS Row, ... Również nie spotkałem się wcześniej z rn, więc Twój przebieg w „uniwersalnym” może się różnić.
Edward Comeau

1
@Quassnoi i drugi hit, artykuł SO - stackoverflow.com/questions/961007/how-do-i-use-row-number kilka odmian, a nie rn ;-)
Edward Comeau

32
Select * from 
(
    Select ROW_NUMBER() OVER ( order by Id) as 'Row_Number', * 
    from tbl_Contact_Us
) as tbl
Where tbl.Row_Number = 5

19

Myślę, że chcesz coś takiego:

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

4
Utwórz alias dla tabeli, jeśli powyższe zapytanie nie działa dla Ciebie. Zmodyfikuj przedostatnią linię From V_EMPLOYEE) A, dodając A jako alias.
Hammad Khan

7

W odpowiedzi na komentarze do odpowiedzi rexem, w odniesieniu do tego, czy widok inline lub CTE byłby szybszy, przekształcam zapytania tak, aby korzystały z tabeli, którą ja i wszyscy mieliśmy dostępni: 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

Utworzone plany zapytań były dokładnie takie same. Spodziewałbym się, że we wszystkich przypadkach optymalizator zapytań wymyśli ten sam plan, przynajmniej w prostej zamianie CTE na widok inline lub odwrotnie.

Oczywiście wypróbuj własne zapytania we własnym systemie, aby sprawdzić, czy istnieje różnica.

Również row_number()w klauzuli where jest częstym błędem w odpowiedziach udzielonych na Stack Overflow. Logicznie row_number()nie jest dostępna, dopóki nie zostanie przetworzona klauzula select. Ludzie o tym zapominają, a kiedy odpowiadają bez sprawdzania odpowiedzi, czasami odpowiedź jest błędna. (Zarzut, który sam jestem winny).


1
Thx Shannon. Jakiej wersji programu SQL Server używasz?
OMG Kucyki

1
Czy to oznacza, że ​​odpowiedź podana w tym linku jest błędna? Ale osoba, która opublikowała pytanie, zgodziła się, że działa .. Zaskakujące .. :-)

2
@Joseph, ale jeśli spojrzysz na inną odpowiedź opublikowaną przez OP w powiązanym pytaniu, zobaczysz, że prowadzi on do wersji kodu, która nie jest taka sama, jak w zaakceptowanej odpowiedzi. Nie wiem, dlaczego przyjął odpowiedź, chociaż nie byłaby ona zgodna z wpisaną. Może został zredagowany w którymś momencie po przyjęciu, może to wystarczyło, żeby zaczął działać, nawet bez całkowitej poprawności.
Shannon Severance,

1
@Rexem: Zarówno SQL Server 2005, jak i SQL Server 2008. Wcześniejsze wersje nie obsługują CTE ani ROW_NUMBER ()
Shannon Severance,

6

Wydaje mi się, że wszystkie odpowiedzi pokazujące użycie CTE lub Sub Query są wystarczającymi poprawkami, ale nie widzę, aby ktokolwiek docierał do sedna, dlaczego OP ma problem. Powodem, dla którego to, co sugeruje OP, nie działa, jest logiczna kolejność przetwarzania zapytań tutaj:

  1. Z
  2. NA
  3. PRZYSTĄP
  4. GDZIE
  5. GRUPUJ WEDŁUG
  6. Z CUBE / ROLLUP
  7. MAJĄCY
  8. WYBIERZ
  9. ODRĘBNY
  10. ZAMÓW PRZEZ
  11. TOP
  12. OFFSET / FETCH

Uważam, że to bardzo przyczynia się do odpowiedzi, ponieważ wyjaśnia, dlaczego pojawiają się problemy takie jak ten. WHEREjest zawsze przetwarzany przed SELECTwykonaniem CTE lub zapytania podrzędnego niezbędnego dla wielu funkcji. Zobaczysz to często w SQL Server.


4

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

Korzystanie z widoku w wierszu / alternatywy bez odpowiednika 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
Który lepszy pod względem wydajności? Używasz CTE lub podzapytania? dzięki

1
Zobacz odpowiedź Shannona - w jego teście są równe.
OMG Kucyki

6
Nie, to nie jest szybsze. W SQL Server, CTE„s inline i poglądy są tak samo i mają taką samą wydajność. Gdy funkcje niedeterministyczne są używane w a CTE, jego wartość jest ponownie oceniana przy każdym wywołaniu. Trzeba używać brudnych sztuczek, aby wymusić materializację CTE. Zobacz te artykuły w moim blogu: explainextended.com/2009/07/28/... explainextended.com/2009/05/28/generating-xml-in-subqueries
Quassnoi

2

na podstawie odpowiedzi OP na pytanie:

Proszę zobaczyć ten link. Ma inne rozwiązanie, które wygląda na działające dla osoby, która zadała pytanie. Próbuję znaleźć takie rozwiązanie.

Zapytanie podzielone na strony przy użyciu sortowania w różnych kolumnach przy użyciu ROW_NUMBER () OVER () w SQL Server 2005

~ Joseph

„metoda 1” jest podobna do zapytania OP z połączonego pytania, a „metoda 2” jest podobna do zapytania z wybranej odpowiedzi. Trzeba było spojrzeć na kod powiązany w tej odpowiedzi, aby zobaczyć, co się naprawdę dzieje, ponieważ kod w wybranej odpowiedzi został zmodyfikowany, aby działał. Spróbuj tego:

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

WYNIK:

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, przy użyciu metody SET SHOWPLAN_ALL ON 1 miała TotalSubtreeCost 0,08424953, podczas gdy metoda 2 wynosiła 0,02627153. metoda 2 była ponad trzy razy lepsza.
KM.

1
@rexem, zarówno metoda 1, jak i 2 używają CTE, sposób podziału na strony i kolejności wierszy jest inny. Nie jestem pewien, dlaczego to rzeczywiste pytanie jest tak różne od pytania, z którym łączy się OP (w odpowiedzi na to pytanie przez OP), ale moja odpowiedź tworzy działający kod w oparciu o łącze, do którego odnosi się OP
KM.

1
Dzięki, próbuję porównać stary post i te odpowiedzi. [Nie wiem, jak to sformatować] Oto odpowiedź udzielona przez Tomalaka. stackoverflow.com/questions/230058?sort=votes#sort-top Czy to źle? Jeśli zamieścił tylko połowę odpowiedzi, w jaki sposób będę mógł lepiej wykonywać moje zapytanie? Proszę, daj mi więcej światła, abym mógł kontynuować ... dzięki

@Joseph, wybrana odpowiedź w podanym przez Ciebie linku ( stackoverflow.com/questions/230058?sort=votes#sort-top ) różni się od działającego kodu, który osoba zadająca pytanie podaje w odpowiedzi: stackoverflow.com/ pytania / 230058 /… jeśli przeczytasz tę odpowiedź, zobaczysz link do ich kodu: pastebin.com/f26a4b403 i link do ich wersji Tomalaka: pastebin.com/f4db89a8e w mojej odpowiedzi podaję działającą wersję każdej wersji za pomocą zmienne tabeli
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
Witamy w Stack Overflow! Chociaż ten fragment kodu może być rozwiązaniem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod.
Johan

Dodaj kontekst do fragmentu kodu z korzyścią dla przyszłych czytelników.
DebanjanB
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.