Длина целого числа в SQL (то есть длина десятичной строки) - PullRequest
9 голосов
/ 21 марта 2011

Быстрая версия: Что является лучшим из следующих и почему? (или есть лучший способ):

SELECT FLOOR(LOG10(Number))+1 AS NumLength FROM Table
SELECT LEN(CONVERT(VARCHAR, Number)) AS NumLength FROM Table
SELECT LEN(CAST(Number AS VARCHAR(10))) AS NumLength FROM Table

Немного подробнее:
Я хочу определить наиболее эффективный механизм для вычисления длины строкового представления целого числа (точнее, натурального числа - всегда> 0).

Я использую MS SQL Server (2005).

Я придумал 3 решения, описанные выше, которые кажутся работающими нормально.

Я знаю, что в третьей версии могут быть проблемы с очень большими целыми числами, но сейчас мы можем предположить, что длина "Number" никогда не превышает 9 десятичных цифр.

Еще более подробно: (вам не нужно читать этот бит, чтобы ответить на мой вопрос)
Этот запрос интенсивно используется в среде обработки транзакций.
До сих пор мне не нравилось предположение, что «Число» всегда ровно 6 цифр.
Однако теперь я должен обновить код для поддержки от 4 до 9 цифр.

Этот SQL является частью условия для идентификации схемы карты карты.

Полный запрос пытается найти записи, соответствующие началу номера карты в диапазоне начала и конца.

Итак, полное условие SQL будет выглядеть примерно так:

WHERE 
-- Start and End match
((Start=End OR End=0) AND (Start=CAST(LEFT('<card number>', FLOOR(LOG10(Start))+1) AS BIGINT))) OR 

-- Start != End
-- >= Start
(Start<=CAST(LEFT('<card number>', FLOOR(LOG10(Start))+1) AS BIGINT) AND 
-- <= End
End>=CAST(LEFT('<card number>', FLOOR(LOG10(Start))+1) AS BIGINT))

Примечание:
Я могу изменить дизайн таблицы, чтобы использовать VARCHAR вместо INT. Это позволило бы мне использовать «LEN (Start)» вместо «FLOOR (LOG10 (Start)) + 1)», однако тогда условие будет иметь гораздо больше CASTS.
Я бы предпочел продолжать работать с INT, поскольку схема БД останется прежней, и в любом случае работа с INT должна выполняться быстрее, чем VARCHAR.

Если я изменю поля на VARCHAR, моё состояние может быть:

WHERE 
-- Start and End match
((Start=End OR LEN(End)=0) AND (Start=LEFT('<card number>', LEN(Start)))) OR 

-- Start != End
-- >= Start
(CAST(Start AS BIGINT)<=CAST(LEFT('<card number>', LEN(Start)) AS BIGINT) AND 
-- <= End
CAST(End AS BIGINT)>=CAST(LEFT('<card number>', LEN(Start)) AS BIGINT))

Большое спасибо за любую помощь,
Dave

Ответы [ 2 ]

2 голосов
/ 21 марта 2011

На моей машине версии 2 и 3 выходят примерно равными и бьют две другие.

Редактировать: Хотя мне только что пришло в голову, что мой первоначальный тест был немного несправедливым на CASE, поскольку упорядочение операторов в порядке возрастания номеров означает, что только 10 возможных чисел будут соответствоватьпервое условие и выход рано.Я добавил дополнительный тест ниже.Вы также можете попробовать вложить операторы CASE для выполнения бинарного поиска.

SET NOCOUNT ON
SET STATISTICS TIME ON

  PRINT 'Test 1';

   WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT MAX(FLOOR(LOG10(N))+1)
   FROM cteTally
  WHERE N <= 10000000;

  PRINT 'Test 2';

     WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT MAX(LEN(CONVERT(VARCHAR, N)))
   FROM cteTally
  WHERE N <= 10000000;


  PRINT 'Test 3';

     WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT MAX(LEN(CAST(N AS VARCHAR(10))))
   FROM cteTally
  WHERE N <= 10000000;

  PRINT 'Test 4';

     WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
SELECT MAX(CASE
             WHEN N < 10 THEN 1
             WHEN N < 100 THEN 2
             WHEN N < 1000 THEN 3
             WHEN N < 10000 THEN 4
             WHEN N < 100000 THEN 5
             WHEN N < 1000000 THEN 6
             WHEN N < 10000000 THEN 7
             WHEN N < 100000000 THEN 8
           END)
FROM   cteTally
WHERE  N <= 10000000;   

  PRINT 'Test 5';

     WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT MAX(CASE 
             WHEN N >= 100000000 THEN NULL
             WHEN N >= 10000000 THEN 8
             WHEN N >= 1000000  THEN 7
             WHEN N >= 100000   THEN 6
             WHEN N >= 10000    THEN 5
             WHEN N >= 1000     THEN 4
             WHEN N >= 100      THEN 3
             WHEN N >= 10       THEN 2   
             ELSE                    1
            END   )
   FROM cteTally
  WHERE N <= 10000000;

Результаты примера, запущенного на моем компьютере,

Test 1
   CPU time = 9422 ms,  elapsed time = 9523 ms.

Test 2
   CPU time = 7021 ms,  elapsed time = 7130 ms.

Test 3
   CPU time = 6864 ms,  elapsed time = 7006 ms.

Test 4
   CPU time = 9328 ms,  elapsed time = 9456 ms.

Test 5
   CPU time = 6989 ms,  elapsed time = 7358 ms.    
1 голос
/ 21 марта 2011

Чтобы ответить на ваш вопрос, вторая версия более ясна о том, что вы на самом деле хотите.Подумайте о том, что подумают те, кто просматривает этот код через шесть месяцев: поймут ли они, что первая версия пытается получить длину числа, представленного в десятичном виде, или они подумают, что вы выполняете какую-то неясную математическую операцию, которую они могут 'не найти документацию, требующую?

В целом, тем не менее, вы, вероятно, должны рассмотреть вопрос о сохранении этих значений в качестве символьных данных в любом случае, поскольку они не представляют вам реальные "числа" (вы не сравниваете на основе относительного значенияВы не выполняете арифметику и т. д.).Вы можете использовать ограничения CHECK, чтобы гарантировать, что в поле присутствуют только числовые цифры.

Я не понимаю, почему хранение их в качестве символьных данных потребует преобразований в ваших запросах, при условии, что вы согласны.Также нет оснований предполагать, что работа с int будет быстрее, чем varchar, особенно если в обоих случаях происходит преобразование.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...