Hàng ngẫu nhiên từ Linq đến Sql


112

Cách tốt nhất (và nhanh nhất) để truy xuất một hàng ngẫu nhiên bằng Linq to SQL là gì khi tôi có một điều kiện, ví dụ như trường nào đó phải đúng?


Bạn có hai lựa chọn cho thứ tự mà bạn kiểm tra các điều kiện thực sự. Nếu điều kiện đúng 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, hãy để cơ sở dữ liệu giới hạn các tùy chọn ở điều kiện đúng và sau đó lấy ngẫu nhiên một tùy chọn.
Rex Logan

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 câu trả lời được chấp nhận.
nikib3ro

Câu trả lời:


169

Bạn có thể thực hiện việc này tại cơ sở dữ liệu, bằng cách sử dụng UDF giả mạo; trong một lớp từng phần, hãy thêm một 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ỉ cần order by ctx.Random(); điều này sẽ thực hiện một thứ tự ngẫu nhiên theo sự cho phép của SQL-Server NEWID(). I E

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àn có kích thước từ nhỏ đến trung bình; đối với các bảng lớn, nó sẽ có tác động đến 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 ngẫu nhiên một hàng ( Skip/First).


đối với 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

3
Nếu nó là 30k sau bộ lọc, tôi sẽ nói không: không sử dụng cách tiếp cận này. Làm 2 chuyến khứ hồi; 1 để nhận Đếm () và 1 để nhận một hàng ngẫu nhiên ...
Marc Gravell

1
Điều gì sẽ xảy ra nếu bạn muốn có năm (hoặc "x") hàng ngẫu nhiên? Tốt nhất là chỉ nên thực hiện sáu chuyến khứ hồi hay có cách nào thuận tiện để thực hiện nó trong một thủ tục được lưu trữ?
Neal Stublen

2
@Neal S.: thứ tự của ctx.Random () có thể được trộn với Take (5); nhưng nếu bạn đang sử dụng phương pháp Count (), tôi cho rằng 6 chuyến khứ hồi là lựa chọn đơn giản nhất.
Marc Gravell

1
đừng quên thêm một tham chiếu đến System.Data.Linq hoặc thuộc tính System.Data.Linq.Mapping.Function sẽ không hoạt động.
Jaguir

8
Tôi biết điều này đã cũ, nhưng nếu bạn đang chọn nhiều hàng ngẫu nhiên từ một bảng lớn, hãy xem phần này: msdn.microsoft.com/en-us/library/cc441928.aspx Tôi không biết có LINQ tương đương hay không.
jwd

60

Một mẫu khác cho Khung thực thể:

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

Điều này không hoạt động với LINQ to SQL. Đơn OrderBygiản là bị bỏ.


4
Bạn đã lập hồ sơ này và xác nhận rằng nó hoạt động chưa? Trong các thử nghiệm của tôi bằng cách sử dụng LINQPad, thứ tự từng điều khoản đang bị loại bỏ.
Jim Wooley,

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

8
Điều này không hoạt động trong LINQ to SQL ... có thể nó hoạt động trong Entity Framework 4 (không xác nhận nó). Bạn chỉ có thể sử dụng .OrderBy với Guid nếu bạn đang sắp xếp một Danh sách ... với DB, nó sẽ không hoạt động.
nikib3ro

2
Cuối cùng chỉ để xác nhận rằng điều này hoạt động trong EF4 - đó là lựa chọn tuyệt vời trong trường hợp đó.
nikib3ro

1
Bạn có thể chỉnh sửa câu trả lời của mình và giải thích lý do tại sao đặt hàng Bằng cách sử dụng Hướng dẫn mới không? Nhân tiện, câu trả lời rất hay :)
Jean-François Côté

32

CHỈNH SỬA: Tôi chỉ nhận thấy đây là LINQ to SQL, không phải LINQ to Object. Sử dụng mã của Marc để lấy cơ sở dữ liệu thực hiện việc này cho bạn. Tôi đã để câu trả lời này ở đây như một điểm quan tâm tiềm năng cho LINQ đối với các Đối tượng.

Thật kỳ lạ, bạn thực sự không cần phải đếm. Tuy nhiên, bạn cần tìm nạp mọi phần tử trừ khi bạn nhận được số lượng.

Những gì 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, hãy 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 1 / n trong đó n là số đếm.

Vì vậy, khi bạn đọc giá trị đầu tiên, bạn luôn đặt đó là giá trị "hiện tại". Khi bạn đọc giá trị thứ hai, bạn có thể biến nó thành giá trị hiện tại (xác suất 1/2). Khi bạn đọc giá trị thứ ba, bạn có thể đặt giá trị đó thành giá trị hiện tại (xác suất 1/3), v.v. Khi bạn dùng 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 giá trị bạn đọc, với xác suất đồng nhất.

Để áp dụng điều đó với một điều kiện, chỉ cần bỏ qua bất kỳ điều gì không đáp ứng điều kiện. Cách dễ nhất để làm điều đó là chỉ xem xét trình tự "khớp" để bắt đầu, bằng cách áp dụng mệnh đề Where trước.

Đây là một triển khai nhanh chóng. Tôi nghĩ nó ổn ...

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 - Tôi đã kiểm tra nhanh và hàm này có phân phối xác suất đồng đều (số lượng tăng dần về cơ bản giống như cơ chế xáo trộn Fisher-Yates nên có vẻ hợp lý).
Greg Beech

@Greg: Tuyệt, cảm ơn. Tôi kiểm tra nhanh thì có vẻ ổn, nhưng rất dễ gặp lỗi từng cái một trong mã như thế này. Tất nhiên, hầu như không liên quan đến LINQ to SQL, nhưng vẫn hữu ích.
Jon Skeet

@JonSkeet, hi, bạn có thể kiểm tra này và cho tôi biết những gì tôi đang mất tích
shaijut

@TylerLaing: Không, không có nghĩa là phải nghỉ ngơi. Trong lần lặp đầu tiên, currentsẽ luôn được đặt thành phần tử đầu tiên. Ở lần lặp thứ hai, có một sự thay đổi 50% là nó sẽ được đặt thành phần tử thứ hai. Ở lần lặp thứ ba, có 33% khả năng nó sẽ được đặt thành phần tử thứ ba. Thêm một câu lệnh break có nghĩa là bạn sẽ luôn thoát ra sau khi đọc phần tử đầu tiên, khiến nó không hề ngẫu nhiên.
Jon Skeet

@JonSkeet Doh! Tôi đã hiểu sai cách sử dụng số đếm của bạn (ví dụ: tôi nghĩ đây là kiểu Fisher-Yates với phạm vi ngẫu nhiên như ni). Nhưng để chọn yếu tố đầu tiên trong Fisher-Yates là chọn một cách công bằng bất kỳ yếu tố nào. Tuy nhiên, điều đó đòi hỏi phải biết tổng số phần tử. Bây giờ tôi thấy rằng giải pháp của bạn là gọn gàng cho IEnumerable ở chỗ không biết tổng số và không cần phải lặp lại toàn bộ nguồn chỉ để lấy số, sau đó lặp lại thành một số chỉ mục được chọn ngẫu nhiên. Thay vì điều này được giải quyết trong một lần vượt qua, như bạn đã nói: "cần tìm nạp mọi phần tử trừ khi bạn nhận được số lượng".
Tyler Laing

19

Một cách để đạt được hiệu quả là thêm một cột vào dữ liệu của bạn Shuffleđược điền bằng một số nguyên ngẫu nhiên (khi mỗi bản ghi được tạo).

Truy vấn từng phần để truy cập 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);

Điều này thực hiện một hoạt động XOR trong cơ sở dữ liệu và sắp xếp theo kết quả của XOR đó.

Ưu điểm: -

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

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


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

Đồng ý, đây là một câu trả lời tuyệt vời mà IMO nên có nhiều phiếu ủng hộ hơn. Tôi đã sử dụng điều này trong một truy vấn Entity Framework và toán tử bitwise-XOR ^ dường như hoạt động trực tiếp do đó làm cho điều kiện sạch hơn một chút: result = result.OrderBy(s => s.Shuffle ^ seed);(tức là không cần triển khai XOR thông qua các toán tử ~, & và |).
Steven Rands

7

nếu bạn muốn lấy var count = 16các 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


1

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


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 hướng dẫn (là ngẫu nhiên), thứ tự với orderby sẽ là ngẫu nhiên.


Guids không phải là "ngẫu nhiên" mà chúng không tuần tự. Có một sự khác biệt. Trong thực tế, nó có thể không thành vấn đề đối với một thứ tầm thường như thế này.
Chris Marisic

0

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

Đây là giải pháp cuối cùng của tôi, làm việc truy vấn với LINQ dựa trên danh sách các trang trong Sharepoint 2010. Nó nằm 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

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


0

Tôi có truy vấn hàm ngẫu nhiên đối với DataTables:

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

0

Ví dụ dưới đây sẽ gọi nguồn để lấy số đếm và sau đó áp dụng biểu thức bỏ qua trên nguồn với một 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 truyền vào lệnh 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);
    }
}

Một số lời giải thích sẽ được tốt đẹp
Andrew Barber

Mã này không threadsafe và chỉ có thể được sử dụng trong mã luồng duy nhất (vì vậy không ASP.NET)
Chris Marisic

0

tôi sử dụng phương pháp này để lấy tin tức ngẫu nhiên và nó hoạt động tốt;)

    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

Sử dụng LINQ to SQL trong LINQPad dưới dạng câu lệnh C # trông như thế nào

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

SQL được tạo là

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

0

Nếu bạn sử dụng LINQPad , hãy chuyển sang chế độ chương trình C # và thực hiện 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 hàng ngẫu nhiên 2


0

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

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

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 sử dụng nó trong mã của mình:

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

Để hoàn thành, đây là cách thực hiện điều này trong văn bản dữ liệu FAKE (sử dụng trong các thực thể bộ nhớ):

public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
    return query.OrderBy(x => Guid.NewGuid());
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.