Обожаю классические задачи, вот и сегодня наткнулся на одну из них.
Суть была такова:
Сейчас переписываем систему оповещений о результатах выполнения процедур.
Результат кладется в таблицы и с использованием sp_send_dbmail рассылается адресатам.
Но есть одна проблема, рассылаемый объем не может превышать 1000000 байт (ограничение MS SQL).
Ладно ок, SQL не хочет, тогда надо вводить ограничение, а как?
Имперический метод себя не оправдает, так как объем вычисляется по суммарной длине строк подзапроса (про запрос см. MSDN) и ограничение по ширине строки тоже, тогда нам необходимо заранее рассчитать: а сколько байт будет наше сообщение.
Обратившись к статистике и финансовому учету (ага забавно как эти дисциплины помогают решать проблемы), можно вспомнить о таком термине как "нарастающий итог" (google расскажет о методе :) ).
Так давайте решим задачу "нарастающего итога"
Пусть у нас есть таблица только с одной ячейкой самого сообщения, например:
- CREATE TABLE msgbodytosend
- (
- mgsstring VARCHAR(max)
- );
* This source code was highlighted with Source Code Highlighter.
Первый вариант - использование одного запроса.
Нам необходимо будет посчитать длины строк и их пронумеровать, в оригинале у нас нет уникального поля.
Дальше используя произведение (INNER JOIN) таблиц и суммирование длин, получаем необходимый результат.
Подзапросы я предпочитаю оборачивать в CTE выражения. На выходе получаем следующий код:
- WITH
- cte_msgbody AS (
- SELECT row_number() (ORDER BY msgstring) row
- , msgstring
- , len(msgstring) msgstring_len
- )
- SELECT
- mb_l.row
- , mb_l.msgstring
- , sum(mb_r.msgstring_len) Running_Total
- FROM cte_msgbody mb_l
- INNER JOIN cte_msgbody mb_r ON mb_l >= mb_r
- GROUP BY
- mb_l.row
- , mb_l.msgstring
- HAVING sum(mb_r.msgstring_len) < 1000000
* This source code was highlighted with Source Code Highlighter.
Второй вариант разбить решение на составные части.
В данном случае мы убираем CTE выражение и делаем предварительный расчет во временной таблице.
- -- Удалим временную таблицу если вдруг она использовалась ранее в тексте
- IF object_id('tempdb..#msgbody')
- DROP TABLE #msgbody;
-
- -- И создадим ее за ново.
- CREATE TABLE #msgbody (
- row INT identity(1, 1) PRIMARY KEY NOT NULL
- , msgstring VARCHAR(max) NULL
- , msgstring_len INT NULL
- , running_total INT NULL
- );
-
- -- Заполняем таблицу
- INSERT INTO #msgbody (
- msgstring
- , msgstring_len
- )
- SELECT msgstring
- , len(msgstring)
- FROM msgbodytosend
-
- -- Теперь сделаем расчет
- UPDATE mb
- SET running_total = (
- SELECT sum(msgstring_len)
- FROM #msgbody mb_i
- WHERE mb_i.row <= mb.row
- )
- FROM #msgbody mb
- -- Добавим небольшое ограничение, этот кусок можно улучшить
- WHERE (
- SELECT sum(msgstring_len)
- FROM #msgbody mb_i
- WHERE mb_i.row <= mb.row
- ) < 1000000
* This source code was highlighted with Source Code Highlighter.
Вот собственно и все, есть еще вариант решения этой задачи с использованием цикла и курсора, но мне они не кажутся столь интересными.
Комментариев нет:
Отправить комментарий