Linq에서 Sql까지의 임의 행


112

조건이있을 때 Linq to SQL을 사용하여 임의의 행을 검색하는 가장 좋은 (그리고 가장 빠른) 방법은 무엇입니까 (예 : 일부 필드가 참이어야 함)?


실제 조건을 확인하는 주문에는 두 가지 옵션이 있습니다. 대부분의 항목에서 참 조건이 발생하면 임의의 항목을 잡고 거짓으로 테스트하고 반복하십시오. 드문 경우 데이터베이스가 옵션을 실제 조건으로 제한 한 다음 임의로 하나를 가져 오십시오.
Rex Logan

1
이 사이트의 많은 답변과 마찬가지로 두 번째 등급은 허용되는 것보다 훨씬 낫습니다.
nikib3ro 2013 년

답변:


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은 Count (), 1은 임의의 행을 얻습니다 ...
Marc Gravell

1
5 개 (또는 "x") 임의의 행을 원하면 어떻게해야합니까? 6 회 왕복하는 것이 가장 좋은가요, 아니면 저장 프로 시저에서 구현하는 편리한 방법이 있나요?
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/en-us/library/cc441928.aspx LINQ에 해당하는 것이 있는지 모르겠습니다.
jwd

60

Entity Framework의 또 다른 샘플 :

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

이것은 LINQ to SQL에서 작동하지 않습니다. 는 OrderBy단순히 떨어졌다되고있다.


4
이것을 프로파일 링하고 작동하는지 확인 했습니까? LINQPad를 사용하는 테스트에서 order by 절이 삭제됩니다.
Jim Wooley 2011 년


8
이것은 LINQ to SQL에서 작동하지 않습니다 ... 아마도 Entity Framework 4에서 작동합니다 (확인하지 않음). List를 정렬하는 경우 Guid와 함께 .OrderBy 만 사용할 수 있습니다 ... DB로 작동하지 않습니다.
nikib3ro

2
마지막으로 이것이 EF4에서 작동하는지 확인하기 위해-이 경우 훌륭한 옵션입니다.
nikib3ro 2013 년

1
당신은 당신의 대답을 편집하고 새로운 Guid를 가진 orderBy가 트릭을 수행하는 이유를 설명 할 수 있습니까? 방법 :에 의해 좋은 답변
장 - 프랑수아 코테

32

편집 : 나는 이것이 LINQ to Objects가 아니라 LINQ to SQL이라는 것을 알았습니다. Marc의 코드를 사용하여 데이터베이스를 가져 오십시오. LINQ to Objects의 잠재적 인 관심 지점으로이 답변을 여기에 남겨 두었습니다.

이상하게도 실제로 카운트를 얻을 필요가 없습니다. 그러나 카운트를 얻지 않는 한 모든 요소를 ​​가져와야합니다.

당신이 할 수있는 것은 "현재"값과 현재 카운트의 아이디어를 유지하는 것입니다. 다음 값을 가져올 때 임의의 숫자를 가져 와서 "current"를 "new"확률로 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;
}

4
참고로-빠른 확인을 실행했으며이 함수는 균일 한 확률 분포를 가지고 있습니다 (증가 카운트는 본질적으로 Fisher-Yates 셔플과 동일한 메커니즘이므로 합리적으로 보입니다).
Greg Beech

@Greg : 쿨, 감사합니다. 빠른 확인으로 나에게는 괜찮아 보였지만 이와 같은 코드에서 하나씩 오류를 발생시키는 것은 매우 쉽습니다. 물론 LINQ to SQL과는 거의 관련이 없지만 그럼에도 불구하고 유용합니다.
Jon Skeet

@JonSkeet 안녕하세요, 당신은 확인할 수 있습니다 나를 내가 놓친 거지 알려
shaijut

@TylerLaing : 아니요, 휴식을 의미하지는 않습니다. 첫 번째 반복에서 current것이다 항상 첫 번째 요소로 설정 될 수있다. 두 번째 반복에서 두 번째 요소로 설정되는 50 % 변경이 있습니다. 세 번째 반복에서는 33 %의 확률로 세 번째 요소로 설정됩니다. break 문을 추가하면 첫 번째 요소를 읽은 후 항상 종료하여 전혀 무작위가 아닙니다.
Jon Skeet

@JonSkeet도! 나는 count의 사용을 잘못 읽었습니다 (예를 들어 이것이 ni와 같은 임의의 범위를 가진 Fisher-Yates 스타일이라고 생각했습니다). 그러나 Fisher-Yates에서 첫 번째 요소를 선택하는 것은 요소를 공정하게 선택하는 것입니다. 그러나이를 위해서는 총 요소 수를 알아야합니다. 총 개수를 알 수 없다는 점에서 IEnumerable에 대한 솔루션이 깔끔하고 개수를 얻기 위해 전체 소스를 반복 한 다음 무작위로 선택한 인덱스로 다시 반복 할 필요가 없다는 것을 알았습니다. 오히려 이것은 "카운트를 얻지 않는 한 모든 요소를 ​​가져와야합니다"라고 말했듯이 한 번의 패스로 해결됩니다.
Tyler Laing

19

효율적으로 달성하는 한 가지 방법 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 필드를 추가하는 대신 기존의 자동 증가 ID 필드를 사용한 경우 임의성에 미치는 영향은 무엇입니까 (시드는 분명히 무작위로 유지됨)? 또한-최대 값이 테이블의 레코드 수와 같은 시드 값이 적절합니까 아니면 더 높아야합니까?
Bryan

동의합니다. 이것은 IMO가 더 많은 찬성표를 가져야하는 훌륭한 답변입니다. 나는 이것을 Entity Framework 쿼리에서 사용했고, 비트 XOR 연산자 ^가 직접 작동하여 조건을 조금 더 깔끔하게 만드는 것 같습니다 result = result.OrderBy(s => s.Shuffle ^ seed);(즉 ~, & 및 | 연산자를 통해 XOR을 구현할 필요가 없음).
Steven Rands

7

예를 들어 var count = 16테이블에서 임의의 행 을 얻으려면 다음과 같이 작성할 수 있습니다.

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

여기에서는 EF를 사용했고 테이블은 Dbset입니다.


1

임의의 행을 얻는 목적이 샘플링 인 경우 여기에서 매우 간략하게 이야기했습니다 . 에서 구체화 된 뷰를 사용하여 Sql Server 용 샘플링 프레임 워크를 개발 한 Microsoft Research 팀인 Larson 등의 멋진 접근 방식에 대해 설명했습니다. 실제 논문에 대한 링크도 있습니다.


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가있는 순서가 무작위가됩니다.


Guid는 "무작위"가 아니며 비 순차적입니다. 차이가 있습니다. 실제로는 이것과 같이 사소한 것에 대해서는 중요하지 않을 것입니다.
Chris Marisic 2014 년

0

적은 수의 무작위 페이지를 가져 오는 방법을 궁금해하며 각 사용자는 다른 무작위 3 페이지를 얻습니다.

이것은 Sharepoint 2010의 페이지 목록에 대해 LINQ를 사용하여 쿼리하는 최종 솔루션입니다. Visual Basic에 있습니다. 죄송합니다.

Dim Aleatorio As New Random()

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

아마도 많은 수의 결과를 쿼리하기 전에 몇 가지 프로파일 링을 받아야하지만 제 목적에는 완벽합니다


0

에 대한 임의의 함수 쿼리가 있습니다 DataTable.

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)
크리스 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 클래스 자체로 작업하지 않는 경우 (예 : 테스트 목적으로 데이터 컨텍스트를 위조하기 위해 프록시를 사용하기 때문에) 정의 된 UDF를 직접 사용할 수 없습니다. 실제 데이터 컨텍스트 클래스의 하위 클래스 또는 부분 클래스.

이 문제에 대한 해결 방법은 프록시에 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());
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.