Мне нужно рекурсивно присоединиться к большому графу, который может иметь циклы.Теперь в SQL-сервере эта версия выглядит так:
-- Array:10'000: 1.6s (postgresql)
-- LIKE: 10'000: 19s (sql-server)
-- JSON: 10'000: 19s (sql-server)
-- XML : 10'000: +infinity s (sql-server)
-- XML : 300 (a): 17s (nullable) (sql-server)
-- XML : 300 (b): 17s (not nullable) (sql-server)
-- XML : 300 (c): 23s (nullable, xpath) (sql-server)
;WITH CTE AS
(
SELECT
1 AS i
,CAST(N',1,' AS national character varying(MAX)) AS paths
UNION ALL
SELECT
CTE.i+1 AS i
,CTE.paths + CAST((CTE.i + 1) AS national character varying(36)) + N',' AS paths
FROM CTE
WHERE CTE.i < 10000
AND CTE.paths LIKE (N'%,' + CAST((CTE.i) AS national character varying(36)) + N',%')
)
SELECT i FROM CTE
OPTION (MAXRECURSION 0)
Поскольку SQL-сервер не поддерживает массивы, я попытался оптимизировать с помощью JSON:
;WITH CTE AS
(
SELECT
1 AS i
,CAST('[1]' AS nvarchar(MAX)) AS paths
UNION ALL
SELECT
CTE.i+1 AS i
,JSON_MODIFY(CTE.paths, N'append $', CTE.i+1) AS paths
FROM CTE
WHERE CTE.i < 1000
AND CTE.i IN
(
SELECT
nda.value
-- OpenJSON: ALTER DATABASE <db_name()> SET COMPATIBILITY_LEVEL = 130
FROM OPENJSON(CTE.paths, N'$') AS nda
)
)
SELECT i FROM CTE
OPTION (MAXRECURSION 0)
, но JSON не работает быстрее, плюс он работает только с SQL-Server 2016+.
Я также попробовал с XML:
;WITH CTE AS
(
SELECT
1 AS i
,CAST(N'<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><row>1</row>' AS nvarchar(MAX)) AS paths
UNION ALL
SELECT
CTE.i+1 AS i
-- <row xsi:nil="true"/>
,CTE.paths + N'<row>' + CAST((CTE.i + 1) AS nvarchar(36)) + N'</row>' AS paths
FROM CTE
WHERE CTE.i < 300
AND CTE.i IN
(
SELECT
xmlRow.xmlElement.value(N'.', N'int') AS v
FROM
(
SELECT CAST(CTE.paths + N'</table>' AS xml) AS xmlArray
) AS tXmlArrayConverter
CROSS APPLY tXmlArrayConverter.xmlArray.nodes(N'//row') AS xmlRow(xmlElement)
)
)
SELECT i FROM CTE
OPTION (MAXRECURSION 0)
, и хотя это будет работать с sql-сервером <2016, это слишком чертовски медленно - гораздо хуже, чем хотелось бы.</p>
Единственный способ получить приличную производительность - это переключиться на PostgreSQL, который поддерживает массивы:
;WITH RECURSIVE CTE AS
(
SELECT
1 AS i
,array[1] AS paths
UNION ALL
SELECT
CTE.i+1 AS i
,CTE.paths || (CTE.i + 1) AS paths
FROM CTE
WHERE CTE.i < 10000
AND CTE.i = ANY(CTE.paths)
-- AND CTE.i <> ALL(CTE.paths)
)
SELECT i FROM CTE
, что завершается за 1.6 с, что примерно так же хорошо, как я ожидал.
Есть ли способ повысить производительность в SQL-сервере?
Примечание: Этот дурацкий пример с CTE.i IN (whatever)
не должен иметь никакого смысла, он просто для сравнения скорости.