65

タイトルがすべてを物語っていますが、SQL Serverのwhere句でウィンドウ関数を使用できないのはなぜですか?

このクエリは完全に理にかなっています。

select id, sales_person_id, product_type, product_id, sale_amount
from Sales_Log
where 1 = row_number() over(partition by sales_person_id, product_type, product_id order by sale_amount desc)

しかし、それは機能しません。CTE /サブクエリよりも良い方法はありますか?

編集

これは、CTEを使用したクエリの価値があります。

with Best_Sales as (
    select id, sales_person_id, product_type, product_id, sale_amount, row_number() over (partition by sales_person_id, product_type, product_id order by sales_amount desc) rank
    from Sales_log
)
select id, sales_person_id, product_type, product_id, sale_amount
from Best_Sales
where rank = 1

編集

サブクエリで表示される回答の+1ですが、実際には、where句でウィンドウ関数を使用できない理由を探しています。

4

8 に答える 8

74

SQL Serverのwhere句でウィンドウ関数を使用できないのはなぜですか?

1つの答えは、特に有益ではありませんが、仕様にはできないと書かれているためです。

Itzik Ben Ganによる記事-論理クエリ処理:それが何であり、それがあなたにとって何を意味するか、特にここの画像を参照してください。ウィンドウ関数は、すべての/ / /句が処理されたSELECT後に残っている結果セットで評価されます(ステップ5.1)。WHEREJOINGROUP BYHAVING

本当に私はwhere句でウィンドウ関数を使用できないことの背後にある理由を探しています。

WHERE条項で許可されていない理由は、あいまいさが生じるためです。ウィンドウ関数を使用して高性能T-SQLからItzikBenGanの例を盗む(p.25)

あなたのテーブルが

CREATE TABLE T1
(
col1 CHAR(1) PRIMARY KEY
)

INSERT INTO T1 VALUES('A'),('B'),('C'),('D'),('E'),('F')

そしてあなたの質問

SELECT col1
FROM T1
WHERE ROW_NUMBER() OVER (ORDER BY col1) <= 3
AND col1 > 'B'

正しい結果は何でしょうか?col1 > 'B'述語が行番号付けの前または後に実行されたと思いますか?

于 2012-12-21T21:24:58.827 に答える
15

CTEは必要ありません。サブクエリでウィンドウ関数を使用するだけです。

select id, sales_person_id, product_type, product_id, sale_amount
from
(
  select id, sales_person_id, product_type, product_id, sale_amount,
    row_number() over(partition by sales_person_id, product_type, product_id order by sale_amount desc) rn
  from Sales_Log
) sl
where rn = 1

編集して、コメントを回答に移動します。

WHEREウィンドウ関数は、句の後にあるデータが実際に選択されるまで実行されません。したがって、句row_numberでaを使用しようとするとWHERE、値はまだ割り当てられていません。

于 2012-12-21T21:12:29.293 に答える
10

「オールアットワンス操作」とは、同じ論理クエリプロセスフェーズのすべての式が同時に論理的に評価されることを意味します。

そして素晴らしい章ウィンドウ関数への影響

あなたが持っているとしましょう:

CREATE TABLE #Test ( Id INT) ;
 
INSERT  INTO #Test VALUES  ( 1001 ), ( 1002 ) ;

SELECT Id
FROM #Test
WHERE Id = 1002
  AND ROW_NUMBER() OVER(ORDER BY Id) = 1;

All-at-Once操作は、これら2つの条件が同時に論理的に評価されたことを示しています。したがって、SQL Serverは、推定実行プランに基づいて、WHERE句の条件を任意の順序で評価できます。したがって、ここでの主な質問は、どの条件が最初に評価されるかです。

ケース1:

If ( Id = 1002 ) is first, then if ( ROW_NUMBER() OVER(ORDER BY Id) = 1 )

結果:1002

ケース2:

If ( ROW_NUMBER() OVER(ORDER BY Id) = 1 ), then check if ( Id = 1002 )

結果:空

つまり、パラドックスがあります。

この例は、WHERE句でウィンドウ関数を使用できない理由を示しています。これについてもっと考えて、ウィンドウ関数がSELECT句とORDERBY句だけで使用できる理由を見つけることができます。


補遺

Teradataは次のQUALIFY句をサポートします。

ユーザー指定の検索条件に従って、以前に計算された順序付き分析関数の結果をフィルター処理します。

SELECT Id
FROM #Test
WHERE Id = 1002
QUALIFY ROW_NUMBER() OVER(ORDER BY Id) = 1;

補遺2:

Snowflake-認定

QUALIFYは、HAVINGが集計関数とGROUP BY句で行うのと同じように、ウィンドウ関数で行います。

したがって、クエリの実行順序では、ウィンドウ関数が計算された後にQUALIFYが評価されます。通常、SELECTステートメントの句は次の順序で評価されます。

から

    Where

    Group by

    Having

    Window

    QUALIFY

    Distinct

    Order by

    Limit
于 2015-11-04T12:43:40.503 に答える
4

必ずしもCTEを使用する必要はありません。row_number()を使用した後、結果セットを照会できます。

select row, id, sales_person_id, product_type, product_id, sale_amount
from (
    select
        row_number() over(partition by sales_person_id, 
            product_type, product_id order by sale_amount desc) AS row,
        id, sales_person_id, product_type, product_id, sale_amount
    from Sales_Log 
    ) a
where row = 1
于 2012-12-21T21:12:41.947 に答える
2

古いスレッドですが、トピックで表現されている質問に具体的に答えようと思います。

where句にウィンドウ関数がないのはなぜですか?

SELECTステートメントには、キー入力された順序で指定された次の主要な句があります。

SELECT DISTINCT TOP list
FROM  JOIN ON / APPLY / PIVOT / UNPIVOT
WHERE
GROUP BY  WITH CUBE / WITH ROLLUP
HAVING
ORDER BY
OFFSET-FETCH

論理クエリ処理順序、またはバインディング順序は、概念的な解釈順序であり、クエリの正確さを定義します。この順序により、1つのステップで定義されたオブジェクトが、後続のステップの句でいつ使用可能になるかが決まります。

----- Relational result
  1. FROM
    1.1. ON JOIN / APPLY / PIVOT / UNPIVOT
  2. WHERE
  3. GROUP BY
    3.1. WITH CUBE / WITH ROLLUP
  4. HAVING
  ---- After the HAVING step the Underlying Query Result is ready
  5. SELECT
    5.1. SELECT list
    5.2. DISTINCT
----- Relational result

----- Non-relational result (a cursor)
  6. ORDER BY
  7. TOP / OFFSET-FETCH
----- Non-relational result (a cursor)

たとえば、クエリプロセッサがFROM句で定義されたテーブルまたはビューにバインド(アクセス)できる場合、これらのオブジェクトとその列は後続のすべてのステップで使用できるようになります。

逆に、句の前にあるすべての句は、SELECT句で定義された列エイリアスまたは派生列を参照できませんSELECT。ただし、これらの列は、句などの後続の句から参照できますORDER BY

OVER句は、関連するウィンドウ関数が適用される前に、行セットの分割と順序を決定します。つまり、この句は、基になるクエリ結果OVERセット内のウィンドウまたはユーザー指定の行セットを定義し、window関数はそのウィンドウに対して結果を計算します。

Msg 4108, Level 15, State 1, …
Windowed functions can only appear in the SELECT or ORDER BY clauses.

背後にある理由は、論理クエリ処理がでどのように機能するかによるものですT-SQL基礎となるクエリ結果は、論理クエリ処理がSELECTステップ5.1に達したときにのみ確立されるためです。(つまり、、、、およびステップを処理した後FROM)、WHEREウィンドウ関数はクエリのand句でのみ許可されます。GROUP BYHAVINGSELECTORDER BY

言及することに注意してください、リレーショナルモデルが順序付けられたデータを処理しない場合でも、ウィンドウ関数は依然としてリレーショナルレイヤーの一部です。SELECTステップ5.1の後の結果。どのウィンドウ関数でもまだリレーショナルです。

また、厳密に言えば、ウィンドウ関数がWHERE句で許可されない理由は、あいまいさを生み出すためではなく、論理クエリ処理SELECTでステートメントを処理する順序のためT-SQLです。

リンク:ここここここ

于 2018-05-10T19:25:34.233 に答える
1

最後に、相関サブクエリを使用した、旧式のSQLServer2005以前の方法があります。

select *
from   Sales_Log sl
where  sl.id = (
    Select Top 1 id
    from   Sales_Log sl2
    where  sales_person_id = sl.sales_person_id
       and product_type = sl.product_type
       and product_id = sl.product_id
    order by sale_amount desc
)

完全を期すためにこれを提供します。

于 2012-12-21T21:19:47.690 に答える
1

基本的に、最初の「WHERE」句の条件はsqlによって読み取られ、同じ列/値IDがテーブルを調べましたが、テーブルにはrow_num=1がまだありません。したがって、それは機能しません。そのため、最初に括弧を使用し、その後、WHERE句を記述します。

于 2018-11-09T11:23:42.130 に答える
0

はい、残念ながら、ウィンドウ関数を実行すると、where述語が正当な場合でも、SQLが怒ります。selectステートメントに値を持つcteまたはネストされたselectを作成し、後でその値を使用してCTEまたはネストされたselectを参照します。自明であるべき簡単な例。大規模なデータセットを実行する際のパフォーマンスの問題でcteが本当に嫌いな場合は、いつでも一時テーブルまたはテーブル変数にドロップできます。

declare @Person table ( PersonID int identity, PersonName varchar(8));

insert into @Person values ('Brett'),('John');

declare @Orders table ( OrderID int identity, PersonID int, OrderName varchar(8));

insert into @Orders values (1, 'Hat'),(1,'Shirt'),(1, 'Shoes'),(2,'Shirt'),(2, 'Shoes');

--Select
--  p.PersonName
--, o.OrderName
--, row_number() over(partition by o.PersonID order by o.OrderID)
--from @Person p 
--  join @Orders o on p.PersonID = o.PersonID
--where row_number() over(partition by o.PersonID order by o.orderID) = 2

-- yields:
--Msg 4108, Level 15, State 1, Line 15
--Windowed functions can only appear in the SELECT or ORDER BY clauses.
;

with a as 
    (
    Select
    p.PersonName
,   o.OrderName
,   row_number() over(partition by o.PersonID order by o.OrderID) as rnk
from @Person p 
    join @Orders o on p.PersonID = o.PersonID
    )
select *
from a 
where rnk >= 2 -- only orders after the first one.
于 2012-12-21T21:16:30.310 に答える