开发者

SQL comma delimted column => to rows then sum totals?

开发者 https://www.devze.com 2022-12-21 03:36 出处:网络
I am using MS 开发者_如何学编程SQL 2005 I have a problem, which currently I am battling to get a solution.

I am using MS 开发者_如何学编程SQL 2005 I have a problem, which currently I am battling to get a solution.

I have a Table, with a these columns : NameList;Time

The Namelist Column has comma delimited data in it. The table data is as such:

Namelist    Time
John Smith, Jeremy Boyle, Robert Brits, George Aldrich  5
John Smith, Peter Hanson    15
Jeremy Boyle, Robert Brits  10
....

I need some sort of SQL expression that will provide me with this end result:

Name    Total_Time
John Smith  20
Jeremy Boyle    15
Robert Brits    15

Etc...... Basically the expression must find All the names in the rows and math those names with the names in the other rows and add the times together for each user.

The Idea I have is to convert the comma delimited data into rows and count the distinct records of each then somehow know what the time for it is... then multiply..... but I have no idea on how to implement it

Any Help would be much appreciated

Thanks,


I prefer the number table approach to split a string in TSQL

For this method to work, you need to do this one time table setup:

SELECT TOP 10000 IDENTITY(int,1,1) AS Number
    INTO Numbers
    FROM sys.objects s1
    CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)

Once the Numbers table is set up, create this split function:

CREATE FUNCTION [dbo].[FN_ListToTable]
(
     @SplitOn  char(1)      --REQUIRED, the character to split the @List string on
    ,@List     varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN 
(

    ----------------
    --SINGLE QUERY-- --this will not return empty rows
    ----------------
    SELECT
        ListValue
        FROM (SELECT
                  LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
                  FROM (
                           SELECT @SplitOn + @List + @SplitOn AS List2
                       ) AS dt
                      INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
                  WHERE SUBSTRING(List2, number, 1) = @SplitOn
             ) dt2
        WHERE ListValue IS NOT NULL AND ListValue!=''

);
GO 

You can now easily split a CSV string into a table and join on it:

select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,')

OUTPUT:

ListValue
-----------------------
1
2
3
4
5
6777

(6 row(s) affected)

Your can now use a CROSS APPLY to split every row in your table like:

DECLARE @YourTable table (NameList varchar(5000), TimeOf int)
INSERT INTO @YourTable VALUES ('John Smith, Jeremy Boyle, Robert Brits, George Aldrich',  5)
INSERT INTO @YourTable VALUES ('John Smith, Peter Hanson',    15)
INSERT INTO @YourTable VALUES ('Jeremy Boyle, Robert Brits',  10)

SELECT
    st.ListValue AS NameOf, SUM(o.TimeOf) AS TimeOf
    FROM @YourTable  o
        CROSS APPLY  dbo.FN_ListToTable(',',o.NameList) AS st
    GROUP BY st.ListValue
    ORDER BY st.ListValue

OUTPUT:

NameOf                  TimeOf     
----------------------- -----------
George Aldrich          5          
Jeremy Boyle            15         
John Smith              20         
Peter Hanson            15         
Robert Brits            15         

(5 row(s) affected)

Using this, I would recommend that you alter your table design and use this output to INSERT into a new table. That would be a more normalized approach. Also Don't use reserved words for column names, it makes it a hassle. Notice how I use "NameOf" and "TimeOf", so I avoid using reserved words.


Either: Search for other answers to fix your data on the fly, slowly, and repeatedly

Or: Normalise. Why do you think normalisation exists and why people bang on about it?


You could create a table-valued function to split the namelist into many rows:

if object_id('dbo.fnSplitNamelist') is not null
    drop function dbo.fnSplitNamelist
go
create function dbo.fnSplitNamelist(
    @namelist varchar(max))
returns @names table (
    name varchar(50))
as 
    begin
    declare @start int
    declare @end int
    set @start = 0
    while IsNull(@end,0) <> len(@namelist) + 1
        begin
        set @end = charindex(',', @namelist, @start)
        if @end = 0
            set @end = len(@namelist) + 1

        insert into @names select ltrim(rtrim(
            substring(@namelist,@start,@end-@start)))

        set @start = @end + 1
        end
    return
    end
go

You can use a cross apply to return the names for each namelist. Then you can use group by to sum the time per user:

declare @YourTable table (namelist varchar(1000), time int)

insert into @YourTable
select 'John Smith, Jeremy Boyle, Robert Brits, George Aldrich',  5
union all select 'John Smith, Peter Hanson',  15
union all select 'Jeremy Boyle, Robert Brits',  10

select fn.name, sum(t.time)
from @YourTable t
cross apply fnSplitNamelist(t.namelist) fn
group by fn.name

This results in:

George Aldrich      5
Jeremy Boyle        15
John Smith          20
Peter Hanson        15
Robert Brits        15


The best option is to normalise the data. Then it would be a lot easier to work with.

The second best option would be to use a recursive query to pick a name at a time from each name list and return as a list of separate names and their respecive time from each record, then use grouping to sum the times for each name.

No need for user defined functions or pre-created tables. ;)

with NameTime ([Name], [Time], Namelist)
as (
  select cast(null as varchar(100)), [Time], Namelist
  from NamelistTime
  union all
  select
    case when Pos = 0 then NameList else substring(Namelist, 1, Pos - 1) end,
    [Time],
    case when Pos = 0 then null else substring(NameList, Pos + 2, len(Namelist) - Pos - 1) end
  from (
    select [Time], Namelist, Pos = charindex(', ', Namelist)
    from NameTime
  ) x
  where Namelist is not null
)
select [Name], sum([Time])
from NameTime
where [Name] is not null
group by [Name]

In contrast, working with normalised data, it would be as simple as:

select p.Name, sum(n.Time)
from NamelistTime n
inner join Person p on p.PersonId = n.PersonId
group by p.Name
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号