重新迭代定界字符串的字符串提取

时间:2019-04-02 13:56:49

标签: sql sql-server

不幸的是,我没有创建函数的权限,因此我编写了以下查询,以提取由page_name分隔的管道的第一,第二,第三和第四部分:

select page_name,
LEFT(page_name, first-1) AS P1,
case when second>0 then SUBSTRING(page_name,first+1,second-first-1) 
     else substring(page_name, first+1,1000) end As P2,
case when third>0 then SUBSTRING(page_name,second+1,third-second-1) 
     when second>0 then substring(page_name, second+1,1000) else '' end AS P3,
case when fourth>0 then SUBSTRING(page_name,third+1,fourth-third-1) 
     when third>0 then substring(page_name, third+1,1000) else '' end AS P4

from (
select distinct page_name,
CHARINDEX('|', page_name) first,
CHARINDEX('|', page_name, CHARINDEX('|', page_name)+1) second,
case when CHARINDEX('|', page_name, CHARINDEX('|', page_name)+1)=0 then 0 
     else CHARINDEX('|', page_name, CHARINDEX('|', page_name, charindex('|', page_name)+1)+1) end third,
case when CHARINDEX('|', page_name, CHARINDEX('|', page_name, charindex('|', page_name)+1)+1)=0 then 0 
     else CHARINDEX('|', page_name, CHARINDEX('|', page_name, CHARINDEX('|', page_name, charindex('|', page_name)+1)+1)+1) end fourth
from adobe_analytics
where page_name like '[a-z]%' and page_name like '%|%' 
) a

问题是,有时大约有10个部分,所以我想知道是否有更好的子查询编写方法,这不会迫使我在创建{{1}时重申相同类型的查询公式} 部分?

示例数据:

page_name

2 个答案:

答案 0 :(得分:1)

如果您事先知道可以有的分隔字符串的最大数量,则可以在以下几行中使用某些内容(它使用递归CTE来构建单独字符串的表,然后旋转它们,产生一行每个page_name字段):

-- Table variable is just for example purposes.
DECLARE @tbl table (page_name varchar(255))
;

INSERT INTO @tbl
VALUES
('it-bae|')
, ('it-bae|content|advisor in newsletter')
, ('it-bae|content|area products|showcase products fideuram|fideuram fonditalia dynamic')
, ('it-bae|content|events|events|events|webinars|new')
;

WITH cteParts
AS
(
    SELECT
        page_name
        , 1 single_page_no
        , CASE WHEN CHARINDEX('|', page_name) = 0 THEN page_name ELSE LEFT(page_name, CHARINDEX('|', page_name) - 1) END single_page_name
        , CASE WHEN CHARINDEX('|', page_name) > 0 THEN RIGHT(page_name, LEN(page_name) - CHARINDEX('|', page_name)) END remainder
        , ROW_NUMBER() OVER (ORDER BY page_name) row_id
    FROM @tbl

    UNION ALL

    SELECT
        page_name
        , single_page_no + 1
        , CASE WHEN CHARINDEX('|', remainder) > 0 THEN LEFT(remainder, CHARINDEX('|', remainder) - 1) ELSE remainder END
        , CASE WHEN CHARINDEX('|', remainder) > 0 THEN RIGHT(remainder, LEN(remainder) - CHARINDEX('|', remainder)) END
        , row_id
    FROM cteParts
    WHERE LEN(remainder) > 0
)

SELECT
    row_id
    , page_name
    , [1]
    , [2]
    , [3]
    , [4]
    , [5]
    , [6]
    , [7]
    , [8]
    , [9]
    , [10]
FROM
    (
        SELECT
            row_id
            , page_name
            , single_page_no
            , single_page_name
        FROM cteParts
    ) Q
    PIVOT
    (
        MAX(single_page_name)
        FOR single_page_no IN ([1], [2], [3], [4], [5], [6], [7], [8], [9], [10])
    ) P

否则,您将需要使用动态SQL来实现类似的目的。

以下是动态SQL解决方案的示例:

-- Temporary table is just for example purposes.
CREATE TABLE #tbl (page_name varchar(255))
;

INSERT INTO #tbl
VALUES
('it-bae|')
, ('it-bae|content|advisor in newsletter')
, ('it-bae|content|area products|showcase products fideuram|fideuram fonditalia dynamic')
, ('it-bae|content|events|events|events|webinars|new')
;

DECLARE @maxStrings int =
(
    SELECT MAX(LEN(page_name) - LEN(REPLACE(page_name, '|', ''))) + 1
    FROM #tbl
)
;

DECLARE @headers varchar(1000)
;

WITH cteHeaders
AS
(
    SELECT 1 Header
    UNION ALL
    SELECT Header + 1
    FROM cteHeaders
    WHERE Header + 1 <= @maxStrings
)

SELECT DISTINCT @headers = STUFF((SELECT ', [' + CAST(Header AS varchar(3)) + ']' FROM cteHeaders FOR XML PATH('')), 1, 2, '')
FROM cteHeaders
;

DECLARE @sql varchar(8000) =
'
WITH cteParts
AS
(
    SELECT
        page_name
        , 1 single_page_no
        , CASE WHEN CHARINDEX(''|'', page_name) = 0 THEN page_name ELSE LEFT(page_name, CHARINDEX(''|'', page_name) - 1) END single_page_name
        , CASE WHEN CHARINDEX(''|'', page_name) > 0 THEN RIGHT(page_name, LEN(page_name) - CHARINDEX(''|'', page_name)) END remainder
        , ROW_NUMBER() OVER (ORDER BY page_name) row_id
    FROM #tbl

    UNION ALL

    SELECT
        page_name
        , single_page_no + 1
        , CASE WHEN CHARINDEX(''|'', remainder) > 0 THEN LEFT(remainder, CHARINDEX(''|'', remainder) - 1) ELSE remainder END
        , CASE WHEN CHARINDEX(''|'', remainder) > 0 THEN RIGHT(remainder, LEN(remainder) - CHARINDEX(''|'', remainder)) END
        , row_id
    FROM cteParts
    WHERE LEN(remainder) > 0
)

SELECT
    row_id
    , page_name
    , ' + @headers + '
FROM
    (
        SELECT
            row_id
            , page_name
            , single_page_no
            , single_page_name
        FROM cteParts
    ) Q
    PIVOT
    (
        MAX(single_page_name)
        FOR single_page_no IN (' + @headers + ')
    ) P
'

EXEC (@sql)

DROP TABLE #tbl

答案 1 :(得分:1)

我不确定您是否需要列或行中的值。如果值的数量未知,则在行中会更有意义。这可以通过xml拆分器来实现。

SELECT page_name,
    Value
FROM @tbl
CROSS APPLY( SELECT cast(('<X>' + replace( page_name, '|' ,'</X><X>') + '</X>') as xml) AS xmlpage_name) AS x
CROSS APPLY( SELECT N.value('.', 'varchar(255)') as value FROM xmlpage_name.nodes('X') as T(N)) AS Split;

如果需要在列中显示值,则可能应具有定义的列数以保留值。

SELECT page_name,
    MAX(CASE WHEN ItemNumber = 1 THEN Value ELSE '' END) AS P1,
    MAX(CASE WHEN ItemNumber = 2 THEN Value ELSE '' END) AS P2,
    MAX(CASE WHEN ItemNumber = 3 THEN Value ELSE '' END) AS P3,
    MAX(CASE WHEN ItemNumber = 4 THEN Value ELSE '' END) AS P4,
    MAX(CASE WHEN ItemNumber = 5 THEN Value ELSE '' END) AS P5,
    MAX(CASE WHEN ItemNumber = 6 THEN Value ELSE '' END) AS P6,
    MAX(CASE WHEN ItemNumber = 7 THEN Value ELSE '' END) AS P7,
    MAX(CASE WHEN ItemNumber = 8 THEN Value ELSE '' END) AS P8,
    MAX(CASE WHEN ItemNumber = 9 THEN Value ELSE '' END) AS P9,
    MAX(CASE WHEN ItemNumber = 10 THEN Value ELSE '' END) AS P10
FROM @tbl
CROSS APPLY( SELECT cast(('<X>' + replace( page_name, '|' ,'</X><X>') + '</X>') as xml) AS xmlpage_name) AS x
CROSS APPLY( SELECT N.value('.', 'varchar(255)') as value, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) ItemNumber FROM xmlpage_name.nodes('X') as T(N)) AS Split
GROUP BY page_name;