簡體   English   中英

在MySQL中使用觸發器進行多行而不是單行數據轉換

[英]Multi-row, instead of single row data transformation with trigger in MYSQL

我有這個查詢:

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形式,因此我用它們所代表的值替換了這些值,所以我只提取了最新插入的數據

我將范圍縮小到應用程序分別插入每個字段的范圍,這將導致觸發器和FOR EACH ROW語法使其逐行地起作用。 在MySQL中,此語法是必需的,它僅允許基於行的觸發器,而不能像在Oracle和其他一些DB語言中那樣基於“查詢”的觸發器。

我在這里針對解決方法提出了一個單獨的問題: MySQL中FOR EACH ROW的解決方法

這看起來像是一個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值的行時,我指望target_table(group_id)上的UNIQUE約束拋出“重復鍵”異常。 發生這種情況時,該語句將變成一個隱含WHERE group_id = VALUES(group_id)的UPDATE語句(無論唯一鍵沖突是否涉及任何列。)

只要不關心通過AUTO_INCREMENT值進行刻錄,這就是最簡單的方法。

我不僅限於INSERT ... ON DUPLICATE KEY語句,還可以“滾動自己的” UPSERT函數。 但是...我想知道可能的比賽條件...如果我先執行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 ;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM