Zufällige Zeile von Linq nach Sql


112

Was ist der beste (und schnellste) Weg, um eine zufällige Zeile mit Linq to SQL abzurufen, wenn ich eine Bedingung habe, z. B. muss ein Feld wahr sein?


Sie haben zwei Möglichkeiten für die Reihenfolge, in der Sie die wahren Bedingungen überprüfen. Wenn die wahre Bedingung bei den meisten Elementen auftritt, nehmen Sie einfach ein zufälliges Element und testen und wiederholen Sie es, während es falsch ist. In seltenen Fällen kann die Datenbank die Optionen auf den tatsächlichen Zustand beschränken und dann nach dem Zufallsprinzip eine auswählen.
Rex Logan

1
Wie bei vielen Antworten auf dieser Website ist die Zweitbewertung viel besser als die akzeptierte.
Nikib3ro

Antworten:


169

Sie können dies in der Datenbank tun, indem Sie eine gefälschte UDF verwenden. Fügen Sie in einer Teilklasse dem Datenkontext eine Methode hinzu:

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

Dann einfach order by ctx.Random(); Dies führt eine zufällige Bestellung auf dem SQL-Server mit freundlicher Genehmigung von NEWID(). dh

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

Beachten Sie, dass dies nur für kleine bis mittelgroße Tische geeignet ist. Bei großen Tabellen wirkt sich dies auf die Leistung des Servers aus, und es ist effizienter, die Anzahl der Zeilen zu ermitteln ( Count) und dann eine zufällig auszuwählen ( Skip/First).


für den Zählansatz:

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
Wenn es 30k nach dem Filter ist, würde ich nein sagen: Verwenden Sie diesen Ansatz nicht. Machen Sie 2 Rundreisen; 1, um den Count () zu erhalten, und 1, um eine zufällige Zeile zu erhalten ...
Marc Gravell

1
Was ist, wenn Sie fünf (oder "x") zufällige Zeilen möchten? Ist es am besten, nur sechs Roundtrips zu machen, oder gibt es eine bequeme Möglichkeit, sie in einer gespeicherten Prozedur zu implementieren?
Neal Stublen

2
@Neal S.: Die Reihenfolge von ctx.Random () könnte mit Take (5) gemischt werden. Wenn Sie jedoch den Count () -Ansatz verwenden, erwarte ich, dass 6 Hin- und Rückfahrten die einfachste Option sind.
Marc Gravell

1
Vergessen Sie nicht, einen Verweis auf System.Data.Linq oder das System.Data.Linq.Mapping.Function-Attribut hinzuzufügen, das nicht funktioniert.
Jaguir

8
Ich weiß, dass dies alt ist, aber wenn Sie viele zufällige Zeilen aus einer großen Tabelle auswählen, lesen Sie Folgendes : msdn.microsoft.com/en-us/library/cc441928.aspx Ich weiß nicht, ob es ein LINQ-Äquivalent gibt.
JWD

60

Ein weiteres Beispiel für Entity Framework:

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

Dies funktioniert nicht mit LINQ to SQL. Das OrderBywird einfach fallen gelassen.


4
Haben Sie dies profiliert und bestätigt, dass es funktioniert? In meinen Tests mit LINQPad wird die order by-Klausel gelöscht.
Jim Wooley

Dies ist die beste Lösung für dieses Problem
erreichen4thelasers

8
Dies funktioniert nicht in LINQ to SQL ... möglicherweise funktioniert es in Entity Framework 4 (ohne es zu bestätigen). Sie können .OrderBy mit Guid nur verwenden, wenn Sie eine Liste sortieren ... mit DB funktioniert es nicht.
Nikib3ro

2
Nur um endlich zu bestätigen, dass dies in EF4 funktioniert - in diesem Fall ist es eine großartige Option.
Nikib3ro

1
Könnten Sie Ihre Antwort bearbeiten und erklären, warum die Bestellung mit einem neuen Guid den Trick macht? Schöne Antwort übrigens :)
Jean-François Côté

32

EDIT: Ich habe gerade erst bemerkt, dass dies LINQ to SQL ist, nicht LINQ to Objects. Verwenden Sie Marc's Code, um die Datenbank dazu zu bringen, dies für Sie zu tun. Ich habe diese Antwort hier als potenziellen Punkt von Interesse für LINQ to Objects hinterlassen.

Seltsamerweise müssen Sie nicht wirklich zählen. Sie müssen jedoch jedes Element abrufen, es sei denn, Sie erhalten die Anzahl.

Was Sie tun können, ist die Idee eines "aktuellen" Werts und der aktuellen Anzahl beizubehalten. Wenn Sie den nächsten Wert abrufen, nehmen Sie eine Zufallszahl und ersetzen Sie die "aktuelle" durch "neue" mit einer Wahrscheinlichkeit von 1 / n, wobei n die Anzahl ist.

Wenn Sie also den ersten Wert lesen, machen Sie diesen immer zum "aktuellen" Wert. Wenn Sie den zweiten Wert lesen, Sie könnten , dass der aktuelle Wert (Wahrscheinlichkeit 1/2) machen. Wenn Sie den dritten Wert zu lesen, Sie können machen , dass der aktuelle Wert (Wahrscheinlichkeit 1/3) usw. Wenn Sie von Daten abgelaufen haben, ist der aktuelle Wert ein gelegentlich man aus allen denen , die Sie mit einheitlicher Wahrscheinlichkeit lesen.

Um dies mit einer Bedingung anzuwenden, ignorieren Sie einfach alles, was die Bedingung nicht erfüllt. Der einfachste Weg, dies zu tun, besteht darin, zunächst nur die "übereinstimmende" Sequenz zu berücksichtigen, indem Sie zuerst eine Where-Klausel anwenden.

Hier ist eine schnelle Implementierung. Ich denke es ist okay ...

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
Zu Ihrer Information - Ich habe eine schnelle Überprüfung durchgeführt und diese Funktion hat eine einheitliche Wahrscheinlichkeitsverteilung (die inkrementelle Anzahl entspricht im Wesentlichen dem Mechanismus des Fisher-Yates-Shuffle, daher erscheint es vernünftig, dass dies der Fall sein sollte).
Greg Beech

@ Greg: Cool, danke. Mit einer kurzen Überprüfung sah es für mich in Ordnung aus, aber es ist so einfach, einzelne Fehler in Code wie diesem zu bekommen. Für LINQ to SQL natürlich praktisch irrelevant, aber dennoch nützlich.
Jon Skeet

@JonSkeet, hallo, können Sie überprüfen dies und lassen Sie mich wissen , was ich bin fehlt
shaijut

@ TylerLaing: Nein, es soll keine Pause geben. currentWird bei der ersten Iteration immer auf das erste Element gesetzt. Bei der zweiten Iteration wird eine Änderung von 50% vorgenommen, die auf das zweite Element gesetzt wird. Bei der dritten Iteration besteht eine Wahrscheinlichkeit von 33%, dass das dritte Element verwendet wird. Das Hinzufügen einer break-Anweisung würde bedeuten, dass Sie nach dem Lesen des ersten Elements immer beenden, sodass es überhaupt nicht zufällig ist.
Jon Skeet

@ JonSkeet Doh! Ich habe Ihre Verwendung von count falsch verstanden (z. B. dachte ich, dies sei ein Fisher-Yates-Stil mit einem zufälligen Bereich wie ni). Das erste Element in Fisher-Yates auszuwählen bedeutet jedoch, eines der Elemente fair auszuwählen. Dies erfordert jedoch die Kenntnis der Gesamtzahl der Elemente. Ich sehe jetzt, dass Ihre Lösung für eine IEnumerable insofern ordentlich ist, als die Gesamtzahl nicht bekannt ist und es nicht erforderlich ist, über die gesamte Quelle zu iterieren, nur um die Anzahl zu erhalten, und dann erneut zu einem zufällig ausgewählten Index zu iterieren. Vielmehr löst sich dies in einem Durchgang, wie Sie sagten: "Sie müssen jedes Element abrufen, es sei denn, Sie erhalten die Zählung".
Tyler Laing

19

Eine Möglichkeit, dies effizient zu erreichen, besteht darin, Ihren Daten eine Spalte hinzuzufügen Shuffle, die mit einem zufälligen int gefüllt ist (während jeder Datensatz erstellt wird).

Die Teilabfrage für den Zugriff auf die Tabelle in zufälliger Reihenfolge lautet ...

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

Dies führt eine XOR-Operation in der Datenbank durch und ordnet nach den Ergebnissen dieses XOR.

Vorteile: -

  1. Effizient: SQL übernimmt die Reihenfolge, ohne dass die gesamte Tabelle abgerufen werden muss
  2. Wiederholbar: (gut zum Testen) - kann denselben zufälligen Startwert verwenden, um dieselbe zufällige Reihenfolge zu generieren

Dies ist der Ansatz, den mein Hausautomationssystem verwendet, um Wiedergabelisten nach dem Zufallsprinzip zu sortieren. Es wählt jeden Tag einen neuen Startwert aus und gibt tagsüber eine einheitliche Reihenfolge (was einfache Pausen- / Wiederaufnahmefunktionen ermöglicht), aber jeden neuen Tag einen neuen Blick auf jede Wiedergabeliste.


Was würde sich auf die Zufälligkeit auswirken, wenn Sie anstelle eines zufälligen int-Felds nur ein vorhandenes automatisch inkrementierendes Identitätsfeld verwenden würden (der Startwert würde offensichtlich zufällig bleiben)? auch - ist ein Startwert mit einem Maximum, das der Anzahl der Datensätze in der Tabelle entspricht, angemessen oder sollte er höher sein?
Bryan

Einverstanden, dies ist eine großartige Antwort, die IMO mehr positive Stimmen haben sollte. Ich habe dies in einer Entity Framework-Abfrage verwendet, und der bitweise XOR-Operator ^ scheint direkt zu funktionieren, wodurch die Bedingung etwas sauberer wird: result = result.OrderBy(s => s.Shuffle ^ seed);(dh das XOR muss nicht über die Operatoren ~, & und | implementiert werden).
Steven Rands

7

Wenn Sie zB var count = 16zufällige Zeilen aus der Tabelle erhalten möchten , können Sie schreiben

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

hier habe ich EF verwendet, und die Tabelle ist ein Dbset


1

Wenn der Zweck des Erhaltens Zufallsreihen abtastet, ich habe sehr kurz gesprochen hier über einen netten Ansatz von Larson et al., Microsoft Research Team , in dem sie einen Stichprobenrahmen für SQL Server materialisierte Ansichten entwickelt haben. Es gibt auch einen Link zum eigentlichen Artikel.


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();

Erläuterung: Durch Einfügen der Guid (die zufällig ist) wäre die Reihenfolge mit orderby zufällig.


Guids sind nicht "zufällig", sondern nicht sequentiell. Da ist ein Unterschied. In der Praxis spielt es wahrscheinlich keine Rolle für etwas Triviales wie dieses.
Chris Marisic

0

Kam hierher und fragte mich, wie man ein paar zufällige Seiten von einer kleinen Anzahl von ihnen bekommt, so dass jeder Benutzer einige andere zufällige 3 Seiten bekommt.

Dies ist meine endgültige Lösung, bei der ich mit LINQ eine Liste von Seiten in Sharepoint 2010 abfrage. Es ist in Visual Basic, sorry: p

Dim Aleatorio As New Random()

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

Wahrscheinlich sollte ein Profil erstellt werden, bevor eine große Anzahl von Ergebnissen abgefragt wird, aber es ist perfekt für meinen Zweck


0

Ich habe zufällige Funktionsabfrage gegen DataTables:

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

0

Im folgenden Beispiel wird die Quelle aufgerufen, um eine Zählung abzurufen, und anschließend ein Sprungausdruck auf die Quelle mit einer Zahl zwischen 0 und n angewendet. Die zweite Methode wendet die Reihenfolge unter Verwendung des zufälligen Objekts an (das alles im Speicher ordnet) und wählt die Nummer aus, die an den Methodenaufruf übergeben wird.

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);
    }
}

Eine Erklärung wäre nett
Andrew Barber

Dieser Code ist nicht threadsicher und kann nur in Single-Thread-Code (also nicht in ASP.NET) verwendet werden
Chris Marisic

0

Ich benutze diese Methode für zufällige Nachrichten und ihre Arbeit gut;)

    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

Die Verwendung von LINQ to SQL in LINQPad als C # -Anweisungen sieht folgendermaßen aus

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

Das generierte SQL ist

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

0

Wenn Sie LINQPad verwenden , wechseln Sie in den C # -Programmmodus und gehen Sie folgendermaßen vor:

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

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

0
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);

Wählen Sie zufällige 2 Zeilen


0

Zur Lösung von Marc Gravell hinzufügen. Wenn Sie nicht mit der Datenkontextklasse selbst arbeiten (weil Sie sie irgendwie als Proxy verwenden, z. B. um den Datenkontext zu Testzwecken zu fälschen), können Sie die definierte UDF nicht direkt verwenden: Sie wird nicht in SQL kompiliert, da Sie sie nicht in a verwenden Unterklasse oder Teilklasse Ihrer realen Datenkontextklasse.

Eine Problemumgehung für dieses Problem besteht darin, eine Zufallsfunktion in Ihrem Proxy zu erstellen und diese mit der Abfrage zu versorgen, die Sie zufällig auswählen möchten:

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());
    }
}

So würden Sie es in Ihrem Code verwenden:

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

Um vollständig zu sein, wird dies folgendermaßen im FAKE-Datenkontext implementiert (der in Speicherentitäten verwendet wird):

public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
    return query.OrderBy(x => Guid.NewGuid());
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.