sqlite 테이블에서 임의의 행 선택


답변:


213

SQLite 테이블에서 임의 행 선택을 살펴보십시오.

SELECT * FROM table ORDER BY RANDOM() LIMIT 1;

1
이 솔루션을 조인으로 확장하는 방법은 무엇입니까? 사용할 때 SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;항상 같은 행을 얻습니다.
Helmut Grohne 2013 년

난수를 시드 할 수 있습니까? 예를 들어 오늘 정오에 unix epoc로 시드 된 오늘의 책은 쿼리가 여러 번 실행 되더라도 하루 종일 동일한 책을 표시합니다. 예,이 사용 사례에서는 캐싱이 더 효율적이라는 것을 알고 있습니다.
danielson317

FWIW 내 질문은 실제로 여기에 답변됩니다. 그리고 대답은 난수를 시드 할 수 없다는 것입니다. stackoverflow.com/questions/24256258/…
danielson317

31

다음 솔루션은 anktastic보다 훨씬 빠릅니다 (count (*)는 비용이 많이 들지만 캐시 할 수 있다면 차이가 크지 않아야합니다). 자체적으로 "order by random ()"보다 훨씬 빠릅니다. 많은 수의 행이 있지만 몇 가지 불편한 점이 있습니다.

rowid가 다소 압축 된 경우 (즉, 삭제가 거의 없음) 다음을 수행 할 수 있습니다 ( 주석에 설명 된대로 (select max(rowid) from foo)+1대신 사용 max(rowid)+1하면 성능이 향상됨).

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1));

구멍이있는 경우 가끔 존재하지 않는 rowid를 선택하려고 시도하고 선택은 빈 결과 집합을 반환합니다. 이것이 허용되지 않는 경우 다음과 같은 기본값을 제공 할 수 있습니다.

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)) or rowid = (select max(rowid) from node) order by rowid limit 1;

이 두 번째 솔루션은 완벽하지 않습니다. 확률 분포는 마지막 행 (가장 높은 rowid를 가진 행)에서 더 높지만 테이블에 항목을 자주 추가하면 이동 대상이되고 확률 분포는 다음과 같아야합니다. 훨씬 낫다.

또 다른 해결책은 구멍이 많은 테이블에서 임의의 항목을 자주 선택하는 경우 임의의 순서로 정렬 된 원래 테이블의 행을 포함하는 테이블을 만들 수 있습니다.

create table random_foo(foo_id);

그런 다음 주기적으로 random_foo 테이블을 다시 채 웁니다.

delete from random_foo;
insert into random_foo select id from foo;

임의의 행을 선택하려면 첫 번째 방법을 사용할 수 있습니다 (여기에는 구멍이 없습니다). 물론이 마지막 방법에는 동시성 문제가 있지만 random_foo를 다시 빌드하는 것은 자주 발생하지 않는 유지 관리 작업입니다.

그러나 최근에 메일 링리스트 에서 찾은 또 다른 방법 은 가장 큰 rowid가있는 행을 현재 삭제 된 행으로 이동하여 구멍이 남지 않도록 삭제 트리거를 설정하는 것입니다.

마지막으로, rowid와 정수 기본 키 자동 증가의 동작은 동일하지 않습니다 (rowid를 사용하면 새 행이 삽입 될 때 max (rowid) +1이 선택됩니다. 기본 키)이므로 마지막 솔루션은 random_foo의 자동 증가와 함께 작동하지 않지만 다른 방법은 작동합니다.


방금 메일 링리스트에서 본 것처럼 폴백 방법 (방법 2) 대신 rowid> = [random]을 = 대신 사용할 수 있지만 실제로는 방법 2에 비해 느리게 느립니다.
Suzanne Dupéron

3
이것은 훌륭한 대답입니다. 그러나 한 가지 문제가 있습니다. SELECT max(rowid) + 1쿼리 속도가 느립니다. 전체 테이블 스캔이 필요합니다. sqlite는 쿼리 만 최적화합니다 SELECT max(rowid). 따라서이 답변은 다음과 같이 개선 될 것입니다. select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)); 자세한 내용은 다음을 참조하십시오. sqlite.1065341.n5.nabble.com/…
dasl

19

질의에 "order by RANDOM ()" 을 넣어야 합니다.

예:

select * from quest order by RANDOM();

완전한 예를 보자

  1. 테이블 만들기 :
CREATE TABLE  quest  (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    quest TEXT NOT NULL,
    resp_id INTEGER NOT NULL
);

일부 값 삽입 :

insert into quest(quest, resp_id) values ('1024/4',6), ('256/2',12), ('128/1',24);

기본 선택 :

select * from quest;

| id |   quest  | resp_id |
   1     1024/4       6
   2     256/2       12
   3     128/1       24
--

무작위 선택 :

select * from quest order by RANDOM();
| id |   quest  | resp_id |
   3     128/1       24
   1     1024/4       6
   2     256/2       12
--
* 선택할 때마다 순서가 달라집니다.

한 행만 반환하려는 경우

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
* 선택할 때마다 반품이 달라집니다.


코드 전용 답변은 금지되어 있지 않지만 이것은 크라우드 소싱 커뮤니티가 아니라 Q & A 커뮤니티이며 일반적으로 OP가 코드가 답변으로 게시 된 것을 이해했다면 해당 커뮤니티가 올라 왔을 것입니다. 그 / 그녀 자신의 유사한 솔루션을 사용하고 처음에 질문을 게시하지 않았을 것입니다. 따라서 답변 및 / 또는 코드가 작동 하는 방법 및 / 또는 이유를 설명하여 컨텍스트 를 제공 하십시오 .
XenoRo

2
이 솔루션을 선호합니다. n 줄을 검색 할 수 있기 때문입니다. 제 경우에는 데이터베이스에서 무작위 샘플 100 개가 필요했습니다. LIMIT 100과 결합 된 ORDER BY RANDOM ()이 정확히 수행합니다.
mnr

17

이건 어떤가요:

SELECT COUNT(*) AS n FROM foo;

그런 다음 [0, n)에서 난수 m 을 선택 하고

SELECT * FROM foo LIMIT 1 OFFSET m;

첫 번째 숫자 ( n )를 어딘가에 저장 하고 데이터베이스 수가 변경 될 때만 업데이트 할 수도 있습니다. 이렇게하면 매번 SELECT COUNT를 수행 할 필요가 없습니다.


1
그것은 좋은 빠른 방법입니다. 1 개 이상의 행을 선택하는 것은 잘 일반화되지 않지만 OP는 1 개만 요청 했으므로 괜찮은 것 같습니다.
Ken Williams

흥미로운 점 OFFSET은 오프셋의 크기에 따라 찾는 데 필요한 시간이 늘어난다는 것입니다. 행 2는 빠르며 행 2 백만은 모든 데이터가 고정 크기이고 데이터가 고정 된 경우에도 시간이 걸립니다. 직접 찾을 수 있어야합니다. 적어도 그것은 SQLite 3.7.13에서 보이는 것과 같습니다.
Ken Williams

@KenWilliams 거의 모든 데이터베이스가 'OFFSET'과 동일한 문제를 가지고 있습니다. 그것은 많은 행을 읽을 필요가 있기 때문에 그것은 단지 1. 반환하더라도 데이터베이스를 조회 할 수있는 매우 비효율적 인 방법입니다
조나단 알렌

1
하지만 / 고정 된 크기 / 레코드에 대해 이야기하고 있었음을 유의하십시오. 데이터의 올바른 바이트로 직접 스캔하는 것이 쉬워야 하지만 (그만큼 많은 행을 읽지 않음 ) 명시 적으로 최적화를 구현해야합니다.
켄 윌리엄스

@KenWilliams : SQLite에는 고정 된 크기의 레코드가 없으며 동적으로 입력되며 데이터가 선언 된 친화 도와 일치 할 필요가 없습니다 ( sqlite.org/fileformat2.html#section_2_1 ). 모든 것은 b- 트리 페이지에 저장되므로 어느 쪽이든 최소한 잎을 향해 b- 트리 검색을 수행해야합니다. 이를 효율적으로 수행하려면 각 하위 포인터와 함께 하위 트리의 크기를 저장해야합니다. 조인, 주문 기준 등에 대한 오프셋을 여전히 최적화 할 수 없기 때문에 약간의 이익을위한 오버 헤드가 너무 많을 것입니다. (그리고 ORDER BY 없이는 주문이 정의되지 않았습니다.)
Yakov Galka

13
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1

11
전체 테이블 내용을 먼저 선택하므로 큰 테이블의 경우 시간이 많이 걸리지 않습니까?
Alex_coder

1
"WHERE"조건을 사용하여 범위를 제한 할 수 없습니까?
jldupont

11

@ank의 솔루션 수정은 다음과 같습니다.

SELECT * 
FROM table
LIMIT 1 
OFFSET ABS(RANDOM()) % MAX((SELECT COUNT(*) FROM table), 1)

이 솔루션은 간격이있는 인덱스에도 적용됩니다. 범위 [0, count)에서 오프셋을 무작위 화하기 때문입니다. MAX빈 테이블이있는 케이스를 처리하는 데 사용됩니다.

다음은 16k 행이있는 테이블에 대한 간단한 테스트 결과입니다.

sqlite> .timer on
sqlite> select count(*) from payment;
16049
Run Time: real 0.000 user 0.000140 sys 0.000117

sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
14746
Run Time: real 0.002 user 0.000899 sys 0.000132
sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
12486
Run Time: real 0.001 user 0.000952 sys 0.000103

sqlite> select payment_id from payment order by random() limit 1;
3134
Run Time: real 0.015 user 0.014022 sys 0.000309
sqlite> select payment_id from payment order by random() limit 1;
9407
Run Time: real 0.018 user 0.013757 sys 0.000208

4

큰 sqlite3 데이터베이스에 대해 다음과 같은 솔루션을 생각해 냈습니다 .

SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1; 

abs (X) 함수는 숫자 인수 X의 절대 값을 반환합니다.

random () 함수는 -9223372036854775808에서 +9223372036854775807 사이의 의사 난수 정수를 반환합니다.

연산자 %는 오른쪽 피연산자와 모듈로 왼쪽 피연산자의 정수 값을 출력합니다.

마지막으로 rowid가 0이되지 않도록 +1을 추가합니다.


1
좋은 시도이지만 이것이 작동하지 않을 것이라고 생각합니다. rowId = 5 인 행이 삭제되었지만 rowIds 1,2,3,4,6,7,8,9,10이 여전히 존재한다면 어떻게 될까요? 그런 다음 선택한 임의의 rowId가 5이면이 쿼리는 아무 것도 반환하지 않습니다.
Calicoder
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.