Sqlite:比较两个数据库并更新旧版本 - Linux

时间:2014-09-02 13:59:56

标签: linux bash shell sqlite

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

例如:

  • A_ .sqlite具有表 Person ,其中包含字段名称和姓氏。
  • A_ .sqlite有表,其中包含字段名称,姓氏和地址。

从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数据库中的模式? (在这种情况下,我需要将字段“地址”添加到表 Person

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

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

首先,.dump命令将发出sql表的sqlite_master列。此值是用于创建表的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。无论您是否希望能够以编程方式将列添加到&#34; old&#34;中的表中,您都必须解析它。架构。

一种无法工作的方法是使用新架构创建 new 数据库,然后使用旧版INSERT中的.dump语句用于填充新数据库的数据库。

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

这会失败,因为INSERT返回的.dump语句是位置的。如果您可以以某种方式在表名后面的括号中插入列名,则可能可以工作。要执行此操作,您需要解析列名称并创建以逗号分隔的列表。然后每个插入行都需要在表名后包含该列表。这似乎是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写一些东西。