我有一个历史记录,每当 1 个值发生变化时,它就会记录 1 行。所以在每一行中应该总是只有 1 个值从前一行发生了变化
我的 table 是这样的。 (我有超过 5 个列并且它们没有通用名称,这些列名称将针对每个项目更改。我想避免硬编码列名称)
INDEX|Column1|Column2|Column3|Column4|Column5|TimeStamp
1 | 0| 0| 0| 0| 0|2017-03-28 13:00:00
2 | 0| 0| 0| 1| 0|2017-03-28 14:00:00
3 | 2| 0| 0| 1| 0|2017-03-28 15:00:00
4 | 3| 0| 0| 1| 0|2017-03-28 16:00:00
5 | 3| 22| 0| 1| 0|2017-03-28 17:00:00
6 | 3| 22| 6| 1| 0|2017-03-28 18:00:00
我想在我的报告中得到的结果。
Name |NewValue|PreviousValue|TimeStamp
Column4| 1| 0|2017-03-28 14:00:00
Column1| 2| 0|2017-03-28 15:00:00
Column1| 3| 2|2017-03-28 16:00:00
Column2| 22| 0|2017-03-28 17:00:00
Column3| 6| 0|2017-03-28 18:00:00
实现此目标的最佳方法是什么?
我正在使用 MariaDB 10.1.22。
最佳答案
使用静态 SQL:
这是您想要的基本代码。但是一切都是硬编码的。如果没有“动态 SQL”,这是无法避免的。
SELECT
CASE
WHEN t1.Column1 <> t2.Column1 THEN 'Column1'
WHEN t1.Column2 <> t2.Column2 THEN 'Column2'
WHEN t1.Column3 <> t2.Column3 THEN 'Column3'
WHEN t1.Column4 <> t2.Column4 THEN 'Column4'
WHEN t1.Column5 <> t2.Column5 THEN 'Column5'
END as Name,
CASE
WHEN t1.Column1 <> t2.Column1 THEN t2.Column1
WHEN t1.Column2 <> t2.Column2 THEN t2.Column2
WHEN t1.Column3 <> t2.Column3 THEN t2.Column3
WHEN t1.Column4 <> t2.Column4 THEN t2.Column4
WHEN t1.Column5 <> t2.Column5 THEN t2.Column5
END as NewValue,
CASE
WHEN t1.Column1 <> t2.Column1 THEN t1.Column1
WHEN t1.Column2 <> t2.Column2 THEN t1.Column2
WHEN t1.Column3 <> t2.Column3 THEN t1.Column3
WHEN t1.Column4 <> t2.Column4 THEN t1.Column4
WHEN t1.Column5 <> t2.Column5 THEN t1.Column5
END as PreviousValue,
t2.TimeStamp
FROM
MyTable t1
JOIN MyTable t2
ON t2.Index = t1.Index + 1
如果列可以是 NULL
无论是之前还是之后,您都需要调整 <>
相应地。
使用动态 SQL:
您可以通过从 information_schema
中提取列数据来生成类似上面的代码,而不是对列名称进行硬编码。表。设置 @tablename
变量为您要处理的表的名称,此代码将设置变量 @sql
持有等同于上述的代码。我用过 \n
添加新行,从而改进生成代码的格式。
-- Avoids the GROUP_CONCAT truncating the code we're building
SET SESSION GROUP_CONCAT_MAX_LEN=65535;
-- Specify your target table here
SELECT @tablename := 'MyTable';
SELECT @sql := GROUP_CONCAT(theCode SEPARATOR '')
FROM
(
-- theOrderBy is for keeping the various parts of this code in the correct sequence
-- SELECT plus the opening of the first CASE statement
SELECT 0 theOrderBy, 'SELECT\nCASE\n' theCode
UNION ALL
-- Generates the inner part first CASE statement
SELECT 1, CONCAT(' WHEN t1.',Column_Name,' <> t2.',Column_Name,' THEN ''', Column_Name,'''','\n')
FROM `information_schema`.`Columns`
WHERE table_name like @tablename
AND Column_Name NOT IN ('Index','Timestamp')
UNION ALL
-- Generates the end of the first CASE (and it's alias) and the beginning of the second one
SELECT 2, 'END as Name,\nCASE\n'
UNION ALL
-- Generates the inner part second CASE statement
SELECT 3, CONCAT(' WHEN t1.',Column_Name,' <> t2.',Column_Name,' THEN t2.', Column_Name, '\n')
from `information_schema`.`Columns`
WHERE table_name LIKE @tablename
AND Column_Name NOT IN ('Index','Timestamp')
UNION ALL
-- Generates the end of the second CASE (and it's alias) and the beginning of the third one
SELECT 4, 'END as NewValue,\nCASE\n'
UNION ALL
-- Generates the inner part third CASE statement
SELECT 5, CONCAT(' WHEN t1.',Column_Name,' <> t2.',Column_Name,' THEN t1.', Column_Name, '\n')
FROM `information_schema`.`Columns`
WHERE table_name like @tablename
AND Column_Name NOT IN ('Index','Timestamp')
UNION ALL
-- Generates the end of the final CASE statement plus the FROM clause
SELECT 6, CONCAT('END as PreviousValue,\nt2.Timestamp\nFROM\n ',@tablename, ' t1\n JOIN ',@tablename, ' t2 ON\n t2.Index = t1.Index + 1')
ORDER BY theOrderBy
) as sub;
SELECT @sql;
如果您对在变量中看到的内容感到满意,您可以重新运行并替换最后一行:
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
此代码的可行性在很大程度上取决于您对任何目标表具有相同的基本结构,即顺序增加的 Index
场和一个Timestamp
.与“静态”代码一样,这也使用了 <>
对于所有比较,如此有效地假设字段不会是 NULL
在每次更改之前或之后。
如果这些假设不成立,您将需要进行调整。如果您需要这方面的帮助,请告诉我。
关于mysql - 选择值和值已更改的列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44376361/