いくつかのフィールドがtrueでなければならないなどの条件がある場合に、Linq to SQLを使用してランダムな行を取得する最良の(そして最も速い)方法は何ですか?
いくつかのフィールドがtrueでなければならないなどの条件がある場合に、Linq to SQLを使用してランダムな行を取得する最良の(そして最も速い)方法は何ですか?
回答:
これは、偽の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
Entity Frameworkの別のサンプル:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
これはLINQ to SQLでは機能しません。OrderBy
単純にドロップされます。
編集:これは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;
}
current
は、常に最初の要素に設定されます。2番目の反復では、50%の変更があり、2番目の要素に設定されます。3回目の反復では、33%の確率で3番目の要素に設定されます。breakステートメントを追加すると、最初の要素を読み取った後は常に終了し、ランダムではなくなります。
効率的に実現する1つの方法Shuffle
は、ランダムなintが入力されたデータに列を追加することです(各レコードが作成されるときに)。
ランダムな順序でテーブルにアクセスする部分クエリは...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
これは、データベースでXOR演算を実行し、そのXORの結果で並べ替えます。
利点:-
これは、ホームオートメーションシステムがプレイリストをランダム化するために使用するアプローチです。それは毎日新しいシードを選び、日中一貫した順序を与えます(簡単な一時停止/再開機能を可能にします)が、各プレイリストを新しい日に新鮮に見ます。
result = result.OrderBy(s => s.Shuffle ^ seed);
(つまり、〜、&、|演算子を使用してXORを実装する必要はありません)。
たとえばvar count = 16
テーブルからランダムな行を取得したい場合は、次のように書くことができます
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
ここではEFを使用し、テーブルはDBセットです
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の順序はランダムになります。
ここに来て、少数のページからいくつかのランダムなページを取得する方法を知り、各ユーザーがいくつかの異なるランダムな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
おそらく、多数の結果をクエリする前にプロファイリングを取得する必要がありますが、それは私の目的に最適です
DataTable
s に対してランダムな関数クエリがあります。
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
以下の例では、ソースを呼び出してカウントを取得し、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);
}
}
私はランダムなニュースとその作業をうまく行うためにこの方法を使用します;)
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;
}
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());
}