#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. Я обновлю вопрос.