如何在不编写函数的情况下在SQL Server中提取URL查询字符串参数?

时间:2016-03-24 19:54:57

标签: sql sql-server string query-string

我无法识别网站上使用的所有查询字符串参数。我想编写一个提取所有参数并对其进行计数的T-SQL查询,但我没有编写SQL函数的权限,因此this solution没有多大帮助。

我正在使用的字段(Query)包含如下所示的数据:

_=1457999955221
tab=profile
tab=tags&sort=votes&page=13
page=5&sort=newest&pagesize=15
...

我需要编写的查询将返回结果:

querystring | count
___________________
_           |  1
tab         |  2
sort        |  2
page        |  2
pagesize    |  1
...

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:6)

您可以借用其中一个函数from here,并将其内联到查询中。

以下示例。我不希望表现良好。到目前为止,创建CLR函数是在SQL Server 2016之前拆分字符串的最有效方法。

DECLARE @QueryStrings Table
(
Query VARCHAR(8000)
)

INSERT INTO @QueryStrings 
VALUES
('INVALID'),
('_=1457999955221'),
('tab=profile'),
('tab=tags&sort=votes&page=13'),
('page=5&sort=newest&pagesize=15');


WITH E1(N)        AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
                        UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
                        UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
    E2(N)        AS (SELECT 1 FROM E1 a, E1 b),
    E4(N)        AS (SELECT 1 FROM E2 a, E2 b),
    E42(N)       AS (SELECT 1 FROM E4 a, E2 b)
    SELECT parameter, count(*)
    FROM   @QueryStrings qs
        CROSS APPLY (SELECT SUBSTRING(qs.Query, t.N + 1, ISNULL(NULLIF(CHARINDEX('&', qs.Query, t.N + 1), 0) - t.N - 1, 8000))
                    FROM   (SELECT 0
                            UNION ALL
                            SELECT TOP (DATALENGTH(ISNULL(qs.Query, 1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
                            FROM   E42) t(N)
                    WHERE  ( SUBSTRING(qs.Query, t.N, 1) = '&'
                                OR t.N = 0 )) ca1(split_result) 
        CROSS APPLY (SELECT CHARINDEX('=',split_result)) ca2(pos) 
        CROSS APPLY (SELECT CASE WHEN pos > 0 THEN LEFT(split_result,pos-1) END, 
                            CASE WHEN pos > 0 THEN SUBSTRING(split_result, pos+1,8000) END
                        WHERE  pos > 0) ca3(parameter,value) 
    GROUP BY parameter

答案 1 :(得分:2)

更接近性感的方式:

DECLARE @xml xml

;WITH cte AS (
SELECT *
FROM (VALUES
('_=1457999955221'),
('tab=profile'),
('tab=tags&sort=votes&page=13'),
('page=5&sort=newest&pagesize=15')
) as T(Query))

SELECT  @xml = (
SELECT CAST(
(
SELECT '<d><param>' + REPLACE(REPLACE((STUFF((
SELECT '/' + REPLACE(REPLACE(Query,'&','/'),'=','!')
FROM cte
FOR XML PATH('')
),1,1,'')),'/','</value><param>'),'!','</param><value>') + '</value></d>') as xml))

;WITH final AS (
SELECT t.v.value('.','nvarchar(20)') as querystring
FROM @xml.nodes('/d/param') as t(v)
)

SELECT querystring, COUNT(*) as [count]
FROM final
GROUP BY querystring 

结果:

querystring          count
-------------------- -----------
_                    1
page                 2
pagesize             1
sort                 2
tab                  2

(5 row(s) affected)

答案 2 :(得分:0)

不断创新。
现在可以使用SQL Server 2016+(13.x +)轻松完成此操作

-- Required for STRING_SPLIT: 
-- ALTER DATABASE <db_name> SET COMPATIBILITY_LEVEL = 130 -- >= 130


BEGIN TRY 
    DECLARE @sql nvarchar(MAX); 
    -- https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-database-transact-sql-compatibility-level?view=sql-server-ver15
    -- SET @sql = N'ALTER DATABASE [COR_Basic_Demo_V4] SET COMPATIBILITY_LEVEL = 130; '; 
    SET @sql = N'ALTER DATABASE ' + QUOTENAME(DB_NAME()) + N' SET COMPATIBILITY_LEVEL = ' + (SELECT CAST(MAX(compatibility_level) AS nvarchar(10)) FROM sys.databases) + '; '; 
    -- PRINT @sql; 
    EXECUTE(@sql); 
END TRY 
BEGIN CATCH 
    -- Execute error retrieval routine. 
    -- EXECUTE usp_GetErrorInfo; 

    SELECT  
         ERROR_NUMBER() AS ErrorNumber  
        ,ERROR_SEVERITY() AS ErrorSeverity  
        ,ERROR_STATE() AS ErrorState  
        ,ERROR_PROCEDURE() AS ErrorProcedure  
        ,ERROR_LINE() AS ErrorLine  
        ,ERROR_MESSAGE() AS ErrorMessage;  
    ; 
END CATCH  


-- Here comes the actual computation 


DECLARE @input nvarchar(4000)
SET @input = N'_=1457999955221
tab=profile
tab=tags&sort=votes&page=13
page=5&sort=newest&pagesize=15'

;WITH CTE AS 
(
    SELECT 
         value 
        ,SUBSTRING(splitted.value, 1, NULLIF(CHARINDEX('=', splitted.value), 0) -1) AS k
        ,SUBSTRING(splitted.value, NULLIF(CHARINDEX('=', splitted.value), 0) + 1, LEN(splitted.value)) AS v 
    FROM STRING_SPLIT
    (
        REPLACE
        (
             REPLACE(@input, CHAR(13), '')
            ,CHAR(10)
            ,'&'
        )
        , '&'
    ) AS splitted 
)
SELECT 
     k
    ,COUNT(v) AS cnt 
    ,COUNT(DISTINCT v) AS dist_cnt 
FROM CTE 
GROUP BY k 

对于早期版本,或者如果您实际上需要分解完整网址,则为:

DECLARE @String nvarchar(4000) 
DECLARE @path nvarchar(MAX) 
DECLARE @hash nvarchar(MAX) 
DECLARE @Delimiter nchar(1)


SET @String = 'http://localhost:10004/Kamikatze/ajax/AnySelect.ashx?sql=Maps.ObjectBounds.sql&BE_ID=123&obj_uid=fd4ea870-82eb-4c37-bb67-3e8d5b7b7ac2&&in_stichtag=1589528927178&no_cache=1589528927178&no_cache=1589528927178&moo=moo#foobar'
SET @String = 'sql=Maps.ObjectBounds.sql&BE_ID=123&obj_uid=fd4ea870-82eb-4c37-bb67-3e8d5b7b7ac2&&in_stichtag=1589528927178&no_cache=1589528927178&no_cache=1589528927178&moo=moo#foobar'
-- SET @String = 'http://localhost:10004/Kamikatze/ajax/AnySelect.ashx?sql=Maps.ObjectBounds.sql&BE_ID=123&obj_uid=fd4ea870-82eb-4c37-bb67-3e8d5b7b7ac2&&in_stichtag=1589528927178&no_cache=1589528927178&no_cache=1589528927178&moo=moo'
SET @Delimiter = '&'



SELECT 
     @path = SUBSTRING(@String, 1, NULLIF(CHARINDEX('?', @String) - 1, -1)) -- AS path 
    ,@hash = RIGHT(@String, LEN(@String) - NULLIF(CHARINDEX(N'#', @String), 0)  ) -- AS hash 


SELECT -- remove hash
    @String = SUBSTRING
    (
         @String
        ,1
        ,COALESCE(NULLIF(CHARINDEX(N'#', @String), 0) - 1, LEN(@String) )
    ) -- AS xxx 
; 

SELECT -- remove path 
    @String = SUBSTRING
    (   @String
        ,CHARINDEX(N'?', @String) + 1 
        ,100000
    )
;   

;WITH Split(id, stpos, endpos, data) 
AS
(
    SELECT 
         0 AS id 
        ,0 AS stpos 
        ,CHARINDEX(@Delimiter, @String) AS endpos 
        ,SUBSTRING(@String, 0, COALESCE(NULLIF(CHARINDEX(@Delimiter, @String), 0), LEN(@String)+1) ) AS data 

    UNION ALL

    SELECT 
         Split.id + 1 AS id 
        ,Split.endpos + 1 AS stpos 
         ,CHARINDEX(@Delimiter, @String, Split.endpos+1) AS endpos 
         ,SUBSTRING(@String, Split.endpos + 1, COALESCE(NULLIF(CHARINDEX(@Delimiter, @String, Split.endpos+1), 0), LEN(@String)+1) - Split.endpos - 1) AS data  
    FROM Split 
    WHERE endpos > 0
)
SELECT  
     id
    -- ,stpos
    -- ,endpos
    -- ,SUBSTRING(@String, stpos, COALESCE(NULLIF(endpos, 0), LEN(@String)+1) - stpos) AS data_simple 
    ,data
    ,@path AS path 
    ,@hash AS hash 
    ,SUBSTRING(data, 1, NULLIF(charindex('=', data), 0) -1) AS k
    ,SUBSTRING(data, NULLIF(charindex('=', data), 0) + 1, LEN(data)) AS v 
FROM Split 

如果需要功能:

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[tfu_DecomposeUrl]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
EXECUTE('CREATE FUNCTION dbo.tfu_DecomposeUrl( ) RETURNS TABLE AS RETURN ( SELECT 123 AS abc) '); 
GO





ALTER FUNCTION dbo.tfu_DecomposeUrl 
(
    @input_string NVARCHAR(4000) 
)
RETURNS TABLE
AS
RETURN
(

    WITH CTE AS 
    (
        SELECT 
             SUBSTRING(@input_string, 1, NULLIF(CHARINDEX('?', @input_string) - 1, -1)) AS query_path 
            ,RIGHT(@input_string, LEN(@input_string) - NULLIF(CHARINDEX(N'#', @input_string), 0)  ) AS query_hash 
            ,SUBSTRING
            (
                 @input_string
                ,1
                ,COALESCE(NULLIF(CHARINDEX(N'#', @input_string), 0) - 1, LEN(@input_string) )
            ) AS PathWithoutHash 
    )
    ,CTE2 AS 
    (
        SELECT 
             CTE.query_path 
            ,CTE.query_hash 
            ,SUBSTRING
            (    PathWithoutHash
                ,CHARINDEX(N'?', PathWithoutHash) + 1 
                ,100000
            ) AS KeyValueString  
        FROM CTE 
    )
    ,Split(id, stpos, endpos, data, query_path, query_hash) 
    AS
    (
        SELECT 
             0 AS id 
            ,0 AS stpos 
            ,CHARINDEX(N'&', CTE2.KeyValueString) AS endpos 
            ,SUBSTRING(CTE2.KeyValueString, 0, COALESCE(NULLIF(CHARINDEX(N'&', CTE2.KeyValueString), 0), LEN(CTE2.KeyValueString)+1) ) AS data 
            ,CTE2.query_path 
            ,CTE2.query_hash 
        FROM CTE2 

        UNION ALL

        SELECT 
             Split.id + 1 AS id 
            ,Split.endpos + 1 AS stpos 
            ,CHARINDEX(N'&', CTE2.KeyValueString, Split.endpos+1) AS endpos 
            ,SUBSTRING(CTE2.KeyValueString, Split.endpos + 1, COALESCE(NULLIF(CHARINDEX(N'&', CTE2.KeyValueString, Split.endpos+1), 0), LEN(CTE2.KeyValueString)+1) - Split.endpos - 1) AS data  
            ,CTE2.query_path 
            ,CTE2.query_hash 
        FROM Split 
        CROSS JOIN CTE2 
        WHERE endpos > 0
    )
    SELECT 
         id
         -- ,stpos
         -- ,endpos
         -- ,SUBSTRING(@String, stpos, COALESCE(NULLIF(endpos, 0), LEN(@String)+1) - stpos) AS data_simple 
        ,data
        ,query_path 
        ,query_hash 
        ,SUBSTRING(data, 1, NULLIF(CHARINDEX('=', data), 0) -1) AS k
        ,SUBSTRING(data, NULLIF(CHARINDEX('=', data), 0) + 1, LEN(data)) AS v 
    FROM Split 
)


GO

这可以简化为

DECLARE @input_string nvarchar(4000) 


SET @input_string = 'http://localhost:10004/Kamikatze/ajax/AnySelect.ashx?sql=Maps.ObjectBounds.sql&BE_ID=123&obj_uid=fd4ea870-82eb-4c37-bb67-3e8d5b7b7ac2&&in_stichtag=1589528927178&no_cache=1589528927178&no_cache=1589528927178&moo=moo#foobar'
-- SET @input_string = 'sql=Maps.ObjectBounds.sql&BE_ID=123&obj_uid=fd4ea870-82eb-4c37-bb67-3e8d5b7b7ac2&&in_stichtag=1589528927178&no_cache=1589528927178&no_cache=1589528927178&moo=moo#foobar'
-- SET @input_string = 'http://localhost:10004/Kamikatze/ajax/AnySelect.ashx?sql=Maps.ObjectBounds.sql&BE_ID=123&obj_uid=fd4ea870-82eb-4c37-bb67-3e8d5b7b7ac2&&in_stichtag=1589528927178&no_cache=1589528927178&no_cache=1589528927178&moo=moo'
-- SET @input_string = 'sql=Maps.ObjectBounds.sql&BE_ID=123&obj_uid=fd4ea870-82eb-4c37-bb67-3e8d5b7b7ac2&&in_stichtag=1589528927178&no_cache=1589528927178&no_cache=1589528927178&moo=moo'




;WITH CTE AS 
(
    SELECT 
         SUBSTRING(@input_string, 1, NULLIF(CHARINDEX('?', @input_string) - 1, -1)) AS query_path 
        ,RIGHT(@input_string, LEN(@input_string) - NULLIF(CHARINDEX(N'#', @input_string), 0)  ) AS query_hash 
        ,SUBSTRING
        (
             @input_string
            ,1
            ,COALESCE(NULLIF(CHARINDEX(N'#', @input_string), 0) - 1, LEN(@input_string) )
        ) AS PathWithoutHash 
)
,CTE2 AS 
(
    SELECT 
         CTE.query_path 
        ,CTE.query_hash 
        ,SUBSTRING
        (    PathWithoutHash
            ,CHARINDEX(N'?', PathWithoutHash) + 1 
            ,100000
        ) AS KeyValueString  
    FROM CTE 
)
SELECT 
     t.id 
    ,t.data
    ,CTE2.query_path 
    ,CTE2.query_hash 
    ,SUBSTRING(t.data, 1, NULLIF(CHARINDEX('=', t.data), 0) -1) AS k
    ,SUBSTRING(t.data, NULLIF(CHARINDEX('=', t.data), 0) + 1, LEN(t.data)) AS v 
FROM CTE2 
OUTER APPLY dbo.tfu_FastSplitString(CTE2.KeyValueString, N'&') AS t 

还有一个表值字符串拆分函数:

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.tfu_FastSplitString') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
EXECUTE('CREATE FUNCTION dbo.tfu_FastSplitString( ) RETURNS TABLE AS RETURN ( SELECT 123 AS abc) '); 
GO




ALTER FUNCTION dbo.tfu_FastSplitString 
(
     @input_string nvarchar(4000) 
    ,@delimiter nchar(1) 
)
RETURNS TABLE
AS
RETURN
( 
    WITH Split(id, stpos, endpos) -- , data) 
    AS
    (
        SELECT 
             0 AS id 
            ,0 AS stpos 
            ,CHARINDEX(@delimiter, @input_string) AS endpos 
            -- ,SUBSTRING(@input_string, 0, COALESCE(NULLIF(CHARINDEX(@delimiter, @input_string), 0), LEN(@input_string)+1) ) AS data 

        UNION ALL

        SELECT 
             Split.id + 1 AS id 
            ,Split.endpos + 1 AS stpos 
            ,CHARINDEX(@delimiter, @input_string, Split.endpos+1) AS endpos 
            -- ,SUBSTRING(@input_string, Split.endpos + 1, COALESCE(NULLIF(CHARINDEX(@delimiter, @input_string, Split.endpos+1), 0), LEN(@input_string)+1) - Split.endpos - 1) AS data  
        FROM Split 
        WHERE endpos > 0
    )
    SELECT  
         id
        -- ,stpos
        -- ,endpos
        -- ,data 
        ,SUBSTRING(@input_string, stpos, COALESCE(NULLIF(endpos, 0), LEN(@input_string)+1) - stpos) AS data 
    FROM Split  
)


GO

这些是内联表值函数,因此它们应该很快。
如果它是一个多语句表值函数,它将很慢。