Fila aleatoria de Linq a Sql

¿Cuál es la mejor (y más rápida) forma de recuperar una fila aleatoria usando Linq a SQL cuando tengo una condición, por ejemplo, algún campo debe ser verdadero?

Puede hacer esto en la base de datos, utilizando un UDF falso; en una clase parcial, agregue un método al contexto de datos:

partial class MyDataContext { [Function(Name="NEWID", IsComposable=true)] public Guid Random() { // to prove not used by our C# code... throw new NotImplementedException(); } } 

Luego solo order by ctx.Random() ; esto hará un pedido aleatorio en el SQL-Server cortesía de NEWID() . es decir

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

Tenga en cuenta que esto solo es adecuado para tablas de tamaño pequeño a mediano; para tablas grandes, tendrá un impacto en el rendimiento en el servidor, y será más eficiente encontrar el número de filas ( Count ), luego elija una al azar ( Skip/First ).


para el enfoque de recuento:

 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 

Otra muestra para Entity Framework:

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

Esto no funciona con LINQ to SQL. El OrderBy simplemente se está descartando.

EDITAR: Acabo de notar que esto es LINQ to SQL, no LINQ to Objects. Use el código de Marc para que la base de datos haga esto por usted. He dejado esta respuesta aquí como un posible punto de interés para LINQ to Objects.

Por extraño que parezca, en realidad no necesitas obtener el conteo. Sin embargo, debe buscar cada elemento a menos que obtenga el conteo.

Lo que puede hacer es mantener la idea de un valor “actual” y el recuento actual. Cuando obtenga el siguiente valor, tome un número aleatorio y reemplace el “actual” con “nuevo” con una probabilidad de 1 / n donde n es el recuento.

Entonces, cuando lee el primer valor, siempre lo convierte en el valor “actual”. Cuando lee el segundo valor, puede hacer que ese sea el valor actual (probabilidad 1/2). Cuando lees el tercer valor, puedes hacer que ese sea el valor actual (probabilidad 1/3), etc. Cuando te quedas sin datos, el valor actual es uno al azar de todos los que lees, con probabilidad uniforme.

Para aplicar eso con una condición, simplemente ignore todo lo que no cumpla con la condición. La manera más fácil de hacerlo es considerar solo la secuencia de “coincidencia” para empezar, aplicando primero una cláusula Where.

Aquí hay una implementación rápida. Creo que está bien …

 public static T RandomElement(this IEnumerable 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; } 

Una manera de lograrlo de manera eficiente es agregar una columna a su Shuffle datos que se rellena con un int aleatorio (a medida que se crea cada registro).

La consulta parcial para acceder a la tabla en orden aleatorio es …

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

Esto hace una operación XOR en la base de datos y ordena por los resultados de ese XOR.

Ventajas:

  1. Eficiente: SQL maneja el pedido, no es necesario buscar toda la tabla
  2. Repetible: (bueno para las pruebas): puede usar la misma semilla aleatoria para generar el mismo orden aleatorio

Este es el enfoque utilizado por mi sistema domótico para aleatorizar listas de reproducción. Escoge una nueva semilla cada día, dando un orden constante durante el día (lo que permite una fácil pausa / reanudación de las funciones), pero una nueva mirada a cada lista de reproducción cada nuevo día.

si quieres obtener, por ejemplo, var count = 16 filas aleatorias de la tabla, puedes escribir

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

aquí utilicé EF, y la tabla es un Dbset

Si el propósito de obtener filas aleatorias es el muestreo, he hablado brevemente aquí sobre un enfoque agradable de Larson et al., Equipo de Microsoft Research donde han desarrollado un marco de muestreo para Sql Server utilizando vistas materializadas. También hay un enlace al documento real.

Vine aquí preguntándome cómo obtener algunas páginas aleatorias de un pequeño número de ellas, por lo que cada usuario obtiene diferentes 3 páginas al azar.

Esta es mi solución final, trabajando consultando con LINQ contra una lista de páginas en Sharepoint 2010. Está en Visual Basic, lo siento: p

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

Probablemente debería obtener algunos perfiles antes de consultar una gran cantidad de resultados, pero es perfecto para mi propósito

 List lst = new List(); 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(); 

Explicación: Al insertar el guid (que es aleatorio), el orden con orderby sería aleatorio.

Tengo una consulta de función aleatoria contra DataTable s:

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

El siguiente ejemplo llamará a la fuente para recuperar un conteo y luego aplicará una expresión de omisión en la fuente con un número entre 0 y n. El segundo método aplicará el orden utilizando el objeto aleatorio (que ordenará todo en la memoria) y seleccionará el número pasado a la llamada al método.

 public static class IEnumerable { static Random rng = new Random((int)DateTime.Now.Ticks); public static T RandomElement(this IEnumerable source) { T current = default(T); int c = source.Count(); int r = rng.Next(c); current = source.Skip(r).First(); return current; } public static IEnumerable RandomElements(this IEnumerable source, int number) { return source.OrderBy(r => rng.Next()).Take(number); } } 

uso este método para tomar noticias aleatorias y funciona bien;)

  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(); 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("
" + "{1}
", news.ID, news.Title); } } return temp; }

Usar LINQ to SQL en LINQPad como declaraciones C # se ven como

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

El SQL generado es

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

Si usa LINQPad , cambie al modo de progtwig C # y haga esto:

 void Main() { YourTable.OrderBy(v => Random()).FirstOrDefault.Dump(); } [Function(Name = "NEWID", IsComposable = true)] public Guid Random() { throw new NotImplementedException(); } 
 var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2); 

Seleccionar 2 filas aleatorias

Para agregar a la solución de Marc Gravell. Si no está trabajando con la clase de contexto de datos en sí (porque la utiliza de algún modo, por ejemplo, para falsificar el contexto de datos para fines de prueba), no puede usar la UDF definida directamente: no se comstackrá en SQL porque no la está utilizando en una subclase o clase parcial de su clase de contexto de datos reales.

Una solución alternativa para este problema es crear una función Aleatorizar en su proxy, alimentándolo con la consulta que desea aleatorizar:

 public class DataContextProxy : IDataContext { private readonly DataContext _context; public DataContextProxy(DataContext context) { _context = context; } // Snipped irrelevant code public IOrderedQueryable Randomize(IQueryable query) { return query.OrderBy(x => _context.Random()); } } 

Así es como lo usarías en tu código:

 var query = _dc.Repository(); query = _dc.Randomize(query); 

Para ser completo, esta es la forma de implementar esto en el contexto de datos FALSOS (que se usa en las entidades de memoria):

 public IOrderedQueryable Randomize(IQueryable query) { return query.OrderBy(x => Guid.NewGuid()); }