MySQL, 600K satırdan 10 rastgele sıra seçer


463

En iyi nasıl toplam 600k'den 10 satır rastgele seçen bir sorgu yazabilirim?


15
İşte 8 teknik ; belki sizin durumunuzda iyi çalışır.
Rick James

Yanıtlar:


386

Basit, boşluklara, boşluklarla üniform olmayanlara kadar çeşitli durumları ele alan harika bir yazı.

http://jan.kneschke.de/projects/mysql/order-by-rand/

Çoğu genel durum için, bunu nasıl yapacağınız aşağıda açıklanmıştır:

SELECT name
  FROM random AS r1 JOIN
       (SELECT CEIL(RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

Bu, id'lerin dağılımının eşit olduğunu ve id listesinde boşluklar olabileceğini varsayar. Daha gelişmiş örnekler için makaleye bakın


52
Evet, kimliklerde potansiyel olarak büyük boşluklar varsa, en düşük kimliğinizin rastgele seçilme olasılığı yüksek kimliklerinizden çok daha düşüktür. Aslında en büyük boşluktan sonraki ilk ID'nin aslında en yüksek olanı olma şansı. Bu nedenle bu tanım gereği rastgele değildir.
lukeocodes

6
10 farklı rastgele sıra nasıl elde edilir? Sınırı 10 olarak ayarlamanız ve ardından 10 kez tekrarlamanız gerekir mysqli_fetch_assoc($result)mi? Yoksa bu 10 sonuç mutlaka ayırt edilemez mi?
Adam

12
Rastgele, aklımda herhangi bir sonuç için eşit bir şans gerektirir. ;)
lukeocodes

4
Makalenin tamamı eşit olmayan dağılımlar ve tekrarlanan sonuçlar gibi konuları ele almaktadır.
Bradd Szonye

1
özellikle, kimliklerinizin başlangıcında bir boşluğunuz varsa, ilk kez zamanla (min / maks-min) seçilir. Bu durumda basit bir tweak MAX () - MIN () * RAND + MIN () şeklindedir ve bu çok yavaş değildir.
Code Abominator

343
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

Etkin çözüm değil, işe yarıyor


139
ORDER BY RAND()nispeten yavaş
Mateusz Charytoniuk

7
Mateusz - kanıt pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 100.0010 alır, LIMIT 10 olmadan 0.0012 aldı (bu tabloda 3500 kelime).
Arthur Kushman

26
@zeusakm 3500 kelime o kadar da değil; sorun şu ki, MySQL her birini okuduktan sonra TÜM kayıtları sıralamak zorunda olduğu için belirli bir noktadan geçiyor; bu işlem sabit diske çarptığında farkı hissedebilirsiniz.
Ja͢ck

16
Kendimi tekrarlamak istemiyorum ama yine de tam masa taraması. Büyük masada çok zaman ve bellek tüketir ve disk üzerinde geçici masa üzerinde & işleminin çok yavaş olmasına neden olabilir .
matt

10
2010'da Facebook ile röportaj yaparken bana bir boyutta bilinmeyen büyüklükte büyük bir dosyadan rastgele bir kayıt seçmeyi sordular. Bir fikir bulduktan sonra, birden fazla kayıt seçmek için onu genelleştirmek kolaydır. Evet, tüm dosyayı sıralamak çok saçma. Aynı zamanda çok kullanışlıdır. Bu yaklaşımı sadece 1.000.000 + satır içeren bir tablodan 10 rastgele satır seçmek için kullandım. Tabii, biraz beklemek zorunda kaldım; ama sadece bir fikir edinmek istedim, bu tablodaki tipik satırlar neye benziyor ...
osa

27

Mükemmel performansa sahip ve boşluklarla çalışan basit sorgu :

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id

Bir 200K masaya Bu sorgu alır 0.08s ve (RAND () LIMIT 10 BY tbl SİPARİŞ SEÇ *) Normal versiyonunu alır 0.35s benim makinede.

Sıralama aşaması yalnızca dizinlenmiş kimlik sütununu kullandığından bu hızlıdır. Bu davranışı aşağıdaki açıklamada görebilirsiniz:

Rbl TARAFINDAN SİPARİŞ SEÇİN () LİMİT 10: Basit Açıklama

SEÇİM * t1'DEN ASLA tbl BİRLEŞİM (tbl ORDER BY RAND () LIMIT 10'dan KİMLİĞİ seç) t2 AÇIK t1.id = t2.id olarak resim açıklamasını buraya girin

Ağırlıklı Sürüm : https://stackoverflow.com/a/41577458/893432


1
Üzgünüm, test ettim! 600k kayıtlarda yavaş performans.
Dylan B

@DylanB Cevabı bir testle güncelledim.
Ali

17

400 K kaydeder MySQL veritabanı önbelleğe alınmamış 2 Gb boyutunda 10 rastgele satır seçerek, yavaş bir cpu ile hızlı sorgular (yaklaşık 0.5 saniye) alıyorum . Kodum: MySQL'de rasgele satırların hızlı seçimi

<?php
$time= microtime_float();

$sql='SELECT COUNT(*) FROM pages';
$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);

$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
   ORDER BY RAND() LIMIT 0,10";
$rquery= BD_Ejecutar($sql);
while(list($id)=mysql_fetch_row($rquery)){
    if($id_in) $id_in.=",$id";
    else $id_in="$id";
}
mysql_free_result($rquery);

$sql="SELECT id,url FROM pages WHERE id IN($id_in)";
$rquery= BD_Ejecutar($sql);
while(list($id,$url)=mysql_fetch_row($rquery)){
    logger("$id, $url",1);
}
mysql_free_result($rquery);

$time= microtime_float()-$time;

logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);
?>

11
14 milyondan fazla kayıt tablom göz önüne alındığında, bu kadar yavaşORDER BY RAND()
Fabrizio

5
@snippetsofcode Sizin durumunuzda - 400 bin satırlık basit "ORDER BY rand ()" kullanabilirsiniz. 3 soruluk hileniz işe yaramaz. Bunu "SELECT id, sayfalardan URL url Nerede id (sayfalardan SELECT id ORDER BY rand () LIMIT 10)" gibi yeniden yazabilirsiniz
Roman Podlinov

4
Tekniğiniz hala bir tablo taraması yapıyor. FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';Görmek için kullanın .
Rick James

4
Ayrıca, 200 req / s web sayfasında bu sorguyu çalıştırmayı deneyin. Eşzamanlılık seni öldürecek.
Marki555

@RomanPodlinov bunun düz üzerinden faydası ORDER BY RAND()sadece kimlikleri (tam satırları değil) sıralamasıdır, bu nedenle geçici tablo daha küçüktür, ancak yine de hepsini sıralamak zorundadır.
Marki555

16

Çok basit ve tek satırlı sorgu.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;

21
FYI, order by rand()masa büyükse çok yavaş
evilReiko

6

Endeksleme tablo büyükse uygulanmalıdır.
Muhammed Azeem

1
Dizin oluşturma burada yardımcı olmaz. Dizinler çok özel şeyler için yararlıdır ve bu sorgu bunlardan biri değildir.
Andrew

13

Kitaptan:

Bir Ofset Kullanarak Rastgele Bir Satır Seçin

Önceki alternatiflerde bulunan problemlerden kaçınan başka bir teknik, veri kümesindeki satırları saymak ve 0 ile sayı arasında rastgele bir sayı döndürmektir. Ardından, veri kümesini sorgularken bu sayıyı ofset olarak kullanın

<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();

Bitişik anahtar değerleri üstlenemediğinizde ve her satırın seçilme şansının eşit olduğundan emin olmanız gerektiğinde bu çözümü kullanın.


1
çok büyük tablolar için SELECT count(*)yavaşlar.
Hans Z

7

Bir tablodan rastgele satırlar nasıl seçilir:

Buradan: MySQL'de rastgele satırlar seçin

"Tablo taraması" üzerinde hızlı bir gelişme, rastgele kimlikleri almak için dizini kullanmaktır.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;

1
Bu MyISAM için bazılarına yardımcı olur, ancak InnoDB için değil (kimliğin kümelenmiş olduğu varsayılarak PRIMARY KEY).
Rick James

7

Anahtarlarınızda boşluk yoksa ve hepsi sayısalsa, rastgele sayıları hesaplayabilir ve bu satırları seçebilirsiniz. ama muhtemelen böyle olmayacak.

Yani bir çözüm şudur:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

Bu, temel olarak anahtarlarınızın aralığında rastgele bir sayı almanızı ve daha sonra bir sonraki en iyi olanı seçmenizi sağlar. bunu 10 kez yapmak zorundasınız.

Ancak bu gerçekten rastgele DEĞİLDİR çünkü anahtarlarınız büyük olasılıkla eşit olarak dağıtılmayacaktır.

Gerçekten büyük bir sorun ve tüm gereksinimleri yerine getirmek kolay değil, MySQL'in rand () gerçekten 10 rastgele satır istiyorsanız elde edebileceğiniz en iyisidir.

Bununla birlikte, hızlı ancak rastgele olma konusunda bir ticarete sahip olan, ancak size daha uygun olabilecek başka bir çözüm var. Burada okuyun: MySQL'in ORDER BY RAND () fonksiyonunu nasıl optimize edebilirim?

Soru, ne kadar rastgele olmanız gerektiğidir.

Size daha iyi bir çözüm sunabilmek için biraz daha açıklayabilir misiniz?

Mesela birlikte çalıştığım bir şirketin mutlak rasgeleliğe son derece hızlı ihtiyaç duyduğu bir çözümü vardı. Veritabanını, azalan ve daha sonra tekrar farklı rastgele değerlere ayarlanmış rastgele değerlerle önceden doldurmakla sonuçlandılar.

Eğer neredeyse hiç güncelleme yapmazsanız, artan bir kimliği doldurabilirsiniz, böylece boşluk kalmazsınız ve seçmeden önce rastgele tuşları hesaplayabilirsiniz ... Kullanım durumuna bağlıdır!


Merhaba Joe. Bu özel durumda anahtarlarda boşluk olmamalıdır, ancak zamanla bu değişebilir. Ve cevabınız çalışırken, birbirini takip eden rastgele 10 satır (10 sınırını yazmam şartıyla) oluşturacak ve konuşmak için daha fazla rastgele olma istedim. :) Teşekkür ederim.
Francisc

10'a ihtiyacınız varsa, 10 benzersiz satır oluşturmak için bir çeşit birleşim kullanın.
johno

ne dediğimi tahts. bunu 10 kez yürütmeniz gerekir. wition birliğini birleştirmek onu bir sorguya koymanın bir yoludur. benim ek 2 dakika önce bakın.
Surrican

1
@TheSurrican, Bu çözüm harika görünüyor, ancak çok kusurlu . Çok büyük bir tane eklemeyi deneyin Idve tüm rastgele sorgularınız size bunu döndürecektir Id.
Pacerier

1
FLOOR(RAND()*MAX(id)), daha büyük kimlikleri döndürmeye eğilimlidir.
Rick James

3

Oldukça büyük bir tablodan çok sayıda rastgele satır döndürmek için bir sorgu gerekli. Ben de bunu buldum. İlk önce maksimum kayıt kimliğini alın:

SELECT MAX(id) FROM table_name;

Ardından bu değeri aşağıdaki gibi değiştirin:

SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;

Max ifadesinin tablodaki maksimum kayıt kimliği ve n sonuç kümenizde olmasını istediğiniz satır sayısıdır. Varsayım, kayıt kimliğinde hiçbir boşluk bulunmadığı halde (eğer denemediysem) sonucu etkileyeceğinden şüphe duyuyorum. Ayrıca bu saklı yordamı daha genel olması için oluşturdu; Tablo adını ve döndürülecek satır sayısını girin. MySQL 5.5.38'i Windows 2008, 32GB, çift 3GHz E5450'de çalıştırıyorum ve 17.361.264 satır içeren bir tabloda 1.000.000 satır döndürmek için ~ .03 sn / ~ 11 saniyede oldukça tutarlı. (süreler MySQL Workbench 6.1'den alınmıştır; tercihinize bağlı olarak 2. seçim deyiminde FLOOR yerine CEIL kullanabilirsiniz)

DELIMITER $$

USE [schema name] $$

DROP PROCEDURE IF EXISTS `random_rows` $$

CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN

SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @t = CONCAT(
    'SELECT * FROM ',
    tab_name,
    ' WHERE id>FLOOR(RAND()*@max) LIMIT ',
    num_rows);

PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$

sonra

CALL [schema name].random_rows([table name], n);

3

Tüm en iyi cevaplar zaten yayınlanmıştır (esas olarak http://jan.kneschke.de/projects/mysql/order-by-rand/ bağlantısına atıfta bulunanlar ).

Başka bir hızlanma olasılığını belirlemek istiyorum - önbellekleme . Neden rastgele satırlar almanız gerektiğini düşünün. Muhtemelen bir web sitesinde rastgele yazı veya rastgele reklam görüntülemek istiyorsunuz. 100 req / s alıyorsanız, her ziyaretçinin rastgele satır alması gerçekten gerekli mi? Genellikle bu X rastgele satırlarını 1 saniye (hatta 10 saniye) önbelleğe almak tamamen iyidir. Aynı 1 saniyedeki 100 benzersiz ziyaretçinin aynı rastgele gönderileri alması önemli değildir, çünkü bir sonraki ikinci 100 ziyaretçinin farklı gönderiler alması gerekir.

Bu önbelleği kullanırken, rasgele verileri almak için daha yavaş çözümlerden bazılarını kullanabilirsiniz, çünkü bu gereksinimleriniz ne olursa olsun MySQL'den saniyede sadece bir kez alınacaktır.


3

@Riedsio'nun cevabını geliştirdim. Bu, boşlukları olan büyük, tekdüze bir şekilde dağıtılmış bir tabloda bulabildiğim en etkili sorgudur (> 2.6B satırları olan bir tablodan 1000 rasgele satır elde edilmesinde test edilmiştir).

(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)

Neler olup bittiğini açayım.

  1. @max := (SELECT MAX(id) FROM table)
    • Maks. Çok büyük tablolar için, MAX(id)bir satıra ihtiyacınız olduğunda hesaplamak için hafif bir ek yük vardır
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Rastgele bir kimlik alır
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • Bu boşlukları doldurur. Temel olarak boşluklarda rastgele bir sayı seçerseniz, sadece bir sonraki kimliği seçecektir. Boşlukların eşit olarak dağıtıldığı varsayıldığında, bu bir sorun olmamalıdır.

Birliğin yapılması, her şeyi 1 sorguya sığdırmanıza yardımcı olur, böylece birden çok sorgu yapmaktan kaçınabilirsiniz. Ayrıca hesaplama yükünü de kaydetmenizi sağlar MAX(id). Uygulamanıza bağlı olarak, bu çok veya çok az önemli olabilir.

Bunun yalnızca kimlikleri ve rastgele sırada aldığını unutmayın. Daha gelişmiş bir şey yapmak istiyorsanız, bunu yapmanızı tavsiye ederim:

SELECT t.id, t.name -- etc, etc
FROM table t
INNER JOIN (
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id

Ben bu yüzden değiştireyim, 30 rasgele kaydına ihtiyacım LIMIT 1için LIMIT 30sorguda her yerde
Hassaan

Kaçınmanız gerekir @Hassaan bu değişiyor LIMIT 1için LIMIT 30tablodaki rastgele bir noktadan size arka arkaya 30 kayıtları tanınacak. Bunun yerine (SELECT id FROM ....ortasındaki parçanın 30 kopyasına sahip olmalısınız .
Hans Z

Denedim ama Riedsiocevap daha verimli görünmüyor . Ben centos 7 PHP 7.0.22 ve MariaDB kullanarak sayfaya saniyede 500 isabet denedim, Riedsiocevap ile cevap sonra 500+ ekstra başarılı yanıt var.
Hassaan

1
@Hassaan riedsio'nun cevabı 1 satır verir, bu size n satır verir ve sorgulama için I / O yükünü azaltır. Daha hızlı satırlar alabilirsiniz, ancak sisteminizde daha fazla yük olabilir.
Hans Z

3

Riedsio tarafından gönderilen bu http://jan.kneschke.de/projects/mysql/order-by-rand/ kullandım (bir veya daha fazla rastgele değer döndüren saklı yordam durumunda kullandım):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        INSERT INTO rands
           SELECT r1.id
             FROM random AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT MAX(id)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.id >= r2.id
            ORDER BY r1.id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

Makalede de çözer boşlukların sorunu neden kimlikleri de o kadar rasgele sonuçlar (... makaleye bakın, vb tetikleyiciler kullanılarak) bir tablo koruyarak; 1'den başlayarak bitişik sayılarla doldurulmuş tabloya başka bir sütun ekleyerek sorunu çözüyorum ( düzenle: bu sütun, çalışma zamanında alt sorgu tarafından oluşturulan geçici tabloya eklenir, kalıcı tablonuzu etkilemez):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        SET @no_gaps_id := 0;

        INSERT INTO rands
           SELECT r1.id
             FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS no_gaps_id FROM random) AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT COUNT(*)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.no_gaps_id >= r2.id
            ORDER BY r1.no_gaps_id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

Makalede kodu optimize etmek için büyük uzunluklara gittiğini görebiliyorum; Değişikliklerimin performansı etkilediği, benim için çok iyi çalıştığı durumlarda hiçbir ideeam yok.


"Değişikliklerimin performansı etkilediği / ne kadar etkilediği konusunda hiçbir fikrim yok" - oldukça fazla. İçin @no_gaps_idhiçbir indeksi kullanılabilir sen bakarsanız, bu yüzden EXPLAINsizin sorgusu için, sahip Using filesortve Using whereözgün sorgusuna aksine, alt sorgular için (index olmadan).
Fabian Schmengler

2

İşte birçoğu için yararlı olabilecek bir oyun değiştirici;

Ben sıralı id ile 200k satır içeren bir tablo var , ben N rastgele satırları seçmek gerekiyordu , bu yüzden tablodaki en büyük kimliğe dayalı rastgele değerler oluşturmak için tercih, en hızlı işlem hangisi bulmak için bu komut dosyası oluşturdu:

logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();

Sonuçlar:

  • Sayı: 36.8418693542479ms
  • Maks: 0.241041183472ms
  • Sıra: 0.216960906982ms

Bu sonuçlara dayanarak, order desc maksimum kimliği almak için en hızlı işlemdir,
İşte soruya cevabım:

SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
    SELECT FLOOR(RAND() * (
        SELECT id FROM tbl ORDER BY id DESC LIMIT 1
    )) n FROM tbl LIMIT 10) a

...
SELECT * FROM tbl WHERE id IN ($result);

Bilginize: 200k'luk bir tablodan 10 rastgele satır almak için, beni 1.78 ms aldı (php tarafındaki tüm işlemler dahil)


3
LIMITHafifçe artırmanızı önerin - kopyaları alabilirsiniz.
Rick James

2

Bu süper hızlı ve boşluklarınız olsa bile% 100 rastgele.

  1. xKullanabileceğiniz satır sayısını sayınSELECT COUNT(*) as rows FROM TABLE
  2. a_1,a_2,...,a_100 ile 0 arasında 10 farklı rastgele sayı seçinx
  3. Satırlarınızı şu şekilde sorgulayın: SELECT * FROM TABLE LIMIT 1 offset a_ifor i = 1, ..., 10

Kitapta bu kesmek bulundu SQL Antipatterns gelen Bill Karwin .


Aynı çözümü düşünüyordum, lütfen söyle bana, diğerlerinden daha hızlı mı?
G. Adnane

@ G.Adnane, kabul edilen cevaptan daha hızlı veya daha yavaş değil, ancak kabul edilen cevap kimliklerin eşit dağılımını varsayar. Bunun garanti edilebileceği bir senaryo hayal edemiyorum. Bu çözelti, O (l) 'de, burada çözelti SELECT column FROM table ORDER BY RAND() LIMIT 10O (nlog (n))' dir. Yani evet, bu oruçlu çözümdür ve herhangi bir kimlik dağıtımı için çalışır.
Adam

hayır, çünkü kabul edilen çözüm için gönderilen bağlantıda başka yöntemler var, bu çözümün diğerlerinden daha hızlı olup olmadığını bilmek istiyorum, başka yollar, başka bir tane bulmaya çalışabiliriz, bu yüzden soruyorum, herhangi bir şekilde, +1 cevabın için. Ben bir şey kullanıyordum
G. Adnane

x satır sayısını almak istediğinizde, ancak ofset tablonun sonuna gider ve bu satır <x satır veya yalnızca 1 satır döndürür. benim yayınlanmadan önce cevabınızı görmedim ama burada daha net yaptım stackoverflow.com/a/59981772/10387008
ZOLDIK

@ZOLDIK, ofsetten sonraki ilk 10 satırı seçmişsiniz gibi görünüyor x. Bunun 10 sıradan oluşan rastgele bir nesil olmadığını iddia ediyorum. Cevabımda, üçüncü adımda 10 kez sorguyu yürütmek zorundasınız, yani bir yürütme başına yalnızca bir satır alır ve ofset tablonun sonundaysa endişelenmenize gerek yoktur.
Adam

1

Yalnızca bir Okuma İsteğiniz varsa

@Redsio'nun cevabını bir geçici tabloyla birleştirin (600K o kadar değil):

DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO tmp_randorder (data_id) select id from datatable;

Sonra da @redsios Cevap sürümünü edinin:

SELECT dt.*
FROM
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM tmp_randorder)) AS id)
        AS rnd
 INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
 INNER JOIN datatable AS dt on dt.id = rndo.data_id
 ORDER BY abs(rndo.id - rnd.id)
 LIMIT 1;

Tablo büyükse, ilk kısımda elek yapabilirsiniz:

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

Çok sayıda okuma isteğiniz varsa

  1. Sürüm: Tabloyu tmp_randorderkalıcı tutabilir, datatable_idlist olarak adlandırabilirsiniz. Bu tabloyu belirli aralıklarla (gün, saat) yeniden oluşturun, çünkü delikler de alır. Masanız gerçekten büyürse, delikleri de doldurabilirsiniz.

    datatable_idlist'ten bütün olarak l.data_id öğesini seçin l dt.id üzerinde datatable dt'yi bir arada bırak = l.data_id; burada dt.id boştur;

  2. Sürüm: Veri Kümenize, doğrudan veri tablosunda veya kalıcı bir ekstra tabloda rasgele_sortorder sütunu verin datatable_sortorder. Bu sütunu dizine ekleyin. Başvurunuzda Rastgele Değer Üretin (onu arayacağım $rand).

    select l.*
    from datatable l 
    order by abs(random_sortorder - $rand) desc 
    limit 1;

Bu çözüm, en yüksek ve en düşük random_sortorder ile 'kenar satırlarını' ayırt eder, bu nedenle bunları aralıklarla (günde bir kez) yeniden düzenleyin.


1

Başka bir basit çözüm, sıraları sıralamak ve bunlardan birini rastgele almaktır ve bu çözümle tabloda herhangi bir 'Id' tabanlı sütuna ihtiyacınız yoktur.

SELECT d.* FROM (
SELECT  t.*,  @rownum := @rownum + 1 AS rank
FROM mytable AS t,
    (SELECT @rownum := 0) AS r,
    (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM mytable))) AS n
) d WHERE rank >= @cnt LIMIT 10;

Sınır değerini istediğiniz sayıda satıra erişme gereksiniminize göre değiştirebilirsiniz, ancak bu çoğunlukla ardışık değerler olur.

Ancak, ardışık rasgele değerler istemiyorsanız, daha büyük bir örnek alıp rastgele seçebilirsiniz. gibi bir şey ...

SELECT * FROM (
SELECT d.* FROM (
    SELECT  c.*,  @rownum := @rownum + 1 AS rank
    FROM buildbrain.`commits` AS c,
        (SELECT @rownum := 0) AS r,
        (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM buildbrain.`commits`))) AS rnd
) d 
WHERE rank >= @cnt LIMIT 10000 
) t ORDER BY RAND() LIMIT 10;

1

Otomatik olarak oluşturulan bir kimlik varsa oldukça iyi bulmanın bir yolu modulo operatörü '%' kullanmaktır. Örneğin, 70.000 üzerinden 10.000 rasgele kayda ihtiyacınız varsa, her 7 satırdan 1'ine ihtiyacınız olduğunu söyleyerek bunu basitleştirebilirsiniz. Bu, bu sorguda basitleştirilebilir:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0;

Hedef satırları kullanılabilir toplamlara bölmenin sonucu bir tamsayı değilse, istediğinden daha fazla satırınız olacaktır, bu nedenle sonuç kümesini şu şekilde kırpmanıza yardımcı olacak bir LIMIT yan tümcesi eklemelisiniz:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0
LIMIT 10000;

Bu tam bir tarama gerektirir, ancak ORDER BY RAND'den daha hızlıdır ve bence bu konuda belirtilen diğer seçeneklerden daha basittir. Ayrıca, DB'ye yazan sistem, gruplar halinde satır kümeleri oluşturuyorsa, beklediğiniz gibi rastgele bir sonuç alamayabilirsiniz.


2
Şimdi öyle düşünüyorum, her aradığınızda rastgele satırlara ihtiyacınız varsa, bu işe yaramaz. Sadece biraz araştırma yapmak için bir setten rastgele satırlar alma ihtiyacını düşünüyordum. Ben hala modulo'nun diğer durumda yardımcı olması için iyi bir şey olduğunu düşünüyorum. Bir ORDER BY RAND işleminin maliyetini düşürmek için modulo'yu ilk geçiş filtresi olarak kullanabilirsiniz.
Nicolas Cohen


1

Tüm cevapları inceledim ve kimsenin bu olasılıktan hiç bahsetmediğini sanmıyorum ve neden olduğundan emin değilim.

En üst düzeyde basitlik ve hız istiyorsanız, küçük bir maliyetle, o zaman bana göre DB'deki her satıra rastgele bir sayı depolamak mantıklı görünüyor. Fazladan bir sütun oluşturmanız random_numberve varsayılan olarak ayarlamanız yeterlidir RAND(). Bu sütunda bir dizin oluşturun.

Daha sonra bir satır almak istediğinizde kodunuzda rastgele bir sayı oluşturun (PHP, Perl, ne olursa olsun) ve bunu sütunla karşılaştırın.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Tek bir satır için çok temiz olmasına rağmen, OP gibi on satır için on ayrı kez çağırmanız gerektiğini sordu (veya hemen beni kaçan akıllı bir tweak ile gel)


Bu aslında çok güzel ve verimli bir yaklaşım. Tek dezavantajı, bence adil bir anlaşma gibi görünen hız için alan ticareti yaptığınızdır.
Tochukwu Nkemdilim

Teşekkürler. Rastgele bir satır istediğim ana masanın 5 milyon satır ve oldukça fazla birleşime sahip olduğu bir senaryom vardı ve bu sorudaki çoğu yaklaşımı denedikten sonra bu yerleştiğim çamurdu. Fazladan bir sütun benim için çok değerli bir denemeydi.
Codemonkey

0

Aşağıdakiler hızlı, tarafsız ve kimlik sütunundan bağımsız olmalıdır. Ancak, döndürülen satır sayısının istenen satır sayısıyla eşleşeceğini garanti etmez.

SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)

Açıklama: 100'den 10 satır istediğinizi varsayarsak, her satırın 1/10 SELECTed alma olasılığı vardır ve bu da elde edilebilir WHERE RAND() < 0.1. Bu yaklaşım 10 satırı garanti etmez; ancak sorgu yeterli sayıda çalıştırılırsa, yürütme başına ortalama satır sayısı yaklaşık 10 olur ve tablodaki her satır eşit olarak seçilir.


0

Limitli rastgele bir ofseti kolayca kullanabilirsiniz

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

Bunun gibi bir where cümlesi de uygulayabilirsiniz

PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

600.000 satır üzerinde test (700MB) tablo sorgu yürütme ~ 0.016sec HDD sürücü aldı

--EDIT--
   belki bir değer daha az satır dönen select deyimi ile sonuçlanacaktır tablonun sonuna yakın, (veya tek 1 sürebilir ofset satır), bunu önlemek offsetiçin, beyan ettikten sonra tekrar kontrol edebiliriz ,

SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;

-1

Bu sorguyu kullanın:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

sorgu süresi: 0.016s


1,2,9,15 gibi PK'lara sahip olmak. yukarıdaki sorgu ile yetersiz olan 4, 7, 14, 11 gibi satırlar alacaksınız!
Junaid Atari

-2

Bunu nasıl yaparım:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

Diğer tabloları gerektirmediği için seviyorum, yazmak basit ve yürütmek çok hızlı.


5
Bu tam tablo taramasıdır ve herhangi bir dizin kullanmaz. Büyük tablolar ve yoğun ortam için bu büyük hayır hayır.
matt

-2

Bir tablodan rastgele veri almak için aşağıdaki basit sorguyu kullanın.

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10

Herhangi bir join deyimi ve nerede filtre kullanabilirsiniz istiyorsanız.
MANOJ

3
Sorgunun hangi bölümünden rastgele bilgi elde edersiniz?
Marki555

-4

Sanırım bu mümkün olan en iyi yol ...

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no

8
Cehennem hayır, bu tablodan rastgele satırlar almanın en kötü yollarından biri. Bu tam tablo taraması + dosya dizisi + tmp tablosu = kötü performans.
matt

1
Performansın yanı sıra, tamamen rastgele olmaktan da uzak; yalnızca rastgele bir sayı ile sipariş vermek yerine, kimliğin ürünü ve rastgele bir sayı ile sipariş veriyorsunuz. Bu, daha düşük kimliğe sahip satırların, sonuç kümenizde daha erken görünmeye doğru eğilimli olacağı anlamına gelir.
Mark Amery
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.