Есть ли способ в SQL или функция в MYSQL, которая суммирует все приращения в столбце? - PullRequest
0 голосов
/ 01 июня 2019

Я хочу найти способ суммировать все приращения в значении столбца.

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

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

Моя хранимая процедура делает выбор клиента в заданном диапазоне дат и вставляет результат ввременная таблица X. После этого она начинает извлекать строки из таблицы X, сравнивая значение баланса в этой строке с предыдущей строкой, и обнаруживает, есть ли приращение.Если приращения нет, появляется другая строка и выполняется та же процедура, при наличии приращения вычисляется разница между этой строкой и предыдущей, а результат вставляется в другую временную таблицу Y.

Когдастрок не осталось, хранимая процедура выполняет SUM во временной таблице Y, и, таким образом, вы можете узнать, на сколько клиент «пополнил» свой баланс.

Это пример таблицы X,и ожидаемый результат:

DATE        BALANCE
----        -------
2019-02-01  200
2019-02-02  195    //from 200 to 195 there is a decrement, so it doesn't matter
2019-02-03  180
2019-02-04  150
2019-02-05  175    //there is an increment from 150 to 175, it's 25 that must be inserted in the temp table
2019-02-06  140
2019-02-07  180    //there is another increment, from 140 to 180, it's 40

Таким образом, результирующая временная таблица Y должна выглядеть примерно так:

REFILL
------
25
40

Ожидаемый результат равен 65. Моя хранимая процедура возвращает это значение, но какЯ сказал, что это довольно медленно (около 22 секунд для обработки 3900 строк, что эквивалентно 3 дням, приблизительно), я думаю, что это из-за циклов.Я хотел бы изучить другие альтернативы.Поскольку некоторые детали, которые я здесь не упоминаю, для одного клиента, я могу иметь 1300 строк в день (пример приводится в днях, но у меня есть строки по минутам).Мои таблицы проиндексированы, я думаю, правильно.Я не могу опубликовать свою хранимую процедуру, но она работает как описано (я знаю, что «Дьявол кроется в деталях»).Так что любое предложение будет оценено.

Ответы [ 3 ]

1 голос
/ 01 июня 2019

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

SELECT SUM(refill) AS total_refill
FROM (
  SELECT GREATEST(0, balance - @prev_balance) AS refill, @prev_balance := balance
  FROM (
    SELECT balance
    FROM tableX
    ORDER BY date) AS t
  CROSS JOIN (SELECT @prev_balance := NULL) AS ars
) AS t
0 голосов
/ 01 июня 2019

Использование lag() в MySQL 8 +:

select sum(balance - prev_balance) as refills
from (select t.*, lag(balance) over (order by date) prev_balance
      from t
     ) t
where balance > prev_balance;

В старых версиях MySQL это сложно. Если значения являются непрерывными датами, тогда работает простой JOIN:

select sum(t.balance - tprev.balance) as refills
from t join
     t tprev
     on tprev.date = t.date - 1
where t.balance > tprev.balance;

Это может быть не так. Тогда следующий лучший метод - это переменные. Но вы должны быть очень осторожны. MySQL не объявляет порядок вычисления выражений в SELECT. Как объясняет документация :

Порядок вычисления выражений с участием пользовательских переменных не определен. Например, нет гарантии, что SELECT @a, @a: = @ a + 1 сначала вычислит @a, а затем выполнит присваивание.

Переменным должны быть присвоены и , используемые в одном выражении:

select sum(balance - prev_balance) as refills
from (select t.*,
             (case when (@temp_prevb := @prevb) = NULL  -- intentionally false
                   then -1 
                   when (@prevb := balance) 
                   then @temp_prevb
              end) as prev_balance
      from (select t.* from t order by date) t cross join
           (select @prevb := NULL) params
     ) t
where balance > prev_balance;

И последний метод - это коррелированный подзапрос:

select sum(balance - prev_balance) as refills
from (select t.*,
             (select t2.balance
              from t t2
              where t2.date < t.date
              order by t2.date desc
             ) as prev_balance
      from t
     ) t
where balance > prev_balance;
0 голосов
/ 01 июня 2019

Существует довольно известный механизм для решения этих проблем: используйте переменную внутри поля.

SELECT @result:=0;
SELECT @lastbalance:=9999999999; -- whatever value is sure to be highe than any real balance
SELECT SUM(increments) AS total FROM (
  SELECT
    IF(balance>@lastbalance, balance-@lastbalance, 0) AS increments,
    @lastbalance:=balance AS ignore
  FROM X -- insert real table name here
  WHERE
    -- insert selector here
  ORDER BY
    -- insert real chronological sorter here
) AS baseview;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...