2009-03-15 53 views
109

Cách tốt nhất (và nhanh nhất) để truy xuất hàng ngẫu nhiên bằng cách sử dụng LINQ to SQL khi tôi có điều kiện, ví dụ: một số lĩnh vực phải đúng?Hàng ngẫu nhiên từ LINQ đến Sql

+0

Bạn có hai tùy chọn cho thứ tự bạn kiểm tra các điều kiện thực. Nếu điều kiện thực sự xảy ra trên hầu hết các mục thì chỉ cần lấy một mục ngẫu nhiên sau đó kiểm tra và lặp lại trong khi sai. Nếu hiếm để cơ sở dữ liệu giới hạn các tùy chọn về điều kiện thực và sau đó lấy một tùy chọn ngẫu nhiên. –

+1

Như với rất nhiều câu trả lời trên trang web này - xếp hạng thứ hai tốt hơn nhiều so với trang được chấp nhận. – kape123

Trả lời

163

Bạn có thể làm điều này tại cơ sở dữ liệu, bằng cách sử dụng một UDF giả; trong một lớp học một phần, thêm phương thức vào ngữ cảnh dữ liệu:

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

Sau đó chỉ order by ctx.Random(); điều này sẽ thực hiện một thứ tự ngẫu nhiên tại phiên bản SQL Server của NEWID(). tức là

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

Lưu ý rằng điều này chỉ phù hợp với các bảng có kích thước nhỏ đến trung bình; đối với các bảng lớn, nó sẽ có tác động hiệu suất tại máy chủ và sẽ hiệu quả hơn khi tìm số hàng (Count), sau đó chọn một hàng ngẫu nhiên (Skip/First).


cho phương pháp đếm:

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 
+0

Bảng của tôi có khoảng 30.000 hàng, bạn có thể nói kích thước từ nhỏ đến trung bình không? –

+3

Nếu là 30k * sau * bộ lọc, tôi muốn nói không: không sử dụng phương pháp này. Thực hiện 2 chuyến đi khứ hồi; 1 để có được Count(), và 1 để có được một hàng ngẫu nhiên ... –

+0

+1 Đây là một cách tiếp cận tốt hơn nhiều so với những gì tôi đã nói. Tôi đã đề cử tôi xóa. –

28

EDIT: Tôi vừa mới nhận thấy đây là LINQ to SQL chứ không phải LINQ to Objects. Sử dụng mã của Marc để có được cơ sở dữ liệu để làm điều này cho bạn. Tôi đã để lại câu trả lời ở đây như là một điểm tiềm năng quan tâm cho LINQ to Objects.

Kỳ lạ, bạn không thực sự cần phải đếm. Bạn làm, tuy nhiên, cần phải lấy tất cả các phần tử trừ khi bạn nhận được số lượng.

Điều bạn có thể làm là giữ ý tưởng về giá trị "hiện tại" và số lượng hiện tại. Khi bạn tìm nạp giá trị tiếp theo, lấy một số ngẫu nhiên và thay thế "hiện tại" bằng "mới" với xác suất là 1/n trong đó n là số đếm.

Vì vậy, khi bạn đọc giá trị đầu tiên, bạn luôn là làm cho giá trị "hiện tại". Khi bạn đọc giá trị thứ hai, bạn có thể làm cho giá trị hiện tại (xác suất 1/2). Khi bạn đọc giá trị thứ ba, bạn có thể làm cho giá trị hiện tại (xác suất 1/3) v.v. Khi bạn đã hết dữ liệu, giá trị hiện tại là một giá trị ngẫu nhiên trong số tất cả những gì bạn đọc, với đồng phục xác suất.

Để áp dụng điều kiện đó, chỉ cần bỏ qua mọi thứ không đáp ứng điều kiện. Cách dễ nhất để làm điều đó là chỉ xem xét chuỗi "phù hợp" để bắt đầu bằng cách áp dụng mệnh đề Where trước tiên.

Đây là triển khai nhanh. Tôi nghĩ nó 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; 
} 
+3

FYI - Tôi chạy kiểm tra nhanh và chức năng này không có phân bố xác suất thống nhất (số gia tăng về cơ bản là cơ chế tương tự như shuffle Fisher-Yates nên có vẻ hợp lý là phải). –

+0

@Greg: Tuyệt vời, cảm ơn. Nó trông ổn với tôi với một kiểm tra nhanh chóng, nhưng nó rất dễ dàng để có được off-by-one lỗi trong mã như thế này. Hầu như không liên quan đến LINQ to SQL tất nhiên, nhưng dù sao cũng hữu ích. –

+0

@JonSkeet, xin chào, bạn có thể kiểm tra [this] (http://stackoverflow.com/q/30752514/2218697) và cho tôi biết những gì tôi đang thiếu – stom

17

Một cách để đạt được hiệu quả là thêm một cột để dữ liệu của bạn Shuffle đó là dân cư với một int ngẫu nhiên (như mỗi bản ghi được tạo ra).

Truy vấn một phần để truy cập vào bảng theo thứ tự ngẫu nhiên là ...

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

này không phẫu thuật XOR trong cơ sở dữ liệu và đơn đặt hàng của các kết quả của XOR đó.

Ưu điểm: -

  1. hiệu quả: SQL xử lý các đặt hàng, không cần phải lấy toàn bộ bảng
  2. lặp: (tốt cho thử nghiệm) - có thể sử dụng cùng một hạt giống ngẫu nhiên để tạo ra cùng một ngẫu nhiên đặt hàng

Đây là phương pháp được hệ thống tự động hóa gia đình của tôi sử dụng để ngẫu nhiên danh sách phát. Nó chọn một hạt giống mới mỗi ngày cho một trật tự nhất quán trong ngày (cho phép dễ dàng tạm dừng/tiếp tục khả năng) nhưng một cái nhìn mới tại mỗi danh sách phát mỗi ngày mới.

+0

điều gì sẽ ảnh hưởng đến tính ngẫu nhiên nếu thay vì thêm một trường int ngẫu nhiên bạn vừa sử dụng trường nhận dạng tự động gia tăng hiện có (hạt giống rõ ràng sẽ vẫn là ngẫu nhiên)? cũng - là một giá trị hạt giống với một giá trị tối đa bằng với số lượng hồ sơ trong bảng đầy đủ hay nó nên cao hơn? – Bryan

+0

Ian, điều này thật tuyệt vời. Cám ơn vì cái này. – MisterJames

+0

Điều này thật tuyệt vời !! –

58

Một mẫu cho Entity Framework:

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

này không làm việc với LINQ to SQL. Các OrderBy chỉ đơn giản là bị bỏ.

+4

Bạn đã lược tả điều này và xác nhận rằng nó hoạt động? Trong các thử nghiệm của tôi bằng cách sử dụng LINQPad, thứ tự theo mệnh đề đang được loại bỏ. –

+0

Chưa định cấu hình, nhưng nó hoạt động. – arviman

+0

Đây là giải pháp tốt nhất cho vấn đề này – reach4thelasers

1

Nếu mục đích lấy hàng ngẫu nhiên là lấy mẫu, tôi đã nói rất ngắn gọn here về cách tiếp cận tốt đẹp từ Larson và cộng sự, nhóm nghiên cứu của Microsoft, nơi họ đã phát triển một khuôn mẫu lấy mẫu cho Sql Server bằng cách sử dụng các khung nhìn vật chất. Có một liên kết đến tờ giấy thực tế.

7

nếu bạn muốn đạt được ví dụ: var count = 16 hàng ngẫu nhiên từ bảng, bạn có thể viết

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

ở đây tôi sử dụng EF và Bảng là một Dbset

0

Tôi có truy vấn hàm ngẫu nhiên chống lại DataTable s:

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

Đến đây tự hỏi làm thế nào để có được một vài trang ngẫu nhiên từ một số nhỏ trong số họ, vì vậy mỗi người dùng nhận được một số ngẫu nhiên khác nhau 3 trang.

Đây là giải pháp cuối cùng của tôi, làm việc truy vấn với LINQ chống lại một danh sách các trang trong Sharepoint 2010. Đó là trong Visual Basic, xin lỗi: p

Dim Aleatorio As New Random() 

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

lẽ sẽ nhận được một số hồ sơ trước khi truy vấn một số lượng lớn các kết quả , nhưng nó hoàn hảo cho mục đích của tôi

0

Sử dụng LINQ to SQL trong LINQPad như C# báo cáo trông giống như

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

SQL được tạo ra là

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

Ví dụ bên dưới sẽ gọi nguồn để truy xuất số và sau đó áp dụng biểu thức bỏ qua trên nguồn có số từ 0 đến n. Phương pháp thứ hai sẽ áp dụng thứ tự bằng cách sử dụng đối tượng ngẫu nhiên (sẽ sắp xếp mọi thứ trong bộ nhớ) và chọn số được chuyển vào cuộc gọi phương thức.

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

Một số giải thích sẽ là tốt đẹp –

+0

Mã này không phải là chủ đề an toàn và chỉ có thể được sử dụng trong mã đơn luồng (vì vậy ** không ** ASP.NET) –

0

tôi sử dụng phương pháp này cho mất tin tức ngẫu nhiên và tốt công việc của mình;)

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

Giải thích: Bằng cách chèn các guid (đó là ngẫu nhiên) các đơn đặt hàng với orderby sẽ là ngẫu nhiên.

+0

"ngẫu nhiên" chúng không liên tục. Có một sự khác biệt. Trong thực tế nó có thể không quan trọng đối với một cái gì đó tầm thường như thế này. –

0

Nếu bạn sử dụng LINQPad, chuyển sang C chương trình # chế độ và làm theo cách này:

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

Chọn ngẫu nhiên 2 hàng

0

Bổ sung vào giải pháp Marc Gravell của. Nếu bạn không làm việc với chính lớp dữ liệu (vì bạn proxy nó bằng cách nào đó giả mạo datacontext cho mục đích thử nghiệm), bạn không thể sử dụng trực tiếp UDF đã định nghĩa: nó sẽ không được biên dịch sang SQL vì bạn không sử dụng nó trong một lớp con hoặc một phần của lớp ngữ cảnh dữ liệu thực của bạn.

Một cách giải quyết cho vấn đề này là tạo ra một hàm Randomize trong proxy của bạn, cho ăn nó với các truy vấn mà bạn muốn được ngẫu nhiên:

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

Đây là cách bạn muốn sử dụng nó trong mã của bạn:

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

để được hoàn thành, đây là làm thế nào để thực hiện điều này trong DataContext GIẢ (trong đó sử dụng vào đơn vị bộ nhớ):

public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query) 
{ 
    return query.OrderBy(x => Guid.NewGuid()); 
} 
Các vấn đề liên quan