如何用Ruby迁移Oracle数据

分享

oracle1

数据迁移是任何web开发项目的顶峰。这感觉就像爬到山顶,只是为了回头看看,然后跳下去。在一个典型的项目中,您可以根据新模式测试所有内容。把谨慎抛到脑后,以每小时100英里的速度做出彻底的改变。但是,仍在生产中的遗留数据呢?

在本文中,我将深入探讨如何在Oracle中成功完成数据迁移。现代web开发人员有许多工具可供使用。在这里,我将解释为什么在Ruby中使用数据迁移脚本是一种乐趣。我将处理Oracle而不是Rails中的迁移。

在我的示例中,我将使用OCI8,这是一个用于Ruby的Oracle适配器。它允许您编写Ruby代码与Oracle数据库服务器通信。

首先,数据迁移涉及数据库规范化。这维基百科的文章解释道:

数据库规范化是组织关系数据库的字段和表以最小化冗余的过程。标准化通常包括将大表划分为更小(冗余度更低)的表,并定义它们之间的关系。目标是隔离数据,以便仅在一个表中对字段进行添加、删除和修改,然后使用定义的关系在数据库的其余部分进行传播。

我刚刚完成了一个项目,其中我的遗留数据库充满了重复的数据。这导致了数据质量问题和一些奇怪的代码。一个臃肿而丑陋的数据库保证了代码充满了错误和奇怪的hack。简而言之,我的项目急需一个美容院。

设置

让我们定义两个表:

CREATE TABLE answer (answer_id NUMBER PRIMARY KEY, question_id NUMBER, answer_sequence NUMBER, answer_type VARCHAR2(7), answer_text VARCHAR2(31), updt_date DATE);创建表问题(question_id NUMBER主键,question_text VARCHAR2(31),位置数字,updt_date DATE);

看这个例子,我们正在处理你的基本问卷。每个答案都属于一个问题,答案有特定的类型。根据问题的不同,答案可以是单选框或复选框。

这就是我们的数据。

第一个问题:

INSERT INTO question (question_id,——为什么不只是id?)question_text,——question得到隐含位置,——很好,我喜欢!Updt_date——创建日期在哪里?)VALUES (1, 'A question', 1, sysdate);INSERT INTO answer (answer_id, question_id, answer_sequence,——如何放置?answer, answer_text, updt_date) VALUES (1,1,1, 'MLT',——duplicate 'First answer', sysdate);INSERT INTO answer (answer_id, question_id, answer_sequence, answer_type, answer_text, updt_date) VALUES (2,1,2, 'MLT',——duplicate 'Second answer', sysdate);INSERT INTO answer (answer_id, question_id, answer_sequence, answer_type, answer_text, updt_date) VALUES (3,1,3, 'MULTI',——what?这太糟糕了!'第三个答案',sysdate);

第二个问题:

INSERT INTO question (question_id, question_text, location, updt_date) VALUES(2, '另一个问题',2,sysdate);INSERT INTO answer (answer_id, question_id, answer_sequence, answer_type, answer_text, updt_date) VALUES (4,2,1, 'CHK',——duplicate 'First answer', sysdate);INSERT INTO answer (answer_id, question_id, answer_sequence, answer_type, answer_text, updt_date) VALUES (5,2,2, 'CHK',——duplicate 'Second answer', sysdate);

让我们看看数据:

SQL> SELECT answer, question_id, answer_sequence, answer_type, updt_date FROM answer;ANSWER_ID QUESTION_ID ANSWER_SEQUENCE ANSWER_ UPDT_DATE ---------- ----------- --------------- ------- --------- 1 1 1 MLT 23-JUN-14 2 1 2 MLT 23-JUN-14 3 1 3 MULTI 23-JUN-14 4 2 1 CHK 23-JUN-14 5 2 2 CHK 23-JUN-14 SQL> SELECT QUESTION_ID, placement, UPDT_DATE FROM question;QUESTION_ID UPDT_DATE位置  ----------- ---------- --------- 1 1 23-JUN-14 2 2 23-JUN-14

讨厌的东西!我有数据重复、不一致且缺乏想象力的列名和半生不熟的时间戳。直接的后果是,我的数据现在出现了严重的数据质量问题。哦男孩。

我们可以做得更好:

CREATE TABLE answers (id NUMBER CONSTRAINT answers_pk PRIMARY KEY, question_id NUMBER, placement NUMBER, text VARCHAR2(31), created_at DATE, updated_at DATE);创建表问题(id NUMBER约束questions_pk PRIMARY KEY, type_id NUMBER,位置NUMBER,文本VARCHAR2(31), created_at DATE, updated_at DATE);CREATE TABLE question_types (id NUMBER CONSTRAINT question_types_pk PRIMARY KEY, name VARCHAR(7), created_at DATE, updated_at DATE);

我的数据模型在很大程度上受到Rails的影响。答案类型被完全从答案表中剥离出来,放在问题中。我没有在表中放置重复的问题类型名称,而是将它放在一个单独的表中,并添加了一个关系。列名简单直观。

陷阱

想必您已经针对新模式编写了代码,并通过了测试。现在让我们把注意力转向生产数据。

请务必安装gem:

Gem安装ruby-oci8

让我们从这个简单的迁移脚本开始:

require 'oci8' src = oci8 .new('username/password@schema') tgt = oci8 .new('username/password@schema') tgt. new('username/password@schema')自动提交= false src。执行("SELECT question_id, ( SELECT answer.answer_type FROM answer WHERE answer.question_id = question.question_id AND ROWNUM = 1 ) question_type, question_text, placement, updt_date FROM question") do |qr| src.exec("SELECT answer_id, answer_sequence, answer_text, updt_date FROM answer WHERE question_id = :1", qr[0].to_i) do |ar| puts ar end end src.logoff tgt.logoff

OCI8的错误消息并不直观,因此我建议采用增量步骤。这里,我在查询src数据来确保一切正常。注意我如何将参数传递给执行方法使用基本的Oracle。我将类型转换为Ruby类型,以确保在OCI8中没有任何误解。的tgt。自动提交= false让我测试我的迁移代码,直到我准备提交更改。的.logoff调用优雅地终止到Oracle的连接。由于我附加答案类型的问题,我必须子查询它。

更多的例子

有了这个坚实的基础,让我们开始填充问题表格假设我们在qr回调函数:

QUESTION_TYPE_IDS = {"CHK" => 1, "MLT" => 2} tgt。exec(“选择questions_seq。nextval FROM dual")做|qid| tgt。执行("INSERT INTO questions ( id, type_id, placement, text, created_at, updated_at ) VALUES ( :1, :2, :3, :4, :5, :6 )", qid[0].to_i, QUESTION_TYPE_IDS[qr[1]].to_i, qr[3].to_i, qr[2].to_s[0..30], qr[4].to_date, qr[4].to_date) end end

我使用了一个简单的哈希QUESTION_TYPE_IDS将问题类型转换为id。Oracle对主键强制执行序列,这迫使我将插入的内部代码qid回调。在我的散列中,我进行了类型转换以确保没有值慢慢进入insert。如果没有匹配的散列,OCI8将抛出令人讨厌和无法理解的错误。

对于问题文本,使用VARCHAR2Types有一个我们不希望超过的最大长度。要做到这一点,只需将其转换为字符串并执行[0 . . 30]截断该值。即使字符串比限制短,也能正常工作。

最后,让我们把注意力集中到答案表格这个代码块被放入基于“增大化现实”技术回调。

tgt。执行("SELECT answers_seq.nextval FROM dual") do |aid| tgt.exec("INSERT INTO answers ( id, question_id, placement, text, created_at, updated_at ) VALUES ( :1, :2, :3, :4, :5, :6 )", aid[0].to_i, qid[0].to_i, ar[1].to_i, ar[2].to_s[0..30], ar[3].to_date, ar[3].to_date) end

这就是我们在问题迁移。这里,我们在映射question_idqid从问题块。这样,我的数据迁移尊重对象之间的关系。

有了这一切,按下开关,看魔术展开。

tgt。自动提交= true

现在是最困难的动作:坐好,把手放在脑后,让它发挥作用。对于我的特定项目,这个迁移花费了15分钟。感觉就像我坠入了深渊的深处,带着难以言喻的焦虑,时间不再存在。但是,它成功地完成了,我安全降落在另一边。

结束

该检查期末考试了答案而且问题表格,看看一切如何。我将省略VARCHAR2字段的简单性。我使用sqlplus在Oracle中:

SQL> SELECT id, question_id, placement, created_at, updated_at FROM answers;ID QUESTION_ID PLACEMENT CREATED_A UPDATED_A ---------- ----------- ---------- --------- --------- 1 1 1 10-JUN-14 10-JUN-14 2 1 2 10-JUN-14 10-JUN-14 10-JUN-14 4 2 1 10-JUN-14 10-JUN-14 5 2 2 10-JUN-14 10-JUN-14 10-JUN-14 SQL> SELECT ID, type_id, PLACEMENT, created_at, updated_at FROM questions;ID TYPE_ID放置CREATED_A UPDATED_A  ---------- ---------- ---------- --------- --------- 1 2 1 10-JUN-14 10-JUN-14 2 1 2 10-JUN-14 10-JUN-14

美丽。

如果感兴趣,您可以从GitHub。

黑客快乐!

Baidu