逗号分隔字符串总计数按行变量

时间:2017-09-11 16:30:28

标签: sql-server sql-server-2008

这是架构:

User_ID Page_ID Timestamp
1   48,51,94    7/26/2017 8:30
2   42,11,84    7/26/2017 9:40
3   4,16,24 7/26/2017 16:20
4   7,2,94  7/27/2017 8:00
1   48,22,94    7/27/2017 13:50
2   42,11   7/27/2017 14:00
3   4,24    7/27/2017 18:15

下面的代码给出了每个用户运行的页面ID的总计数(非故意的唯一):

SELECT User_ID, sum(len(Page_ID) - len(replace(Page_ID, ',', '')) +1) as TotalPageCount
FROM DBTABLE
group by User_ID

输出:

User_ID TotalPageCount
1   6
2   5
3   5
4   3

但是,我希望添加一个(逗号分隔)列,每个用户ID的每页ID为页数。即。作为简报id 1的列:count,newsletter id 2:count等(基本上是字典)。可以是不同的格式,但需要在页面ID级别具有描述性,并具有相应的计数。

这样的事情:

User_ID PageIDCount TotalPageCount
1   48:2, 51:1, 94:2, 22:1, 6
2   42:2, 11:2, 84:1, 5
3   4:2, 16:1, 24:2, 5
4   7:1, 2:1, 94:1, 3

非常感谢您的帮助!

编辑:

根据SeanLange的惊人解决方案,您可以将定义更改为MyCTE,以避免使用任何功能:

select user_id, page_id, page_count = count(*) 
FROM (
SELECT user_id, Split.a.value('.', 'NVARCHAR(max)') AS page_id FROM
    ( SELECT user_id, CAST ('<M>' + REPLACE(page_id, ',', '</M><M>') + '</M>' AS XML) page_id 
    FROM #temp
    ) AS A 
CROSS APPLY page_id.nodes ('/M') AS Split(a)
) x
group by user_id, page_id

2 个答案:

答案 0 :(得分:1)

哇,这是一场噩梦。您将需要一个字符串拆分器来开始。我最喜欢的是这个。 http://www.sqlservercentral.com/articles/Tally+Table/72993/这里有许多其他出色的选择。 https://sqlperformance.com/2012/07/t-sql-queries/split-strings

从您的数据开始,您需要执行以下操作。

declare @Something table
(
    User_ID int
    , Page_ID varchar(100)
    , MyDate datetime
)

insert @Something
select 1, '48,51,94', '7/26/2017 8:30' union all
select 2, '42,11,84', '7/26/2017 9:40' union all
select 3, '4,16,24', '7/26/2017 16:20' union all
select 4, '7,2,94', '7/27/2017 8:00' union all
select 1, '48,22,94', '7/27/2017 13:50' union all
select 2, '42,11', '7/27/2017 14:00' union all
select 3, '4,24', '7/27/2017 18:15'

select User_ID
    , Page_ID = x.Item
    , count(*)
from @Something s
cross apply dbo.DelimitedSplit8K(s.Page_ID, ',') x
group by User_ID
    , x.Item
order by User_ID
    , x.Item

这会获得您想要的数据。从那里你将不得不把它推回到你想要的非规范化结构。您可以使用FOR XML执行此操作。这篇文章解释了如何做到这一点。 Simulating group_concat MySQL function in Microsoft SQL Server 2005?

- - - - - - - - 编辑

好的,这是完整的工作解决方案。你显然一直在努力尝试解决这个问题。我在这里使用DelimitedSplit8K函数,所以我没有像你的解决方案那样内联XML。

with MyCTE as
(
    select User_ID
        , Page_ID = x.Item
        , PageCount = count(*)
    from @Something s
    cross apply dbo.DelimitedSplit8K(s.Page_ID, ',') x
    group by User_ID
        , x.Item
)
, GroupedPageViews as
(       
    select c.User_ID
        , sum(c.PageCount) as TotalPageCount
        , PageViews = STUFF((select ', ' + convert(varchar(4), c2.Page_ID) + ':' + convert(varchar(4), c2.PageCount)
        from MyCTE c2
        where c.User_ID = c2.User_ID
        order by c2.Page_ID
        for xml path('')), 1, 1, '')
    from MyCTE c
    group by c.User_ID
)

select gpv.User_ID
    , gpv.PageViews
    , gpv.TotalPageCount
from GroupedPageViews gpv
join MyCTE c on c.User_ID = gpv.User_ID
group by gpv.PageViews
    , gpv.User_ID
    , gpv.TotalPageCount
order by gpv.User_ID

这将返回您的数据。

User_ID PageViews               TotalPageCount
1       22:1, 48:2, 51:1, 94:2  6
2       11:2, 42:2, 84:1        5
3       16:1, 24:2, 4:2         5
4       2:1, 7:1, 94:1          3

答案 1 :(得分:0)

你去吧

SELECT DISTINCT User_Id
    , (
        SELECT CAST(t.Value AS VARCHAR) + ':' + CAST(COUNT(t.value) AS VARCHAR) + ', '
        FROM TBL_46160346_DBTABLE ii
        CROSS APPLY (
            SELECT *
            FROM fn_ParseText2Table(Page_ID, ',')
            ) t
        WHERE pp.User_Id = ii.User_Id
        GROUP BY User_Id
            , VALUE
        ORDER BY User_Id
        FOR XML PATH('')
        ) PageIDCount
    , (
        SELECT  COUNT(*)
        FROM TBL_46160346_DBTABLE ii
        CROSS APPLY (
            SELECT *
            FROM fn_ParseText2Table(Page_ID, ',')
            ) t
        WHERE pp.User_Id = ii.User_Id
        GROUP BY User_Id
        ) TotalPageCount
FROM TBL_46160346_DBTABLE pp

fn_ParseText2Table函数

ALTER FUNCTION [dbo].[fn_ParseText2Table] (
    @p_SourceText VARCHAR(8000), @p_Delimeter VARCHAR(10) = ',' --default comma
    )
RETURNS @retTable TABLE (Value BIGINT)
AS
BEGIN
    DECLARE @w_Continue INT, @w_StartPos INT, @w_Length INT, @w_Delimeter_pos INT, @w_tmp_txt VARCHAR(48), @w_Delimeter_Len TINYINT

    IF LEN(@p_SourceText) = 0
    BEGIN
        SET @w_Continue = 0 -- force early exit
    END
    ELSE
    BEGIN
        -- parse the original @p_SourceText array into a temp table
        SET @w_Continue = 1
        SET @w_StartPos = 1
        SET @p_SourceText = RTRIM(LTRIM(@p_SourceText))
        SET @w_Length = DATALENGTH(RTRIM(LTRIM(@p_SourceText)))
        SET @w_Delimeter_Len = LEN(@p_Delimeter)
    END

    WHILE @w_Continue = 1
    BEGIN
        SET @w_Delimeter_pos = CHARINDEX(@p_Delimeter, SUBSTRING(@p_SourceText, @w_StartPos, @w_Length - @w_StartPos + @w_Delimeter_Len))

        IF @w_Delimeter_pos > 0 -- delimeter(s) found, get the value
        BEGIN
            SET @w_tmp_txt = LTRIM(RTRIM(SUBSTRING(@p_SourceText, @w_StartPos, @w_Delimeter_pos - 1)))
            SET @w_StartPos = @w_Delimeter_pos + @w_StartPos + @w_Delimeter_Len - 1
        END
        ELSE -- No more delimeters, get last value
        BEGIN
            SET @w_tmp_txt = LTRIM(RTRIM(SUBSTRING(@p_SourceText, @w_StartPos, @w_Length - @w_StartPos + @w_Delimeter_Len)))

            SELECT @w_Continue = 0
        END

        INSERT INTO @retTable
        VALUES (@w_tmp_txt)
    END

    RETURN
END