Столбец с разделителями-запятыми в SQL => к строкам, а затем сумма итогов? - PullRequest
2 голосов
/ 26 февраля 2010

Я использую MS SQL 2005 У меня есть проблема, с которой я сейчас борюсь, чтобы найти решение.

У меня есть таблица со следующими столбцами: NameList; Время

Столбец списка имен содержит данные, разделенные запятыми. Данные таблицы таковы:

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

Мне нужно какое-то выражение SQL, которое даст мне такой конечный результат:

Name    Total_Time
John Smith  20
Jeremy Boyle    15
Robert Brits    15

Etc ...... В основном, выражение должно найти все имена в строках и объединить их с именами в других строках и сложить время для каждого пользователя.

Идея, которую я имею, состоит в том, чтобы преобразовать данные, разделенные запятыми, в строки и подсчитать отдельные записи каждого из них, затем каким-то образом узнать, сколько времени для этого ... затем умножить ..... но я не знаю, как реализовать это

Любая помощь будет высоко ценится

Спасибо

Ответы [ 4 ]

5 голосов
/ 26 февраля 2010

Я предпочитаю подход таблицы чисел для разделения строки в TSQL

Чтобы этот метод работал, вам нужно выполнить настройку единовременного расписания:

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)

После настройки таблицы чисел создайте эту функцию разделения:

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 

Теперь вы можете легко разбить строку CSV на таблицу и присоединиться к ней:

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

ВЫВОД:

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

(6 row(s) affected)

Теперь вы можете использовать CROSS APPLY для разделения каждой строки в вашей таблице, например:

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

ВЫВОД:

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

(5 row(s) affected)

Используя это, я бы порекомендовал вам изменить дизайн таблицы и использовать этот вывод для ВСТАВКИ в новую таблицу. Это был бы более нормализованный подход. Также не используйте зарезервированные слова для имен столбцов, это создает трудности. Обратите внимание, как я использую «NameOf» и «TimeOf», поэтому я избегаю использования зарезервированных слов.

1 голос
/ 26 февраля 2010

Вы можете создать табличную функцию для разделения списка имен на несколько строк:

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

Вы можете использовать cross apply, чтобы вернуть имена для каждого списка имен. Затем вы можете использовать group by для суммирования времени на пользователя:

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

В результате:

George Aldrich      5
Jeremy Boyle        15
John Smith          20
Peter Hanson        15
Robert Brits        15
1 голос
/ 26 февраля 2010

Либо: поиск других ответов , чтобы исправить данные на лету, медленно и многократно

Или: нормализовать. Как вы думаете, почему нормализация существует и почему люди бьют по этому поводу?

0 голосов
/ 26 февраля 2010

Лучший вариант - нормализовать данные. Тогда было бы намного проще работать.

Вторым лучшим вариантом будет использование рекурсивного запроса для выбора имени за раз из каждого списка имен и возврата в виде списка отдельных имен и их соответствующего времени из каждой записи, а затем группировка для суммирования времени для каждого имя.

Нет необходимости для пользовательских функций или предварительно созданных таблиц. ;)

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]

Напротив, для работы с нормализованными данными это будет так просто:

select p.Name, sum(n.Time)
from NamelistTime n
inner join Person p on p.PersonId = n.PersonId
group by p.Name
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...