Как фильтровать nvarchar больше, чем числовое значение? - PullRequest
4 голосов
/ 08 апреля 2009

У меня есть таблица MS SQL McTable со столбцом BigMacs nvarchar (255). Я хотел бы получить строки со значением BigMacs больше 5.

Что я делаю:

select * from
  (
    select 
      BigMacs BigMacsS, 
      CAST(BigMacs as Binary) BigMacsB, 
      CAST(BigMacs as int) BigMacsL
    from 
      McTable
    where 
      BigMacs Like '%[0-9]%'
  ) table
where 
  Cast(table.BigMacsL as int) > 5 

И в результате я получаю ошибку:

Состояние 1, строка 67 Преобразование не выполнено при преобразовании значения nvarchar "***" для тип данных int.

Но когда я удаляю последний фильтр where Cast(table.BigMacsL as int) > 5, он работает, и я получаю такой результат:

6    0x360000000000000000000000000000000000000000000000000000000000 6
23   0x320033000000000000000000000000000000000000000000000000000000 23
22   0x320032000000000000000000000000000000000000000000000000000000 22
24   0x320034000000000000000000000000000000000000000000000000000000 24
25   0x320035000000000000000000000000000000000000000000000000000000 25
3    0x330000000000000000000000000000000000000000000000000000000000 3
17   0x310037000000000000000000000000000000000000000000000000000000 17
17   0x310037000000000000000000000000000000000000000000000000000000 17
19   0x310039000000000000000000000000000000000000000000000000000000 19
20   0x320030000000000000000000000000000000000000000000000000000000 20
659  0x360035003900000000000000000000000000000000000000000000000000 659
1    0x310000000000000000000000000000000000000000000000000000000000 1
43   0x340033000000000000000000000000000000000000000000000000000000 43
44   0x340034000000000000000000000000000000000000000000000000000000 44
45   0x340035000000000000000000000000000000000000000000000000000000 45
46   0x340036000000000000000000000000000000000000000000000000000000 46
47   0x340037000000000000000000000000000000000000000000000000000000 47
44   0x340034000000000000000000000000000000000000000000000000000000 44
44   0x340034000000000000000000000000000000000000000000000000000000 44
47   0x340037000000000000000000000000000000000000000000000000000000 47
43   0x340033000000000000000000000000000000000000000000000000000000 43
50   0x350030000000000000000000000000000000000000000000000000000000 50
44   0x340034000000000000000000000000000000000000000000000000000000 44

И когда я изменяю в первом запросе «select * from» на «select top 18 * from», я тоже не получаю ошибку!

Я не знаю, в чем проблема и как заставить это работать! Не могли бы вы помочь мне?

Еще раз: здесь я пытаюсь получить строки McTable со значением BigMacs больше 5.

UPDATE

Действия по воспроизведению этой ошибки:

Я подготовил запросы, чтобы вы могли легко получить эту ошибку в своей базе данных:

Создать базу данных TestDB, создать таблицу с:

USE [TestDB]
GO
/****** Object:  Table [dbo].[TestTable]    Script Date: 04/08/2009 16:27:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TestTable](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [MyVal] [nvarchar](255) COLLATE Polish_CI_AS NOT NULL,
 CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

вставить значения с помощью:

delete from TestDB.dbo.TestTable
insert into TestDB.dbo.TestTable (MyVal) values ('fd')
insert into TestDB.dbo.TestTable (MyVal) values ('54543534')
insert into TestDB.dbo.TestTable (MyVal) values ('fat3tv3gv5')
insert into TestDB.dbo.TestTable (MyVal) values ('fdf4v43 4v434v')
insert into TestDB.dbo.TestTable (MyVal) values (' g dfg dfg df')
insert into TestDB.dbo.TestTable (MyVal) values ('f sd 4t4gsdf')
insert into TestDB.dbo.TestTable (MyVal) values ('f df 4 trwefg')
insert into TestDB.dbo.TestTable (MyVal) values ('f sd f4  fgsfg sd')
insert into TestDB.dbo.TestTable (MyVal) values ('54534534')
insert into TestDB.dbo.TestTable (MyVal) values ('454')

Этот запрос:

    select 
        CAST(MyVal as int) MyValInt 
    from 
        dbo.TestTable 
    where 
        IsNumeric(MyVal) = 1

приводит к действительным числам, как показано ниже:

54543534

54534534

454

И при попытке получить отфильтрованные значения с помощью этого запроса:

select 
    * 
from
    (
        select 
            CAST(MyVal as int) MyValInt 
        from 
            dbo.TestTable 
        where 
            IsNumeric(MyVal) = 1
    ) tabela
where 
    tabela.MyValInt > 6

Вы должны получить эту ошибку, которая не должна возникать:

Сообщение 245, Уровень 16, Состояние 1, Строка 1 Ошибка преобразования при преобразовании значения nvarchar 'fd' в тип данных int.

Ответы [ 9 ]

6 голосов
/ 08 апреля 2009

Новый ответ для ваших исправленных скриптов. Происходит то, что оптимизатор запросов SQL Server оптимизирует ваш подзапрос. Он выполняет одно сканирование тестовой таблицы и объединяет внутреннее и внешнее предложения WHERE в одно. Вот почему вы все еще получаете ошибку. Чтобы увидеть это, просмотрите примерный план выполнения запроса и наведите указатель мыши на значок Clustered Index Scan, чтобы увидеть, что на самом деле выполняется. Вы увидите следующий предикат, примененный к сканированию:

CONVERT(int,[testdb].[dbo].[TestTable].[MyVal],0)>(6)
AND isnumeric(CONVERT_IMPLICIT(varchar(510),
    [testdb].[dbo].[TestTable].[MyVal],0))=(1)

Таким образом, независимо от структуры вашего запроса, он пытается выполнить CAST / CONVERT для каждой строки в таблице ...

Чтобы избежать этого, используйте переменную таблицы или временную таблицу, которую нельзя оптимизировать:

DECLARE @integers table (
    MyValInt int
)

INSERT
INTO    @integers
SELECT  CAST(MyVal AS int)
FROM    dbo.TestTable 
WHERE   ISNUMERIC(MyVal) = 1

SELECT  *
FROM    @integers
WHERE   MyValInt > 6

Набор результатов, который вы на самом деле хотите вернуть, будет другим, поэтому я бы предложил сохранить первичный ключ вместе со значением int в табличной переменной, а затем выполнить окончательный запрос как объединение, например:

DECLARE @integers table (
    ID int,
    MyValInt int
)

INSERT
INTO    @integers
SELECT  ID, CAST(MyVal AS int)
FROM    dbo.TestTable 
WHERE   ISNUMERIC(MyVal) = 1

SELECT  b.*
FROM    @integers t
        INNER JOIN
                TestTable b
                ON b.ID = t.ID
WHERE   t.MyValInt > 6
2 голосов
/ 08 апреля 2009

Состояние 1, строка 67 Сбой преобразования при преобразовании значения nvarchar '***' в тип данных int.

Вы получаете это значение, так как некоторые из значений в BigMacs.BigMac содержат нечисловое значение. В вашем случае "***".

И когда я изменяю в первом запросе «select * from» на «select top 18 * from», я тоже не получаю ошибку!

Это потому, что как минимум первые возвращенные 18 строк имеют числовые значения BigMacs.BigMac.

Создайте новый определяемый пользователем метод с именем isReallyNumeric () , который обращается к тому, что действительно является числовым или нет.

Отфильтруйте только числовой BigMac с помощью функции isReallyNumeric ()
Я также оптимизировал запрос для преобразования BigMacs.BigMac в целое число один раз, используя CTE (Common Table Expression).

with NumericBigMacs as (
    select 
      BigMacs as BigMacsS, 
      CAST(BigMacs as Binary) as BigMacsB, 
      CAST(BigMacs as int) as BigMacsL
    from 
      McTable
    where 
      -- Filter only numeric values to compare.
      -- BigMacs Like '%[0-9]%'
      dbo.isReallyNumeric(BigMacs) = 1
)
select  *
from    NumericBigMacs NBM
where   BigMacsL > 5
1 голос
/ 08 апреля 2009

ОК не числовой не всегда работает при сохранении символьных данных и чисел в одном столбце. Он также не ограничен элементами, которые можно преобразовать в целые числа. Смотрите эту ссылку для объяснения: http://www.tek -tips.com / faqs.cfm? FID = 6423

Мой первый вопрос: почему вы храните вещи, которые хотите использовать в качестве цифр и символов, в одном столбце? Это серьезный конструктивный недостаток, и его следует исправить, если это вообще возможно.

Я полагаю, что ссылка может помочь вам понять, что делать, если вы не можете изменить структуру.

1 голос
/ 08 апреля 2009

Проблема в том, что вы можете CAST только значение int, если оно действительно содержит int. Очевидно, что ваши первые 18 строк. Но затем, если вы включите больше строк, он достигнет строки, в которой значение не может быть преобразовано в int, и вы получите ошибку, которую вы описали. Как насчет этого:

select 
      BigMacs BigMacsS, 
      CAST(BigMacs as Binary) BigMacsB 
    from 
      McTable
    where 
      BigMacs Like '%[6-9]%'
    or
      BigMacs LIKE '%[1-9][0-5]%'

Это найдет все строки, содержащие в тексте этого столбца число, которое больше 5 (при условии, что в нем нет десятичных или отрицательных чисел).

0 голосов
/ 08 апреля 2009

Этот код должен работать:

select
    tabela.*
from
    (
        select
                CAST(MyVal as int) MyValInt
        from
                dbo.TestTable
        where
                IsNumeric(MyVal) = 1
    ) tabela
    left join (select 1 a )a on tabela.MyValInt > 6

Я думаю, что причина сбоя исходного запроса может быть связана с порядком, в котором запрос оценивается SQL.

0 голосов
/ 08 апреля 2009

Я думаю, что Дэвид М. прибил его по голове, но для тех, кто попросил сценарий, который воспроизводит проблему:

CREATE TABLE dbo.Test_Int_Conversion
(
    my_id   INT IDENTITY    NOT NULL,
    my_str  VARCHAR(20)     NOT NULL,
    CONSTRAINT PK_Test_Int_Conversion PRIMARY KEY CLUSTERED (my_id)
)
GO

INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('1')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('2')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('3')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('4')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('5')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('6')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('7')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('8')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('9')
INSERT INTO dbo.Test_Int_Conversion (my_str) VALUES ('*')
GO

SELECT * FROM (
SELECT
    my_id,
    CAST(my_str AS INT) my_strI
FROM
    dbo.Test_Int_Conversion
WHERE
    my_str LIKE '%[0-9]%'
) SQ
WHERE
    CAST(SQ.my_strI AS INT) > 5
0 голосов
/ 08 апреля 2009

Я думаю, что использование функции ISNUMERIC () также может помочь.

Пример:

SELECT * FROM
(
    SELECT CAST(CASE WHEN ISNUMERIC(myval)=1 THEN myval ELSE 0 END AS INT) AS mi
    FROM dbo.TestTable 
) AS t2
WHERE mi>5
0 голосов
/ 08 апреля 2009

Вот что происходит: предикат BigMacs Like '%[0-9]%' не совсем делает то, что вы думаете. Он выбирает строки, которые имеют хотя бы одну цифру где-то в строке.

Это не то, что вы хотите. Вы хотите строки, которые только имеют цифры. К сожалению, карточки LIKE не дают нам простой способ попросить об этом.

Мы можем подойти достаточно близко к вашей проблеме. Если мы попросим

not (BigMacs like "%[A-Za-z!@#$%^&*()=;:'""]%") 

мы отфильтруем большинство строк, у которых есть что-нибудь , но чисел. «Большинство», потому что наш символ подстановки не содержит все возможные нечисловые символы. Это, в свою очередь, должно позволить броску работать.

Итак:

select * from
  (
    select 
      BigMacs BigMacsS, 
      CAST(BigMacs as Binary) BigMacsB, 
      CAST(BigMacs as int) BigMacsL
    from 
      McTable
    where 
      not (BigMacs like "%[A-Za-z!@#$%^&*()=;:'""]%") 
  ) table
where 
  Cast(table.BigMacsL as int) > 5
0 голосов
/ 08 апреля 2009

Это, кажется, дает ожидаемые результаты

редактировать

и включает ваше предложение where

DECLARE @McTable TABLE (BigMacs VARCHAR(20))

INSERT INTO @McTable VALUES ('1')
INSERT INTO @McTable VALUES ('1dqsf')
INSERT INTO @McTable VALUES ('qsfsq1')
INSERT INTO @McTable VALUES ('10')

select   
  BigMacs,
  cast(BigMacs as Binary) as BigMacsB, 
  cast(BigMacs as int) as BigMacsL
from @McTable
where IsNumeric(BigMacs) = 1
      and cast(BigMacs as int) > 5
...