迁移行以导入到现有数据库中,负责更改ID等

时间:2012-04-19 21:55:50

标签: mysql mysqldump data-migration

这样做的目的是将一些行从一个环境复制到另一个环境而不覆盖现有行。

示例DB:

INSERT INTO `school` (school_id,name) VALUES (15,'Middle');
INSERT INTO `class` (class_id,school_id,name) VALUES (12,15,'Sample');

这个想法是school_idclass_id是自动递增,而class的外键链接回school。但是我想转储只是这些行并将它们插入另一个已经有school_id为15的数据库。

它可能看起来像:

INSERT INTO `school` (name) VALUES ('Middle');
INSERT INTO `class` (school_id,name) VALUES (LAST_INSERT_ID(),'Sample');

但那只是为了这个简单的例子。想象一下,如果我有50个班级,每个班级有25名学生,每个学生/班级组合有几百个等级。如果不将LAST_INSERT_ID()存储在一系列变量中,您可以看到mysqldump可能无法正常工作。

进行此类操作的适当工具是什么? {{1}}可以做任何聪明的事吗?

7 个答案:

答案 0 :(得分:4)

你可以这样做:

  • 在目标school_id表中找到MAX school -

    SELECT MAX(school_id) INTO @max_school_id FROM school;

  • 更改源表格中的所有school_id值(schoolclass) - 从前一点添加MAX school_id -

    UPDATE school SET school_id = school_id + @max_school_id + 1;

ON UPDATE CASCADE操作添加到外键可能非常有用,它将有助于自动更改子表中的school_id,例如 -

ALTER TABLE class
  DROP FOREIGN KEY FK_name;
ALTER TABLE class
  ADD CONSTRAINT FK_name FOREIGN KEY (school_id)
    REFERENCES school(school_id) ON UPDATE CASCADE;
  • 进行转储和导入。

解释和示例:

创建源表:

CREATE TABLE school(
  school_id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20)
);

INSERT INTO school (school_id, name) VALUES
  (1, 'Middle1'),
  (2, 'Middle2'),
  (3, 'Middle3'),
  (15, 'Middle');

CREATE TABLE class(
  class_id INT(11) NOT NULL,
  school_id INT(11) DEFAULT NULL,
  name VARCHAR(20) DEFAULT NULL,
  PRIMARY KEY (class_id),
  CONSTRAINT FK_class_school_school_id FOREIGN KEY (school_id)
  REFERENCES school (school_id) ON DELETE RESTRICT ON UPDATE CASCADE
)
ENGINE = INNODB;

INSERT INTO class (class_id, school_id, name) VALUES (11, 1, 'Sample1');
INSERT INTO class (class_id, school_id, name) VALUES (12, 15, 'Sample');

创建目标表:

CREATE TABLE school(
  school_id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20)
);

INSERT INTO school (school_id, name) VALUES
  (1, 'Top'),
  (2, 'Middle'),
  (3, 'Bottom'),
  (15, 'Top');

CREATE TABLE class(
  class_id INT(11) NOT NULL,
  school_id INT(11) DEFAULT NULL,
  name VARCHAR(20) DEFAULT NULL,
  PRIMARY KEY (class_id),
  CONSTRAINT FK_class_school_school_id FOREIGN KEY (school_id)
  REFERENCES school (school_id) ON DELETE RESTRICT ON UPDATE CASCADE
)
ENGINE = INNODB;

INSERT INTO class (class_id, school_id, name) VALUES (10, 2, 'Sample2');
INSERT INTO class (class_id, school_id, name) VALUES (12, 15, 'Sample');

更新源表,增加id值: 我们应该更新所有唯一值,在我们的例子中,我们必须更新class_id表中的classschool_id表中的school

查找TARGET class_id表格的最大class

SELECT MAX(class_id) + 1000 FROM class; -- This will return => 1012

增加所有来源class_idclass_id + 1012

UPDATE class SET class_id = class_id + 1012;

查找TARGET school_id表格的最大school

SELECT max(school_id) + 1000 FROM school; -- This will return =>1015

增加所有来源school_idschool_id + 1015

UPDATE school SET school_id = school_id + 1015;

就是这样。我们可以转储源表:

INSERT INTO school VALUES
  (1016, 'Middle1'),
  (1017, 'Middle2'),
  (1018, 'Middle3'),
  (1030, 'Middle');

INSERT INTO class VALUES
  (1023, 1016, 'Sample1'),
  (1024, 1030, 'Sample');

现在我们可以轻松地针对目标数据库运行此脚本。

答案 1 :(得分:2)

你需要在SQL中这样做吗?即使是最基本的ETL工具也会更适合。试试pentaho或talend。

答案 2 :(得分:0)

如果您不需要纯SQL解决方案,则可以非常轻松地创建从旧数据库读取并写入新数据库的脚本。我在想PHP,Python,Ruby,Perl,......

这是PHP中的一个简单解决方案,假设您正在使用mysql并在数据库之间移动数据(未经测试,可能包含错误):

$dbh1 = new PDO("mysql:host=db1.host;dbname=db1", "user1", "pass1");
$dbh2 = new PDO("mysql:host=db2.host;dbname=db2", "user2", "pass2");

$sth1 = $dbh1->query("
    SELECT
        school.school_id as school_id,
        school.name as school_name, 
        class.name as class_name
    FROM school
    JOIN class ON (school.school_id = class.school_id)
");

$sth3 = $dbh2->prepare("INSERT INTO school (name) VALUES (:name)");
$sth4 = $dbh2->prepare("INSERT INTO class (school_id, name) VALUES (:school_id, :name)");

$schools = array();

// get schools and classes
while ($school = $sth1->fetch(PDO::FETCH_ASSOC)) {
    $school_id = $school['school_id'];
    $school_name = $school['school_name'];

    $schools[$school_id]['school_name'] = $school_name;

    $schools[$school_id]['classes'][] = array(
        'class_name' => $school['class_name']
    );
}

// insert schools and classes
foreach ($schools as $school_id => $school) {
    // insert school
    $sth3->bindParam(':name', $school['school_name'], PDO::PARAM_INT);
    $sth3->execute();

    $new_school_id = $dbh2->lastInsertId();

    // a loop for classes
    foreach ($school['classes'] as $class) {
        // insert class
        $sth4->bindParam(':school_id', $new_school_id, PDO::PARAM_INT);
        $sth4->bindParam(':name', $class['class_name'], PDO::PARAM_STR);

        $sth4->execute();
    }

    // a loop for another joined table
    /*
    foreach ($school['joined'] as $join) {
        // insert join
        $sth4->bindParam(':school_id', $new_school_id, PDO::PARAM_INT);
        $sth4->bindParam(':name', $join['join_name'], PDO::PARAM_STR);

        $sth4->execute();
    }
    */
}

答案 3 :(得分:0)

如果您拥有临时表权限,则可以执行以下操作:

CREATE TEMPORARY TABLE tmp_school LIKE school;
LOAD DATA LOCAL INFILE 'school.dat' INTO TABLE tmp_school;
CREATE TEMPORARY TABLE tmp_class LIKE class;
LOAD DATA LOCAL INFILE 'class.dat' INTO TABLE tmp_class;
INSERT INTO school (name) SELECT name FROM tmp_school;
INSERT INTO class (school_id,name) 
  SELECT school.school_id, class.name FROM school school JOIN tmp_school new
USING(name) JOIN tmp_class class ON new.school_id = class.school_id

我认为这是对的,但需要进行一些检查。

答案 4 :(得分:0)

一个非常简单的技巧就是将id与-1相乘。负id与任何id一样好,我假设你的auto_increment列以正数开头。

从一个环境导出:

select -1*school_id, name from school into outfile 'school.out';
select -1*class_id, -1*school_id, name from class into outfile 'class.out';

导入第二个:

load data infile 'school.out' into table school;
load data infile 'class.out' into table class;

显然,这不是您问题的通用解决方案,但您需要多久一次? :)

通用解决方案是使用自己编写迁移逻辑,无论是在ETL工具中还是作为独立脚本,正如其他人所说的那样。并使该工具/脚本使用INFORMATION_SCHEMA.TABLES和INFORMATION_SCHEMA.COLUMNS来动态查找需要调整的表和列。

答案 5 :(得分:0)

我认为最好的方法是从传输中消除ID。 假设学校名称是唯一的,这似乎适用于deviant的模式,为简单起见,将两个数据库放在同一台服务器上:

将数据复制到新数据库中没有id的表中,只需要名称:

CREATE TEMPORARY TABLE tmp AS SELECT s.name as school_name, c.name as class_name
FROM test.school s JOIN test.class c USING(school_id);

将新学校添加到学校表:

INSERT INTO school (name) SELECT DISTINCT school_name FROM tmp
LEFT JOIN school ON school_name = name WHERE name IS NULL;

将新课程(适用于现有和新学校)添加到课堂表中:

INSERT INTO class (name, school_id) SELECT class_name, school_id
FROM tmp t JOIN school s ON s.name = t.school_name;

如果源数据库中没有该类的学校的目标数据库中有类,您想要什么样的语义?这是一个联盟,如果你想删除,你将不得不改变它。

答案 6 :(得分:0)

如果你在php中使用这个命令,那么有一个简单的函数可以为你提供插入查询的最后一个id。即mysql_insert_id()。

代码可能是这样的:

<?php
    $query = mysql_query("INSERT INTO `school` (school_id,name) VALUES (15,'Middle')");
    $last_id = mysql_insert_id();
    INSERT INTO `class` (class_id,school_id,name) VALUES ('$last_id','Sample');
?>

如果您使用其他语言,我不知道该怎么做。