7

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

示例数据库:

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

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

它可能看起来像:

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

但这仅适用于这个简单的示例。想象一下,如果我有 50 个班级,每个班级 25 名学生,每个学生/班级组合有几百个成绩。LAST_INSERT_ID()如果不将其存储在一系列变量中,您可以看到它可能无法工作。

进行这种操作的合适工具是什么?能做mysqldump这么聪明的事吗?

4

7 回答 7

4

你可以这样做:

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

    SELECT MAX(school_id) INTO @max_school_id FROM school;

  • 更改school_id源表 ( school, ) 中的所有值 -从上一点class添加 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_idschool

class_id查找目标class表的最大值

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

增加所有 SOURCEclass_idclass_id + 1012

UPDATE class SET class_id = class_id + 1012;

school_id查找目标school表的最大值

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

增加所有 SOURCEschool_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');

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

于 2012-04-20T07:53:05.483 回答
2

您需要在 SQL 中执行此操作吗?即使是最基本的 ETL 工具也会更适合。改用 pentaho 或 talend。

于 2012-04-22T16:09:18.733 回答
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 动态找出需要调整的表和列。

于 2012-04-29T12:02:17.050 回答
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();
    }
    */
}
于 2012-04-22T17:12:08.687 回答
0

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

将数据复制到新数据库的表中,没有 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;

如果源中学校的目标数据库中没有该课程,您想要什么语义?这是一个联合,如果你想删除,你必须改变它。

于 2012-04-29T14:09:28.977 回答
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

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

于 2012-04-28T14:41:46.013 回答
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');
?>

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

于 2012-04-29T14:10:46.900 回答