SQL Server молча усекает varchar в хранимых процедурах

В соответствии с эта дискуссия форума, SQL Server (я использую 2005, но, как я понимаю, это относится и к 2000 и 2008 годам), тихо обрезает любые varchar вы указываете в качестве параметров хранимой процедуры длину varchar, даже если вставка этой строки непосредственно с помощью INSERT приведет к ошибке. например. Если я создам эту таблицу:

CREATE TABLE testTable(
    [testStringField] [nvarchar](5) NOT NULL
)

тогда, когда я выполняю следующее:

INSERT INTO testTable(testStringField) VALUES(N'string which is too long')

Я получаю сообщение об ошибке:

String or binary data would be truncated.
The statement has been terminated.

Великий. Сохранение целостности данных, и вызывающий абонент знает об этом. Теперь давайте определим хранимую процедуру, чтобы вставить это:

CREATE PROCEDURE spTestTableInsert
    @testStringField [nvarchar](5)
AS
    INSERT INTO testTable(testStringField) VALUES(@testStringField)
GO

и выполните его:

EXEC spTestTableInsert @testStringField = N'string which is too long'

Нет ошибок, 1 строка затронута. Строка вставляется в таблицу, а testStringField - как "strin". SQL Server молча усекал параметр хранимой процедуры varchar.

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

Сначала объявите сохраненный параметр proc @testStringField размером 6 и проверьте, не превышает ли его длину 5. Это кажется немного взломанным и включает в себя раздражающее количество кода шаблона.

Во-вторых, просто объявите ВСЕ параметры хранимой процедуры varchar как varchar(max), а затем пусть оператор INSERT в хранимой процедуре завершится с ошибкой.

Последний, похоже, работает нормально, поэтому мой вопрос: хорошо ли использовать varchar(max) ВСЕГДА для строк в хранимых процедурах SQL Server, если я действительно хочу, чтобы хранимый proc завершился с ошибкой, когда слишком длинна строка передана? Может ли это быть лучшей практикой? Безмолвное усечение, которое нельзя отключить, кажется мне глупым.

Ответ 1

Это просто.

Я никогда не замечал проблемы, хотя один из моих проверок заключался в том, чтобы мои параметры соответствовали длинам столбцов таблицы. В клиентском коде тоже. Лично я ожидаю, что SQL никогда не увидит слишком длинные данные. Если бы я видел усеченные данные, было бы кровоточащим, что бы это вызвало.

Если вы чувствуете потребность в varchar (max), остерегайтесь массовой проблемы с производительностью из-за приоритета datatype. varchar (max) имеет более высокий приоритет, чем varchar (n) (самый длинный - самый высокий). Таким образом, в этом типе запроса вы получите сканирование, а не поиск, и каждое значение varchar (100) - это CAST для varchar (max)

UPDATE ...WHERE varchar100column = @varcharmaxvalue

Edit:

В этой проблеме есть открыть элемент Microsoft Connect.

И это, вероятно, достойно включения в Erland Sommarkog Strict settingsСоответствующий элемент подключения).

Редактировать 2, после комментария Мартинса:

DECLARE @sql VARCHAR(MAX), @nsql nVARCHAR(MAX);
SELECT @sql = 'B', @nsql = 'B'; 
SELECT 
   LEN(@sql), 
   LEN(@nsql), 
   DATALENGTH(@sql), 
   DATALENGTH(@nsql)
;

DECLARE @t table(c varchar(8000));
INSERT INTO @t values (replicate('A', 7500));

SELECT LEN(c) from @t;
SELECT 
   LEN(@sql + c), 
   LEN(@nsql + c), 
   DATALENGTH(@sql + c), 
   DATALENGTH(@nsql + c) 
FROM @t;

Ответ 2

Спасибо, как всегда, за StackOverflow за выявление такого углубленного обсуждения. Недавно я просматривал свои хранимые процедуры, чтобы сделать их более надежными, используя стандартный подход к транзакциям и блоки try/catch. Я не согласен с Джо Стефанелли в том, что "Мое предложение состояло бы в том, чтобы привлечь внимание к заявке", и полностью согласен с Jez: "Если SQL Server проверяет длину строки, это будет намного предпочтительнее". Весь смысл использования хранимых процедур заключается в том, что они написаны на языке, родном для базы данных, и должны действовать как последняя линия защиты. На стороне приложения разница между 255 и 256 - это просто бесполезное число, но в среде базы данных поле с максимальным размером 255 просто не примет 256 символов. Механизмы проверки приложений должны отражать бэкэнд-db как можно лучше, но техническое обслуживание сложно, поэтому я хочу, чтобы база данных давала мне хорошую обратную связь, если приложение ошибочно допускает неподходящие данные. Вот почему я использую базу данных вместо связки текстовых файлов с CSV или JSON или что-то еще.

Я был озадачен, почему один из моих SPs бросил ошибку 8152, а другой молча усечен. Я, наконец, twigged: SP, который выбросил ошибку 8152, имел параметр, который позволял одному символу больше, чем связанный столбец таблицы. Столбец таблицы был установлен в nvarchar (255), но параметр был nvarchar (256). Итак, не мой "ошибка" адрес gbn касается: "массовая производительность"? Вместо использования max, возможно, мы могли бы последовательно установить размер столбца таблицы, скажем, 255, а параметр SP - на один символ дольше, скажем, 256. Это решает проблему молчаливого усечения и не несет каких-либо штрафных санкций.  Предположительно, есть и другие недостатки, о которых я не думал, но это кажется хорошим компромиссом для меня.

Обновление: Боюсь, что эта техника не последовательна. Дальнейшее тестирование показывает, что иногда я могу вызвать ошибку 8152, и иногда данные молча усекаются. Я был бы очень благодарен, если бы кто-то помог мне найти более надежный способ справиться с этим.

Обновление 2: Ответьте на Pyitoechito на этой странице.

Ответ 3

То же поведение можно увидеть здесь:

declare @testStringField [nvarchar](5)
set @testStringField = N'string which is too long'
select @testStringField

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

Ответ 4

Обновление: я боюсь, что этот метод несовместим. Дальнейшее тестирование показывает, что иногда я могу вызвать ошибку 8152, и иногда данные молча усекаются. Я был бы очень благодарен, если бы кто-то помог мне найти более надежный способ справиться с этим.

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

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

Ответ 5

Одно из решений:

  • Измените все входящие параметры varchar(max)
  • Имеет ли прямую переменную sp правильной длины datalength (просто скопируйте и вставьте все в параметры и добавьте "int" в конец
  • Объявить переменную таблицы с именами столбцов так же, как имена переменных
  • Вставьте в таблицу строку, в которой каждая переменная попадает в столбец с тем же именем
  • Выберите из таблицы внутренние переменные

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

Это исходный код:

create procedure spTest
(
    @p1 varchar(2),
    @p2 varchar(3)
)

Это новый код:

create procedure spTest
(
    @p1 varchar(max),
    @p2 varchar(max)
)
declare @p1Int varchar(2), @p2Int varchar(3)
declare @test table (p1 varchar(2), p2 varchar(3)
insert into @test (p1,p2) varlues (@p1, @p2)
select @p1Int=p1, @p2Int=p2 from @test

Обратите внимание, что если длина входящих параметров будет больше предела, а не будет отключать строку SQL Server, вы сбросите ошибку.

Ответ 6

Вы всегда можете вывести оператор if в ваш sp, который проверяет длину их, и если они превышают указанную длину, выведите ошибку. Это довольно трудоемко, хотя и будет больно обновляться, если вы обновите размер данных.

Ответ 7

Это не тот ответ, который решит вашу проблему сегодня, но он включает в себя предложение функций для MSSQL, чтобы рассмотреть возможность добавления, которое решило бы эту проблему.
Важно назвать это недостатком MSSQL, поэтому мы можем помочь им решить его, повысив осведомленность об этом.
Вот официальное предложение, если вы хотите проголосовать за него:
https://feedback.azure.com/forums/908035-sql-server/suggestions/38394241-request-for-new-rule-string-truncation-error-for

Я разделяю ваше разочарование.
Смысл установки Character-Size в Parameters заключается в том, что другие разработчики сразу узнают
  что такое ограничения размера (через Intellisense) при передаче данных.
Это похоже на то, как будто ваша документация запечатлена прямо в подписи Sproc.

Послушайте, я понимаю, неявное преобразование во время назначения переменных является виновником.
Тем не менее, нет веских оснований тратить такое количество сценариев на борьбу с энергией
  где вы вынуждены обходить эту функцию.
Если вы спросите меня, у Sprocs и функций должны быть одинаковые правила движка,
  для присвоения параметров, которые используются при заполнении таблиц. Это действительно слишком много, чтобы спросить?

Все эти предложения по использованию более крупных символов
  а затем добавлять валидацию для КАЖДОГО параметра в КАЖДОЙ Sproc просто смешно.
Я знаю, что это единственный способ избежать усечения, но на самом деле MSSQL?
Мне все равно, если это стандарт ANSI/ISO или что-то еще, он тупой!

Когда значения слишком длинные - я хочу, чтобы мой код ломался - каждый раз.
Должно быть: не проходи и исправь свой код.
У вас могут быть множественные ошибки усечения в течение многих лет, и вы никогда их не поймаете.
Что случилось с обеспечением вашей целостности данных?

Опасно предполагать, что ваш код SQL будет вызываться только после проверки всех параметров.
Я пытаюсь добавить одну и ту же проверку как на мой веб-сайт, так и в Sproc, который он вызывает,
  и я все еще ловлю ошибки в моем Sproc, который проскользнул мимо веб-сайта. Это отличная проверка на здоровье!
Что делать, если вы хотите повторно использовать Sproc для веб-сайта /WebService, а также вызывать его из других
  Sprocs/Jobs/Deployment/Ad-Hoc сценарии (где нет внешнего интерфейса для проверки параметров)?

MSSQL необходим параметр "NO_TRUNC" для применения этого к любой строковой переменной не макс.
  (даже те, которые используются в качестве параметров для Sprocs и функций).
Это может быть Connection/Session-Scoped:
  (например, как опция "TRANSACTION ISOLATION LEVEL READ UNCOMMITTED" влияет на все запросы)
Или сосредоточены на одной переменной:
  (например, как "NOLOCK" является подсказкой таблицы только для 1 таблицы).
Или флаг трассировки или свойство базы данных, которое вы включаете, чтобы применить его ко всем параметрам Sproc/Function в базе данных.

Я не прошу разрушить десятилетия Legacy Code.
Просто спрашиваю MS о возможности лучше управлять нашими базами данных.