MySQL запрос, чтобы получить общее процентное изменение - PullRequest
0 голосов
/ 21 марта 2019

Как добавить столбец процентного изменения (не процентных пунктов ) в MySQL?

есть таблица со столбцом изменений в процентах:

+---------+
| percent |
+---------+
|   -0.50 |
|    0.50 |
|    1.00 |
|   -0.20 |
|    0.50 |
|   -1.00 |
|   -2.00 |
|    0.75 |
|    1.00 |
|    0.50 |
+---------+

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

ожидаемый результат:

+---------+---------------+---------------+
| percent | nominal_value | total_percent |
+---------+---------------+---------------+
|   -0.50 |          0.50 |         -0.50 |
|    0.50 |          0.75 |         -0.25 |
|    1.00 |          1.50 |          0.50 |
|   -0.20 |          1.20 |          0.20 |
|    0.50 |          1.80 |          0.80 |
|   -1.00 |          0.00 |         -1.00 |
|   -2.00 |         -2.00 |         -3.00 |
|    0.75 |         -0.50 |         -1.50 |
|    1.00 |          0.00 |         -1.00 |
|    0.50 |          0.50 |         -0.50 |
+---------+---------------+---------------+

Где nominal_value - произвольное значение, которое было изменено на percent, то есть для первой строки, если номинальное значение было 1,0 (100%), но было изменено на -0.50 (-50%), это привело к номинальному значениюзначение 0.5.

Затем во втором ряду percent изменение было +0.50 (+50%), поэтому номинальное значение было увеличено вдвое 0.5 => 0.75, но можно также сказать, что оно былотолько уменьшен на -0.25 (-25%) от его исходного значения, так как с 1.0 до 0.75 составляет -0.25 (-25%) 1.0.

Это именно то, что я сделал после total_percent изменения, nominal_value был только для пояснительной цели и не нужен.

Я использую MySQL 8, поэтому запрос можетиспользуйте оконные функции / диапазоны и т. д.

вот тестовая таблица для репликации:

CREATE TABLE IF NOT EXISTS test
(
    percent DECIMAL(5,2) NOT NULL
)
ENGINE = InnoDB
;

INSERT INTO test (percent) VALUES 
(-0.50)
,(0.50)
,(1.00)
,(-0.20)
,(0.50)
,(-1.0)
,(-2.0)
,(0.75)
,(1.0)
,(0.50)
;

Ответы [ 4 ]

2 голосов
/ 22 марта 2019

Этот запрос даст вам результаты, которые вы хотите.Он использует два CTE, первый из которых просто добавляет номер строки к данным, а второй - рекурсивный CTE, который генерирует значения nominal_value из текущего percent и предшествующего nominal_value (где предшествующее определяется номером строки).Наконец, total_percent вычисляется из nominal_value.

Примечание

Чтобы этот (и любой подобный) запрос работал надежно, должен быть PRIMARY KEY что первый CTE может иметь результаты, упорядоченные по.Для этого я добавил для этой цели столбец AUTO_INCREMENT INT 1014 *.

WITH RECURSIVE cte AS (
  SELECT percent, ROW_NUMBER() OVER () AS rn
  FROM test
  ORDER BY id),
cte2 AS (
  SELECT 1 + percent AS nominal_value, rn
  FROM cte
  WHERE rn = 1
  UNION ALL
  SELECT CASE WHEN nominal_value = 0 THEN percent
              ELSE nominal_value + percent * ABS(nominal_value)
              END,
         cte.rn
  FROM cte
  JOIN cte2 ON cte2.rn = cte.rn - 1
  )
SELECT percent, nominal_value, (nominal_value - 1) AS total_percent
FROM cte2
JOIN cte ON cte.rn = cte2.rn

Вывод:

percent nominal_value   total_percent
-0.5    0.5             -0.5
0.5     0.75            -0.25
1       1.5             0.5
-0.2    1.2             0.2
0.5     1.8             0.8
-1      0               -1
-2      -2              -3
0.75    -0.5            -1.5
1       0               -1
0.5     0.5             -0.5

Демо на dbfiddle

1 голос
/ 24 марта 2019

Альтернативный способ вычисления этих данных - использование хранимой процедуры.Преимущество этого подхода состоит в том, что он не требует рекурсивного CTE или переменных, но недостатком является то, что использование результатов может быть сложным (например, в JOIN).Эта процедура создает временную таблицу для хранения результатов перед их возвратом;эта таблица может быть сохранена вместо того, чтобы быть DROP ped в конце процедуры, если потребуется дальнейшая обработка.Как и в других ответах, этот подход требует, чтобы данные имели PRIMARY KEY, чтобы гарантировать согласованные результаты.

DELIMITER //
CREATE PROCEDURE total_percent()
BEGIN
  DECLARE nominal_value DECIMAL(10,2) DEFAULT 1;
  DECLARE this_percent DECIMAL(5,2);
  DECLARE done INT DEFAULT 0;
  DECLARE p_cursor CURSOR FOR SELECT percent FROM test ORDER BY id;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
  CREATE TEMPORARY TABLE p (percent DECIMAL(5, 2),
                            nominal_value DECIMAL(10, 2),
                            total_percent DECIMAL(10, 2));
  OPEN p_cursor;
  compute: LOOP
    FETCH p_cursor INTO this_percent;
    IF done THEN
      LEAVE compute;
    END IF;
    IF nominal_value = 0 THEN
      SET nominal_value = this_percent;
    ELSE
      SET nominal_value = nominal_value + this_percent * ABS(nominal_value);
    END IF;
    INSERT INTO p VALUES (this_percent, nominal_value, nominal_value -1);
  END loop;
  SELECT * FROM p;
  DROP TABLE p;
END //
DELIMITER ;

CALL total_percent();

Вывод:

percent  nominal_value   total_percent
-0.5     0.5             -0.5
0.5      0.75            -0.25
1        1.5             0.5
-0.2     1.2             0.2
0.5      1.8             0.8
-1       0               -1
-2       -2              -3
0.75     -0.5            -1.5
1        0               -1
0.5      0.5             -0.5

Демонстрация на dbfiddle

1 голос
/ 23 марта 2019

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

Запрос:

DROP TABLE IF EXISTS test;

CREATE TABLE test
( 
 id SERIAL PRIMARY KEY
 , percent DECIMAL(5,2) NOT NULL
);

INSERT INTO test (percent) VALUES 
(-0.50)
,(0.50)
,(1.00)
,(-0.20)
,(0.50)
,(-1.0)
,(-2.0)
,(0.75)
,(1.0)
,(0.50)
;

SELECT 
    percent,

    CASE @i 
        WHEN 0 THEN ROUND(@i:=(@i+(percent * 1)),2) -1
        ELSE ROUND(@i:=(@i+(percent * ABS(@i))) ,2) -1
    END total_percent

FROM 
    test
    , (SELECT @i:=1) vars         
ORDER 
    BY id; 

Результат:

+---------+---------------+
| percent | total_percent |
+---------+---------------+
|   -0.50 |         -0.50 |
|    0.50 |         -0.25 |
|    1.00 |          0.50 |
|   -0.20 |          0.20 |
|    0.50 |          0.80 |
|   -1.00 |         -1.00 |
|   -2.00 |         -3.00 |
|    0.75 |         -1.50 |
|    1.00 |         -1.00 |
|    0.50 |         -0.50 |
+---------+---------------+
10 rows in set, 3 warnings (0.00 sec)

Обратите внимание, что принятый ответ останавливает вычисления после достижения нулевого номинального значения, а затем, независимо от того, что процентное изменение не делает различий, а номинальное значение одинаково = 0. Для некоторых случаев это может быть правильным подходом. Для других вот этот, который продолжает вычисление через ноль или ответ @Nick, если вы используете MySQL 8.

1 голос
/ 22 марта 2019
DROP TABLE IF EXISTS test;

CREATE TABLE test
( id SERIAL PRIMARY KEY
, percent DECIMAL(5,2) NOT NULL
);

INSERT INTO test (percent) VALUES 
(-0.5)
,(0.5)
,(1)
,(-0.2)
,(0.5)
,(-1)
;

SELECT ROUND(@i:=(@i+(@i*percent)),2)n 
  FROM test
     , (SELECT @i:=1) vars 
 ORDER 
    BY id;
+------+
| n    |
+------+
| 0.50 |
| 0.75 |
| 1.50 |
| 1.20 |
| 1.80 |
| 0.00 |
+------+
6 rows in set (0.00 sec)

mysql>
...