Установление бизнес-иерархии в лог-файлах - заменить CURSOR чем-то другим? - PullRequest
0 голосов
/ 10 мая 2018

Я использую SQL Server в качестве хранилища для анализа файлов журнала. Эти файлы журналов имеют своего рода иерархию бизнес-процессов (в данном примере это работник):

Log Entry Id, Log Message
1           , Start Worker
2           , Do Cool Stuff
3           , Start Worker
4           , Do further cool stuff
5           , Start Worker
6           , This is a lot of working
7           , End worker
8           , End worker
9           , End worker

Мне нужно связать записи в журнале с текущим работником. Правило довольно простое: как только будет найдено сообщение «Начать работу», назначьте этому сотруднику все последующие записи журнала. В примере иерархии это означает:

Log Entry Id, Log Message              , Worker
1           , Start Worker             , 1 (we take the entry id as worker id)
2           , Do Cool Stuff            , 1
3           , Start Worker             , 3
4           , Do further cool stuff    , 3
5           , Start Worker             , 5
6           , This is a lot of working , 5
7           , End worker               , 5
8           , End worker               , 3
9           , End worker               , 1

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

CREATE PROCEDURE CalculateRelations
AS
BEGIN
    DECLARE entries_cur CURSOR FOR
    SELECT Id, LogMessage
    FROM LogEntries
    ORDER BY Id;

    DECLARE @Id BIGINT;
    DECLARE @LogMessage VARCHAR(128);
    DECLARE @ParentWorker BIGINT;
    DECLARE @WorkerStack VARCHAR(MAX) = '';

    OPEN entries_cur;
    FETCH NEXT FROM entries_cur INTO @Id, @LogMessage;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC dbo.GetParentWorker @WorkerStack OUT, @Id, @LogMessage, @ParentWorker OUT;

        UPDATE LogEntries
        SET ParentWorker = @ParentWorker
        WHERE Id = @Id;

        FETCH NEXT FROM entries_cur INTO @Id, @LogMessage;
    END;

    CLOSE entries_cur;
    DEALLOCATE entries_cur;
END;
GO

GetParentWorker - это хранимая процедура, которая использует данную VARCHAR переменную WorkerStack в качестве стека. Это значит

  • Сообщение «Начать работу» приводит к добавлению (push) Id к этому VARCHAR
  • Сообщение «Конечный работник» приводит к удалению и возврату (всплывающего) последнего Id из этого VARCHAR
  • все остальные сообщения приводят к тому, что просто возвращаются (читаются) последние Id из этого VARCHAR без его изменения

Теперь мне интересно, можно ли заменить эту конструкцию курсора на оператор UPDATE. Я не настолько глубоко разбираюсь в SQL и SQL Server, но может быть возможно реализовать это с помощью динамического присваивания переменной CASE и использования возвращаемого значения GetParentWorker?

Ответы [ 2 ]

0 голосов
/ 10 мая 2018

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

Я использую CTE для вычисления уровня отступа (в основном это просто сложение и вычитаниеодин всякий раз, когда мы нажимаем начало или конец, используя оконную функцию в предыдущих строках с особым случаем окончания рабочего в текущей строке).За пределами этого игрушечного решения вы хотели бы ограничить предыдущие строки строками без назначенного работника, а также рядами до последнего раза, когда уровень был равен нулю до этого.

Тогда мы можем просто найти предыдущие 'Начните Worker 'с того же уровня.Вероятно, они могут быть помечены при предварительной обработке и проиндексированы для более быстрого поиска.

ОБНОВЛЕНИЕ:

Упрощенный оператор обновления за счет введения оконной функции CTE для вычисления идентификатора работника.Это должно уменьшить поиск отдельных строк и повысить производительность обновления.См. SQL Fiddle

WITH 
    WorkerNestingLevel AS (
        SELECT
            AuditLog.LogId
        ,   AuditLog.LogMessage
        ,   SUM( CASE LogMessage WHEN 'Start Worker' THEN 1 WHEN 'End Worker' THEN -1 ELSE 0 END ) OVER (ORDER BY LogId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
        +   CASE LogMessage WHEN 'End Worker' THEN 1 ELSE 0 END AS [WorkerLevel]
        FROM
            AuditLog
    )
,   WorkerBatch AS (
        SELECT
            WorkerNestingLevel.LogId
        ,   MAX( CASE WorkerNestingLevel.LogMessage WHEN 'Start Worker' THEN WorkerNestingLevel.LogId ELSE NULL END) OVER (PARTITION BY WorkerNestingLevel.WorkerLevel ORDER BY WorkerNestingLevel.LogId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS WorkerId
        FROM
            WorkerNestingLevel
    )
UPDATE
    AuditLog
SET
    WorkerId = WorkerBatch.WorkerId
FROM
    AuditLog
JOIN
    WorkerBatch ON (WorkerBatch.LogID = AuditLog.LogId);
0 голосов
/ 10 мая 2018

Приношу свои извинения за недопонимание с первой попытки, надеюсь, на этот раз я понял, что каждое значение «Конечный работник» отменяет одно из «Начального работника», которое предшествует ему.Здесь используется оператор WITH, который генерирует набор данных с полем с именем indent, которое необходимо установить, как далеко назад искать правильный [Log Entry ID].Это соответствует требованию?

WITH indenttable AS (SELECT [Log Entry ID]
    , [Log Message]
    , ((SELECT COUNT(*)
        FROM yourtable y2 
        WHERE [Log Message]='Start Worker' 
            AND y2.[Log Entry ID]<=yourtable.[Log Entry ID])
        -(SELECT COUNT(*)   
        FROM yourtable y2 
        WHERE [Log Message]='End Worker' 
            AND y2.[Log Entry ID]<yourtable.[Log Entry ID])) indent
    FROM yourtable)
UPDATE yourtable
SET worker=(
    SELECT TOP(1) [Log Entry ID]
    FROM indenttable y2 
    WHERE [Log Message]='Start Worker' 
        AND y2.[Log Entry ID]<=indenttable.[Log Entry ID]
        AND y2.indent<=indenttable.indent
    ORDER BY [Log Entry ID] DESC)
FROM indenttable JOIN yourtable ON indenttable.[Log Entry ID]=yourtable.[Log Entry ID];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...