Випадковий рядок від Linq до Sql


112

Який найкращий (і найшвидший) спосіб отримати випадковий рядок за допомогою Linq до SQL, коли у мене є умова, наприклад, якесь поле має бути правдивим?


У вас є два варіанти замовлення, на яке ви перевіряєте справжні умови. Якщо справжня умова трапляється у більшості предметів, тоді просто захопіть випадковий предмет, а потім випробуйте та повторіть помилкове. Якщо рідко, нехай база даних обмежує параметри справжньою умовою, а потім схопимо одну навмання.
Рекс Логан

1
Як і багато відповідей на цьому веб-сайті - другорядний набагато кращий за прийнятий.
nikib3ro

Відповіді:


169

Це можна зробити в базі даних, використовуючи підроблений 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-Server 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
Що робити, якщо ви хочете п'ять (або "х") випадкових рядків? Краще всього зробити шість об’їзних подорожей чи є зручний спосіб здійснити це у зберіганій процедурі?
Ніл Стублен

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

1
не забудьте додати посилання на System.Data.Linq або на атрибут System.Data.Linq.Mapping.Function.
Ягуїр

8
Я знаю, що це старе, але якщо ви вибираєте багато випадкових рядків з великої таблиці, дивіться це: msdn.microsoft.com/en-us/library/cc441928.aspx Я не знаю, чи є еквівалент LINQ.
jwd

60

Ще один зразок для Entity Framework:

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

Це не працює з LINQ в SQL. OrderByПросто при падінні.


4
Ви профілювали це і підтвердили, що це працює? У моїх тестах, що використовують LINQPad, порядок за пунктом відміняється.
Jim Wooley

Це найкраще рішення цієї проблеми
24412

8
Це не працює в LINQ в SQL ... можливо, воно працює в Entity Framework 4 (не підтверджуючи це). Ви можете використовувати .OrderBy з Guid, лише якщо ви сортуєте Список ... з БД, він не працюватиме.
nikib3ro

2
Тільки щоб остаточно підтвердити, що це працює в EF4 - це чудовий варіант у такому випадку.
nikib3ro

1
Не могли б ви відредагувати свою відповідь та пояснити, чому замовленняБой з новим керівництвом роблять фокус? Приємна відповідь до речі :)
Жан-Франсуа Коте

32

EDIT: Я тільки що помітив, що це LINQ для SQL, а не LINQ для об'єктів. Використовуйте код Марка, щоб отримати базу даних, щоб зробити це за вас. Цю відповідь я залишив тут як потенційну цікаву для LINQ об’єкти.

Як не дивно, вам не потрібно насправді рахувати. Однак вам потрібно отримати кожен елемент, якщо ви не отримаєте рахунок.

Що ви можете зробити, це зберегти уявлення про "поточне" значення та кількість поточних. Коли ви отримаєте наступне значення, візьміть випадкове число і замініть "струм" на "новий" з ймовірністю 1 / n, де n - це число.

Отже, читаючи перше значення, ви завжди робите це "поточне" значення. Коли ви читаєте друге значення, ви могли б зробити , що поточне значення (ймовірність 1/2). Коли ви читаєте третє значення, ви могли б зробити , що поточне значення (ймовірність 1/3) і т.д. Коли ви біжите з даних, поточне значення є випадковою один з усіх тих , які ви читаєте, з равновероятно.

Щоб застосувати це до умови, просто ігноруйте все, що не відповідає умові. Найпростіший спосіб зробити це - лише розглянути послідовність «узгодження» для початку, застосувавши спочатку пункт «Де».

Ось швидка реалізація. Я думаю, що це нормально ...

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 - я здійснив швидку перевірку, і ця функція має рівномірний розподіл ймовірностей (приріст підрахунку по суті є тим самим механізмом, що і переміщення Fisher-Yates, тому здається, що це повинно бути).
Грег Бук

@Greg: Класно, дякую. Мені це здалося нормальним шляхом швидкої перевірки, але так легко отримати помилки в одному коді, як цей. Фактично не має значення для LINQ для SQL, звичайно, але корисна, тим не менш.
Джон Скіт

@JonSkeet, привіт, ви можете перевірити це і дайте мені знати , що я НЕ вистачає
shaijut

@TylerLaing: Ні, перерви не передбачається. На першій ітерації, currentбуде завжди бути встановлений на перший елемент. Під час другої ітерації є 50% -на зміна, що вона буде встановлена ​​на другий елемент. На третій ітерації є 33% шансів, що вона буде встановлена ​​на третій елемент. Додавання заяви про перерву означатиме, що ви завжди будете виходити, прочитавши перший елемент, зробивши його зовсім не випадковим.
Джон Скіт

@JonSkeet Doh! Я неправильно прочитав ваше використання count (наприклад, думав, що це стиль Фішера-Йейта з випадковим діапазоном, як ni). Але вибрати перший елемент у Фішера-Йейта - це справедливо вибрати будь-який з елементів. Однак для цього потрібно знати загальну кількість елементів. Тепер я бачу, що ваше рішення є чітким для IE Численні тим, що загальний підрахунок не відомий, і немає необхідності перебирати по всьому джерелу, щоб отримати підрахунок, щоб потім повторити його до якогось випадково вибраного індексу. Швидше це вирішується за один прохід, як ви заявили: "потрібно отримати кожен елемент, якщо ви не отримаєте кількість".
Тайлер Лаїнг

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

Якщо метою отримання випадкових рядків вибірки, я говорив дуже коротко тут про хороше підході від команди Larson і ін., Microsoft Research , де вони розробили основи вибірки для 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();

Пояснення: Вставивши довідник (який є випадковим), порядок із замовленням буде випадковим.


Посібники не є "випадковими", вони не є послідовними. Є різниця. На практиці це, мабуть, не має значення для чогось тривіального.
Кріс Марісіч

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)
Chris Marisic

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 для 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 (тому що ви якось проксі-сервер, наприклад, щоб підробити 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);

Щоб завершити, ось як це реалізувати в контексті FAKE даних (який використовується в об'єктах пам'яті):

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.