it-swarm-eu.dev

Jaký je nejlepší způsob, jak získat náhodné objednání?

Mám dotaz, kde chci, aby výsledné záznamy byly uspořádány náhodně. Používá seskupený index, takže pokud nebudu obsahovat order by pravděpodobně vrátí záznamy v pořadí podle tohoto indexu. Jak mohu zajistit náhodné pořadí řádků?

Chápu, že to pravděpodobně nebude „skutečně“ náhodné, pseudonáhodné je dost dobré pro mé potřeby.

29
goric

OBJEDNAT NOVINKU () budou záznamy třídit náhodně. Příklad zde

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

Je to stará otázka, ale podle mého názoru chybí jeden aspekt diskuse - VÝKON. ORDER BY NewId() je obecná odpověď. Když někdo získá fantazii, dodají, že byste měli opravdu zabalit NewID() do CheckSum(), víte, pro výkon!

Problém s touto metodou spočívá v tom, že máte stále zaručeno úplné prohledávání indexu a poté kompletní druh dat. Pokud jste pracovali s jakýmkoli vážným objemem dat, může to rychle stát drahé. Podívejte se na tento typický prováděcí plán a poznamenejte si, jak to zabere 96% vašeho času ...

enter image description here

Abych vám dal představu o tom, jak toto měřítko, uvedu dva příklady z databáze, se kterou pracuji.

  • TableA - má 50 000 řádků na 2500 datových stránkách. Náhodný dotaz vygeneruje 145 čtení za 42 ms.
  • Tabulka B - má 1,2 milionu řádků na 114 000 datových stránkách. Spuštění Order By newid() v této tabulce vygeneruje 53 700 přečtení a trvá 16 sekund.

Morální příběh je, že pokud máte velké tabulky (například miliardy řádků) nebo potřebujete tento dotaz často spouštět, metoda newid() se rozpadne. Tak co má chlapec dělat?

Seznamte se s TABLESAMPLE ()

V SQL 2005 byla vytvořena nová funkce s názvem TABLESAMPLE. Viděl jsem pouze jeden článek o použití ... mělo by jich být víc. MSDN Dokumenty zde . První příklad:

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

Myšlenka za ukázkou tabulky je poskytnout přibližně velikost podmnožiny, kterou požadujete. SQL čísluje každou datovou stránku a vybírá X procent z těchto stránek. Skutečný počet řádků, které získáte, se může lišit v závislosti na tom, co existuje na vybraných stránkách.

Jak to mohu použít? Vyberte velikost podmnožiny, která více než pokryje počet potřebných řádků, a potom přidejte Top(). Myšlenka je, že můžete udělat svůj drahocenný stůl, aby vypadal menší předchozí k drahému třídění.

Osobně jsem to používal k omezení velikosti stolu. Takže v této milionové řádkové tabulce provedené top(20)...TABLESAMPLE(20 PERCENT) dotaz klesne na 5600 čtení za 1600ms. K dispozici je také možnost REPEATABLE(), kde můžete pro výběr stránky předat „Seed“. Výsledkem by měl být stabilní výběr vzorku.

Každopádně si myslel, že by to mělo být přidáno do diskuse. Doufám, že to někomu pomůže.

16
EBarr

První návrh Pradeep Adiga, ORDER BY NEWID(), je v pořádku a něco, co jsem v minulosti z tohoto důvodu použil.

Při používání Rand() buďte opatrní - v mnoha kontextech je prováděna pouze jednou na příkaz, takže ORDER BY Rand() nebude mít žádný účinek (protože z Rand () pro každý řádek dostanete stejný výsledek).

Například:

SELECT display_name, Rand() FROM tr_person

vrací každé jméno z naší tabulky osob a „náhodné“ číslo, které je stejné pro každý řádek. Číslo se mění při každém spuštění dotazu, ale je stejné pro každý řádek pokaždé.

Abych ukázal, že to samé platí pro případ Rand() použitý v klauzule ORDER BY, Zkusím:

SELECT display_name FROM tr_person ORDER BY Rand(), display_name

Výsledky jsou stále seřazeny podle názvu označujícího, že dřívější pole řazení (pole, u kterého se očekává, že bude náhodné) nemá žádný účinek, takže pravděpodobně má vždy stejnou hodnotu.

Řazení podle NEWID() však funguje, protože pokud NEWID () nebylo vždy , přehodnoceno, účel UUID by byl při vložení mnoha nových přerušen řádky v jednom statemtu s jedinečnými identifikátory, jak oni klíčují, tak:

SELECT display_name FROM tr_person ORDER BY NEWID()

uspořádá jména „náhodně“.

Ostatní DBMS

Výše uvedené platí pro MSSQL (minimálně v letech 2005 a 2008, a pokud si dobře pamatuji také 2000). Funkce vracející nové UUID by měla být vyhodnocena pokaždé ve všech DBMS NEWID () je pod MSSQL, ale stojí za to to ověřit v dokumentaci a/nebo vlastními testy. Chování dalších funkcí s libovolným výsledkem, jako je Rand (), se mezi DBMS s větší pravděpodobností liší, proto znovu zkontrolujte dokumentaci.

Také jsem viděl, že řazení v hodnotách UUID bylo v některých kontextech ignorováno, protože databáze předpokládá, že typ nemá smysluplné řazení. Pokud zjistíte, že se jedná o tento případ, explicitně obsadí UUID na typ řetězce v klauzule pro objednávání nebo omotá kolem něj nějakou jinou funkci, například CHECKSUM() na serveru SQL (může se od toho také mírně lišit výkon, protože řazení bude provedeno na 32bitových hodnotách, nikoli na 128bitových, ačkoli zda výhoda převažuje nad náklady na provoz CHECKSUM() na hodnotu, nechám vás nejprve vyzkoušet).

vedlejší poznámka

Pokud chcete libovolné, ale poněkud opakovatelné řazení, uspořádejte podle relativně nekontrolované podmnožiny dat v samotných řádcích. Například buď nebo tyto vrátí jména v libovolném, ale opakovatelném pořadí:

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)

Libovolné, ale opakovatelné objednávky nejsou v aplikacích často užitečné, ale mohou být užitečné při testování, pokud chcete testovat nějaký kód na výsledcích v různých objednávkách, ale chcete být schopni opakovat každý běh stejným způsobem několikrát (pro získání průměrného načasování) výsledky během několika běhů nebo testování, že oprava, kterou jste provedli v kódu, odstraní problém nebo neefektivnost dříve zvýrazněnou konkrétním vstupním výsledkovým souborem, nebo jen pro testování, zda je váš kód „stabilní“, a vrací vždy stejný výsledek pokud byla stejná data zaslána v daném pořadí).

Tento trik lze také použít k získání libovolnějších výsledků z funkcí, které v jejich těle neumožňují nedeterministická volání, jako je NEWID (). Opět platí, že to není něco, co by bylo často užitečné v reálném světě, ale mohlo by se hodit, pokud chcete, aby funkce vrátila něco náhodného a „náhodný-ish“ je dost dobrý (ale pamatujte si pravidla, která určují když jsou funkce definované uživatelem vyhodnoceny, tj. obvykle pouze jednou za řádek, nebo vaše výsledky nemusí být to, co očekáváte/požadujete).

Výkon

Jak EBarr zdůrazňuje, mohou se vyskytnout problémy s výkonem u kteréhokoli z výše uvedených. Pro více než několik řádků máte téměř zaručeno, že se výstup zařazený do tempdb zobrazí před tím, než se požadovaný počet řádků přečte zpět ve správném pořadí, což znamená, že i když hledáte 10 nejlepších, můžete najít plný index skenování (nebo horší, skenování tabulky) se děje spolu s obrovským blokem zápisu do tempdb. Proto může být životně důležité, stejně jako u většiny věcí, srovnávat realistická data před použitím ve výrobě.

16
David Spillett

Mnoho tabulek má relativně hustý (několik chybějících hodnot) indexovaný sloupec číselných ID.

To nám umožňuje určit rozsah existujících hodnot a vybrat řádky pomocí náhodně generovaných hodnot ID v tomto rozsahu. Toto funguje nejlépe, když je počet řádků, které mají být vráceny, relativně malý a rozsah hodnot ID je hustě vyplněn (takže šance na vygenerování chybějící hodnoty je dostatečně malá).

Pro ilustraci si následující kód vybere 100 různých náhodných uživatelů z tabulky Přetečení zásobníku uživatelů, která má 8 123 937 řádků.

Prvním krokem je určení rozsahu hodnot ID, efektivní operace díky indexu:

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;

Range query

Plán čte jeden řádek z každého konce indexu.

Nyní vygenerujeme 100 různých náhodných ID v rozsahu (s odpovídajícími řádky v tabulce uživatelů) a vrátíme tyto řádky:

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

random rows query

Plán ukazuje, že v tomto případě bylo zapotřebí 601 náhodných čísel, aby bylo nalezeno 100 shodných řádků. Je to docela rychlé:

 Tabulka „Uživatelé“. Počet skenování 1, logické čtení 1937, fyzické čtení 2, čtení dopředu 408 
 Tabulka 'Pracovní stůl'. Počet skenování 0, logické čtení 0, fyzické čtení 0, čtení před čtení 0 
 Tabulka 'Workfile'. Počet skenování 0, logické čtení 0, fyzické čtení 0, čtení dopředu 0 
 
 Časy spuštění serveru SQL: 
 Čas CPU = 0 ms, uplynulý čas = 9 ms. 

Vyzkoušejte to v aplikaci Exchange Exchange Data Explorer.

3
Paul White 9

Jak jsem vysvětlil v tento článek , pro zamíchání výsledkové sady SQL musíte použít volání funkce specifické pro databázi.

Uvědomte si, že třídění velké sady výsledků pomocí funkce NÁHODNÉ se může ukázat jako velmi pomalé, takže to u malých sad výsledků udělejte.

Pokud musíte zamíchat velkou sadu výsledků a poté ji omezit, je lepší použít SQL Server TABLESAMPLE v SQL Server místo náhodných funkcí v klauzuli ORDER BY.

Předpokládáme tedy následující tabulku databáze:

enter image description here

A následující řádky v tabulce 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                      |

Na serveru SQL musíte použít funkci NEWID, jak ukazuje následující příklad:

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

Při spuštění výše uvedeného dotazu SQL na serveru SQL získáme následující sadu výsledků:

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

Všimněte si, že skladby jsou uvedeny v náhodném pořadí díky volání funkce NEWID, které používá klauzule ORDER BY.

0
Vlad Mihalcea