TSQL Генерация строки длиной 5 символов со всеми цифрами [0-9], которая еще не существует в базе данных. - PullRequest
2 голосов
/ 07 октября 2009

Какой лучший способ сделать это?

Мне нужно создать строку длиной 5 цифр, в которой все символы являются числовыми. Тем не менее, я должен быть в состоянии сделать это «х» количество раз (пользовательская переменная) и хранить эти случайные строки в базе данных. Кроме того, я не могу сгенерировать одну и ту же строку дважды. Старые строки будут удалены через 6 месяцев.

Псевдо-код

DECLARE @intIterator INT,
 @intMax

SET @intIterator = 1
SET @intMax = 5 (number of strings to generate)

WHILE @intIterator <= @intMax
 BEGIN

  -- GENERATE RANDOM STRING OF 5 NUMERIC DIGITS 
   ???

  -- INSERT INTO DB IF DOESN'T ALREADY EXIST 
  INSERT INTO TSTRINGS
  SELECT @RANDOMSTRING

  IF @@ERROR = 0
    SET @intIterator = @intIterator + 1

 END

Я знаю, что это, вероятно, не лучший способ сделать это, поэтому совет приветствуется. Но действительно ищите идеи о том, как генерировать числовые строки длиной 5

.

Ответы [ 6 ]

6 голосов
/ 07 октября 2009

«Очевидный» способ может быть описан как «key = random; while (key already selected) { key = random }». Он работает , но парадокс дня рождения подразумевает увеличение вероятности столкновения клавиш с пугающей экспоненциальной скоростью пропорционально количеству уже использованных ключей. Таким образом, выбор случайного ключа занимает в среднем экспоненциально больше времени с каждым новым ключом, и, скорее всего, в конечном итоге может оказаться в ловушке бесконечного или произвольно длинного цикла.

Вам гораздо лучше сгенерировать список ключей заранее следующим образом:

  • Содержит таблицу UniqueKeys, содержащую все предварительно вычисленные строки '00000' .. '99999' в дополнение к полю keyOrder, которое всегда инициализируется на newId() при вставке. keyOrder должен быть проиндексирован.

  • Когда вам нужно «сгенерировать» строку, вы можете SELECT TOP 1 <code>key FROM UniqueKeys ORDER BY keyOrder , который будет тянуть следующую доступную клавишу в почти постоянное время. Теперь, когда у вас есть ключ, вы можете удалить его из UniqueKeys, чтобы предотвратить его повторное использование.

  • Каждые шесть месяцев обрезайте и регенерируйте таблицу UniqueKeys.

Преимуществом этого стиля является относительно простая реализация, почти постоянное время для генерации следующего ключа и избежание неприятного сценария "проверьте, существует ли в цикле", описанного выше.

3 голосов
/ 07 октября 2009

Все в одном. Это должно найти оставшиеся значения @intMax, если у вас уже есть (100000 - @intMax) строк с только перестановками @intMax, оставленными

INSERT TOP (@intMax) MyTable (RndColumn)
SELECT
    RndValue
FROM
    (
    SELECT DISTINCT TOP 100000 -- covers potential range from 00000 to 99999
        RIGHT('00000' + CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)), 5) AS RndValue
    FROM
        sys.columns c1, sys.columns c2
    ) foo
WHERE
    NOT EXISTS (SELECT *
        FROM
            MyTable T
        WHERE
            T.RndColumn = foo.RndValue
0 голосов
/ 07 октября 2009

Вот подход, основанный на множествах, с использованием синтаксиса SQL 2005 (было бы немного проще с SQL 2008, но вы не указали). Кроме того, если у вас есть таблица чисел, можно вырезать большую ее часть.

Без зацикливания, без дубликатов и должно быть почти мгновенным (при условии, что целевой столбец проиндексирован).

DECLARE @intMax integer
SET @intMax = 5

INSERT INTO TSTRINGS
SELECT q.nString
    FROM ( SELECT ROW_NUMBER() OVER (ORDER BY (newID())) AS N2,
             RIGHT(REPLICATE('0', 5) + CONVERT(varchar(5), N), 5) as nString
           FROM   --the subquery below could be replaced by a numbers table
                  (SELECT TOP 100000
                  ROW_NUMBER() OVER (ORDER BY (ac1.Object_ID))-1 AS N
            FROM Master.sys.columns ac1
                 CROSS JOIN Master.sys.columns ac2
                 CROSS JOIN Master.sys.columns ac3) numbers 

          WHERE RIGHT(REPLICATE('0', 5) + CONVERT(varchar(5), N), 5)
            NOT IN (SELECT nString FROM TSTRINGS) --check to see if reused
    ) q
    WHERE q.N2 <= @intMax
0 голосов
/ 07 октября 2009

Один из способов создать такую ​​строку:

DECLARE @Foo char(5)
SET @Foo = right(str((checksum(newid()) + 100000), 11, 0), 5)
PRINT @Foo

Что касается уникальности, вам придется построить цикл вокруг таблицы, содержащей (проиндексированные!) Ранее существующие значения, выходя из цикла только при генерировании «нового» идентификатора. Вы можете столкнуться с проблемами параллелизма, если два отдельных процесса каким-то образом генерируют одно и то же значение, когда первый не вводит его в таблицу до того, как второй проверяет наличие ... но многое зависит от того, когда и как это значение фактически используется.

0 голосов
/ 07 октября 2009

Как то так?

CREATE FUNCTION RandNumber2(@Min int, @Max int)
RETURNS float
AS
BEGIN
  DECLARE @TheNumber INT

  SET @TheNumber = (SELECT CONVERT(INT, Rand()*(@Max-@Min)+@Min))

  WHILE (SELECT COUNT(IndexColumn) WHERE CONVERT(INT, IndexColumn) = @TheNumber) > 0
  BEGIN
    -- Do it again - we have a collision
    SET @TheNumber = (SELECT CONVERT(INT, Rand()*(@Max-@Min)+@Min))
  END

  DECLARE @Result VARCHAR(5)
  SET @Result = RIGHT('00000' + CONVERT(VARCHAR(5), @TheNumber), 5)

  RETURN @Result

END
0 голосов
/ 07 октября 2009

Вам нужна логика, чтобы проверить, существует ли число?

Вы можете использовать следующее для генерации случайного числа:

CREATE FUNCTION RandNumber()
RETURNS float
AS
  BEGIN
  RETURN (SELECT RandNumber FROM vRandNumber)
END

CREATE FUNCTION RandNumber2(@Min int, @Max int)
RETURNS float
AS
 BEGIN
 RETURN @Min + (select RandNumber from RetRandNumber) * (@Max-@Min)
END

Тогда просто позвоните RandNumber по вашему выбору.

Вот сайт, который я нашел с помощью этого скрипта: Здесь

...