ランダムな順序を取得する最良の方法は何ですか?


27

結果のレコードをランダムに並べるクエリがあります。クラスター化インデックスを使用するため、含まれていない場合order byは、インデックスの順序でレコードを返す可能性があります。ランダムな行順序を確保するにはどうすればよいですか?

私はそれが「真に」ランダムではない可能性が高いことを理解しています。擬似ランダムで十分です。

回答:


19

ORDER BY NEWID()は、レコードをランダムにソートします。ここの例

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()

7
ORDER BY NEWID()は事実上ランダムですが、統計的にランダムではありません。小さな違いがありますが、ほとんどの場合、違いは重要ではありません。
mrdenny

4
パフォーマンスの観点から、これは非常に遅いです
マイルD

1
@mrdenny-「統計的にランダムではない」の根拠は何ですか?ここでの答えCryptGenRandomは、最終的に使用することになります。dba.stackexchange.com/a/208069/3690
マーティンスミス

15

Pradeep Adigaの最初の提案であるがORDER BY NEWID()、これは私が過去にこの理由で使用したものです。

RAND()-の使用には注意してください-多くのコンテキストでは、ステートメントごとに1回だけ実行されるためORDER BY RAND()、効果はありません(各行でRAND()から同じ結果が得られるため)。

例えば:

SELECT display_name, RAND() FROM tr_person

personテーブルの各名前と、各行で同じ「ランダムな」番号を返します。数値はクエリを実行するたびに異なりますが、毎回各行で同じです。

同じことが節でRAND()使用されている場合であることを示すためにORDER BY、私は試してみます:

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

結果はまだ名前順に並べられており、以前の並べ替えフィールド(ランダムであると予想されるもの)には効果がないため、常に同じ値になると考えられます。

注文はNEWID()NEWID()がいなかったかのために、かかわらず、仕事をし、常に:そう、彼らキーとして一意の識別子と1 statemntに多くの新しい行を挿入する際のUUIDの目的は壊れてしまう再評価します

SELECT display_name FROM tr_person ORDER BY NEWID()

ない「ランダム」の名前を注文します。

その他のDBMS

上記はMSSQLに当てはまります(少なくとも2005年と2008年、そして私が正しく2000を覚えていれば)。新しいUUID 返す関数は、すべてのDBMSで毎回評価する必要があります。NEWID()はMSSQLの下にありますが、ドキュメントや独自のテストでこれを検証する価値があります。RAND()などの他の任意の結果関数の動作は、DBMSによって異なる可能性が高いため、ドキュメントを再度確認してください。

また、DBは型に意味のある順序がないと想定しているため、一部のコンテキストではUUID値による順序が無視されるのを見てきました。これがその場合、ordering句でUUIDを文字列型に明示的にキャストするかCHECKSUM()、SQL Serverのように他の関数をラップする(順序付けが行われるため、これとはわずかなパフォーマンスの違いがある可能性があります) 128ビットの値ではなく32ビットの値ですが、その利点がCHECKSUM()最初に値ごとに実行するコストを上回るかどうかをテストします)。

サイドノート

任意ではあるがある程度反復可能な順序が必要な場合は、行自体のデータの比較的制御されていないサブセットによる順序付けを行います。たとえば、いずれかまたはこれらは、任意ではあるが繰り返し可能な順序で名前を返します。

SELECT display_name FROM tr_person ORDER BY CHECKSUM(display_name), display_name -- order by the checksum of some of the row's data
SELECT display_name FROM tr_person ORDER BY SUBSTRING(display_name, LEN(display_name)/2, 128) -- order by part of the name field, but not in any an obviously recognisable order)

任意ではあるが繰り返し可能な順序付けは、アプリケーションではあまり有用ではありませんが、さまざまな順序で結果のコードをテストしたいが、各実行を同じ方法で数回繰り返すことができる場合はテストに役立ちます(平均的なタイミングを取得するため)数回の実行の結果、またはコードに対して行った修正が特定の入力結果セットによって以前に強調された問題または非効率性を除去するテスト、またはコードが「安定」しているというテストのために毎回同じ結果を返す同じデータを特定の順序で送信した場合)。

このトリックを使用して、関数内でよりarbitrary意的な結果を取得することもできます。これにより、本体内でNEWID()などの非決定論的な呼び出しが許可されません。繰り返しますが、これは現実の世界でしばしば役立つ可能性が高いものではありませんが、関数がランダムな何かを返したい場合に便利になる可能性があり、「ランダムな」が十分ですユーザー定義関数が評価されたとき、つまり、通常は行ごとに1回だけ、または結果が期待/要求と異なる場合があります)。

性能

EBarrが指摘しているように、上記のいずれかにパフォーマンスの問題がある可能性があります。数行を超える場合、要求された行数が正しい順序で読み戻される前に、出力がtempdbにスプールされることがほぼ保証されます。つまり、上位10を探している場合でも、完全なインデックスが見つかる可能性がありますスキャン(さらに悪いことに、テーブルスキャン)は、tempdbへの書き込みの巨大なブロックと共に発生します。そのため、本番環境で使用する前に、ほとんどの場合と同様に、現実的なデータでベンチマークすることが非常に重要です。


14

これは古い質問ですが、議論の1つの側面が欠落しています。私の意見では、パフォーマンスです。 ORDER BY NewId()一般的な答えです。誰かの取得の空想彼らはあなたが本当にラップする必要があることを追加NewID()してCheckSum()パフォーマンスを得るために、あなたが知っています、!

この方法の問題点は、完全なインデックススキャンが保証され、その後データの完全な並べ替えが保証されることです。深刻なデータ量を扱った場合、これは急速に高価になる可能性があります。この典型的な実行計画を見て、並べ替えに96%の時間がかかることに注意してください...

ここに画像の説明を入力してください

これがどのように拡大するかを理解するために、私が使用しているデータベースから2つの例を示します。

  • TableA-2500データページに50,000行あります。ランダムクエリは42ミリ秒で145の読み取りを生成します。
  • 表B-114,000データページに120万行あります。Order By newid()このテーブルで実行すると、53,700の読み取りが生成され、16秒かかります。

話の教訓は、大きなテーブル(数十億行)がある場合、またはこのクエリを頻繁に実行する必要がある場合、newid()メソッドが機能しなくなることです。それで、少年は何をすべきか?

TABLESAMPLE()に会う

SQL 2005では、と呼ばれる新しい機能TABLESAMPLEが作成されました。私はそれの使用を議論する1つの記事を見ました...もっとあるはずです。MSDN ドキュメントはこちら。最初の例:

SELECT Top (20) *
FROM Northwind..Orders TABLESAMPLE(20 PERCENT)
ORDER BY NEWID()

テーブルサンプルの背後にあるアイデアは、おおよそのサブセットサイズを求めることです。SQLは各データページに番号を付け、それらのページのXパーセントを選択します。返される実際の行数は、選択したページに存在するものによって異なる場合があります。

それで、どのように使用しますか?必要な行数をカバーするサブセットサイズを選択し、を追加しTop()ます。アイデアは、高価なソートのに、巨大なテーブルを小さく見せることができるということです。

個人的には、テーブルのサイズを事実上制限するために使用しています。そのtop(20)...TABLESAMPLE(20 PERCENT)ため、クエリを実行する100万行のテーブルでは、1600ミリ秒で5600の読み取りになります。REPEATABLE()ページ選択用の「シード」を渡すことができるオプションもあります。これにより、安定したサンプル選択が可能になります。

とにかく、これを議論に追加すべきだと思っただけです。それが誰かを助けることを願っています。


スケールアップするだけでなく、小さなデータセットで機能するスケーラブルなランダム順序付けクエリを作成できると便利です。持ってTABLESAMPLE()いるデータの量に基づいて、手動で有と無を切り替える必要があるようです。私は考えていないTABLESAMPLE(x ROWS)にもいることを保証するであろう、少なくとも xドキュメントが返される行の実際の数は大きく異なります」と言うので、行が返されます。5などの小さな数を指定すると、サンプルで結果が返されない可能性があります。」— ROWS構文は実際にはPERCENT内部でマスクされたままですか?
ビンキ14年

確かに、自動マジックは素晴らしいです。実際には、5行のテーブルが予告なしに数百万行に拡大することはめったにありません。TABLESAMPLE()は、テーブル内のページ数の選択に基づいているようです。そのため、指定された行サイズは、返される内容に影響します。テーブルサンプルのポイントは、少なくとも私が見る限り、派生テーブルのような、選択可能な優れたサブセットを提供することです。
EBarr 14年

3

多くのテーブルには、比較的密な(欠損値が少ない)インデックス付きの数値ID列があります。

これにより、既存の値の範囲を決定し、その範囲でランダムに生成されたID値を使用して行を選択できます。これは、返される行の数が比較的少なく、ID値の範囲が密集している場合に最適に機能します(したがって、欠損値が生成される可能性は十分に小さくなります)。

たとえば、次のコードは、8,123,937行のユーザーのスタックオーバーフローテーブルから100人のランダムなユーザーを選択します。

最初のステップは、ID値の範囲を決定することです。これは、インデックスによる効率的な操作です。

DECLARE 
    @MinID integer,
    @Range integer,
    @Rows bigint = 100;

--- Find the range of values
SELECT
    @MinID = MIN(U.Id),
    @Range = 1 + MAX(U.Id) - MIN(U.Id)
FROM dbo.Users AS U;

範囲クエリ

プランは、インデックスの各端から1行を読み取ります。

次に、範囲内で100個の個別のランダムIDを生成し(usersテーブルに一致する行がある)、それらの行を返します。

WITH Random (ID) AS
(
    -- Find @Rows distinct random user IDs that exist
    SELECT DISTINCT TOP (@Rows)
        Random.ID
    FROM dbo.Users AS U
    CROSS APPLY
    (
        -- Random ID
        VALUES (@MinID + (CONVERT(integer, CRYPT_GEN_RANDOM(4)) % @Range))
    ) AS Random (ID)
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.Users AS U2
            -- Ensure the row continues to exist
            WITH (REPEATABLEREAD)
        WHERE U2.Id = Random.ID
    )
)
SELECT
    U3.Id,
    U3.DisplayName,
    U3.CreationDate
FROM Random AS R
JOIN dbo.Users AS U3
    ON U3.Id = R.ID
-- QO model hint required to get a non-blocking flow distinct
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

ランダム行クエリ

計画では、この場合、100個の一致する行を見つけるために601個の乱数が必要であったことが示されています。それはかなり速いです:

テーブル「ユーザー」。スキャンカウント1、論理読み取り1937、物理読み取り2、先読み読み取り408
テーブル「ワークテーブル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0
テーブル「ワークファイル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0

 SQL Serverの実行時間:
   CPU時間= 0ミリ秒、経過時間= 9ミリ秒。

Stack Exchange Data Explorerで試してください。


0

この記事で説明したように、SQL結果セットをシャッフルするには、データベース固有の関数呼び出しを使用する必要があります。

RANDOM関数を使用して大きな結果セットをソートすると、非常に遅くなることがあるので、小さな結果セットでそれを行うようにしてください。

大きな結果セットをシャッフルして後で制限する必要がある場合は、ORDER BY句のランダム関数ではなくSQL Serverの SQL Serverを使用することをお勧めTABLESAMPLEます

したがって、次のデータベーステーブルがあると仮定します。

ここに画像の説明を入力してください

また、song表の次の行:

| id | artist                          | title                              |
|----|---------------------------------|------------------------------------|
| 1  | Miyagi & Эндшпиль ft. Рем Дигга | I Got Love                         |
| 2  | HAIM                            | Don't Save Me (Cyril Hahn Remix)   |
| 3  | 2Pac ft. DMX                    | Rise Of A Champion (GalilHD Remix) |
| 4  | Ed Sheeran & Passenger          | No Diggity (Kygo Remix)            |
| 5  | JP Cooper ft. Mali-Koa          | All This Love                      |

SQL ServerではNEWID、次の例に示すように、関数を使用する必要があります。

SELECT
    CONCAT(CONCAT(artist, ' - '), title) AS song
FROM song
ORDER BY NEWID()

前述のSQLクエリをSQL Serverで実行すると、次の結果セットが取得されます。

| song                                              |
|---------------------------------------------------|
| Miyagi & Эндшпиль ft. Рем Дигга - I Got Love      |
| JP Cooper ft. Mali-Koa - All This Love            |
| HAIM - Don't Save Me (Cyril Hahn Remix)           |
| Ed Sheeran & Passenger - No Diggity (Kygo Remix)  |
| 2Pac ft. DMX - Rise Of A Champion (GalilHD Remix) |

NEWIDORDER BY句で使用される関数呼び出しのおかげで、曲がランダムな順序でリストされていることに注意してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.