Linq'den Sql'ye rastgele satır


112

Bir koşulum olduğunda, Linq to SQL kullanarak rastgele bir satırı almanın en iyi (ve en hızlı) yolu nedir, örneğin bazı alanların doğru olması gerekir?


Doğru koşulları kontrol ettiğiniz sipariş için iki seçeneğiniz vardır. Çoğu öğede doğru koşul meydana gelirse, rastgele bir öğe alın ve yanlışken test edin ve tekrarlayın. Nadiren, veritabanının seçenekleri gerçek koşulla sınırlamasına ve ardından rastgele birini almasına izin verin.
Rex Logan

1
Bu sitedeki birçok cevapta olduğu gibi - ikinci puan, kabul edilenden çok daha iyidir.
nikib3ro

Yanıtlar:


170

Bunu, sahte bir UDF kullanarak veritabanında yapabilirsiniz; kısmi bir sınıfta, veri bağlamına bir yöntem ekleyin:

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

O zaman sadece order by ctx.Random(); bu, SQL-Server'ın izniyle rastgele bir sıralama yapacaktır NEWID(). yani

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

Bunun yalnızca küçük ve orta büyüklükteki tablolar için uygun olduğunu unutmayın; büyük tablolar için, sunucuda performans etkisi olacaktır ve satır sayısını ( Count) bulmak ve ardından rastgele birini seçmek ( ) daha verimli olacaktır Skip/First.


sayma yaklaşımı için:

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
Filtreden sonra 30k ise, hayır derim: bu yaklaşımı kullanmayın. 2 gidiş-dönüş yapın; 1'i Count'u () almak için ve 1'i rastgele sırayı almak için ...
Marc Gravell

1
Ya beş (veya "x") rasgele sıra istiyorsanız? Yalnızca altı gidiş-dönüş yapmak en iyisi mi yoksa bunu saklı yordamda uygulamanın uygun bir yolu var mı?
Neal Stublen

2
@Neal S .: ctx.Random () sıralaması Take (5) ile karıştırılabilir; ancak Count () yaklaşımını kullanıyorsanız, 6 gidiş dönüşün en basit seçenek olmasını bekliyorum.
Marc Gravell

1
System.Data.Linq veya System.Data.Linq.Mapping.Function özniteliğine bir başvuru eklemeyi unutmayın.
Jaguir

8
Bunun eski olduğunu biliyorum, ancak büyük bir tablodan çok sayıda rastgele satır seçiyorsanız, şuna bakın : msdn.microsoft.com/en-us/library/cc441928.aspx LINQ eşdeğeri olup olmadığını bilmiyorum.
jwd

60

Entity Framework için başka bir örnek:

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

Bu LINQ to SQL ile çalışmaz. OrderByBasitçe bırakılıyor.


4
Bunun profilini çıkardınız ve çalıştığını doğruladınız mı? LINQPad kullanan testlerimde, yan tümceye göre sıralama bırakılıyor.
Jim Wooley

Bu, bu soruna en iyi çözüm
reach4thelasers

8
Bu LINQ to SQL'de çalışmıyor ... Belki Entity Framework 4'te çalışıyor (onaylamıyor). Guid ile sadece .OrderBy komutunu kullanabilirsiniz eğer bir Listeyi DB ile sıralıyorsanız çalışmaz.
nikib3ro

2
Sonunda bunun EF4'te çalıştığını doğrulamak için - bu durumda harika bir seçenek.
nikib3ro

1
Cevabınızı düzenleyebilir ve yeni bir Guid ile siparişin neden hile yaptığını açıklayabilir misiniz? Bu arada güzel cevap :)
Jean-François Côté

32

DÜZENLEME: Sadece LINQ to SQL, LINQ to Objects değil fark ettim. Bunu sizin için yapacak veritabanını almak için Marc'ın kodunu kullanın. Bu yanıtı burada LINQ to Objects için potansiyel bir ilgi noktası olarak bıraktım.

Garip bir şekilde, aslında sayıma ihtiyacınız yok. Bununla birlikte, sayımı almadığınız sürece her öğeyi getirmeniz gerekir.

Yapabileceğiniz şey, "güncel" bir değer ve mevcut sayım fikrini korumaktır. Bir sonraki değeri getirdiğinizde, rastgele bir sayı alın ve "mevcut" olanı "yeni" ile değiştirin, 1 / n olasılıkla, burada n sayıdır.

Yani ilk değeri okuduğunuzda, daima "mevcut" değer . Eğer ikinci değer okuduğunuzda belki o anki değeri (olasılık 1/2) yapmak. Üçüncü değer okuduğunuzda belki veri tükendi akım değeri (olasılık 1/3) vb şimdiki değer üniforma olasılıkla, okunan tüm olanlar dışında rastgele biri olduğunu olun.

Bunu bir koşulla uygulamak için, koşulu karşılamayan her şeyi göz ardı edin. Bunu yapmanın en kolay yolu, önce bir Where cümlesi uygulayarak, yalnızca "eşleştirme" dizisinin başlamasını düşünmektir.

İşte hızlı bir uygulama. Ben düşünüyorum sorun yok ...

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
Bilginize - Hızlı bir kontrol yaptım ve bu fonksiyonun tekdüze bir olasılık dağılımı var (artan sayım esasen Fisher-Yates shuffle ile aynı mekanizma olduğundan olması gerektiği gibi makul görünüyor).
Greg Beech

@Greg: Harika, teşekkürler. Hızlı bir kontrolle bana iyi göründü, ancak bunun gibi kodlarda tek tek hatalar almak çok kolay. LINQ to SQL ile hemen hemen alakasız, ancak yine de kullanışlıdır.
Jon Skeet

@JonSkeet, selam kontrol edebilirsiniz bu ve beni ben eksik ben bildirin
shaijut

@TylerLaing: Hayır, mola verilecek bir şey yok. İlk yinelemede, currenther zaman ilk öğeye ayarlanacaktır. İkinci yinelemede, ikinci öğeye ayarlanacak% 50'lik bir değişiklik var. Üçüncü yinelemede, üçüncü öğeye ayarlanma şansı% 33'tür. Bir break ifadesi eklemek, ilk öğeyi okuduktan sonra her zaman çıkacağınız anlamına gelir, bu da onu hiç rastgele yapmaz.
Jon Skeet

@JonSkeet Doh! Count kullanımınızı yanlış anladım (örneğin bunun, ni gibi rastgele bir aralıkla Fisher-Yates tarzı olduğunu düşünüyordum). Ancak Fisher-Yates'te ilk öğeyi seçmek, öğelerden herhangi birini adil bir şekilde seçmektir. Ancak bu, toplam eleman sayısının bilinmesini gerektirir. Şimdi, çözümünüzün bir IEnumerable için düzgün olduğunu görüyorum, çünkü toplam sayım bilinmemektedir ve sadece sayımı elde etmek için tüm kaynak üzerinde yinelemeye gerek yoktur, ardından rastgele seçilen bir dizine tekrar yineleme yapmaya gerek yoktur. Aksine, belirttiğiniz gibi, bu tek geçişte çözülür: "sayıyı almadıkça her öğeyi getirmeniz gerekir".
Tyler Laing

19

Verimli bir şekilde başarmanın bir yolu, verilerinize Shufflerastgele int ile doldurulmuş bir sütun eklemektir (her kayıt oluşturulurken).

Tabloya rastgele sırayla erişmek için kısmi sorgu ...

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

Bu, veritabanında bir XOR işlemi yapar ve bu XOR'un sonuçlarına göre sıralar.

Avantajları: -

  1. Verimli: SQL sıralamayı yönetir, tüm tabloyu getirmeye gerek yoktur
  2. Tekrarlanabilir: (test için iyidir) - aynı rastgele sıralamayı oluşturmak için aynı rastgele tohumu kullanabilir

Bu, ev otomasyon sistemimin oynatma listelerini rastgele hale getirmek için kullandığı yaklaşımdır. Her gün, gün boyunca tutarlı bir sipariş veren (kolay duraklatma / devam ettirme yeteneklerine izin veren) ancak her yeni gün her oynatma listesine yeni bir bakış veren yeni bir tohum seçer.


Rastgele bir int alanı eklemek yerine mevcut bir otomatik artan kimlik alanını kullanırsanız (tohum açıkça rastgele kalacaktır) rasgelelik üzerindeki etkisi ne olur? ayrıca - maksimum tablodaki kayıt sayısına eşit olan bir çekirdek değer yeterli midir yoksa daha yüksek mi olmalıdır?
Bryan

Kabul edildi, bu IMO'nun daha fazla oy alması gereken harika bir cevap. Bunu bir Entity Framework sorgusunda kullandım ve ^ bitsel XOR operatörü doğrudan çalışıyor gibi görünüyor, bu da koşulu biraz daha temiz hale getiriyor: result = result.OrderBy(s => s.Shuffle ^ seed);(yani XOR'u ~, & ve | operatörleri aracılığıyla uygulamaya gerek yok).
Steven Rands

7

Örneğin var count = 16tablodan rastgele satırlar almak istiyorsanız, yazabilirsiniz

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

burada EF kullandım ve Tablo bir Dbset


1

Rastgele satırlar elde etmenin amacı örnekleme ise, burada kısaca Larson ve diğerlerinin, somutlaştırılmış görünümleri kullanarak Sql Server için bir örnekleme çerçevesi geliştirdikleri Microsoft Araştırma ekibinin güzel bir yaklaşımından bahsetmiştim . Gerçek makaleye de bir bağlantı var.


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

Açıklama: Rastgele kılavuzun eklenmesi ile, orderby ile sıra rastgele olacaktır.


Kılavuzlar "rastgele" değildir, sıralı değildir. Bir fark var. Pratikte bu kadar önemsiz bir şeyin önemi yoktur.
Chris Marisic

0

Buraya, az sayıda rastgele birkaç sayfanın nasıl alınacağını merak etmeye geldim, böylece her kullanıcı farklı rastgele 3 sayfa alır.

Bu benim son çözümüm, Sharepoint 2010'daki bir sayfa listesine karşı LINQ ile sorgulama yaparak çalışıyor. Visual Basic'te, üzgünüm: p

Dim Aleatorio As New Random()

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

Muhtemelen çok sayıda sonucu sorgulamadan önce biraz profilleme almalıyım, ancak amacım için mükemmel


0

S'ye karşı rastgele işlev sorgum var DataTable:

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

0

Aşağıdaki örnek, kaynağı bir sayımı almak için çağıracak ve ardından 0 ile n arasında bir sayı ile kaynakta bir atlama ifadesi uygulayacaktır. İkinci yöntem, rastgele nesneyi kullanarak (bellekteki her şeyi sıralayacak) sıralamayı uygulayacak ve yöntem çağrısına geçirilen sayıyı seçecektir.

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

Bazı açıklamalar güzel olurdu
Andrew Barber

Bu kod ÅŸan değildir ve sadece tek dişli kodu (şimdiye kullanılabilir değil ASP.NET)
Chris Marisic

0

Bu yöntemi rastgele haberler almak için kullanıyorum ve işi iyi;)

    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'de C # ifadeleri gibi LINQ to SQL kullanma

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

Oluşturulan SQL

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

0

Eğer kullanırsanız LINQPad için, anahtar C # programı modu ve bu şekilde yaparsak:

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

Rastgele 2 sıra seç


0

Marc Gravell'in çözümüne eklemek için. Veri bağlamı sınıfının kendisiyle çalışmıyorsanız (örneğin, test amacıyla veri bağlamını taklit etmek için ona bir şekilde proxy uyguladığınız için), tanımlanan UDF'yi doğrudan kullanamazsınız: SQL'e derlenmeyecektir çünkü onu bir gerçek veri bağlamı sınıfınızın alt sınıfı veya kısmi sınıfı.

Bu problem için geçici bir çözüm, proxy'nizde bir Randomize işlevi oluşturmak ve onu randomize edilmesini istediğiniz sorgu ile beslemektir:

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

Bunu kodunuzda nasıl kullanacağınız aşağıda açıklanmıştır:

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

Tam olmak için, bunu FAKE veri bağlamında (bellek varlıklarında kullanılan) şu şekilde uygulayabilirsiniz:

public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
    return query.OrderBy(x => Guid.NewGuid());
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.