Сортировка иерархии в строках в Transact-SQL

Иногда возникает необходимость сортировки иерархического списка в строковых полях ранее уже созданной таблицы. Это очень просто сделать с помощью конвейера CTE в SQL.
DECLARE @t TABLE (txt varchar(50))

INSERT INTO @t (txt) VALUES ('1_____')
INSERT INTO @t (txt) VALUES ('1.2. _____')
INSERT INTO @t (txt) VALUES ('1.2.1._____')
INSERT INTO @t (txt) VALUES ('1.2.10._____')
INSERT INTO @t (txt) VALUES ('1.2.7._____')
INSERT INTO @t (txt) VALUES ('2. _____')
INSERT INTO @t (txt) VALUES ('2.2 _____')
INSERT INTO @t (txt) VALUES ('2.2.10._____')
INSERT INTO @t (txt) VALUES ('2.2.3.1._____')
INSERT INTO @t (txt) VALUES ('22_____')

;WITH points (id, p1, p2, p3, p4, p5, p6, p7, p8, p9, it) AS
(   SELECT
        id = RTRIM(LEFT(t.txt, PATINDEX('%[^.0-9]%', t.txt) - 1)),
        p1 = CONVERT(int, NULL),
        p2 = CONVERT(int, NULL), 
        p3 = CONVERT(int, NULL),
        p4 = CONVERT(int, NULL),
        p5 = CONVERT(int, NULL),
        p6 = CONVERT(int, NULL),
        p7 = CONVERT(int, NULL),
        p8 = CONVERT(int, NULL),
        p9 = CONVERT(int, NULL),
        it = 0
    FROM @t t
    UNION ALL
    SELECT
        id = p.id,
        p1 = NULLIF(CHARINDEX('.', p.id, 1),        0),
        p2 = NULLIF(CHARINDEX('.', p.id, 1 + p.p1), 0),
        p3 = NULLIF(CHARINDEX('.', p.id, 1 + p.p2), 0),
        p4 = NULLIF(CHARINDEX('.', p.id, 1 + p.p3), 0),
        p5 = NULLIF(CHARINDEX('.', p.id, 1 + p.p4), 0),
        p6 = NULLIF(CHARINDEX('.', p.id, 1 + p.p5), 0),
        p7 = NULLIF(CHARINDEX('.', p.id, 1 + p.p6), 0),
        p8 = NULLIF(CHARINDEX('.', p.id, 1 + p.p7), 0),
        p9 = NULLIF(CHARINDEX('.', p.id, 1 + p.p8), 0),
        it = p.it + 1
    FROM points p
    WHERE p.it < 10
), 
levels (id, v1, v2, v3, v4, v5, v6, v7, v8, v9) AS
(   SELECT 
        id = p.id,
        v1 = CONVERT(int, SUBSTRING(id,        1, ISNULL(p.p1        - 1, LEN(p.id)       ))),
        v2 = CONVERT(int, SUBSTRING(id, p.p1 + 1, ISNULL(p.p2 - p.p1 - 1, LEN(p.id) - p.p1))),
        v3 = CONVERT(int, SUBSTRING(id, p.p2 + 1, ISNULL(p.p3 - p.p2 - 1, LEN(p.id) - p.p2))),
        v4 = CONVERT(int, SUBSTRING(id, p.p3 + 1, ISNULL(p.p4 - p.p3 - 1, LEN(p.id) - p.p3))),
        v5 = CONVERT(int, SUBSTRING(id, p.p4 + 1, ISNULL(p.p5 - p.p4 - 1, LEN(p.id) - p.p4))),
        v6 = CONVERT(int, SUBSTRING(id, p.p5 + 1, ISNULL(p.p6 - p.p5 - 1, LEN(p.id) - p.p5))),
        v7 = CONVERT(int, SUBSTRING(id, p.p6 + 1, ISNULL(p.p7 - p.p6 - 1, LEN(p.id) - p.p6))),
        v8 = CONVERT(int, SUBSTRING(id, p.p7 + 1, ISNULL(p.p8 - p.p7 - 1, LEN(p.id) - p.p7))),
        v9 = CONVERT(int, SUBSTRING(id, p.p8 + 1, ISNULL(p.p9 - p.p8 - 1, LEN(p.id) - p.p8)))
    FROM points p
    WHERE p.it = 10
)
SELECT 
    num = CASE RIGHT(v.id, 1)
              WHEN '.' THEN v.id
              ELSE v.id + '.'
          END,
    txt = LTRIM(RIGHT(t.txt, LEN(t.txt) - LEN(v.id)))
FROM @t t
    INNER JOIN levels v
    ON v.id = RTRIM(LEFT(t.txt, PATINDEX('%[^.0-9]%', t.txt) - 1))
ORDER BY 
    ISNULL(v.v1, 0), 
    ISNULL(v.v2, 0), 
    ISNULL(v.v3, 0), 
    ISNULL(v.v4, 0),
    ISNULL(v.v5, 0),
    ISNULL(v.v6, 0),
    ISNULL(v.v7, 0),
    ISNULL(v.v8, 0),
    ISNULL(v.v9, 0)

Комментарии