【问题标题】:Select value and column that value have changed选择值和该值已更改的列
【发布时间】:2017-06-05 19:32:50
【问题描述】:

我有一个历史学家,每次有 1 个值更改时记录 1 行。因此,在每一行中,应该始终只有 1 个值与前一行相比发生了变化

我的桌子是这样的。 (我有超过 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。

【问题讨论】:

  • 一共有多少列?
  • 我有大约 30 列,列的数量和名称将在每个项目 id 中有所不同,以避免硬编码列名称

标签: mysql sql mariadb


【解决方案1】:

使用静态 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,您需要相应地调整 &lt;&gt;s。

使用动态 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。与“静态”代码一样,这也使用&lt;&gt; 进行所有比较,因此有效地假设字段在每次更改之前或之后都不是NULL

如果这些假设不成立,您需要进行调整。如果您需要这方面的帮助,请告诉我。

【讨论】:

  • 我用t1t2替换了beforeafter,因为原来是保留字。
  • 我现在还添加了一个使用动态 SQL 的版本。这在很大程度上取决于您对任何目标表具有相同的基本结构,即顺序增加的“索引”字段和“时间戳”。我也坚持使用&lt;&gt;,所以如果你有NULLs,你可能需要改变它,这可能有点复杂,它的完成方式取决于数据类型。如果它们都是数字,就像你的例子一样,那也不会太糟糕。
  • 我做了一些小的修改以改进生成的 SQL 中的格式,并添加了一些“叙述”以使其更易于理解。
【解决方案2】:
SELECT 'Column5', column5, max(timestamp) from mytable where column5 <> 0 group by column5
Union
SELECT 'Column4', column4, max(timestamp) from mytable where column4 <> 0 group by column4
Union
SELECT 'Column3', column3, max(timestamp) from mytable where column3 <> 0 group by column3
Union
SELECT 'Column2', column2, max(timestamp) from mytable where column2 <> 0 group by column2
Union
SELECT 'Column1', column1, max(timestamp) from mytable where column1 <> 0 group by column1

【讨论】:

  • @no666 哦,发帖前没看到你的评论,让我再检查一下
猜你喜欢
  • 2012-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-04
  • 1970-01-01
  • 1970-01-01
  • 2020-12-20
  • 2020-10-20
相关资源
最近更新 更多