web-dev-qa-db-ja.com

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

タイトルはそれをすべて言っていますが、なぜ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 /サブクエリよりも良い方法はありますか?

[〜#〜] edit [〜#〜]

これが価値があるのは、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

[〜#〜] edit [〜#〜]

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

46
Crisfole

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

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

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

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

WHERE句で許可されない理由は、あいまいさが生じるためです。 ウィンドウ関数を使用した高性能T-SQLからItzik Ben Ganの例を盗む (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'述部が行番号付けの前または後に実行されると期待しますか?

57
Martin Smith

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句の後のデータが実際に選択されるまで実行されません。したがって、WHERE句でrow_numberを使用しようとしても、値はまだ割り当てられていません。

11
Taryn

まず第一に、_all-at-once operation_

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

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

あなたが持っていると仮定します:

_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 [〜#〜]およびORDERだけで使用できる理由を見つけることができます。 BY句!


補遺

Terradataは QUALIFY 句をサポートしています:

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

_SELECT Id
FROM #Test
WHERE Id = 1002
QUALIFY ROW_NUMBER() OVER(ORDER BY Id) = 1;
_
7
Lukasz Szozda

必ずしも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
3
Khan

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

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
)

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

1
Ann L.

基本的に最初の "WHERE"句条件はsqlによって読み込まれ、同じ列/値IDがテーブルを調べましたが、テーブルrow_num = 1にはまだありません。したがって、動作しません。それが、最初に括弧を使用し、その後WHERE句を記述する理由です。

1
Ayush Garg

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

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

SELECTステートメントには、keyed-in orderで指定された次の主な句があります。

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句は、ウィンドウまたはnderlying Query Resultセット内のユーザー指定の行セットを定義し、ウィンドウ関数が結果を計算しますその窓に対して。

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

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

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

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

リンク: herehere および here

1
drumsta

残念ながら、ウィンドウ化された関数を実行すると、where述語が正当であってもSQLが怒ってしまいます。 selectステートメントに値を持つcteまたはネストされた選択を作成し、後でその値でCTEまたはネストされた選択を参照します。自明なはずの単純な例。大きなデータセットを実行する際のパフォーマンスの問題で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.
1
djangojazz