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.