【问题标题】:Sqlite: compare two databases and update older - LinuxSqlite:比较两个数据库并更新旧版本 - Linux
【发布时间】:2014-09-03 03:29:00
【问题描述】:

我有两个数据库,比如说 A_old.sqliteA_new.sqlite
两者都有很多表,但新数据库有一个(或多个)表,字段数不相等。

例如:

  • A_old.sqlite 有表 Person 字段名称和姓氏。
  • A_new.sqlite 有表 Person 字段名称、姓氏和地址。

我可以从 shell 转储每个数据库并查看差异在哪里:

   echo .dump | sqlite3 A_old.sqlite > A_old.sqlite.dump
   echo .dump | sqlite3 A_new.sqlite > A_new.sqlite.dump
   diff A_old.sqlite A_new.sqlite

问题是:如何在不手动解析 diff 输出的情况下更新 OLD db 中的模式? (在这种情况下,我需要将字段“地址”添加到表 Person

【问题讨论】:

  • 我不确定我是否理解您的问题。您想在不看差异的情况下说出两件事之间的区别吗?您期待什么样的解决方案?这怎么能行?我不是在讽刺——只是希望你的解释有助于理解这个问题。
  • 我的意思是“手动”解析差异输出。我尝试使用“补丁”程序,但我不确定我需要什么。
  • 所以您想自动化/编写更新过程 - 是吗?那么这个脚本只会告诉你缺少的字段名称,还是更新每条记录中的所有字段?它必须检测到哪里还有其他字段并删除它们吗?它也必须检测到丢失的表吗?
  • 基本上我只需要更新架构。因此,如果有新表,请添加它。如果表中有新字段,请更新它。如果已删除表,请将其删除。等等。 Diff 肯定是一个起点,但我想避免编写解析器并启动 sql 命令..
  • 搜索database version control 喜欢这里techportal.inviqa.com/2011/01/11/database-version-control

标签: linux bash shell sqlite


【解决方案1】:

你可以在 Perl 中做这样的事情:

#!/usr/bin/perl
use strict;
use warnings;
use DBI;

my $olddbh = DBI->connect('dbi:SQLite:old.db');
my $newdbh = DBI->connect('dbi:SQLite:new.db');

my %oldtables = $olddbh->tables();
my %newtables = $newdbh->tables();
my @oldtablenames;
my @newtablenames;
my $tmp;

print "Tables in new database\n";
print "======================\n";
foreach (%newtables){
   $tmp=$_;
   $tmp =~ s/.*"."//;
   $tmp =~ s/".*//;
   next if /sqlite_/;
   print $tmp,"\n";
   push(@newtablenames,$tmp);
}
print  "\n";

print "Tables in old database\n";
print "======================\n";
foreach (%oldtables){
   $tmp=$_;
   $tmp =~ s/.*"."//;
   $tmp =~ s/".*//;
   next if /sqlite_/;
   print $tmp,"\n";
   push(@oldtablenames,$tmp);
}
print  "\n";

# Check no tables missing from old
foreach (keys %newtables){
   printf "Table: %s is missing in old database\n",$_ if ! exists $oldtables{$_};
}

# Check no tables in old but not in new
foreach (keys %oldtables){
   printf "Table: %s is superfluous in old database\n",$_ if ! exists $newtables{$_};
}

# Work out tablenames common to new and old
my @common;
foreach my $table (@newtablenames){
   foreach my $oldtable (@oldtablenames){
      if($oldtable eq $table){
        push(@common,$table);
        last;
      }
   }
}

print  "\n";
# For all tables, check fields match
foreach my $table (@common){
   my $i;
   printf "Checking fields in common table: %s\n",$table;

   my @newfields;
   my $sth = $newdbh->prepare("SELECT * FROM $table LIMIT 1");
   $sth->execute();
   my $nnfields=$sth->{NUM_OF_FIELDS};
   for ($i = 0 ; $i < $nnfields ; $i++ ) {
        push(@newfields,$sth->{NAME}->[$i]);
   }

   my @oldfields;
   $sth = $olddbh->prepare("SELECT * FROM $table LIMIT 1");
   $sth->execute();
   my $nofields=$sth->{NUM_OF_FIELDS};
   for ($i = 0 ; $i < $nofields ; $i++ ) {
        push(@oldfields,$sth->{NAME}->[$i]);
   }
   if($nnfields != $nofields){
      printf "Number of fields differs: %d vs %d\n",$nnfields,$nofields;
   }

}

这将给出如下输出:

Tables in new database
======================
AdditionalTable
Person

Tables in old database
======================
OldTable
Person

Table: "main"."AdditionalTable" is missing in old database
Table: "main"."OldTable" is superfluous in old database

Checking fields in common table: Person
Number of fields differs: 3 vs 2

【讨论】:

  • 感谢您的支持!脚本或手工程序是我想避免的,但如果我找不到其他任何东西,我会考虑这个脚本!
【解决方案2】:

这是一个棘手的问题,我怀疑你会找到解决它的简单方法。

一方面,.dump 命令将发出sqlite_master 表的sql 列。此值是用于创建表的 SQL。这里的问题是列顺序可能不同但相同。

vagrant@precise32:~/.ash$ sqlite3 new.db <<< "create table foo(a int, b int);"
vagrant@precise32:~/.ash$ sqlite3 old.db <<< "create table foo(b int, a int);"
vagrant@precise32:~/.ash$ sqlite3 new.db <<< .dump | grep foo
CREATE TABLE foo(a int, b int);
vagrant@precise32:~/.ash$ sqlite3 old.db <<< .dump | grep foo
CREATE TABLE foo(b int, a int);

如果您忽略这个问题,您仍然需要记住,发出的值只是用于创建表的 SQL。无论您是否希望能够以编程方式将列添加到“旧”模式中的表中,都必须对其进行解析。

一种行不通的方法是使用新架构创建一个数据库,然后使用旧数据库的.dump 中的INSERT 语句来填充新数据库.

sqlite3 new.db <<< .dump | grep -v "^INSERT" | sqlite3 temp.db
sqlite3 old.db <<< .dump | grep "^INSERT" | sqlite3 temp.db

这会失败,因为 .dump 返回的 INSERT 语句是位置性的。如果您能以某种方式在表名之后的括号中插入列名,那么它可能会起作用。为此,您需要解析列名并创建一个以逗号分隔的列表。然后每个插入行都需要在表名之后包含该列表。这似乎是 awk 的工作,但在快速尝试了几次之后,我发现这不是微不足道的

如果您想手动获取列名列表并将其放入变量中,您可以近似于这样的解决方案:

sqlite3 new.db <<< .dump | grep -v "^INSERT" | sqlite3 temp.db
OLD_NAMES_CSV="b,a"
sqlite3 old.db <<< .dump \
  | grep "^INSERT" \
  | sed -e "s: VALUES: ($OLD_NAMES_CSV) VALUES:" \
  | sqlite3 temp.db

在此之后,temp.db 应该有来自old.db 的数据,但使用来自new.db 的架构。

如果您在新架构中重命名列或删除它们,这将不起作用,但听起来这可能是您的一个可能的解决方案。

对于比这更难的事情,我建议用 python 编写一些东西。

【讨论】:

  • +1 表示“列顺序”。我只需要更新模式,所以我不需要做任何“插入......值......;”我需要做“alter table A add newColumn columnType”或“create table newTable”。删除/删除也是如此。
  • 这种方法应该可行,尽管它会将您的数据复制到temp.db。无论如何,如果出现问题,这可能是一个更好的解决方案 - 您的旧数据库应该保持不变。只需确保 new.db 具有您想要的架构。
猜你喜欢
  • 2011-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-22
  • 1970-01-01
  • 1970-01-01
  • 2022-11-11
相关资源
最近更新 更多