Неявное преобразование типов: JSON_VALUE() против TRY_CAST(JSON_VALUE())

#sql-server #tsql

#sql-сервер #tsql

Вопрос:

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

Поэтому вместо создания нескольких таблиц с именем Link{SourceTableName} я создаю одну таблицу с GUID для представления идентификатора на нашей стороне и NVARCHAR(MAX) столбцом для представления JSON , содержащим первичный ключ исходной таблицы. Я сохраняю первичный ключ как JSON потому что я должен поддерживать составные первичные ключи. Если мне нужны данные в исходной таблице, я могу JOIN JSON столбец с JSON_VALUE вместе с первичным ключом в исходной таблице.

Поскольку JSON_VALUE возвращает NVARCHAR(MAX) , я получу неявное преобразование типов, когда первичный ключ исходной таблицы не является NVARCHAR(MAX) , чего следовало ожидать. Однако, если я оберну это JSON_VALUE с TRY_CAST , это неявное преобразование больше не требуется. Я решил это сделать, но мне стало любопытно, была ли какая-либо потеря производительности из-за дополнительного вызова функции. И вот тут все стало странно…

Ниже приведено сравнение простой SELECT * исходной таблицы с a SELECT * joined on TRY_CAST(JSON_VALUE()) против a SELECT * joined on JSON_VALUE() .

введите описание изображения здесь
введите описание изображения здесь
введите описание изображения здесь

введите описание изображения здесь

Я создал образец запроса, который воспроизводит проблему.

 CREATE TABLE [#Link] (
    [Id] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [DF_Link_Id] DEFAULT(NEWID()),
    [LinkId] NVARCHAR(MAX) NOT NULL,
 CONSTRAINT [PK_Link] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY])
ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

CREATE TABLE [#Customer] (
    [Id] CHAR(7) NOT NULL,
    [Name] NVARCHAR(200) NULL,
 CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY])
ON [PRIMARY]

DECLARE @i INT = 0
WHILE @i < 20
BEGIN
    WITH seed AS (
        SELECT (@i * 32767)   1 AS n
        UNION ALL
        SELECT n 1 FROM seed WHERE n 1<=(@i * 32767)   32767
    )
    INSERT INTO
        [#Customer]
    SELECT
        RIGHT('0000000' CAST([n] AS VARCHAR(7)),7)
        ,RIGHT('0000000' CAST([n] AS VARCHAR(7)),7)
    FROM
        seed OPTION(maxrecursion 32767)
    SET @i = @i   1
END

INSERT INTO
    [#Link] ([LinkId])
SELECT
    [value] FROM OPENJSON((SELECT [Id] FROM [#Customer] FOR JSON PATH))


SELECT TOP 10 *
FROM [#Customer]

SELECT TOP 10 *
FROM [#Customer]
JOIN [#Link] ON [#Customer].[Id] = TRY_CAST(JSON_VALUE([#Link].[LinkId], '$.Id') AS char(7))

SELECT TOP 10 *
FROM [#Customer]
JOIN [#Link] ON [#Customer].[Id] = JSON_VALUE([#Link].[LinkId], '$.Id')

SELECT TOP 100 *
FROM [#Customer]

SELECT TOP 100 *
FROM [#Customer]
JOIN [#Link] ON [#Customer].[Id] = TRY_CAST(JSON_VALUE([#Link].[LinkId], '$.Id') AS char(7))

SELECT TOP 100 *
FROM [#Customer]
JOIN [#Link] ON [#Customer].[Id] = JSON_VALUE([#Link].[LinkId], '$.Id')

DROP TABLE [#Customer]
DROP TABLE [#Link]
  

введите описание изображения здесь
введите описание изображения здесь

Как вы можете видеть, существует разница между планом выполнения первого JSON_VALUE() запроса и второго JSON_VALUE() запроса. Я не герой в расшифровке планов выполнения, но интересен сам факт того, что есть разница.

Присутствует предупреждение с жалобой на неявное преобразование типов, которое присутствует в обоих планах выполнения. Это должно быть причиной разницы в планах выполнения, потому что при изменении типа данных [#Customer].[Id] на NVARCHAR(7) планы выполнения TOP 10 и TOP 100 остаются неизменными.

Я знаю, что CONVERT_IMPLICIT снижает производительность, но я бы не ожидал увидеть разницу между ТОП-10 и ТОП-100. Поскольку второй запрос выполняется быстрее первого, очевидно, что первый запрос может быть таким же быстрым. Почему оптимизатор запросов не всегда выбирает второй план выполнения или я прошу слишком многого?

Комментарии:

1. Почему бы не использовать функцию JSON_VALUE() в функции OPENJSON при первом создании #Link? Таким образом, вы можете просто присоединиться непосредственно к #Link . Идентификатор, который будет намного быстрее.

2. @Jon На самом деле я должен поддерживать составные первичные ключи. Если я не правильно понимаю ваше предложение, невозможно поддерживать это без сохранения в формате JSON. Я обновлю вопрос.