从Linq到Sql的随机行


112

当我有条件(例如某些字段必须为真)时,使用Linq to SQL检索随机行的最佳(最快)方法是什么?


对于检查真实条件的订单,您有两个选择。如果大多数物品上都出现了真实情况,则只需随机抓取一个物品,然后测试并在错误时重复。如果很少,请让数据库将选项限制为真实条件,然后随机获取一个。
雷克斯·洛根

1
与该站点上的许多答案一样,第二评价要比公认的好得多。
nikib3ro

Answers:


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 ,我会说不:不要使用这种方法。进行两次往返;1获得Count(),1获得随机行...
Marc Gravell

1
如果要五行(或“ x”)随机行怎么办?是最好只进行六次往返,还是有便捷的方法在存储过程中实现它?
Neal Stublen

2
@Neal S .:可以将ctx.Random()的顺序与Take(5)混合;但是,如果您使用Count()方法,那么我希望6次往返是最简单的选择。
Marc Gravell

1
不要忘记添加对System.Data.Linq的引用,否则System.Data.Linq.Mapping.Function属性将无法正常工作。
Jaguir

8
我知道这很旧,但是如果您要从一个大表中选择许多随机行,请参见:msdn.microsoft.com/zh-cn/library/cc441928.aspx 我不知道是否有LINQ等效项。
2011年

60

实体框架的另一个示例:

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

这不适用于LINQ to SQL。在OrderBy简单地被丢弃。


4
您是否对此资料进行了分析并确认它有效?在我使用LINQPad的测试中,order by子句被删除。
Jim Wooley

这是解决此问题的最佳方法
到达4thelasers 2012年

8
这在LINQ to SQL中不起作用...也许在Entity Framework 4中有效(未确认)。如果您要对DB排序List ...,则只能将.OrderBy与Guid一起使用,它将无法工作。
nikib3ro 2012年

2
只是为了最终确认它可以在EF4中使用-在这种情况下,这是一个不错的选择。
nikib3ro

1
您能编辑答案并解释为什么orderBy通过新的Guid可以解决问题吗?顺便说一下:)尼斯的答案
让-弗朗索瓦的Côté

32

编辑:我只注意到这是LINQ to SQL,而不是LINQ to Objects。使用Marc的代码获取数据库来为您完成此任务。我在这里留下了这个答案,作为LINQ to Objects的潜在兴趣点。

奇怪的是,您实际上并不需要计数。但是,您确实需要获取每个元素,除非获得计数。

您可以做的是保留“当前”值和当前计数的概念。当您获取下一个值时,请取一个随机数,然后以“ / n”的概率将“ current”替换为“ new”,其中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
仅供参考-我进行了快速检查,此函数的确具有均匀的概率分布(递增计数与Fisher-Yates随机播放本质上是相同的机制,因此似乎应该是合理的)。
格雷格·比奇

@Greg:太好了,谢谢。快速检查对我来说似乎还可以,但是在这样的代码中容易出现一个错误是很容易的。当然,这实际上与LINQ与SQL无关,但是仍然很有用。
乔恩·斯基特

@JonSkeet,您好,您能检查一下并让我知道我在想什么吗
shaijut 2015年

@TylerLaing:不,这并不意味着休息。在第一次迭代中,current始终设置为第一个元素。在第二次迭代中,有50%的更改将其设置为第二个元素。在第三次迭代中,有33%的机会将其设置为第三个元素。添加break语句意味着您将始终在读取第一个元素后退出,从而使其根本不是随机的。
乔恩·斯基特

@JonSkeet Doh!我误读了您对count的使用(例如,以为这是带有ni等随机范围的Fisher-Yates风格)。但是要选择Fisher-Yates中的第一个元素,就是要公平地选择任何元素。但是,这需要知道元素的总数。我现在看到的是,您的解决方案对于IEnumerable而言是很整洁的,因为总计数是未知的,并且不需要遍历整个源仅获取计数,然后再次遍历一些随机选择的索引。就像您所说的那样,它可以一口气解决:“除非获得计数,否则需要获取每个元素”。
Tyler Laing

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字段而是仅使用现有的自动递增身份字段(种子显然会保持随机),将会对随机性产生什么影响?还有-最大值等于表中记录数的种子值是否足够或应该更高?
布莱恩(Bryan)

同意,这是一个很好的答案,国际海事组织应该对此做出更多投票。我在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研究团队Larson等人的一种不错的方法,他们在其中使用实例化视图为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);
    }
}

一些解释会很好
Andrew Barber 2012年

此代码不是线程安全的,只能在单线程代码中使用(因此不能在 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

像C#语句一样在LINQPad中使用LINQ to SQL

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
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);

选择随机2行


0

添加到Marc Gravell的解决方案中。如果您不使用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());
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.