วิธีใดที่ดีที่สุด (และเร็วที่สุด) ในการดึงข้อมูลแถวสุ่มโดยใช้ Linq เป็น SQL เมื่อฉันมีเงื่อนไขเช่นบางฟิลด์ต้องเป็นจริง
วิธีใดที่ดีที่สุด (และเร็วที่สุด) ในการดึงข้อมูลแถวสุ่มโดยใช้ Linq เป็น SQL เมื่อฉันมีเงื่อนไขเช่นบางฟิลด์ต้องเป็นจริง
คำตอบ:
คุณสามารถทำได้ที่ฐานข้อมูลโดยใช้ 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
อีกตัวอย่างสำหรับ Entity Framework:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
สิ่งนี้ใช้ไม่ได้กับ LINQ เป็น SQL OrderBy
เป็นเพียงการลดลง
แก้ไข: ฉันเพิ่งสังเกตว่านี่คือ LINQ เป็น 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;
}
current
จะเสมอถูกตั้งค่าเป็นองค์ประกอบแรก ในการทำซ้ำครั้งที่สองมีการเปลี่ยนแปลง 50% ซึ่งจะถูกตั้งค่าเป็นองค์ประกอบที่สอง ในการทำซ้ำครั้งที่สามมีโอกาส 33% ที่จะถูกตั้งค่าเป็นองค์ประกอบที่สาม การเพิ่มคำสั่งแบ่งจะหมายความว่าคุณจะออกหลังจากอ่านองค์ประกอบแรกเสมอทำให้ไม่สุ่มเลย
วิธีหนึ่งในการบรรลุอย่างมีประสิทธิภาพคือการเพิ่มคอลัมน์ให้กับข้อมูลของคุณShuffle
ที่เติมด้วย int แบบสุ่ม (เมื่อสร้างแต่ละระเบียน)
แบบสอบถามบางส่วนเพื่อเข้าถึงตารางตามลำดับแบบสุ่มคือ ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
นี่เป็นการดำเนินการ XOR ในฐานข้อมูลและสั่งการตามผลลัพธ์ของ XOR นั้น
ข้อดี: -
นี่เป็นวิธีที่ระบบอัตโนมัติในบ้านของฉันใช้ในการสุ่มเพลย์ลิสต์ มันเลือกเมล็ดพันธุ์ใหม่ในแต่ละวันโดยให้ลำดับที่สม่ำเสมอในระหว่างวัน (ช่วยให้สามารถหยุดชั่วคราว / เล่นต่อได้ง่าย) แต่ดูใหม่ในแต่ละเพลย์ลิสต์ในแต่ละวันใหม่
result = result.OrderBy(s => s.Shuffle ^ seed);
(กล่าวคือไม่จำเป็นต้องใช้ XOR ผ่านตัวดำเนินการ ~, & และ |)
ถ้าคุณต้องการรับเช่นvar count = 16
สุ่มแถวจากตารางคุณสามารถเขียน
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
ที่นี่ฉันใช้ EF และตารางเป็น Dbset
หากจุดประสงค์ในการสุ่มตัวอย่างแถวสุ่มฉันได้พูดคุยสั้น ๆที่นี่เกี่ยวกับแนวทางที่ดีจาก Larson et al. ทีมวิจัยของ Microsoft ซึ่งพวกเขาได้พัฒนากรอบการสุ่มตัวอย่างสำหรับ Sql Server โดยใช้มุมมองที่เป็นรูปธรรม มีลิงค์ไปยังกระดาษจริงด้วย
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 จะเป็นแบบสุ่ม
มาที่นี่ด้วยความสงสัยว่าจะสุ่มหน้าจากจำนวนน้อย ๆ ได้อย่างไรดังนั้นผู้ใช้แต่ละคนจึงได้รับ 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
น่าจะได้รับการทำโปรไฟล์ก่อนที่จะค้นหาผลลัพธ์จำนวนมาก แต่ก็เหมาะสำหรับวัตถุประสงค์ของฉัน
ฉันมีแบบสอบถามฟังก์ชันสุ่มเทียบกับDataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
ตัวอย่างด้านล่างนี้จะเรียกแหล่งที่มาเพื่อดึงจำนวนจากนั้นใช้นิพจน์การข้ามกับแหล่งที่มาโดยมีตัวเลขระหว่าง 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);
}
}
ฉันใช้วิธีนี้เพื่อรับข่าวสารแบบสุ่มและทำงานได้ดี;)
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;
}
การใช้ 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()
หากคุณใช้LINQPadให้เปลี่ยนเป็นโหมดโปรแกรม C #และดำเนินการดังนี้:
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);
เลือกสุ่ม 2 แถว
เพื่อเพิ่มลงในโซลูชันของ Marc Gravell หากคุณไม่ได้ทำงานกับคลาส datacontext เอง (เนื่องจากคุณใช้พร็อกซีอย่างใดเช่นปลอมบริบทข้อมูลเพื่อวัตถุประสงค์ในการทดสอบ) คุณไม่สามารถใช้ UDF ที่กำหนดได้โดยตรง: จะไม่ถูกคอมไพล์ลงใน SQL เนื่องจากคุณไม่ได้ใช้งานใน a คลาสย่อยหรือคลาสบางส่วนของคลาสบริบทข้อมูลจริงของคุณ
วิธีแก้ปัญหาสำหรับปัญหานี้คือการสร้างฟังก์ชัน 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 datacontext (ซึ่งใช้ในเอนทิตีหน่วยความจำ):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}