UPDATE CattleProds
SET SheepTherapy=(ROUND((RAND()* 10000),0))
WHERE SheepTherapy IS NULL
Если я затем сделаю SELECT, я вижу, что мое случайное число идентично в каждой строке . Есть идеи, как сгенерировать уникальные случайные числа?
UPDATE CattleProds
SET SheepTherapy=(ROUND((RAND()* 10000),0))
WHERE SheepTherapy IS NULL
Если я затем сделаю SELECT, я вижу, что мое случайное число идентично в каждой строке . Есть идеи, как сгенерировать уникальные случайные числа?
Ответы:
Вместо rand()
, используйте newid()
, который пересчитывается для каждой строки результата. Обычный способ - использовать контрольную сумму по модулю. Обратите внимание, что это checksum(newid())
может привести к результату -2 147 483 648 и вызвать целочисленное переполнение abs()
, поэтому нам нужно использовать по модулю для возвращаемого значения контрольной суммы перед преобразованием его в абсолютное значение.
UPDATE CattleProds
SET SheepTherapy = abs(checksum(NewId()) % 10000)
WHERE SheepTherapy IS NULL
Это генерирует случайное число от 0 до 9999.
Если вы используете SQL Server 2008, вы также можете использовать
CRYPT_GEN_RANDOM(2) % 10000
Что кажется несколько проще (он также оценивается один раз для каждой строки как newid
есть - показано ниже)
DECLARE @foo TABLE (col1 FLOAT)
INSERT INTO @foo SELECT 1 UNION SELECT 2
UPDATE @foo
SET col1 = CRYPT_GEN_RANDOM(2) % 10000
SELECT * FROM @foo
Возврат (2 случайных числа, вероятно, разных )
col1
----------------------
9693
8573
Обдумывая необъяснимое отрицательное голосование, единственная законная причина, о которой я могу думать, заключается в том, что поскольку сгенерированное случайное число находится в диапазоне от 0 до 65535, которое не делится на 10 000 без остатка, некоторые числа будут представлены немного больше. Обойти это можно, заключив его в скалярную UDF, которая отбрасывает любое число, превышающее 60000, и рекурсивно вызывает себя, чтобы получить заменяющий номер.
CREATE FUNCTION dbo.RandomNumber()
RETURNS INT
AS
BEGIN
DECLARE @Result INT
SET @Result = CRYPT_GEN_RANDOM(2)
RETURN CASE
WHEN @Result < 60000
OR @@NESTLEVEL = 32 THEN @Result % 10000
ELSE dbo.RandomNumber()
END
END
Хотя мне нравится использовать КОНТРОЛЬНУЮ СУММ, я считаю, что лучше использовать NEWID()
, просто потому, что вам не нужно выполнять сложную математику для генерации простых чисел.
ROUND( 1000 *RAND(convert(varbinary, newid())), 0)
Вы можете заменить 1000
любое число, которое хотите установить в качестве ограничения, и вы всегда можете использовать знак плюса для создания диапазона, скажем, вам нужно случайное число между 100
и 200
, вы можете сделать что-то вроде:
100 + ROUND( 100 *RAND(convert(varbinary, newid())), 0)
Объединяя это в свой запрос:
UPDATE CattleProds
SET SheepTherapy= ROUND( 1000 *RAND(convert(varbinary, newid())), 0)
WHERE SheepTherapy IS NULL
Я протестировал 2 метода рандомизации на основе наборов против RAND (), сгенерировав по 100000000 строк для каждого. Для выравнивания поля вывод представляет собой число с плавающей запятой между 0-1, чтобы имитировать RAND (). Большая часть кода - это инфраструктура тестирования, поэтому я резюмирую алгоритмы здесь:
-- Try #1 used
(CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)%500000000000000000+500000000000000000.0)/1000000000000000000 AS Val
-- Try #2 used
RAND(Checksum(NewId()))
-- and to have a baseline to compare output with I used
RAND() -- this required executing 100000000 separate insert statements
Использование CRYPT_GEN_RANDOM было явно наиболее случайным, поскольку вероятность увидеть хотя бы 1 дубликат составляет всего 0,0000001% при извлечении 10 ^ 8 чисел ИЗ набора из 10 ^ 18 чисел. IOW мы не должны были видеть никаких дубликатов, а этого не было! На создание этого набора на моем ноутбуке ушло 44 секунды.
Cnt Pct
----- ----
1 100.000000 --No duplicates
Время выполнения SQL Server: время ЦП = 134795 мс, затраченное время = 39274 мс.
IF OBJECT_ID('tempdb..#T0') IS NOT NULL DROP TABLE #T0;
GO
WITH L0 AS (SELECT c FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c)) -- 2^4
,L1 AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B) -- 2^8
,L2 AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B) -- 2^16
,L3 AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B) -- 2^32
SELECT TOP 100000000 (CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)%500000000000000000+500000000000000000.0)/1000000000000000000 AS Val
INTO #T0
FROM L3;
WITH x AS (
SELECT Val,COUNT(*) Cnt
FROM #T0
GROUP BY Val
)
SELECT x.Cnt,COUNT(*)/(SELECT COUNT(*)/100 FROM #T0) Pct
FROM X
GROUP BY x.Cnt;
Почти на 15 порядков меньше случайности, этот метод был не вдвое быстрее, всего за 23 секунды для генерации 100 миллионов чисел.
Cnt Pct
---- ----
1 95.450254 -- only 95% unique is absolutely horrible
2 02.222167 -- If this line were the only problem I'd say DON'T USE THIS!
3 00.034582
4 00.000409 -- 409 numbers appeared 4 times
5 00.000006 -- 6 numbers actually appeared 5 times
Время выполнения SQL Server: время ЦП = 77156 мс, затраченное время = 24613 мс.
IF OBJECT_ID('tempdb..#T1') IS NOT NULL DROP TABLE #T1;
GO
WITH L0 AS (SELECT c FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c)) -- 2^4
,L1 AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B) -- 2^8
,L2 AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B) -- 2^16
,L3 AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B) -- 2^32
SELECT TOP 100000000 RAND(Checksum(NewId())) AS Val
INTO #T1
FROM L3;
WITH x AS (
SELECT Val,COUNT(*) Cnt
FROM #T1
GROUP BY Val
)
SELECT x.Cnt,COUNT(*)*1.0/(SELECT COUNT(*)/100 FROM #T1) Pct
FROM X
GROUP BY x.Cnt;
Сама по себе функция RAND () бесполезна для генерации на основе наборов, поэтому генерация базовой линии для сравнения случайности заняла более 6 часов, и ее пришлось перезапускать несколько раз, чтобы наконец получить нужное количество строк вывода. Также кажется, что случайность оставляет желать лучшего, хотя это лучше, чем использование контрольной суммы (newid ()) для повторного заполнения каждой строки.
Cnt Pct
---- ----
1 99.768020
2 00.115840
3 00.000100 -- at least there were comparitively few values returned 3 times
Из-за перезапусков не удалось зафиксировать время выполнения.
IF OBJECT_ID('tempdb..#T2') IS NOT NULL DROP TABLE #T2;
GO
CREATE TABLE #T2 (Val FLOAT);
GO
SET NOCOUNT ON;
GO
INSERT INTO #T2(Val) VALUES(RAND());
GO 100000000
WITH x AS (
SELECT Val,COUNT(*) Cnt
FROM #T2
GROUP BY Val
)
SELECT x.Cnt,COUNT(*)*1.0/(SELECT COUNT(*)/100 FROM #T2) Pct
FROM X
GROUP BY x.Cnt;
require_once('db/connect.php');
//rand(1000000 , 9999999);
$products_query = "SELECT id FROM products";
$products_result = mysqli_query($conn, $products_query);
$products_row = mysqli_fetch_array($products_result);
$ids_array = [];
do
{
array_push($ids_array, $products_row['id']);
}
while($products_row = mysqli_fetch_array($products_result));
/*
echo '<pre>';
print_r($ids_array);
echo '</pre>';
*/
$row_counter = count($ids_array);
for ($i=0; $i < $row_counter; $i++)
{
$current_row = $ids_array[$i];
$rand = rand(1000000 , 9999999);
mysqli_query($conn , "UPDATE products SET code='$rand' WHERE id='$current_row'");
}