Rasgele bir sipariş almanın en iyi yolu nedir?


27

Elde edilen kayıtların rasgele sıralanmasını istediğim bir sorgu var. Kümelenmiş bir dizin kullanır, bu yüzden bir eklemezsem order by, büyük olasılıkla o dizinin sırasına göre kayıtları döndürür. Rasgele bir sıra sırasını nasıl sağlayabilirim?

Bunun "gerçekten" rastgele olmayacağının farkındayım, sözde rastgele benim ihtiyaçlarım için yeterince iyi.

Yanıtlar:


19

ORDER BY NEWID () kayıtları rasgele sıralar. Burada bir örnek

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

7
SİPARİŞ BY NEWID () etkili bir şekilde rasgele, ancak istatistiksel olarak rasgele değil. Küçük bir fark var ve çoğu zaman fark önemli değil.
mrdenny

4
Performans açısından, bu oldukça yavaştır - ORDER BY CHECKSUM (NEWID ())
Miles D

1
@mrdenny - "İstatistiksel olarak rastgele değil" ifadesini neye dayandırıyorsunuz? Buradaki cevap CryptGenRandomsonunda kullanılacağını söylüyor . dba.stackexchange.com/a/208069/3690
Martin Smith

15

Pradeep Adiga'nın ilk önerisi,, ORDER BY NEWID()gayet iyi ve geçmişte bu sebepten kullandığım bir şey.

Kullanırken dikkatli olun RAND()- birçok bağlamda, her ifade için yalnızca bir kez yürütülür, bu nedenle ORDER BY RAND()herhangi bir etkisi olmaz (her satır için RAND'dan () aynı sonucu elde ettiğiniz için).

Örneğin:

SELECT display_name, RAND() FROM tr_person

kişi tablomuzdaki her adı ve her satır için aynı olan "rasgele" bir sayı döndürür. Sayı, sorguyu her çalıştırdığınızda değişebilir, ancak her satırda her satır için aynıdır.

Aynı maddenin RAND()bir ORDER BYcümlede kullanılan durum olduğunu göstermek için şunu deniyorum:

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

Sonuçlar, daha önceki sıralama alanının (rastgele olması beklenen) etkisinin olmadığını belirten adla hala sıralanmıştır, bu nedenle muhtemelen her zaman aynı değere sahiptir.

Tarafından Sipariş NEWID()NEWID () değildi çünkü eğer, iş olsa yapar hep yeniden değerlendirilmesi UUID amacı dökümü yapılır benzersiz tanımlayıcılar ile bir statemnt birçok yeni satırlar eklerken onlar anahtar, yani:

SELECT display_name FROM tr_person ORDER BY NEWID()

yok "rastgele" isimleri sipariş.

Diğer DBMS

Yukarıdakiler MSSQL için geçerlidir (en azından 2005 ve 2008 ve 2000'i de doğru hatırlıyorsam). Yeni bir UUID dönen bir fonksiyon olmalıdır tüm DBMSs NewID (her zaman değerlendirilecektir) MSSQL altında ama ve / veya kendi testlerle belgelerinde bu doğrulayarak değer. RAND () gibi diğer rasgele sonuç fonksiyonlarının davranışının DBMS'ler arasında değişme olasılığı daha yüksektir, bu nedenle dokümanları tekrar kontrol edin.

Ayrıca, UUID değerlerine göre sıralamanın, bazı tiplerde DB tipinin anlamlı bir sıralamaya sahip olmadığını varsaydığı gibi göz ardı edildiğini de gördüm. Bunu bu durumda bulursanız, açıkça UUID'yi sıralama cümlesinde bir dize türüne aktarın veya CHECKSUM()SQL Server'daki gibi başka bir işlevi etrafına sarın (bu işlemden sonra yapılacak gibi küçük bir performans farkı olabilir. 128 bitlik olmayan bir 32 bitlik değerler olsa da, bunun yararı CHECKSUM()ilk önce değer başına çalıştırmanın maliyetinden ağır basıp basmadığıdır .

Kenar notu

İsteğe bağlı ancak biraz tekrarlanabilir bir sipariş istiyorsanız, satırların içindeki verilerin kontrolsüz bir alt kümesine göre sipariş verin. Örneğin ya bunlar ya da bunlar, isimleri keyfi fakat tekrarlanabilir bir sıra ile döndürür:

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)

İsteğe bağlı ancak yinelenebilir siparişler, uygulamalarda genellikle yararlı değildir, ancak çeşitli siparişler üzerinde bazı kodlar üzerinde test yapmak istiyorsanız, ancak her bir çalışmayı birkaç kez aynı şekilde tekrarlayabilmek istiyorsanız (ortalama zamanlamayı almak için) Birkaç çalıştırma sonucu elde edilen sonuçlar veya koda yaptığınız bir düzeltmenin daha önce belirli bir giriş sonucu tarafından vurgulanan bir sorunu veya verimsizliği ortadan kaldırdığını veya sadece kodunuzun "kararlı" olduğunu test etmek için her seferinde aynı sonucu döndürdüğünü test etmek aynı veriyi verilen bir siparişte gönderilirse).

Bu numara, kendi gövdeleri içerisinde NEWID () gibi deterministik olmayan çağrılara izin vermeyen fonksiyonlardan daha keyfi sonuçlar elde etmek için de kullanılabilir. Yine, bu gerçek dünyada sıklıkla yararlı olabilecek bir şey değildir, ancak eğer bir işlevi rastgele bir şey döndürmek istiyorsanız ve "rastgele-ish" yeterince iyi ise (kullanışlı olan kuralları hatırlamaya dikkat edin) kullanışlı olabilir. kullanıcı tanımlı fonksiyonlar değerlendirildiğinde, yani genellikle her satırda yalnızca bir kez, veya sonuçlarınız beklediğiniz / ihtiyaç duyduğunuz olmayabilir).

performans

EBarr'ın işaret ettiği gibi, yukarıdakilerden herhangi birinde performans sorunları olabilir. Birkaç satırdan fazla için, istenen satır sayısı doğru sırayla okunmadan önce çıktının tempdb'ye biriktirilmiş olduğunu görmek için garanti altına alınacaksınız; bu, ilk 10'u aramanıza rağmen tam bir dizin bulabileceğiniz anlamına gelir tarama (veya daha kötüsü tablo taraması), tempdb'ye çok büyük bir yazı bloğu ile birlikte gerçekleşir. Bu nedenle, çoğu şeyi olduğu gibi, bunu üretimde kullanmadan önce gerçekçi verilerle kıyaslama yapmak hayati önem taşıyabilir.


14

Bu eski bir soru, ancak bence tartışmanın bir yönü eksik, bence - PERFORMANS. ORDER BY NewId()genel cevaptır. Onlar gerçekten sarmak gerektiğini eklediğinizde birilerini en süslü NewID()içinde CheckSum(), performans için hani!

Bu yöntemle ilgili sorun, hala tam bir dizin taraması ve daha sonra da tam bir veri türünden emin olmanızdır. Herhangi bir ciddi veri hacmiyle çalıştıysanız, bu hızla pahalı olabilir. Bu tipik uygulama planına bakın ve zamanınızın% 96'sını nasıl aldığına dikkat edin ...

görüntü tanımını buraya girin

Bunun nasıl ölçeklendiğine dair bir fikir vermek için, birlikte çalıştığım bir veritabanından size iki örnek vereceğim.

  • Tablo A - 2500 veri sayfasında 50.000 satır var. Rastgele sorgu 42ms içinde 145 okuma üretir.
  • Tablo B - 114.000 veri sayfasında 1,2 milyon satır var. Order By newid()Bu tabloda çalışan 53.700 okuma üretir ve 16 saniye sürer.

Hikayenin ahlaki, eğer büyük masalarınız varsa (milyarlarca satır düşünün) veya bu sorguyu sık sık çalıştırmanız gerekiyorsa, newid()metot bozulur. Peki, ne yapacak bir çocuk?

TABLESAMPLE () ile tanışın

SQL 2005'te çağrılan yeni bir yetenek TABLESAMPLEyaratıldı. Sadece onun kullanımını tartışan bir makale gördüm ... daha fazlası olmalıydı. MSDN Dokümanlar burada . İlk önce bir örnek:

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

Tablo örneğinin arkasındaki fikir, size yaklaşık olarak istediğiniz alt küme boyutunu vermektir . SQL her veri sayfasını numaralandırır ve bu sayfaların yüzde X'ini seçer. Gerçek geri aldığınız satır sayısı, seçilen sayfalarda bulunanlara göre değişebilir.

Peki nasıl kullanırım? Gereksinim duyduğunuz satır sayısını kapsayan bir altküme boyutu seçin, ardından a ekleyin Top(). Buradaki fikir, pahalı masuradan önce ginor masanızı daha küçük gösterebilmenizdir .

Şahsen benim masamın boyutunu sınırlamak için kullanıyorum. Yani bu milyon top(20)...TABLESAMPLE(20 PERCENT)satırlık masanın üzerinde yaptığı sorgulama 1600ms'de 5600'e düşer. REPEATABLE()Sayfa seçimi için "Tohum" u geçebileceğiniz bir seçenek de vardır . Bu, kararlı bir örnek seçimi ile sonuçlanmalıdır.

Her neyse, bunun tartışmaya eklenmesi gerektiğini düşündüm. Umarım birine yardımcı olur.


Ölçeklendirilebilir bir rasgele sıralı sorgu yazabilmek yalnızca ölçeklendirmekle kalmaz aynı zamanda küçük veri kümeleriyle de çalışabilir. Sahip olduğunuz TABLESAMPLE()veriye bağlı olarak sahip olup olmamak arasında manuel olarak geçiş yapmanız gerektiğine benziyor . Dokümanların “Döndürülen satırların sayısı önemli ölçüde değişebilir çünkü“ En azından satırların döndürülmesini TABLESAMPLE(x ROWS)bile sağlayacağını sanmıyorum . 5 gibi küçük bir sayı belirtirseniz, numunede sonuç alamayabilirsiniz. ”- yani sözdizimi gerçekten de sadece maskeli mi? xROWSPERCENT
binki

Elbette, otomatik büyü güzel. Uygulamada, nadiren 5 satırlık bir masa ölçeğini görmeden milyonlarca satıra gördüm. TABLESAMPLE () , bir tablodaki sayfa sayısının seçimini temel alıyor gibi görünmektedir , bu nedenle verilen satır boyutu geri dönüşleri etkiler. Tablo örneğinin amacı, en azından gördüğüm gibi, türetilmiş bir tabloya benzer şekilde, içinden seçim yapabileceğiniz iyi bir alt-set vermektir.
EBarr

3

Pek çok tablonun nispeten yoğun (birkaç eksik değer) indekslenmiş sayısal ID sütunu vardır.

Bu, mevcut değerlerin aralığını belirlememize ve rastgele oluşturulmuş ID değerlerini kullanarak o aralıktaki satırları seçmemize olanak sağlar. Bu, en iyi şekilde döndürülecek satır sayısı nispeten küçük olduğunda ve ID değerleri aralığı yoğun bir şekilde doldurulduğunda çalışır (bu nedenle eksik bir değer üretme şansı yeterince küçüktür).

Göstermek için, aşağıdaki kod, 8,123,937 satır içeren kullanıcıların Stack Overflow tablosundan 100 farklı rasgele kullanıcı seçer.

İlk adım, indeks nedeniyle verimli bir işlem olan ID değerleri aralığını belirlemektir:

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;

Aralık sorgusu

Plan, dizinin her bir ucundan bir satır okur.

Şimdi aralıkta 100 farklı rasgele kimlik üretiyoruz (kullanıcılar tablosundaki eşleşen satırlarla) ve bu satırları döndürüyoruz:

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'));

rastgele satır sorgusu

Plan, bu durumda 100 eşleşen sıra bulmak için 601 rasgele sayıya ihtiyaç duyulduğunu göstermektedir. Oldukça hızlı:

Tablo 'Kullanıcılar'. Tarama sayısı 1, mantıksal 1937, fiziksel okuma 2, okumaya devam 408 okuma
'Çalışma Masası' tablosu. Tarama sayısı 0, mantıksal okuma 0, fiziksel okuma 0, okumaya devam okuma 0
Tablo 'İş Dosyası'. Tarama sayısı 0, mantıksal okuma 0, fiziksel okuma 0, okumaya devam okuma 0

 SQL Server Yürütme Süreleri:
   CPU zamanı = 0 ms, geçen süre = 9 ms.

Stack Exchange Veri Gezgini'nde deneyin.


0

Bu makalede açıkladığım gibi , SQL sonuç kümesini karıştırmak için veritabanına özgü bir işlev çağrısı kullanmanız gerekir.

RANDOM işlevini kullanarak büyük bir sonuç kümesinin sıralanmasının çok yavaş olabileceğini unutmayın, bu nedenle küçük sonuç kümelerinde yaptığınızdan emin olun.

Büyük bir sonuç kümesi karıştırmak ve daha sonra bunu sınırlamak varsa, o zaman SQL Server kullanmak daha iyidir TABLESAMPLEiçinde SQL Server ORDER BY yan tümcesinde rastgele fonksiyonunun yerine.

Öyleyse, aşağıdaki veritabanı tablosuna sahip olduğumuzu varsayalım:

görüntü tanımını buraya girin

Ve aşağıdaki songtablodaki satırlar :

| 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'da, NEWIDaşağıdaki örnekte gösterildiği gibi , işlevi kullanmanız gerekir :

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

Yukarıda bahsedilen SQL sorgusunu SQL Server'da çalıştırırken, aşağıdaki sonuç kümesini elde edeceğiz:

| 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 deyimi tarafından kullanılan işlev çağrısı sayesinde, şarkıların rasgele sırada olduğuna dikkat edin .

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.