4

この質問は、特定のケースで SQL ランキング機能を使用するかどうかについての議論から生じています。

一般的な RDBMS にはランキング機能が含まれています。つまり、クエリ言語にはTOP n ... ORDER BY keyROW_NUMBER() OVER (ORDER BY key)、またはORDER BY key LIMIT n( overview ) などの要素があります。

膨大な数のレコードから小さなチャンクのみを表示したい場合、パフォーマンスを向上させるのに非常に役立ちます。しかし、これらは大きな落とし穴ももたらします:keyが一意でない場合、結果は非決定論的です。次の例を検討してください。


users

user_id name
1       John
2       Paul
3       George
4       Ringo

logins

login_id user_id login_date
1        4       2009-08-17
2        1       2009-08-18
3        2       2009-08-19
4        3       2009-08-20

クエリは、最後にログインした人を返すことになっています:

SELECT TOP 1 users.*
FROM
  logins JOIN
  users ON logins.user_id = users.user_id
ORDER BY logins.login_date DESC

期待どおりGeorgeに返され、すべてが正常に見えます。しかし、新しいレコードがテーブルに挿入されloginsます:

1        4       2009-08-17
2        1       2009-08-18
3        2       2009-08-19
4        3       2009-08-20
5        4       2009-08-20

上記のクエリは何を返しますか? Ringo? George? わかりません。私が覚えている限り、たとえば MySQL 4.1 は、基準に一致する物理的に作成された最初のレコードを返します。つまり、結果はGeorge. ただし、これはバージョンや DBMS によって異なる場合があります。何を返すべきだった?Ringo彼が最後にログインしたようだからと言う人もいるかもしれませんが、これは純粋な解釈です。私の意見では、利用可能なデータから明確に判断することはできないため、両方が返されるべきでした。

したがって、このクエリは要件に一致します。

SELECT users.*
FROM
  logins JOIN
  users ON
    logins.user_id = users.user_id AND
    logins.login_date = (
      SELECT max(logins.login_date)
      FROM
        logins JOIN
        users ON logins.user_id = users.user_id)

別の方法として、一部の DBMS は特別な機能を提供します (たとえば、Microsoft SQL Server 2005 ではTOP n WITH TIES ... ORDER BY key( gbnが推奨)、RANK、およびDENSE_RANKこの目的のために を導入しています)。


たとえば、SO を検索ROW_NUMBERすると、ランキング機能の使用を提案し、考えられる問題を指摘することを逃した多数のソリューションが見つかります。

質問: ランキング機能を含むソリューションが提案された場合、どのようなアドバイスが必要ですか?

4

5 に答える 5

3

rankそしてrow_number、もっと自由に使うべき素晴らしい機能です、IMO. 人々は彼らのことを知らないだけです。

そうは言っても、ランキングの基準がユニークであることを確認する必要があります。重複のバックアップ計画を立てます (特に日付)。返されるデータは、入力したデータと同程度です。

ここでの落とし穴は、クエリでもまったく同じだと思います。

select top 2 * from tblA order by date desc

何を注文しているのかを認識し、常に勝者を獲得する方法があることを確認する必要があります。そうでない場合は、(潜在的に) ランダムな 2 つの行が最大日付で取得されます。

また、レコードについては、SQL Server は挿入された物理的な順序で行を格納しません。レコードを 8k ページに格納し、テーブルのクラスター化インデックスに従って最も効率的な方法でそれらのページを並べ替えます。したがって、SQL Server での順序の保証はまったくありません。

于 2009-08-20T11:28:56.143 に答える
2

すべてのデータベース エンジンは、2 つの行を区別できるように、何らかの行識別子を使用します。

これらの識別子は次のとおりです。

  • 行ポインタMyISAM
  • 定義済みInnoDBのテーブルの主キーPRIMARY KEY
  • Uniquifier定義されてInnoDBいないテーブル内PRIMARY KEY
  • RIDinSQL Serverのヒープ テーブル
  • SQL Serverクラスタ化された のテーブルの主キーPRIMARY/UNIQUE KEY
  • 一意でないキーでクラスター化されたインデックス キー + uniquifierinのテーブルSQL Server
  • ROWID/UROWIDOracle
  • CTIDPostgreSQL

次のものにすぐにアクセスすることはできません。

  • 行ポインタMyISAM
  • Uniquifier定義されてInnoDBいないテーブル内PRIMARY KEY
  • RIDinSQL Serverのヒープ テーブル
  • 一意でないキーでクラスター化されたインデックス キー + uniquifierinのテーブルSQL Server

さらに、次のものを制御することはできません。

  • ROWID/UROWIDOracle
  • CTIDPostgreSQL

(更新時またはバックアップからの復元時に変更される可能性があります)

これらのテーブルで 2 つの行が同一である場合、アプリケーションの観点からは同一である必要があることを意味します。

それらはまったく同じ結果を返し、究極の一意化子として扱うことができます。

これは、順序付けの一貫性を維持するために、順序付け句を完全に制御できるある種の一意化子を常に含める必要があることを意味します。

テーブルに主キーまたは一意のキー (複合キーも含む) がある場合は、それを順序付け条件に含めます。

SELECT  *
FROM    mytable
ORDER BY
        ordering_column, pk

それ以外の場合は、すべての列を順序付け条件に含めます。

SELECT  *
FROM    mytable
ORDER BY
        ordering_column, column1, ..., columnN

後者の条件では、他の方法では区別できない行が常に返されますが、とにかく区別できないため、アプリケーションの観点からは一貫しているように見えます。

PRIMARY KEYところで、これは、常にテーブルにを含めるもう 1 つの正当な理由です。

ただし、行の順序付けをROWID/に依存しないでください。CTID

簡単に変更できるUPDATEため、結果の順序が安定しなくなります。

于 2009-08-20T15:06:21.403 に答える
2

上記の例で WITH TIES 句を使用します

SELECT TOP 1 WITH TIES users.*
FROM
  logins JOIN
  users ON logins.user_id = users.user_id
ORDER BY logins.login_date DESC

あなたが言及したようにDENSE_RANKを使用してください

自分自身をこの立場に置かない例: 時間も保存し (datetime)、同じ 3.33 ミリ秒の瞬間に非常にまれな重複が発生するという非常に低いリスクを受け入れます (SQL 2008 は異なります)。

于 2009-08-20T12:02:04.000 に答える
1

ROW_NUMBERは確かに素晴らしいツールです。誤用すると、非決定論的な結果が得られる可能性がありますが、他のSQL関数も同様です。ORDERBYで非決定論的な結果を返すこともできます。

あなたが何をしているのかを知ってください。

于 2009-08-20T11:36:18.173 に答える
0

This is the summary:

  • Use your head first. Should be obvious, but it is always a good point to start. Do you expect n rows exactly or do you expect a possibly varying number of rows that fulfill a constraint? Reconsider your design. If you're expecting n rows exactly, your model might be designed poorly if it's impossible to identify a row unambiguously. If you expect a possibly varying number of rows, you might need to adjust your UI in order to present your query results.
  • Add columns to key that make it unique (e.g. PK). You at least gain back control on the returned result. There is almost always a way to do this as Quassnoi pointed out.
  • Consider using possibly more suitable functions like RANK, DENSE_RANK and TOP n WITH TIES. They are available in Microsoft SQL Server by 2005 version and in PosgreSQL from 8.4 onwards. If these functions are not available, consider using nested queries with aggregation instead of ranking functions.
于 2009-10-25T17:59:38.683 に答える