【问题标题】:Renaming the columns of a table columns according to a mapping defined in another table - with MYSQL根据另一个表中定义的映射重命名表列的列 - 使用 MYSQL
【发布时间】:2019-10-13 19:55:14
【问题描述】:

我想创建一个 MYSQL 存储过程,它动态地TABLE_MASTERTABLE_EXAMPLE列名 /strong> -> 预期结果显示在 TABLE_RESULT 上。

以下是表格内容:

    TABLE_EXAMPLE (THE HEADERS MIGHT CHANGE BUT THEY ARE MAPPED WITH A COLUMN IN TABLE_EXAMPLE):

           |header01 | header02|...|header n|
           |data01 ..| data02..|...|data 0n|
           |data11 ..| data12..|...|data 1n|
             ..........etc.................
           |data n1..|data n2..|...|data nn|

    TABLE_MASTER (STATIC TABLE, UNCHANGED):

           |ORIGIN| TARGET| NAME|
           |header01|header_master01|Paul|
           |header02|header_master02|Paul|
            ..........etc.................
           |header n|header_master n|Paul|

预期结果包含来自 TABLE_EXAMPLE 的数据,但具有通过 TABLE_MASTER.TARGET 找到的映射列名称:

         TABLE_RESULT:
            |data_master01|data_master02|...|data_master0n| NAME|
            |data01.......|data02.......|...|data 0n.......|Paul|
            |data11.......|data12.......|...|data 1n.......|Paul|  
            .........................etc.........................
           |data n1..|data n2...........|...|data nn.......|Paul|

PS:一个简单的:“ALTER TABLE table_example CHANGE COLUMN old new char(250)”,是不行的。

感谢您的帮助!

编辑 1:我试图写这个但没有成功,因为 'oldname' 和 'newname' 不被视为变量。

BEGIN

DECLARE n INT(10) ; DECLARE i INT(10) ;
DECLARE oldname VARCHAR(40); DECLARE newname VARCHAR(40);

SET n=(SELECT count(id) FROM `master_table` where `name`='paul');

SET i=1; WHILE i<n DO 

SET oldname=(SELECT `COLUMN_NAME`  FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE 
`TABLE_SCHEMA`='mydb'AND `TABLE_NAME`='table_example' LIMIT 1, 1) ; 
SET newname=(SELECT TARGET FROM MASTER_TABLE WHERE ORIGIN='oldname');

ALTER TABLE `table_example` CHANGE oldname newname VARCHAR(50) NOT NULL;

SET i=i+1 ; END WHILE ;
END

【问题讨论】:

  • 什么是表头?列名?你应该重新考虑你的数据库结构,因为你不应该把数据和列名混在一起。
  • 是的 - 表头是列名。我正是想用另一个表格内容的标题创建一个新表格。 PS: header01 |页眉02|值可能会有所不同,具体取决于我要规范化的 table_example。
  • 您不应该将数据与数据库结构混合。如果你描述了你想用这个实现什么,你可能会得到一个更好的结构的建议。
  • 您是在问如何使用AS 定义查询的列别名?你上面的问题不清楚。示例表和主表如何形成预期结果?
  • @BillKarwin :好点,我刚刚在我的问题中添加了预期结果和之前表格之间的链接/推理。

标签: mysql multiple-columns


【解决方案1】:

我想我了解您想按名称选择列,并且名称是 TABLE_MASTER 中的字符串。

您不能在单个 SQL 查询中执行此操作,因为 SQL 无法使用字符串表达式选择列。字符串和标识符之间是有区别的。例如,这通过标识符从列中选择数据:

SELECT header01 ...

但是下面是一个字符串表达式(一个简单的,就是一个常量值)。它只返回一个固定字符串'header01',而不是来自该名称的列中的数据:

SELECT 'header01' ...

同样,在选择列表中使用任何其他表达式只会选择该表达式的值,而不是存储在由表达式的字符串值命名的列中的数据。

因此,如果您希望查询返回由其他变量或表达式命名的动态列,则不能在读取该表达式的同一查询中执行此操作。您必须根据读取的值格式化新的 SQL 查询。这称为动态 SQL 语句(已由 spencer7593 提到,他在我编写自己的答案时发布了答案)。

您可以使用 TABLE_MASTER 格式化动态 SQL 语句来获取列并重新定义它们的别名:

SELECT CONCAT(
  'SELECT ', 
   GROUP_CONCAT(CONCAT(ORIGIN, ' AS ', TARGET)), ', ', 
   QUOTE(MAX(NAME)), ' AS NAME ',
  'FROM TABLE_EXAMPLE'
) INTO @sql
FROM TABLE_MASTER;

这样的结果是形成另一个 SELECT 语句的字符串,这个可以根据需要重命名列:

SELECT header01 AS header_master01,header02 AS header_master02, 'Paul' AS NAME FROM TABLE_EXAMPLE  

那么就可以使用@sql中存储的字符串作为动态SQL查询了。

这是执行此操作的过程:

DELIMITER ;;

CREATE PROCEDURE MyProc()
BEGIN
    SELECT CONCAT(
      'SELECT ', 
       GROUP_CONCAT(CONCAT(ORIGIN, ' AS ', TARGET)), ', ', 
       QUOTE(MAX(NAME)), ' AS NAME ',
      'FROM TABLE_EXAMPLE'
    ) INTO @sql
    FROM TABLE_MASTER;
    PREPARE stmt FROM @sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
END

DELIMITER ;

调用过程,得到结果:

CALL MyProc();

+-----------------+-----------------+------+
| header_master01 | header_master02 | NAME |
+-----------------+-----------------+------+
| data01          | data02          | Paul |
| data11          | data12          | Paul |
+-----------------+-----------------+------+

我不得不评论说,这很麻烦。我宁愿获取数据库中的数据,然后在我的应用程序代码中重新格式化它。 这样我就不必使用任何动态 SQL 来格式化列。

【讨论】:

  • 您的回答很有帮助,也很有见地。我很惭愧地问你,但我怎样才能从中创建一个新表?例如。我尝试添加失败: CREATE TABLE table03 AS ( [YOUR CODE] )。
  • 同样的技术——您可以使用 TABLE_MASTER 的内容来格式化包含 CREATE TABLE 语句或 ALTER TABLE 语句的字符串。然后将该字符串作为动态 SQL 语句执行。抱歉,除非我了解您要做什么,否则我无法更具体。
  • 您已多次被要求解释得更清楚,但我想您无法做到这一点。软件工程是一个复杂而困难的领域(与任何其他类型的工程一样),您需要像练习技术技能一样练习沟通技巧。
  • 嗨,Bill - 感谢您的指导,我能够使用相同的技术添加 CREATE TABLE 语句,例如在您的代码中并生成具有预期结果的新表 TABLE03。我对动态 SQL 有点陌生。我需要更多地学习它,以确保更放心。
  • 我同意这应该是应用程序端的逻辑。当它真正是表示逻辑时,它会导致复杂的数据库代码
【解决方案2】:

规范并不完全清楚,它究竟是我们想要实现的目标。

看起来我们想要一个返回结果集的存储过程。

这意味着该过程将执行一个 SELECT 语句。

SELECT 语句可以为 SELECT 列表中返回的表达式分配别名,然后该别名将成为结果集中的列名。例如

  SELECT 'some_expression' AS foo

此 SELECT 语句返回的结果集将包含一个名为 foo 的列。

SELECT 语句中引用的标识符(表名、列名、函数名)不是动态的;这些不能在执行语句时动态修改。必须在语句中指定要分配给列的别名

  SELECT some_expr AS new_column_name 

并且通过动态获得new_column_name 的值,并且从某个表中检索,我们需要运行一个单独的 SQL 语句来检索要使用的列名。

在过程中,我们可以动态准备我们想要执行的 SQL 文本。

例如,在存储过程的主体中使用这样的内容:

  DECLARE col_name VARCHAR(80);

  SET col_name = 'foo' ;

  SET @sql = CONCAT('SELECT ''some_expr'' AS `',col_name,'`') ;

  PREPARE stmt FROM @sql;
  EXECUTE stmt;
  DEALLOCATE PREPARE stmt;  

执行该过程将导致执行如下语句:

  SELECT 'some_expr' AS `foo`

返回列名为foo 的结果集。


参考:https://dev.mysql.com/doc/refman/8.0/en/sql-syntax-prepared-statements.html

【讨论】:

  • 请@spencer7593 查看我的编辑并重新考虑您的答案是否仍然相关。
  • 如果我们想从一个过程返回一个结果集,那么这个过程需要执行一个SELECT语句。如果目的是更改表中的列名,那么可以,我们将使用 ALTER TABLE 语句来重命名列。如果我们想动态地这样做,那么我们可能需要检索列的定义,而不仅仅是名称,即在一般情况下,我们不能假设列的数据类型是VARCHAR(50)。在现有表中通过 ALTER TABLE 重命名列的过程似乎对于它打算解决的任何问题都是不明智的解决方案。
  • 为了清楚起见,请注意表列有一个名称,我们通常将该标识符称为“列名”。我们不称它为“标题”。
  • 好的 - 谢谢。我认为这很清楚,但也许不是。我已经更新了问题:)
【解决方案3】:

这是一个存储过程,用于根据table_master 中定义的映射重命名表table_example 的列。它的工作原理是获取映射的内容,然后动态生成一系列ALTER TABLE ... RENAME COLUMN ... TO ... 命令(注意这种语法只在SQL 8.0 中支持)。

显然这是一个一次性的过程,因为一旦完成,列就会被重命名。

您取消注释SELECT @q 语句并注释以下三行以执行该过程并显示重命名命令而不实际运行它们。

DELIMITER $$
CREATE PROCEDURE RenameColumns()
BEGIN

    DECLARE finished INTEGER DEFAULT 0;
    DECLARE old_col VARCHAR(50);
    DECLARE new_col VARCHAR(50);

    DECLARE curs CURSOR FOR SELECT origin, target FROM table_master;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;

    OPEN curs;
    renameLoop: LOOP

        FETCH curs INTO old_col, new_col;
        IF finished = 1 THEN 
            LEAVE renameLoop;
        END IF;

        SET @q = CONCAT('ALTER TABLE table_example RENAME COLUMN ', old_col, ' TO ', new_col);
        -- SELECT @q;
        PREPARE stmt FROM @q;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt; 

    END LOOP renameLoop;
    CLOSE curs;

END$$
DELIMITER ;

Demo on DB Fiddle

select * from table_master;

| origin   | target          |
| -------- | --------------- |
| header01 | header_master01 |
| header02 | header_master02 |
| header03 | header_master03 |


select * from table_example;

| header01 | header02 | header03 |
| -------- | -------- | -------- |
| 1        | 2        | 3        |


CALL RenameColumns();

select * from table_example;

| header_master01 | header_master02 | header_master03 |
| --------------- | --------------- | --------------- |
| 1               | 2               | 3               |

【讨论】:

  • 我的 MYSQL 版本接受这种语法:ALTER TABLE table CHANGE old new varchar(40);因此我替换为: ('ALTER TABLE tabletestbis CHANGE ', old_col , new_col, 'VARCHAR(40)');但由于某种原因它不起作用。
  • @SamanthaAlexandria:我的错,应该是:CONCAT('ALTER TABLE tabletestbis CHANGE COLUMN ', old_col , new_col, ' VARCHAR(40)')
  • @SamanthaAlexandria:这是更改列名的正确语法。您可以尝试直接运行此语句,例如:ALTER TABLE tabletestbis CHANGE COLUMN old_column new_column varchar(40)。或者您可以显示生成的语句(如我的回答中所述),然后直接运行它们。
  • 列名之间需要一个空格:CONCAT('ALTER TABLE tabletestbis CHANGE COLUMN ', old_col , ' ', new_col, ' VARCHAR(40)')
  • 万岁 - 它有效。这是实现相同结果的另一种方法 - 它完成了比尔的答案。谢谢你们俩。
猜你喜欢
  • 1970-01-01
  • 2021-03-08
  • 2011-10-02
  • 2022-08-09
  • 2020-09-18
  • 2020-10-03
  • 2013-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多