【问题标题】:Random row from Linq to Sql从 Linq 到 Sql 的随机行
【发布时间】:2010-10-13 11:43:27
【问题描述】:

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

【问题讨论】:

  • 检查真实条件的顺序有两种选择。如果大多数项目都发生了真实情况,那么只需抓取一个随机项目,然后测试并在错误时重复。如果很少,让数据库将选项限制为真实条件,然后随机抓取一个。
  • 和这个网站上的很多答案一样——二等奖比公认的要好得多。

标签: c# .net linq-to-sql


【解决方案1】:

您可以在数据库中使用伪造的 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();这将在NEWID() 提供的 SQL-Server 上进行随机排序。即

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

【讨论】:

  • 如果是 30k 过滤器,我会说不:不要使用这种方法。做2次往返; 1 获取 Count(),1 获取随机行...
  • 如果你想要五个(或“x”)随机行怎么办?最好只进行六次往返,还是有一种方便的方法可以在存储过程中实现它?
  • @Neal S.:ctx.Random() 的顺序可以与 Take(5) 混合;但如果您使用的是 Count() 方法,我预计 6 次往返是最简单的选择。
  • 不要忘记添加对 System.Data.Linq 的引用,否则 System.Data.Linq.Mapping.Function 属性将不起作用。
  • 我知道这是旧的,但如果您从大表中选择许多随机行,请参阅:msdn.microsoft.com/en-us/library/cc441928.aspx我不知道是否有 LINQ 等效项。
【解决方案2】:

实体框架的另一个示例:

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

这不适用于 LINQ to SQL。 OrderBy 只是被删除了。

【讨论】:

  • 您是否对此进行了分析并确认它有效?在我使用 LINQPad 的测试中,order by 子句被删除了。
  • 这是解决这个问题的最好办法
  • 这在 LINQ to SQL 中不起作用......也许它在 Entity Framework 4 中起作用(未确认)。如果要对 List 进行排序,则只能将 .OrderBy 与 Guid 一起使用...使用 DB 将无法正常工作。
  • 只是为了最终确认这在 EF4 中有效 - 在这种情况下这是一个很好的选择。
  • 您能否编辑您的答案并解释为什么带有新 Guid 的 orderBy 可以解决问题?顺便说一句,答案很好:)
【解决方案3】:

编辑:我刚刚注意到这是 LINQ to SQL,而不是 LINQ to Objects。使用 Marc 的代码让数据库为您执行此操作。我把这个答案留在这里作为 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;
}

【讨论】:

  • 仅供参考 - 我快速检查了一下,这个函数确实有一个均匀的概率分布(递增计数与 Fisher-Yates shuffle 的机制基本相同,所以它应该是合理的)。
  • @Greg:很酷,谢谢。快速检查一下对我来说还不错,但是在这样的代码中很容易出现一个错误。当然,与 LINQ to SQL 几乎无关,但仍然有用。
  • @JonSkeet,嗨,你能检查一下this,让我知道我错过了什么
  • @TylerLaing:不,这不是要休息的意思。在第一次迭代中,current始终设置为第一个元素。在第二次迭代中,将设置为第二个元素有 50% 的变化。在第三次迭代中,有 33% 的机会将其设置为第三个元素。添加一个 break 语句意味着您总是在读取第一个元素后退出,使其根本不是随机的。
  • @JonSkeet Doh!我误读了您对计数的使用(例如,认为这是 Fisher-Yates 风格,具有像 n-i 这样的随机范围)。但是选择 Fisher-Yates 中的第一个元素就是公平地选择任何元素。但是,这需要知道元素的总数。我现在看到您的解决方案对于 IEnumerable 来说是整洁的,因为总计数未知,并且无需迭代整个源以获得计数,然后再次迭代到一些随机选择的索引。相反,正如您所说,这可以一次性解决:“除非您获得计数,否则需要获取每个元素”。
【解决方案4】:

一种有效实现的方法是向您的数据Shuffle 添加一列,该列填充了随机整数(在创建每条记录时)。

以随机顺序访问表的部分查询是...

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 字段,而是使用现有的自动递增标识字段(种子显然会保持随机),对随机性的影响是什么?还有 - 最大等于表中记录数的种子值是否足够或应该更高?
  • 同意,这是一个很好的答案,IMO 应该有更多的赞成票。我在实体框架查询中使用了它,按位异或运算符 ^ 似乎可以直接工作,从而使条件更清晰:result = result.OrderBy(s =&gt; s.Shuffle ^ seed);(即无需通过 ~、& 和 | 运算符实现异或)。
【解决方案5】:

如果你想得到例如var count = 16表中的随机行,你可以写

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

这里我用的是 E.F,表是一个 Dbset

【讨论】:

    【解决方案6】:

    如果获取随机行的目的是采样,我已经非常简短地讨论了here 来自微软研究团队 Larson 等人的一个很好的方法,他们使用物化视图为 Sql Server 开发了一个采样框架。还有实际论文的链接。

    【讨论】:

      【解决方案7】:

      来到这里想知道如何从少数几个随机页面中获取一些随机页面,以便每个用户获得一些不同的随机 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
      

      在查询大量结果之前可能应该进行一些分析,但这非常适合我的目的

      【讨论】:

        【解决方案8】:

        我有针对DataTables 的随机函数查询:

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

        【讨论】:

          【解决方案9】:

          下面的示例将调用源来检索计数,然后在源上应用一个跳过表达式,其中包含一个介于 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)
          【解决方案10】:

          我使用这种方法来获取随机新闻并且效果很好;)

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

          【讨论】:

            【解决方案11】:

            在 LINQPad 中使用 LINQ to SQL 作为 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()
            

            【讨论】:

              【解决方案12】:

              如果您使用 LINQPad,请切换到 C# 程序 模式并这样做:

              void Main()
              {
                  YourTable.OrderBy(v => Random()).FirstOrDefault.Dump();
              }
              
              [Function(Name = "NEWID", IsComposable = true)]
              public Guid Random()
              {
                  throw new NotImplementedException();
              }
              

              【讨论】:

                【解决方案13】:
                var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);
                

                随机选择 2 行

                【讨论】:

                  【解决方案14】:

                  添加到 Marc Gravell 的解决方案中。如果您不使用 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());
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-06-04
                    • 1970-01-01
                    相关资源
                    最近更新 更多