LinqからSqlへのランダムな行


112

いくつかのフィールドがtrueでなければならないなどの条件がある場合に、Linq to SQLを使用してランダムな行を取得する最良の(そして最も速い)方法は何ですか?


真の条件を確認する順序には2つのオプションがあります。ほとんどのアイテムで真の状態が発生した場合は、ランダムなアイテムを取得して、偽の間にテストして繰り返します。まれな場合は、データベースでオプションを真の条件に制限し、ランダムに選択してください。
レックスローガン

1
このサイトの多くの回答と同様に、2番目の評価は、承認されたものよりはるかに優れています。
nikib3ro 2013年

回答:


169

これは、偽のUDFを使用してデータベースで実行できます。部分クラスで、データコンテキストにメソッドを追加します。

partial class MyDataContext {
     [Function(Name="NEWID", IsComposable=true)] 
     public Guid Random() 
     { // to prove not used by our C# code... 
         throw new NotImplementedException(); 
     }
}

それからorder by ctx.Random(); これにより、SQL Serverでランダムに順序付けされますNEWID()。すなわち

var cust = (from row in ctx.Customers
           where row.IsActive // your filter
           orderby ctx.Random()
           select row).FirstOrDefault();

これは、中小規模のテーブルにのみ適していることに注意してください。巨大なテーブルの場合、サーバーのパフォーマンスに影響します。行数を見つけ(Count)、ランダムに1つ選択する()と、より効率的Skip/Firstです。


カウントアプローチの場合:

var qry = from row in ctx.Customers
          where row.IsActive
          select row;

int count = qry.Count(); // 1st round-trip
int index = new Random().Next(count);

Customer cust = qry.Skip(index).FirstOrDefault(); // 2nd round-trip

3
フィルター 30kの場合は、「いいえ」と言います。このアプローチは使用しないでください。2つの往復を行います。1はCount()を取得し、1はランダムな行を取得します...
Marc Gravell

1
5(または "x")のランダムな行が必要な場合はどうなりますか?ラウンドトリップを6回行うだけが最適ですか、それともストアドプロシージャに実装する便利な方法がありますか?
Neal Stublen、2009

2
@Neal S .: ctx.Random()による順序はTake(5)と混合できます。しかし、Count()アプローチを使用している場合、6回のラウンドトリップが最も簡単なオプションになると思います。
Marc Gravell

1
System.Data.Linqへの参照を追加することを忘れないでください。そうでない場合、System.Data.Linq.Mapping.Function属性は機能しません。
Jaguir 2010

8
私はこれが古いことを知っていますが、大きなテーブルから多くのランダムな行を選択している場合は、これを参照してください
jwd

60

Entity Frameworkの別のサンプル:

var customers = db.Customers
                  .Where(c => c.IsActive)
                  .OrderBy(c => Guid.NewGuid())
                  .FirstOrDefault();

これはLINQ to SQLでは機能しません。OrderBy単純にドロップされます。


4
これをプロファイリングし、機能することを確認しましたか?LINQPadを使用したテストでは、order by句が削除されています。
ジムウーリー

これは、この問題に対する最善の解決策である
reach4thelasers

8
これはLINQ to SQLでは動作しません...多分それはEntity Framework 4で動作します(確認していません)。.OrderByは、リストを並べ替える場合にのみGuidで使用できます... DBでは機能しません。
nikib3ro

2
最後に、これがEF4で機能することを確認します。この場合、これは素晴らしいオプションです。
nikib3ro 2013年

1
回答を編集して、新しいGuidを使用したorderByがなぜうまく機能するのか説明してください。道によるニースの答え:)
ジャン=フランソワ・コテ

32

編集:これはLINQ to SQLではなくLINQ to Objectsであることに気づきました。Marcのコードを使用して、データベースにこれを実行させます。ここでは、この回答をLINQ to Objectsの潜在的な関心ポイントとして残しました。

奇妙なことに、実際にカウントを取得する必要はありません。ただし、カウントを取得しない限り、すべての要素をフェッチする必要があります。

あなたができることは、「現在の」値と現在の数の考えを保つことです。次の値をフェッチするときは、乱数を取り、「現在の」を「新しい」に置き換えます。確率は1 / nで、nはカウントです。

したがって、最初の値を読み取るときは、常に「現在の」値にします。2番目の値を読み取るときに、それを現在の値(確率1/2)にすることができます。あなたは3番目の値を読んだとき、あなたは可能性がありますあなたはデータが不足してきた電流値(確率1/3)などは、現在の値を使用して、均一な確率で、読み込まれたすべてのもののうちランダム1であることを確認します。

これを条件付きで適用するには、条件を満たさないものは無視してください。これを行う最も簡単な方法は、最初にWhere句を適用することで、最初に「一致する」シーケンスのみを考慮することです。

これが簡単な実装です。私は考えて、それは大丈夫です...

public static T RandomElement<T>(this IEnumerable<T> source,
                                 Random rng)
{
    T current = default(T);
    int count = 0;
    foreach (T element in source)
    {
        count++;
        if (rng.Next(count) == 0)
        {
            current = element;
        }            
    }
    if (count == 0)
    {
        throw new InvalidOperationException("Sequence was empty");
    }
    return current;
}

4
FYI-私はクイックチェックを実行しましたが、この関数は均一な確率分布を持っています(増分カウントは本質的にフィッシャーイェーツのシャッフルと同じメカニズムなので、それが適切であるように思われます)。
グレッグブナ

@グレッグ:クール、ありがとう。簡単なチェックで大丈夫に見えましたが、このようなコードで1つずれたエラーが発生するのは簡単です。もちろん、LINQ to SQLには事実上無関係ですが、それでも役に立ちます。
Jon Skeet、

@JonSkeetさん、こんにちは。これを確認し、足りないものを教えてください
shaijut

@TylerLaing:いいえ、休憩するつもりはありません。最初の反復でcurrentは、常に最初の要素に設定されます。2番目の反復では、50%の変更があり、2番目の要素に設定されます。3回目の反復では、33%の確率で3番目の要素に設定されます。breakステートメントを追加すると、最初の要素を読み取った後は常に終了し、ランダムではなくなります。
Jon Skeet、2018

@JonSkeet Doh!カウントの使い方を誤解しました(たとえば、これはniのようなランダムな範囲のフィッシャーイェーツスタイルだと思っていました)ただし、Fisher-Yatesの最初の要素を選択することは、任意の要素を公平に選択することです。ただし、そのためには要素の総数を知る必要があります。合計数がわからないという点でIEnumerableのソリューションが適切であることがわかりました。カウントを取得するためだけにソース全体を反復処理し、ランダムに選択されたインデックスに対して再度反復処理する必要はありません。むしろ、あなたが述べたように、これは1つのパスで解決します:「カウントを取得しない限り、すべての要素をフェッチする必要があります」。
Tyler Laing、2018

19

効率的に実現する1つの方法Shuffleは、ランダムなintが入力されたデータに列を追加することです(各レコードが作成されるときに)。

ランダムな順序でテーブルにアクセスする部分クエリは...

Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);

これは、データベースでXOR演算を実行し、そのXORの結果で並べ替えます。

利点:-

  1. 効率的:SQLが順序を処理し、テーブル全体をフェッチする必要はありません
  2. 再現性:(テストに適しています)-同じランダムシードを使用して同じランダムな順序を生成できます。

これは、ホームオートメーションシステムがプレイリストをランダム化するために使用するアプローチです。それは毎日新しいシードを選び、日中一貫した順序を与えます(簡単な一時停止/再開機能を可能にします)が、各プレイリストを新しい日に新鮮に見ます。


ランダムなintフィールドを追加する代わりに、既存の自動インクリメントIDフィールドを使用した場合、ランダムさへの影響は何ですか(シードは明らかにランダムのままです)?また、最大値がテーブル内のレコード数と等しいシード値は適切ですか、それともそれ以上高くする必要がありますか?
ブライアン

同意しました、これはIMOがより多くの賛成票を持つべき素晴らしい答えです。私はこれをEntity Frameworkクエリで使用しました。ビットごとのXOR演算子^は直接機能するため、条件が少しわかりやすくなりますresult = result.OrderBy(s => s.Shuffle ^ seed);(つまり、〜、&、|演算子を使用してXORを実装する必要はありません)。
Steven Rands、2016年

7

たとえばvar count = 16テーブルからランダムな行を取得したい場合は、次のように書くことができます

var rows = Table.OrderBy(t => Guid.NewGuid())
                        .Take(count);

ここではEFを使用し、テーブルはDBセットです


1

ランダムな行を取得する目的がサンプリングである場合、ここでは、マテリアライズドビューを使用してSQL Serverのサンプリングフレームワークを開発したLarsonらのMicrosoft Researchチームからの素晴らしいアプローチについて簡単に説明しました。実際の論文へのリンクもあります。


1
List<string> lst = new List<string>();
lst.Add("Apple"); 
lst.Add("Guva");
lst.Add("Graps"); 
lst.Add("PineApple");
lst.Add("Orange"); 
lst.Add("Mango");

var customers = lst.OrderBy(c => Guid.NewGuid()).FirstOrDefault();

説明:GUID(ランダム)を挿入すると、orderbyの順序はランダムになります。


GUIDは「ランダム」ではなく、順次ではありません。違いがあります。実際には、これは些細なことでは問題になりません。
Chris Marisic 14

0

ここに来て、少数のページからいくつかのランダムなページを取得する方法を知り、各ユーザーがいくつかの異なるランダムな3ページを取得するようにしました。

これは私の最後のソリューションであり、Sharepoint 2010のページのリストに対してLINQでクエリを実行します。VisualBasicにあります。申し訳ありません:p

Dim Aleatorio As New Random()

Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3

おそらく、多数の結果をクエリする前にプロファイリングを取得する必要がありますが、それは私の目的に最適です


0

DataTables に対してランダムな関数クエリがあります。

var result = (from result in dt.AsEnumerable()
              order by Guid.NewGuid()
              select result).Take(3); 

0

以下の例では、ソースを呼び出してカウントを取得し、0〜nの数値を持つスキップ式をソースに適用します。2番目のメソッドは、ランダムオブジェクト(メモリ内のすべてを順序付ける)を使用して順序を適用し、メソッド呼び出しに渡される番号を選択します。

public static class IEnumerable
{
    static Random rng = new Random((int)DateTime.Now.Ticks);

    public static T RandomElement<T>(this IEnumerable<T> source)
    {
        T current = default(T);
        int c = source.Count();
        int r = rng.Next(c);
        current = source.Skip(r).First();
        return current;
    }

    public static IEnumerable<T> RandomElements<T>(this IEnumerable<T> source, int number)
    {
        return source.OrderBy(r => rng.Next()).Take(number);
    }
}

いくつかの説明がいいだろう
アンドリュー・バーバー

このコードはスレッドセーフではなく、シングルスレッドコード(ASP.NETではない)でのみ使用できます
Chris Marisic

0

私はランダムなニュースとその作業をうまく行うためにこの方法を使用します;)

    public string LoadRandomNews(int maxNews)
    {
        string temp = "";

        using (var db = new DataClassesDataContext())
        {
            var newsCount = (from p in db.Tbl_DynamicContents
                             where p.TimeFoPublish.Value.Date <= DateTime.Now
                             select p).Count();
            int i;
            if (newsCount < maxNews)
                i = newsCount;
            else i = maxNews;
            var r = new Random();
            var lastNumber = new List<int>();
            for (; i > 0; i--)
            {
                int currentNumber = r.Next(0, newsCount);
                if (!lastNumber.Contains(currentNumber))
                { lastNumber.Add(currentNumber); }
                else
                {
                    while (true)
                    {
                        currentNumber = r.Next(0, newsCount);
                        if (!lastNumber.Contains(currentNumber))
                        {
                            lastNumber.Add(currentNumber);
                            break;
                        }
                    }
                }
                if (currentNumber == newsCount)
                    currentNumber--;
                var news = (from p in db.Tbl_DynamicContents
                            orderby p.ID descending
                            where p.TimeFoPublish.Value.Date <= DateTime.Now
                            select p).Skip(currentNumber).Take(1).Single();
                temp +=
                    string.Format("<div class=\"divRandomNews\"><img src=\"files/1364193007_news.png\" class=\"randomNewsImg\" />" +
                                  "<a class=\"randomNews\" href=\"News.aspx?id={0}\" target=\"_blank\">{1}</a></div>",
                                  news.ID, news.Title);
            }
        }
        return temp;
    }

0

LINQPadでLINQ to SQLをC#ステートメントのように使用する

IEnumerable<Customer> customers = this.ExecuteQuery<Customer>(@"SELECT top 10 * from [Customers] order by newid()");
customers.Dump();

生成されるSQLは

SELECT top 10 * from [Customers] order by newid()

0

LINQPadを使用する場合は、C#プログラムモードに切り替えて、次のようにします。

void Main()
{
    YourTable.OrderBy(v => Random()).FirstOrDefault.Dump();
}

[Function(Name = "NEWID", IsComposable = true)]
public Guid Random()
{
    throw new NotImplementedException();
}


0

Marc Gravellのソリューションに追加します。datacontextクラス自体を操作していない場合(たとえば、テスト目的でdatacontextを偽造するためにプロキシを使用しているため)、定義されたUDFを直接使用することはできません。実際のデータコンテキストクラスのサブクラスまたは部分クラス。

この問題の回避策は、プロキシにランダム化関数を作成し、ランダム化するクエリを入力することです。

public class DataContextProxy : IDataContext
{
    private readonly DataContext _context;

    public DataContextProxy(DataContext context)
    {
        _context = context;
    }

    // Snipped irrelevant code

    public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
    {
        return query.OrderBy(x => _context.Random());
    }
}

コードでの使用方法は次のとおりです。

var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);

完全にするために、これはFAKEデータコンテキスト(メモリエンティティで使用)にこれを実装する方法です。

public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
    return query.OrderBy(x => Guid.NewGuid());
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.