mysql - 在MySQL中使用触发器进行多行而不是单行数据转换

标签 mysql sql

我有这个查询:

CREATE TRIGGER move_form_data
AFTER INSERT ON schema.original_table
FOR EACH ROW
INSERT INTO schema.new_table (name, street_address, 
            street_address_line_2, city, state, zip, country, dob)
SELECT name, street_address, street_address_line_2, city, state, zip, country, dob 
from view_data_submits


与调用此视图:

CREATE VIEW view_data_submits AS 

SELECT  
        MAX(CASE WHEN element_label = 0 THEN element_value end) AS name,
        MAX(CASE WHEN element_label = 1 THEN element_value end) AS street_address,
        MAX(CASE WHEN element_label = 2 THEN element_value end) AS street_address_line_2,
        MAX(CASE WHEN element_label = 3 THEN element_value end) AS city,
        MAX(CASE WHEN element_label = 4 THEN element_value end) AS state,
        MAX(CASE WHEN element_label = 5 THEN element_value end) AS zip,
        MAX(CASE WHEN element_label = 6 THEN element_value end) AS country,
        MAX(CASE WHEN element_label = 7 THEN element_value end) AS dob
FROM schema.original_table
WHERE group_id = (select MAX(group_id) from schema.original_table)
group by group_id


我要返回1行,并且触发器仅按以下代码运行即可,无需触发器部分:

INSERT INTO schema.new_table (name, street_address, 
                street_address_line_2, city, state, zip, country, dob)
    SELECT name, street_address, street_address_line_2, city, state, zip, country, dob 
    from view_data_submits


当前,当用户提交表单时,它会给我返回插入的行,但是它从原始表转换为新表,如下所示:

# id, name, street_address, street_address_line_2, city, state, zip, country, dob
2, fsa asdadFQ, , , , , , , 
3, fsa asdadFQ, BOOGYBOOGYBOOGY, , , , , , 
4, fsa asdadFQ, BOOGYBOOGYBOOGY, YOUdooWORK, , , , , 
5, fsa asdadFQ, BOOGYBOOGYBOOGY, YOUdooWORK, A, , , , 
6, fsa asdadFQ, BOOGYBOOGYBOOGY, YOUdooWORK, A, DD, , , 
7, fsa asdadFQ, BOOGYBOOGYBOOGY, YOUdooWORK, A, DD, 09876, , 
8, fsa asdadFQ, BOOGYBOOGYBOOGY, YOUdooWORK, A, DD, 09876, Belize, 
9, fsa asdadFQ, BOOGYBOOGYBOOGY, YOUdooWORK, A, DD, 09876, Belize, 2014-02-05  <--only row that I want (=the total form submission)


不仅仅是:

# id, name, street_address, street_address_line_2, city, state, zip, country, dob

9, fsa asdadFQ, BOOGYBOOGYBOOGY, YOUdooWORK, A, DD, 09876, Belize, 2014-02-05


我感觉这要么是与FOR EACH ROW语法有关,要么是应用程序以某种复合方式保存。我倾向于第一个。

有人对补救有任何建议吗?我几乎感觉到好像是我刚刚忘记的一些菜鸟般的错误。

~~编辑每个请求:

这是从中提取最大id的原始表中的select *:

# id, form_id, element_label, element_value, group_id
----+--------+--------------+--------------+---------
 207,       2,             0,          name,       25
 208,       2,             1,     address 1,       25
 209,       2,             2,     address 2,       25
 210,       2,             3,          city,       25
 211,       2,             4,         state,       25
 212,       2,             5,           zip,       25
 213,       2,             6,       country,       25
 214,       2,             7,           dob,       25


由于这些值是blob形式,因此我用它们所代表的值替换了这些值,所以我只提取了最新插入的数据

最佳答案

这看起来像是一个EAV模式(哦!很高兴!)。

看起来根本的问题是应用程序未按照您希望的方式插入“行”;它将多行插入到同一表中,每一行代表一个属性值。

该应用程序正在使用实体属性值(EAV)模型,并且您想要的是看起来像传统关系模型的行。

相当难看的“ MAX(),MAX(),MAX()... GROUP BY”查询所做的是将所有这些EAV行转换为单行的列。



看起来您想即时进行转换并在每次将行插入original_table中时维护target_table的内容。

如果我正在解决该问题,则将group_id包含在我的target_table中,因为这是将所有单个EAV行关联在一起的值(如您的视图查询中所示)。

而且我绝对不会使用SELECT MAX(group_id)查询来引用刚刚插入original_table的行上的值。在AFTER INSERT触发器的上下文中,我已经具有刚刚插入的行的group_id值;它对我来说是“ NEW.group_id”。

(我避免使用MAX(group_id)查询获取该值的真正原因是,我不能保证在进程运行时其他进程不会为group_id插入较大的值。我不能保证MAX(group_id)会返回刚刚插入的group_id的值。(当然,我永远不会在单用户测试中看到问题发生;我必须在处理过程中包含一些故意的延迟,并且两个进程同时运行以使其发生。这是在生产中而不是在测试中弹出的问题之一,基本上是因为我们不必费心设置测试用例来发现问题。)

如果我只希望target_table中的每个group_id值中有一行,那么我将在target_table的group_id列上创建一个唯一约束。然后,我将使用“ upsert”类型的函数来更新该行(如果已存在),或者插入一行(如果不存在)。

我可以使用MySQL的INSERT ... ON DUPLICATE KEY ...语句轻松地做到这一点。这需要一个唯一的约束,但是我们已经解决了。该语句的缺点是,如果我的target_table具有AUTO_INCREMENT列,则即使已经存在一行,也会通过auto_increment值“刻录”。

根据触发器/视图中的内容,我可以执行以下操作:

INSERT INTO target_table (group_id, name, street_address, ... )
SELECT o.group_id
       MAX(CASE WHEN o.element_label = 0 THEN o.element_value end) AS name,
       MAX(CASE WHEN o.element_label = 1 THEN o.element_value end) AS street_address,
       MAX(CASE WHEN o.element_label = 2 THEN o.element_value end) AS street_address_line_2,
       MAX(CASE WHEN o.element_label = 3 THEN o.element_value end) AS city,
       MAX(CASE WHEN o.element_label = 4 THEN o.element_value end) AS state,
       MAX(CASE WHEN o.element_label = 5 THEN o.element_value end) AS zip,
       MAX(CASE WHEN o.element_label = 6 THEN o.element_value end) AS country,
       MAX(CASE WHEN o.element_label = 7 THEN o.element_value end) AS dob
  FROM schema.original_table o
 WHERE o.group_id = NEW.group_id
 GROUP BY o.group_id
    ON DUPLICATE KEY
UPDATE name                  = VALUES(name)
     , street_address        = VALUES(street_address)
     , street_address_line_2 = VALUES(street_address_line2)
     , city                  = VALUES(city)
     , state                 = VALUES(state)
     , zip                   = VALUES(zip)
     , country               = VALUES(country)
     , dob                   = VALUES(dob)


请注意,当我尝试target_table(group_id)上的UNIQUE约束试图插入具有target_table中已经存在的group_id值的行时,将引发“重复键”异常。发生这种情况时,该语句将变成带有隐含WHERE group_id = VALUES(group_id)的UPDATE语句(无论唯一键冲突是否涉及任何列。)

只要不关心通过AUTO_INCREMENT值进行刻录,这就是最简单的方法。

我不仅限于INSERT ... ON DUPLICATE KEY语句,我可以“滚动自己的” UPPERT函数。但是...我想意识到可能的比赛条件...如果我先执行SELECT然后执行随后的INSERT操作,我将留一个小窗口供其他进程潜入...

我可以改用NOT EXISTS谓词来测试行的存在:

INSERT INTO target_table ( ...
SELECT ...
  FROM original_table o
 WHERE o.group_id = NEW.group_id
   AND NOT EXISTS (SELECT 1 FROM target_table d WHERE d.group_id = NEW.group_id)


然后,我将测试是否插入了一行(通过检查受影响的行数),如果没有插入行,则可以尝试进行更新。 (我依靠SELECT语句返回一行。)

为了获得更好的性能,我可以使用反联接模式进行相同的检查(是否存在现有行),但是对于一行,NOT EXISTS(子查询)很好,并且我认为它更容易理解。

INSERT INTO target_table ( ...
SELECT ...
  FROM original_table o
  LEFT
  JOIN target_table t
    ON t.group_id = NEW.group_id
 WHERE o.group_id = NEW.group_id
   AND t.group_id IS NULL


(来自原始表的SELECT可能需要包装为内联视图,因为它引用的是插入的同一张表。如果有问题,将该查询转换为派生表应该可以解决此问题。)



我说过“可以”从触发器的视图中使用该查询。但这不是我会选择使用的方法。这不是必需的。我真的不需要运行MAX(), MAX(), MAX()查询来获取每一列。

我将所有行的值都插入到original_table中,因此我已经知道要插入哪个element_label,并且target_table中实际上只需要更改一列。 (我想要MAX(element_value),还是我只想要刚刚插入的值?)

这是我将在触发器中使用的方法。我会完全避免对original_table进行查询,而只对target_table中的一列进行更新:

IF NEW.element_label = 0 THEN
   -- name
   INSERT INTO target_table (group_id,       `name`) 
   VALUES (NEW.group_id, NEW.element_value)
   ON DUPLICATE KEY UPDATE                   `name` = VALUES(`name`);
ELSEIF NEW.element_label = 1 THEN
   -- street_address
   INSERT INTO target_table (group_id,       `street_address`) 
   VALUES (NEW.group_id, NEW.element_value)
   ON DUPLICATE KEY UPDATE                   `street_address` = VALUES(`street_address`);
ELSEIF NEW.element_label = 2 THEN
   -- street_address2
   INSERT INTO target_table (group_id,       `street_address2`) 
   VALUES (NEW.group_id, NEW.element_value)
   ON DUPLICATE KEY UPDATE                   `street_address2` = VALUES(`street_address2`);
ELSEIF NEW.element_label = 3 THEN
   -- city
   INSERT INTO target_table (group_id,       `city`) 
   VALUES (NEW.group_id, NEW.element_value)
   ON DUPLICATE KEY UPDATE                   `city` = VALUES(`city`);
ELSEIF NEW.element_label = 4 THEN
   ...
END


我知道这不是很漂亮,但是我认为如果必须在将行插入原始表中时对target_table进行维护,这是最好的方法。 (问题实际上不是这里的数据库,问题在于EAV模型,或者实际上是EAV模型(每个属性值一行)和关系模型(每个属性每一行每一列)之间的“阻抗不匹配”属性值)。

这比MAX(),MAX(),MAX()查询更丑陋。

我还将放弃目标表中的AUTO_INCREMENT id,并仅将group_id(原始表中的值)用作我的target_table中的主键,因为我只希望每个group_id包含一行。



更新

当触发器主体包含分号时,必须将定界符从分号更改为其他内容。此处的文档:http://dev.mysql.com/doc/refman/5.5/en/trigger-syntax.html

例如

DELIMITER $$

CREATE TRIGGER trg_original_table_ai
AFTER INSERT ON original_table
FOR EACH ROW
BEGIN
   IF NEW.element_label = 0 THEN
      -- name
      INSERT INTO target_table (group_id,       `name`) 
      VALUES (NEW.group_id, NEW.element_value)
      ON DUPLICATE KEY UPDATE                   `name` = VALUES(`name`);
   ELSEIF NEW.element_label = 1 THEN
      -- street_address
      INSERT INTO target_table (group_id,       `street_address`) 
      VALUES (NEW.group_id, NEW.element_value)
      ON DUPLICATE KEY UPDATE                   `street_address` = VALUES(`street_address`);
   END IF;
END$$

DELIMITER ;

关于mysql - 在MySQL中使用触发器进行多行而不是单行数据转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22290287/

相关文章:

mysql - 没有显示输出值

PHP并行或串行进程执行?

javascript - 无法弄清楚为什么我在创建数据库表时出错

sql - 在一个查询中将薪水总和与每年的加薪相加 - SQL PostgreSQL

php - 带引号的 URL 导致错误

mysql - 需要获取 MySQL 结果

SQLPlus 试图删除包两次

mysql - mysql上的 bool 类型

php - 合并具有相同 pcode 的项目并计数为 1

mysql - 如何在一个查询中组合六个独立的查询。 TABLE IS SINGLE 但条件不同