Случайная строка от Linq до Sql


112

Каков наилучший (и самый быстрый) способ получить случайную строку с помощью Linq to SQL, когда у меня есть условие, например, какое-то поле должно быть истинным?


У вас есть два варианта порядка проверки истинных условий. Если истинное условие встречается с большинством предметов, просто возьмите случайный предмет, затем проверьте и повторите, пока ложно. Если редко, позвольте базе данных ограничить варианты истинным условием, а затем выберите случайным образом.
Рекс Логан,

1
Как и в случае с множеством ответов на этом сайте, второй рейтинг намного лучше принятого.
nikib3ro

Ответы:


170

Вы можете сделать это в базе данных, используя поддельный 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-сервере NEWID(). т.е.

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

Обратите внимание, что это подходит только для столов малого и среднего размера; для огромных таблиц это повлияет на производительность сервера, и будет более эффективно найти количество строк ( Count), а затем выбрать одну случайным образом ( 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

3
Если это 30k после фильтра, я бы сказал нет: не используйте этот подход. Сделайте 2 поездки туда и обратно; 1, чтобы получить счетчик (), и 1, чтобы получить случайную строку ...
Марк Грейвелл

1
Что если вам нужно пять (или «x») случайных строк? Лучше всего сделать шесть циклов приема-передачи или есть удобный способ реализовать это в хранимой процедуре?
Нил Штублен, 02

2
@Neal S .: заказ ctx.Random () может быть смешан с Take (5); но если вы используете подход Count (), я думаю, что 6 циклов приема-передачи - самый простой вариант.
Марк Грейвелл

1
не забудьте добавить ссылку на System.Data.Linq или атрибут System.Data.Linq.Mapping.Function, который не будет работать.
Jaguir

8
Я знаю, что это устарело , но если вы выбираете много случайных строк из большой таблицы, см. Это: msdn.microsoft.com/en-us/library/cc441928.aspx Я не знаю, есть ли эквивалент LINQ.
jwd 08

60

Еще один пример для Entity Framework:

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

Это не работает с LINQ to SQL. OrderByПросто при падении.


4
Вы профилировали это и подтвердили, что это работает? В моих тестах с использованием LINQPad предложение order by отбрасывается.
Джим Вули,

Это лучшее решение этой проблемы
seek4thelasers

8
Это не работает в LINQ to SQL ... возможно, это работает в Entity Framework 4 (не подтверждая это). Вы можете использовать .OrderBy с Guid, только если вы сортируете список ... с БД это не сработает.
nikib3ro

2
Просто чтобы окончательно подтвердить, что это работает в EF4 - в таком случае это отличный вариант.
nikib3ro

1
Не могли бы вы отредактировать свой ответ и объяснить, почему orderBy с новым Guid помогает? Кстати, хороший ответ :)
Жан-Франсуа Коте

32

РЕДАКТИРОВАТЬ: Я только что заметил, что это LINQ to SQL, а не LINQ to Objects. Используйте код Марка, чтобы база данных сделала это за вас. Я оставил этот ответ здесь как потенциальный интерес для LINQ to Objects.

Как ни странно, на самом деле вам не нужно подсчитывать. Однако вам нужно получить каждый элемент, если вы не получите счетчик.

Что вы можете сделать, так это сохранить представление о «текущем» значении и текущем счетчике. Когда вы выбираете следующее значение, возьмите случайное число и замените «текущее» на «новое» с вероятностью 1 / n, где n - количество.

Поэтому, когда вы читаете первое значение, вы всегда делаете его «текущим» значением. Когда вы читаете второе значение, вы можете сделать это текущее значение (вероятность 1/2). Когда вы читаете третье значение, вы можете сделать это текущее значение (вероятность 1/3) и т. Д. Когда у вас закончились данные, текущее значение является случайным из всех считанных вами значений с равномерной вероятностью.

Чтобы применить это с условием, просто игнорируйте все, что не соответствует условию. Самый простой способ сделать это - рассмотреть для начала только «совпадающую» последовательность, применив сначала предложение 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;
}

4
К вашему сведению - я провел быструю проверку, и эта функция действительно имеет равномерное распределение вероятностей (увеличение счетчика - это, по сути, тот же механизм, что и тасование Фишера-Йетса, поэтому кажется разумным, что это должно быть).
Грег Бич,

@Greg: Круто, спасибо. Быстрая проверка показалась мне нормой, но в таком коде очень легко избавиться от единичных ошибок. Конечно, практически не имеет отношения к LINQ to SQL, но тем не менее полезно.
Джон Скит,

@JonSkeet, привет, ты можешь проверить это и сообщить мне, что мне не хватает
Shaijut

@TylerLaing: Нет, перерывов быть не должно. На первой итерации, currentбудет всегда быть установлен на первый элемент. На второй итерации происходит изменение на 50%, которое будет установлено для второго элемента. На третьей итерации с вероятностью 33% будет установлен третий элемент. Добавление оператора break будет означать, что вы всегда будете выходить после прочтения первого элемента, что делает его совсем не случайным.
Джон Скит,

@JonSkeet Doh! Я неправильно понял, как вы используете счетчик (например, думал, что это стиль Фишера-Йейтса со случайным диапазоном, например ni). Но выбрать первый элемент в Fisher-Yates - значит справедливо выбрать любой из элементов. Однако для этого необходимо знать общее количество элементов. Теперь я вижу, что ваше решение подходит для IEnumerable в том смысле, что общее количество неизвестно, и нет необходимости перебирать весь источник только для того, чтобы получить количество, а затем снова перебирать до некоторого случайно выбранного индекса. Скорее это решает за один проход, как вы заявили: «необходимо получить каждый элемент, если вы не получите счетчик».
Тайлер Лэйнг

19

Один из способов добиться эффективного - добавить к вашим данным столбец, Shuffleкоторый заполняется случайным int (по мере создания каждой записи).

Частичный запрос для доступа к таблице в случайном порядке ...

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

Это выполняет операцию XOR в базе данных и упорядочивает результаты этого XOR.

Преимущества: -

  1. Эффективно: SQL обрабатывает порядок, нет необходимости извлекать всю таблицу
  2. Повторяемый: (подходит для тестирования) - можно использовать одно и то же случайное начальное число для генерации одного и того же случайного порядка

Это подход, используемый моей системой домашней автоматизации для рандомизации списков воспроизведения. Он выбирает новое начальное число каждый день, давая постоянный порядок в течение дня (позволяя легко приостанавливать / возобновлять воспроизведение), но каждый день свежим взглядом на каждый список воспроизведения.


как бы повлиял на случайность, если бы вместо добавления случайного поля int вы просто использовали существующее автоматически увеличивающееся поле идентификации (семя, очевидно, останется случайным)? также - является ли начальное значение с максимальным значением, равным количеству записей в таблице, или оно должно быть выше?
Брайан

Согласен, это отличный ответ, за который ИМО должно набрать больше голосов. Я использовал это в запросе Entity Framework, и побитовый оператор XOR ^, кажется, работает напрямую, что делает условие немного более чистым: result = result.OrderBy(s => s.Shuffle ^ seed);(т.е. нет необходимости реализовывать XOR с помощью операторов ~, & и |).
Стивен Рэндс,

7

если вы хотите получить, например, var count = 16случайные строки из таблицы, вы можете написать

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

здесь я использовал EF, а таблица - это Dbset


1

Если целью получения случайных строк является выборка, я очень кратко рассказал здесь о хорошем подходе Ларсона и др., Исследовательской группы Microsoft, где они разработали структуру выборки для Sql Server с использованием материализованных представлений. Также есть ссылка на сам документ.


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

Объяснение: При вставке guid (который является случайным) порядок с помощью orderby будет случайным.


Гиды не являются «случайными», они непоследовательны. Есть разница. На практике это, вероятно, не имеет значения для такой тривиальной вещи.
Крис Марисич

0

Пришел сюда, задаваясь вопросом, как получить несколько случайных страниц из небольшого их количества, чтобы каждый пользователь получил несколько разных случайных 3 страниц.

Это мое последнее решение: работа с запросами с LINQ по списку страниц в Sharepoint 2010. Это в Visual Basic, извините: p

Dim Aleatorio As New Random()

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

Вероятно, следует провести профилирование, прежде чем запрашивать большое количество результатов, но это идеально подходит для моей цели


0

У меня есть случайный запрос функции к DataTables:

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

0

В приведенном ниже примере вызывается источник для получения счетчика, а затем применяется выражение пропуска к источнику с числом от 0 до n. Второй метод применяет порядок, используя случайный объект (который упорядочит все в памяти) и выбирает число, переданное в вызов метода.

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

Некоторое объяснение было бы неплохо
Эндрю Барбер

Этот код не является потокобезопасным и может использоваться только в однопоточном коде (но не в ASP.NET)
Крис Марисич

0

Я использую этот метод для получения случайных новостей, и он отлично работает;)

    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

Использование LINQ to SQL в LINQPad, поскольку операторы C # выглядят так

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

Сгенерированный SQL

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

0

Если вы используете LINQPad , переключитесь в программный режим C # и сделайте следующее:

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

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


0

Чтобы добавить к раствору Марка Гравелла. Если вы не работаете с самим классом datacontext (потому что вы каким-то образом проксируете его, например, чтобы подделать текст данных в целях тестирования), вы не можете напрямую использовать определенный UDF: он не будет скомпилирован в SQL, потому что вы не используете его в подкласс или частичный класс вашего реального класса контекста данных.

Обходной путь для этой проблемы - создать функцию Randomize в вашем прокси-сервере, передав ей запрос, который вы хотите рандомизировать:

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

Чтобы завершить, вот как это реализовать в поддельном тексте данных (который используется в объектах памяти):

public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
    return query.OrderBy(x => Guid.NewGuid());
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.