Есть ли более быстрый способ, чем НРАВИТСЯ РЕКУРСИВНО объединяться в SQL-сервере, без циклов? - PullRequest
0 голосов
/ 31 мая 2019

Мне нужно рекурсивно присоединиться к большому графу, который может иметь циклы.Теперь в 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) не должен иметь никакого смысла, он просто для сравнения скорости.

...