Виберіть n випадкових рядків із таблиці SQL Server


309

У мене є таблиця SQL Server з близько 50 000 рядків. Я хочу вибрати близько 5000 цих рядків навмання. Я думав про складний спосіб: створити темп-таблицю зі стовпцем "випадкове число", скопіювати свою таблицю в неї, прокрутити таблицю темп і оновити кожен рядок RAND(), а потім вибрати з цієї таблиці, де стовпець випадкового числа < 0,1 Я шукаю більш простий спосіб зробити це, якщо можливо, в одному заяві.

У цій статті пропонується використовувати NEWID()функцію. Це виглядає перспективно, але я не бачу, як я міг би надійно вибрати певний відсоток рядків.

Хтось колись робив це раніше? Будь-які ідеї?


3
MSDN має хорошу статтю, яка охоплює багато таких питань: Вибір випадкових рядків з великої таблиці
KyleMit

Відповіді:


387
select top 10 percent * from [yourtable] order by newid()

У відповідь на коментар "чистого сміття", що стосується великих таблиць: ви можете зробити це так, щоб покращити продуктивність.

select  * from [yourtable] where [yourPk] in 
(select top 10 percent [yourPk] from [yourtable] order by newid())

Вартість цього буде ключовим скануванням значень плюс вартість приєднання, яка на великій таблиці з невеликим вибором відсотків повинна бути розумною.


1
Мені подобається такий підхід набагато краще, ніж використання статті, на яку він посилався.
JoshBerke

14
Завжди добре пам’ятати, що newid () - це не дуже хороший генератор псевдовипадкових чисел, принаймні, не так добре, як rand (). Але якщо вам просто потрібні кілька невиразно випадкових зразків і не піклуються про математичні якості та інше, це буде досить добре. В іншому випадку вам потрібно: stackoverflow.com/questions/249301 / ...
user12861

1
Гм, вибачте, якщо це очевидно .. але на що [yourPk]йдеться? EDIT: Nvm, зрозумів це ... Первинний ключ. Durrr
Snailer

4
newid - guid розроблений як унікальний, але не випадковий .. неправильний підхід
Brans Ds

2
наприклад, велика кількість рядків, наприклад, понад 1 мільйон, newid()сортування. Оцінка впорядкування введення / виводу, буде дуже високою і призведе до ефективності.
aadi1295

81

Залежно від ваших потреб, TABLESAMPLEви отримаєте майже як випадкові та кращі показники. це доступно на сервері MS SQL 2005 та новіших версій.

TABLESAMPLE поверне дані з випадкових сторінок замість випадкових рядків і, отже, део навіть не отримає дані, які вони не повернуть.

На дуже великому столі я тестував

select top 1 percent * from [tablename] order by newid()

зайняло більше 20 хвилин.

select * from [tablename] tablesample(1 percent)

зайняло 2 хвилини.

Продуктивність також покращиться на менших зразках, TABLESAMPLEтоді як це не буде newid().

Зауважте, що це не так випадково, як newid()метод, але дасть вам гідну вибірку.

Див. Сторінку MSDN .


7
Як вказував Роб Бок нижче, таблиця скомпонує результати, і тому це не гарний спосіб отримати невелику кількість випадкових результатів
Оскар Аустегард

Ви заперечуєте питання про те, як це працює: виберіть верхній 1 відсоток * із [tablename] порядку від newid (), оскільки newid () не є стовпцем у [tablename]. Чи додає сервер sql внутрішнє стовпчик newid () у кожен рядок, а потім робить сортування?
FrenkyB

Таблиця зразків була найкращою відповіддю для мене, оскільки я робив складний запит на дуже великому столі. Немає сумніву, що це було надзвичайно швидко. Я отримав різницю в кількості повернених записів, коли я запускав це кілька разів, але всі вони були в межах допустимої помилки.
jessier3

38

newid () / замовлення буде працювати, але буде коштувати дуже дорого для великих наборів результатів, оскільки він повинен генерувати ідентифікатор для кожного ряду, а потім сортувати їх.

TABLESAMPLE () хороший з точки зору продуктивності, але ви отримаєте сукупність результатів (всі рядки на сторінці будуть повернуті).

Для кращого виконання справжньої випадкової вибірки найкращим способом є фільтрування рядків випадковим чином. Я знайшов такий зразок коду в онлайн-статті "Книги SQL Server" Обмеження наборів результатів за допомогою TABLESAMPLE :

Якщо ви дійсно хочете випадкову вибірку окремих рядків, змініть свій запит, щоб фільтрувати рядки випадковим чином, а не використовувати TABLESAMPLE. Наприклад, наступний запит використовує функцію NEWID для повернення приблизно одного відсотка рядків таблиці Sales.SalesOrderDetail:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

Стовпчик SalesOrderID включається у вираз CHECKSUM, щоб NEWID () оцінював один раз у ряд для досягнення вибірки на основі рядка. Вираз CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) обчислюється до випадкового значення поплавця між 0 і 1.

Якщо ви зіткнулися з таблицею з 1 000 000 рядків, ось мої результати:

SET STATISTICS TIME ON
SET STATISTICS IO ON

/* newid()
   rows returned: 10000
   logical reads: 3359
   CPU time: 3312 ms
   elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()

/* TABLESAMPLE
   rows returned: 9269 (varies)
   logical reads: 32
   CPU time: 0 ms
   elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)

/* Filter
   rows returned: 9994 (varies)
   logical reads: 3359
   CPU time: 641 ms
   elapsed time: 627 ms
*/    
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float) 
              / CAST (0x7fffffff AS int)

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

Якщо ви можете піти з використання TABLESAMPLE, це дасть вам найкращі показники. В іншому випадку використовуйте метод newid () / filter. newid () / order by має бути в крайньому випадку, якщо у вас великий набір результатів.


Я також бачив цю статтю і пробую її на своєму коді, здається, що NewID()її оцінюють лише один раз, а не за рядком, що мені не подобається ...
Ендрю Мао

23

Вибір випадкових рядків з великої таблиці на MSDN має просте, чітко сформульоване рішення, яке вирішує проблеми масштабної продуктивності.

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

Дуже цікаво. Прочитавши статтю, я насправді не розумію, чому RAND()не повертає однакове значення для кожного ряду (що би перемогло BINARY_CHECKSUM()логіку). Це тому, що він викликається всередині іншої функції, а не є частиною пропозиції SELECT?
Джон М Гант

Цей запит проходив за столом з 6ММ рядків менше ніж за секунду.
Марк Мелвілл

2
Я проводив цей запит по таблиці з 35 записами і дуже часто мав два з них у наборі результатів дуже часто. Це може бути проблемою rand()або комбінацією вищезазначеного, але я відмовився від цього рішення з цієї причини. Також кількість результатів коливалася від 1 до 5, тому це також може бути неприйнятним у деяких сценаріях.
Олівер

Чи RAND () не повертає однакове значення для кожного рядка?
Сарсапарілья

RAND()повертає однакове значення для кожного рядка (саме тому це рішення швидко). Однак рядки з двійковими контрольними сумами, які знаходяться дуже близько один до одного, піддаються великому ризику отримання аналогічних результатів контрольної суми, що призводить до збивання, коли RAND()вона невелика. Наприклад, (ABS(CAST((BINARY_CHECKSUM(111,null,null) * 0.1) as int))) % 100== SELECT (ABS(CAST((BINARY_CHECKSUM(113,null,null) * 0.1) as int))) % 100. Якщо ваші дані страждають від цієї проблеми, помножте BINARY_CHECKSUMна 9923.
Брайан,

12

Це посилання має цікаве порівняння між Orderby (NEWID ()) та іншими методами для таблиць з 1, 7 та 13 мільйонами рядків.

Часто, коли в групах для обговорення задаються питання про те, як вибрати випадкові рядки, пропонується запит NEWID; це просто і дуже добре працює для невеликих столів.

SELECT TOP 10 PERCENT *
  FROM Table1
  ORDER BY NEWID()

Однак у запиту NEWID є великий недолік, коли ви використовуєте його для великих таблиць. Становище ORDER BY призводить до того, що всі рядки таблиці копіюються в базу даних tempdb, де вони сортуються. Це спричиняє дві проблеми:

  1. Операція сортування, як правило, пов'язана з високою вартістю. Для сортування можна використовувати багато вводу-виводу диска і може працювати тривалий час.
  2. У гіршому випадку, у tempdb може не вистачити місця. У найкращому випадку сценарій tempdb може займати велику кількість дискового простору, який ніколи не буде повернутий без команди ручного зменшення.

Вам потрібен спосіб вибору рядків випадковим чином, які не використовуватимуть tempdb і не стануть набагато повільніше, оскільки таблиця збільшується. Ось нова ідея, як це зробити:

SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

Основна ідея цього запиту полягає в тому, що ми хочемо генерувати випадкове число від 0 до 99 для кожного рядка таблиці, а потім вибирати всі ті рядки, випадкове число яких менше значення вказаного відсотка. У цьому прикладі ми хочемо приблизно 10 відсотків рядків, вибраних випадковим чином; тому ми обираємо всі рядки, випадкове число яких менше 10.

Прочитайте повну статтю в MSDN .


2
Привіт Deumber, приємно знайдено, ти можеш розібрати це, оскільки лише відповіді на посилання будуть видалені.
буммі

1
@bummi Я змінив це, щоб уникнути відповіді лише на посилання :)
QMaster

Це найкраща відповідь. "ЗАМОВЛЕННЯ НА НОВИДІ ()" працює в більшості випадків (менші таблиці), але як орієнтири у посиланні refrenced чітко показують, він відстає під час зростання таблиці
pedram bashiri

10

Якщо вам (на відміну від ОП) потрібна певна кількість записів (що робить підхід CHECKSUM складним) і ви хочете отримати більш випадкову вибірку, ніж надає TABLESAMPLE, а також бажаєте кращої швидкості, ніж CHECKSUM, ви можете зробити злиття Методи ТАБЛЕСАМПЛЬ та NEWID (), такі:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

SELECT TOP (@sampleCount) * 
FROM [yourtable] TABLESAMPLE(10 PERCENT)
ORDER BY NEWID()

SET STATISTICS TIME OFF

У моєму випадку це найпростіший компроміс між випадковістю (я це насправді не знаю) та швидкістю. Відміняйте відсотковий відсоток (або рядки) ТАБЛЕСАМПЛЕ - чим вище відсоток, тим більше випадкова вибірка, але очікуйте лінійного падіння швидкості. (Зверніть увагу, що TABLESAMPLE не прийме змінну)


9

Просто впорядкуйте таблицю випадковим числом і отримайте перші 5000 рядків, використовуючи TOP.

SELECT TOP 5000 * FROM [Table] ORDER BY newid();

ОНОВЛЕННЯ

Просто спробував, і newid()дзвінок достатній - не потрібно всіх кастингу та всієї математики.


10
Причина того, що "всі касти та вся математика" використовується для кращої продуктивності.
hkf

6

Це поєднання початкової ідеї про насіння та контрольної суми, яка, як мені здається, дає належні випадкові результати без витрат на NEWID ():

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())

3

У MySQL ви можете зробити це:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;

3
Це не вийде. Оскільки оператор select є атомним, він захоплює лише одне випадкове число і дублює його для кожного рядка. Вам доведеться перезавантажити його на кожному ряду, щоб змусити його змінитись.
Том Н

4
Ммм ... люблю різниці продавців. Вибір є атомним на MySQL, але я вважаю, що це по-іншому. Це буде працювати в MySQL.
Джефф Ферланд

2

Ще не бачив такої варіації у відповідях. У мене було додаткове обмеження, де мені потрібно, давши початкове насіння, щоразу вибирати один і той же набір рядків.

Для MS SQL:

Мінімальний приклад:

select top 10 percent *
from table_name
order by rand(checksum(*))

Нормалізований час виконання: 1.00

Приклад NewId ():

select top 10 percent *
from table_name
order by newid()

Нормалізований час виконання: 1,02

NewId()це незначно повільніше rand(checksum(*)), тому ви, можливо, не захочете використовувати його проти великих записів.

Відбір з початковим насінням:

declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */

select top 10 percent *
from table_name
order by rand(checksum(*) % @seed) /* any other math function here */

Якщо вам потрібно вибрати той самий набір із даним насінням, це, здається, спрацює.


Чи є якась перевага використання спеціального @seed проти RAND ()?
QMaster

абсолютно ви використовували параметр насіння і заповнюєте його параметром дати, функція RAND () робить те саме, за винятком використання повного значення часу, я хочу знати, чи є якась перевага використання зручного створеного параметра, як насіння вище RAND (), чи ні?
QMaster

Ах !. Гаразд, це було вимогою проекту. Мені потрібно було генерувати список n-випадкових рядків детермінованим чином. В основному керівництво хотіло знати, які "випадкові" рядки ми вибираємо за кілька днів до того, як рядки будуть відібрані та оброблені. Створюючи значення насіння на основі року / місяця, я міг би гарантувати будь-який дзвінок на запит, який рік поверне той самий "випадковий" список. Я знаю, це було дивно, і, мабуть, були кращі способи, але це спрацювало ...
klyd

ХАХА :) Я бачу, але я думаю, що загальне значення випадкових вибраних записів - це не однакові записи в різних запущених запитах.
QMaster


0

Здається, newid () не може бути використаний у тому пункті, де це рішення вимагає внутрішнього запиту:

SELECT *
FROM (
    SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
    FROM MyTable
) vw
WHERE Rnd % 100 < 10        --10%

0

Я використовував його в підзапиті, і він повертав мені ті самі рядки в підзапиті

 SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

то я вирішив, включаючи змінну батьківської таблиці, де

SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              Where Mytable.ID>0
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

Зверніть увагу на умову де


0

Використовувана мова обробки сервера на стороні сервера (наприклад, PHP, .net тощо) не вказана, але якщо це PHP, захопіть необхідне число (або всі записи) і замість рандомізації в запиті використовуйте функцію переміщення PHP. Я не знаю, чи .net має еквівалентну функцію, але якщо вона використовується, якщо ви використовуєте .net

ORDER BY RAND () може мати певну ефективність покарання, залежно від кількості записів.


Я не пригадую, для чого я цим часом користувався, але я, мабуть, працював у C #, можливо, на сервері чи, можливо, у клієнтській програмі, не впевнений. У C # немає нічого, що безпосередньо можна порівняти з афаіком перетасування PHP, але це можна зробити, застосувавши функції від об'єкта Random в операції Select, упорядкувавши результат, а потім взявши верхній десяток відсотків. Але нам доведеться прочитати всю таблицю з диска на сервері БД і передати її по мережі, тільки щоб відкинути 90% цих даних. Обробка його безпосередньо в БД майже напевно є більш ефективною.
Джон М Гант

-2

Це працює для мене:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]

9
@ user537824, ви пробували це на SQL Server? RANDOM - це не функція, а LIMIT - це не ключове слово. Синтаксис SQL Server для того, що ви робите, був би select top 10 percent from table_name order by rand(), але це також не працює, тому що rand () повертає однакове значення у всіх рядках.
Джон М Гант
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.