무작위 주문을 얻는 가장 좋은 방법은 무엇입니까?


27

결과 레코드를 무작위로 주문하려는 쿼리가 있습니다. 클러스터형 인덱스를 사용하므로 포함하지 않으면 order by해당 인덱스 순서대로 레코드를 반환합니다. 임의의 행 순서를 어떻게 보장 할 수 있습니까?

나는 그것이 "정확하게"무작위가 아닐 수도 있다는 것을 알고, 의사 난수는 내 필요에 충분하다.

답변:


19

ORDER BY NEWID ()는 레코드를 무작위로 정렬합니다. 여기

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()

7
ORDER BY NEWID ()는 사실상 무작위이지만 통계적으로 무작위는 아닙니다. 작은 차이가 있으며 대부분 차이가 중요하지 않습니다.
mrdenny

4
성능 관점에서 보면 속도가 매우 느립니다. ORDER BY CHECKSUM (NEWID ())
Miles D

1
@mrdenny- "통계적으로 무작위가 아닌"에 기초한 것은 무엇입니까? 여기의 대답 CryptGenRandom은 결국에 사용된다고 말합니다 . dba.stackexchange.com/a/208069/3690
Martin Smith

15

Pradeep Adiga의 첫 번째 제안 ORDER BY NEWID()은 훌륭하고 과거에 이런 이유로 사용한 적이 있습니다.

사용에주의 RAND()- 많은 상황에서 단지 그렇게 문에 한 번 실행 ORDER BY RAND()에는 영향을주지 않습니다 (당신이 () 각 행에 대해 RAND에서 같은 결과를 얻고있다으로).

예를 들어 :

SELECT display_name, RAND() FROM tr_person

person 테이블에서 각 이름과 각 행에 대해 동일한 "임의"숫자를 반환합니다. 쿼리를 실행할 때마다 숫자가 달라 지지만 매번 행마다 동일합니다.

RAND()에서 사용 된 경우와 동일하다는 것을 보여주기 ORDER BY위해 다음을 시도합니다.

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

이전 정렬 필드 (임의로 예상되는 필드)에는 영향이 없으므로 결과는 항상 동일한 값을 가짐을 나타내는 결과가 여전히 이름별로 정렬됩니다.

NEWID()그러나 NEWID ()가 항상 재평가 되지 않은 경우 고유 식별자가있는 하나의 statemnt에 많은 새 행을 키로 삽입 할 때 UUID의 목적이 깨지기 때문에 정렬 기준 이 작동합니다 .

SELECT display_name FROM tr_person ORDER BY NEWID()

않는다 "무작위"이름을 주문한다.

다른 DBMS

위의 내용은 MSSQL (2005 년과 2008 년, 2000 년을 올바르게 기억하는 경우)에 해당합니다. 새로운 UUID를 반환하는 함수 는 모든 DBMS에서 매번 평가 해야 합니다. NEWID ()는 MSSQL하에 있지만 설명서 및 / 또는 자체 테스트에서이를 확인할 가치가 있습니다. RAND ()와 같은 다른 임의의 결과 함수의 동작은 DBMS마다 다를 수 있으므로 문서를 다시 확인하십시오.

또한 DB가 유형에 의미있는 순서가 없다고 가정하기 때문에 일부 컨텍스트에서 UUID 값이 무시되는 순서를 보았습니다. 이 경우 ordering 절에서 UUID를 문자열 유형으로 명시 적으로 캐스팅하거나 CHECKSUM()SQL Server 와 같은 다른 함수를 래핑 하십시오 (주문이 수행 될 때 약간의 성능 차이가있을 수 있습니다) 32 비트 값은 128 비트 값이 아니지만 값의 이점이 CHECKSUM()값 당 실행 비용보다 큰지 여부는 먼저 테스트하도록하겠습니다.

사이드 노트

임의적이지만 다소 반복 가능한 순서를 원한다면 행 자체에서 상대적으로 제어되지 않는 데이터의 하위 집합을 기준으로 순서를 정하십시오. 예를 들어 또는 이들 중 하나는 임의의 반복 가능한 순서로 이름을 반환합니다.

SELECT display_name FROM tr_person ORDER BY CHECKSUM(display_name), display_name -- order by the checksum of some of the row's data
SELECT display_name FROM tr_person ORDER BY SUBSTRING(display_name, LEN(display_name)/2, 128) -- order by part of the name field, but not in any an obviously recognisable order)

임의이지만 반복 가능한 순서는 응용 프로그램에서 종종 유용하지는 않지만 다양한 순서로 결과에 대한 일부 코드를 테스트하고 각 단계를 동일한 방식으로 여러 번 반복 할 수있는 경우 (평균 타이밍을 얻기 위해) 테스트에 유용 할 수 있습니다. 여러 실행에 대한 결과 또는 코드 수정으로 테스트 한 결과 특정 입력 결과 집합에서 이전에 강조 표시된 문제 나 비 효율성을 제거하거나 코드가 "안정적"인지 테스트하기 위해 매번 동일한 결과를 반환합니다 주어진 순서로 동일한 데이터를 보낸 경우).

이 트릭은 함수 내에서 더 임의의 결과를 얻는 데 사용될 수 있으며, 본문에서 NEWID ()와 같은 비 결정적 호출을 허용하지 않습니다. 다시 말하지만, 이것은 현실 세계에서 종종 유용 할 수있는 것이 아니지만 임의의 무언가를 반환하는 함수를 원하고 "무작위"가 충분하다면 유용 할 수 있습니다 (그러나 결정하는 규칙을 기억해 두십시오) 사용자 정의 함수가 줄어든 경우 (즉, 일반적으로 행당 한 번만 또는 결과가 예상 / 필요한 것이 아닐 수 있음)

공연

EBarr이 지적했듯이 위의 항목 중 하나에 성능 문제가있을 수 있습니다. 몇 개 이상의 행에 대해 요청 된 행 수를 올바른 순서로 다시 읽기 전에 출력이 tempdb에 스풀 아웃되는 것을 거의 보증 할 수 있습니다. 즉, 상위 10 개를 찾고 있어도 전체 인덱스를 찾을 수 있습니다. 스캔 (또는 더 나쁜 테이블 스캔)은 tempdb에 큰 쓰기 블록과 함께 발생합니다. 따라서 대부분의 경우와 마찬가지로 프로덕션 환경에서 사용하기 전에 현실적인 데이터로 벤치마킹하는 것이 매우 중요합니다.


14

이것은 오래된 질문이지만 내 의견으로는 퍼포먼스라는 논의의 한 측면이 빠져 있습니다. ORDER BY NewId()일반적인 답변입니다. 누군가가 화를 내면 성능을 위해 실제로 포장해야한다고 덧붙 NewID()입니다 CheckSum().

이 방법의 문제점은 여전히 ​​전체 인덱스 스캔과 완전한 데이터 정렬이 보장된다는 것입니다. 심각한 데이터 볼륨으로 작업 한 경우 비용이 많이들 수 있습니다. 이 일반적인 실행 계획을보고 시간이 96 % 걸리는 방법에 주목하십시오.

여기에 이미지 설명을 입력하십시오

이 확장성에 대한 이해를 돕기 위해 작업하는 데이터베이스에서 두 가지 예를 보여 드리겠습니다.

  • TableA-2500 개 데이터 페이지에 50,000 개의 행이 있습니다. 무작위 쿼리는 42ms에서 145 개의 읽기를 생성합니다.
  • 표 B-114,000 개의 데이터 페이지에 120 만 개의 행이 있습니다. Order By newid()이 테이블에서 실행 하면 53,700 개의 읽기가 생성되고 16 초가 걸립니다.

이야기의 교훈은 큰 테이블이 있거나 (수십억 개의 행을 생각할 때)이 쿼리를 자주 실행해야하는 경우 newid()메서드가 중단 된다는 것 입니다. 그래서 소년은 무엇입니까?

TABLESAMPLE () 만나기

SQL 2005에서는라는 새로운 기능 TABLESAMPLE이 생성되었습니다. 나는 그것이 사용에 대해 논의 한 기사 만 보았습니다 ... 더 많은 것이 있어야합니다. MSDN 문서는 여기 . 먼저 예를 들면 다음과 같습니다.

SELECT Top (20) *
FROM Northwind..Orders TABLESAMPLE(20 PERCENT)
ORDER BY NEWID()

테이블 샘플의 기본 개념은 요청한 부분 집합 크기를 대략적 으로 제공하는 것 입니다. SQL은 각 데이터 페이지에 번호를 매기고 해당 페이지의 X 퍼센트를 선택합니다. 되돌아 오는 실제 행 수는 선택한 페이지에있는 내용에 따라 달라질 수 있습니다.

어떻게 사용합니까? 필요한 행 수보다 많은 부분 집합 크기를 선택한 다음을 추가하십시오 Top(). 아이디어는 비싼 정렬 전에 거대한 테이블을 더 작게 만들 수 있다는 것 입니다.

개인적으로 나는 그것을 사용하여 테이블 크기를 제한했습니다. 따라서 백만 행 테이블 top(20)...TABLESAMPLE(20 PERCENT)에서 쿼리를 수행 하면 1600ms에서 5600 읽기로 떨어집니다. REPEATABLE()페이지 선택을 위해 "시드"를 전달할 수 있는 옵션 도 있습니다. 이로 인해 샘플이 안정적으로 선택됩니다.

어쨌든, 이것이 토론에 추가되어야한다고 생각했습니다. 그것이 누군가를 돕기를 바랍니다.


확장 할뿐만 아니라 작은 데이터 세트와 함께 작동하는 확장 가능한 임의 순서 쿼리를 작성할 수 있으면 좋을 것입니다. 보유하고있는 TABLESAMPLE()데이터 양에 따라 수동으로 전환해야하는 것처럼 들립니다 . 문서에“반환되는 실제 행 수는 크게 다를 수 있기 때문에 최소한 행이 반환 TABLESAMPLE(x ROWS)될 것이라고 생각하지도 않습니다 . 5와 같이 작은 숫자를 지정하면 샘플에 결과가 표시되지 않을 수 있습니다.”— 구문은 실제로 마스크 안에 숨겨져 있습니까? xROWSPERCENT
binki

물론, 자동 마술이 좋습니다. 실제로 5 행 테이블이 예고없이 수백만 행으로 확장되는 것을 거의 보지 못했습니다. TABLESAMPLE ()은 테이블 의 페이지 수를 선택하는 것으로 보이 므로 주어진 행 크기가 되돌아 오는 내용에 영향을줍니다. 적어도 내가 본 것처럼 테이블 샘플의 요점은 파생 테이블과 같은 종류를 선택할 수있는 좋은 하위 집합을 제공하는 것입니다.
EBarr

3

많은 테이블에 상대적으로 밀도가 높은 (누락 된 값이 거의 없음) 인덱스 숫자 ID 열이 있습니다.

이를 통해 기존 값의 범위를 결정하고 해당 범위에서 임의로 생성 된 ID 값을 사용하여 행을 선택할 수 있습니다. 이것은 반환되는 행 수가 상대적으로 적고 ID 값의 범위가 밀집되어있을 때 가장 잘 작동합니다 (따라서 결 측값을 생성 할 가능성이 충분히 적음).

예를 들어, 다음 코드는 8,123,937 개의 행이있는 스택 오버플로 사용자 테이블에서 100 명의 개별 임의 사용자를 선택합니다.

첫 번째 단계는 인덱스로 인한 효율적인 작업 인 ID 값의 범위를 결정하는 것입니다.

DECLARE 
    @MinID integer,
    @Range integer,
    @Rows bigint = 100;

--- Find the range of values
SELECT
    @MinID = MIN(U.Id),
    @Range = 1 + MAX(U.Id) - MIN(U.Id)
FROM dbo.Users AS U;

범위 쿼리

계획은 인덱스의 각 끝에서 한 행을 읽습니다.

이제 범위에서 100 개의 고유 한 임의 ID를 생성하고 (users 테이블의 일치하는 행 포함) 해당 행을 반환합니다.

WITH Random (ID) AS
(
    -- Find @Rows distinct random user IDs that exist
    SELECT DISTINCT TOP (@Rows)
        Random.ID
    FROM dbo.Users AS U
    CROSS APPLY
    (
        -- Random ID
        VALUES (@MinID + (CONVERT(integer, CRYPT_GEN_RANDOM(4)) % @Range))
    ) AS Random (ID)
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.Users AS U2
            -- Ensure the row continues to exist
            WITH (REPEATABLEREAD)
        WHERE U2.Id = Random.ID
    )
)
SELECT
    U3.Id,
    U3.DisplayName,
    U3.CreationDate
FROM Random AS R
JOIN dbo.Users AS U3
    ON U3.Id = R.ID
-- QO model hint required to get a non-blocking flow distinct
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

임의의 행 쿼리

계획은이 경우 100 개의 일치하는 행을 찾기 위해 601 개의 난수가 필요함을 보여줍니다. 꽤 빠릅니다.

'사용자'표. 스캔 카운트 1, 논리적 읽기 1937, 물리적 읽기 2, 미리 읽기 408
'작업대'표. 스캔 횟수 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기
'워크 파일'테이블. 스캔 횟수 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기

 SQL Server 실행 시간 :
   CPU 시간 = 0ms, 경과 시간 = 9ms

Stack Exchange Data Explorer에서 사용해보십시오.


0

이 기사 에서 설명한 것처럼 SQL 결과 세트를 섞으려면 데이터베이스 별 함수 호출을 사용해야합니다.

RANDOM 함수를 사용하여 큰 결과 집합을 정렬하면 속도가 매우 느릴 수 있으므로 작은 결과 집합에서는 그렇게해야합니다.

큰 결과 집합을 섞어서 나중에 제한해야하는 경우 ORDER BY 절의 임의 함수 대신 SQL Server TABLESAMPLE에서 SQL Server 를 사용하는 것이 좋습니다 .

따라서 다음과 같은 데이터베이스 테이블이 있다고 가정합니다.

여기에 이미지 설명을 입력하십시오

그리고 song테이블 의 다음 행 :

| id | artist                          | title                              |
|----|---------------------------------|------------------------------------|
| 1  | Miyagi & Эндшпиль ft. Рем Дигга | I Got Love                         |
| 2  | HAIM                            | Don't Save Me (Cyril Hahn Remix)   |
| 3  | 2Pac ft. DMX                    | Rise Of A Champion (GalilHD Remix) |
| 4  | Ed Sheeran & Passenger          | No Diggity (Kygo Remix)            |
| 5  | JP Cooper ft. Mali-Koa          | All This Love                      |

SQL Server에서는 NEWID다음 예제와 같이 함수 를 사용해야합니다 .

SELECT
    CONCAT(CONCAT(artist, ' - '), title) AS song
FROM song
ORDER BY NEWID()

위에서 언급 한 SQL 쿼리를 SQL Server에서 실행하면 다음과 같은 결과 집합이 나타납니다.

| song                                              |
|---------------------------------------------------|
| Miyagi & Эндшпиль ft. Рем Дигга - I Got Love      |
| JP Cooper ft. Mali-Koa - All This Love            |
| HAIM - Don't Save Me (Cyril Hahn Remix)           |
| Ed Sheeran & Passenger - No Diggity (Kygo Remix)  |
| 2Pac ft. DMX - Rise Of A Champion (GalilHD Remix) |

NEWIDORDER BY 절에서 사용 하는 함수 호출로 인해 노래가 무작위 순서로 나열됩니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.