Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sqlite/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
ALTER TABLE ADD COLUMN(如果SQLite中不存在)_Sqlite_Alter Table - Fatal编程技术网

ALTER TABLE ADD COLUMN(如果SQLite中不存在)

ALTER TABLE ADD COLUMN(如果SQLite中不存在),sqlite,alter-table,Sqlite,Alter Table,我们最近需要向一些现有的SQLite数据库表中添加列。这可以通过以下方式完成。当然,如果这个表已经被修改过,我们就不去管它了。不幸的是,SQLite不支持ALTER TABLE上的IF NOT EXISTS子句 我们当前的解决方法是执行ALTERTABLE语句并忽略任何“重复列名”错误,就像(但在C++中) 但是,我们设置数据库模式的常用方法是使用.sql脚本,其中包含CREATE TABLE IF NOT EXISTS和CREATE INDEX IF NOT EXISTS语句,这些语句可以使用

我们最近需要向一些现有的SQLite数据库表中添加列。这可以通过以下方式完成。当然,如果这个表已经被修改过,我们就不去管它了。不幸的是,SQLite不支持
ALTER TABLE
上的
IF NOT EXISTS
子句

我们当前的解决方法是执行ALTERTABLE语句并忽略任何“重复列名”错误,就像(但在C++中)

但是,我们设置数据库模式的常用方法是使用.sql脚本,其中包含
CREATE TABLE IF NOT EXISTS
CREATE INDEX IF NOT EXISTS
语句,这些语句可以使用
sqlite3\u exec
sqlite3
命令行工具执行。我们不能将
altertable
放在这些脚本文件中,因为如果该语句失败,之后的任何语句都不会执行


我希望将表定义放在一个位置,而不是在.sql和.cpp文件之间拆分。如果纯SQLite SQL中不存在
ALTER TABLE ADD COLUMN,是否有一种方法可以编写一个变通方法来解决这个问题?

我有一个99%的纯SQL方法。其思想是对您的模式进行版本化。您可以通过两种方式执行此操作:

  • 使用“user_version”pragma命令()存储数据库架构版本的增量编号

  • 将您的版本号存储在您自己定义的表中


这样,当软件启动时,它可以检查数据库模式,如果需要,可以运行
altertable
查询,然后增加存储的版本。这比尝试各种“盲”更新要好得多,尤其是在数据库多年来增长和更改了几次的情况下。

SQLite还支持名为“table_info”的pragma语句,该语句在表中每列返回一行,其中包含列的名称(以及有关列的其他信息)。您可以在查询中使用它来检查缺少的列,如果不存在,则更改表

PRAGMA table_info(foo_table_name)
样本输出:

cid 名称 类型 notnull dflt_值 主键 0 身份证件 整数 0 无效的 1. 1. 类型 文本 0 无效的 0 2. 数据 json 0 无效的 0
如果您在flex/AdobeAIR中遇到此问题,并首先在此处找到自己,我找到了一个解决方案,并将其发布在一个相关问题上:


我在这里的评论是:

一种解决方法是只创建列,并捕获列已经存在时出现的异常/错误。添加多个列时,将它们添加到单独的ALTER TABLE语句中,这样一个重复项就不会阻止创建其他列

有了,我们做了类似的事情。它并不完美,因为我们无法区分重复的sqlite错误和其他sqlite错误

Dictionary<string, string> columnNameToAddColumnSql = new Dictionary<string, string>
{
    {
        "Column1",
        "ALTER TABLE MyTable ADD COLUMN Column1 INTEGER"
    },
    {
        "Column2",
        "ALTER TABLE MyTable ADD COLUMN Column2 TEXT"
    }
};

foreach (var pair in columnNameToAddColumnSql)
{
    string columnName = pair.Key;
    string sql = pair.Value;

    try
    {
        this.DB.ExecuteNonQuery(sql);
    }
    catch (System.Data.SQLite.SQLiteException e)
    {
        _log.Warn(e, string.Format("Failed to create column [{0}]. Most likely it already exists, which is fine.", columnName));
    }
}
Dictionary columnname to addcolumnsql=新字典
{
{
“第1栏”,
“ALTER TABLE MyTable添加列Column1 INTEGER”
},
{
“第2栏”,
“ALTER TABLE MyTable添加列Column2文本”
}
};
foreach(columnNameToAddColumnSql中的变量对)
{
string columnName=pair.Key;
字符串sql=pair.Value;
尝试
{
this.DB.ExecuteNonQuery(sql);
}
catch(System.Data.SQLite.SQLite异常)
{
_log.Warn(e,string.Format(“未能创建列[{0}]。很可能它已经存在,这很好。”,columnName));
}
}

是一种PRAGMA is table_info(table_name)的方法,它返回table的所有信息

下面是如何将其用于检查列是否存在的实现

    public boolean isColumnExists (String table, String column) {
         boolean isExists = false
         Cursor cursor;
         try {           
            cursor = db.rawQuery("PRAGMA table_info("+ table +")", null);
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    String name = cursor.getString(cursor.getColumnIndex("name"));
                    if (column.equalsIgnoreCase(name)) {
                        isExists = true;
                        break;
                    }
                }
            }

         } finally {
            if (cursor != null && !cursor.isClose()) 
               cursor.close();
         }
         return isExists;
    }
也可以使用此查询而不使用循环

cursor = db.rawQuery("PRAGMA table_info("+ table +") where name = " + column, null);

如果在DB upgrade语句中执行此操作,最简单的方法可能是捕获在尝试添加可能已经存在的字段时引发的异常

try {
   db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN foo TEXT default null");
} catch (SQLiteException ex) {
   Log.w(TAG, "Altering " + TABLE_NAME + ": " + ex.getMessage());
}

我在C#/.Net中获得了上面的答案,并为Qt/C++重新编写了它,没有太大的改变,但我想把它留在这里,留给将来寻找C++答案的任何人

    bool MainWindow::isColumnExisting(QString &table, QString &columnName){

    QSqlQuery q;

    try {
        if(q.exec("PRAGMA table_info("+ table +")"))
            while (q.next()) {
                QString name = q.value("name").toString();     
                if (columnName.toLower() == name.toLower())
                    return true;
            }

    } catch(exception){
        return false;
    }
    return false;
}

您也可以将CASE-WHEN TSQL语句与pragma_table_info结合使用,以了解列是否存在:

select case(CNT) 
    WHEN 0 then printf('not found')
    WHEN 1 then printf('found')
    END
FROM (SELECT COUNT(*) AS CNT FROM pragma_table_info('myTableName') WHERE name='columnToCheck') 

这是我的解决方案,但在python中(我尝试过,但没有找到任何关于python主题的帖子):

#修改旧版本的表,该旧版本没有rings3表中的休假类型和休假时间列。
sql='PRAGMA table_info(rings3)'get table info。返回列的数组。
结果=查询(sql)#调用自制函数执行查询

如果len(result)用于那些希望将
pragma table_info()
的结果作为更大SQL的一部分的人

select count(*) from
pragma_table_info('<table_name>')
where name='<column_name>';

从中选择计数(*)
pragma_表_信息(“”)
其中name='';
关键部分是使用
pragma\u table\u info(“”)
而不是
pragma table\u info(“”)



这个答案的灵感来源于@Robert Hawkey的回答。我将其作为新答案发布的原因是我没有足够的声誉将其作为评论发布。

如果一次执行一行,所有这些答案都可以。然而,最初的问题是输入一个sql脚本,该脚本将由一个db execute和所有解决方案执行(比如检查列是否提前出现)需要执行程序了解正在更改/添加的表和列,或者对输入脚本进行预处理和解析,以确定此信息。通常情况下,您不会实时或经常运行它。因此捕获异常的想法是可以接受的,然后继续。这就是问题所在……如何继续前进。幸运的是,错误消息为我们提供了执行此操作所需的所有信息。我们的想法是,如果在alter table调用中出现异常,则执行sql。我们可以在sql中找到alter table行,并返回剩余的行,然后执行,直到它成功或找不到更多匹配的alter table行为止。下面是一些示例代码,其中数组中包含sql脚本。我们迭代执行每个脚本的数组。我们调用它两次以使ALTERTABLE命令失败,但是
select count(*) from
pragma_table_info('<table_name>')
where name='<column_name>';

#!/bin/sh
# the next line restarts using wish \

exec /opt/usr8.6.3/bin/tclsh8.6  "$0" ${1+"$@"}
foreach pkg {sqlite3 } {
    if { [ catch {package require {*}$pkg } err ] != 0 } {
    puts stderr "Unable to find package $pkg\n$err\n ... adjust your auto_path!";
    }
}
array set sqlArray {
    1 {
    CREATE TABLE IF NOT EXISTS Notes (
                      id INTEGER PRIMARY KEY AUTOINCREMENT,
                      name text,
                      note text,
                      createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                      updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
                      );
    CREATE TABLE IF NOT EXISTS Version (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        version text,
                        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) )
                        );
    INSERT INTO Version(version) values('1.0');
    }
    2 {
    CREATE TABLE IF NOT EXISTS Tags (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name text,
        tag text,
        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
        );
    ALTER TABLE Notes ADD COLUMN dump text;
    INSERT INTO Version(version) values('2.0');
    }
    3 {
    ALTER TABLE Version ADD COLUMN sql text;
    INSERT INTO Version(version) values('3.0');
    }
}

# create db command , use in memory database for demonstration purposes
sqlite3 db :memory:

proc createSchema { sqlArray } {
    upvar $sqlArray sql
    # execute each sql script in order 
    foreach version [lsort -integer [array names sql ] ] {
    set cmd $sql($version)
    set ok 0
    while { !$ok && [string length $cmd ] } {  
        try {
        db eval $cmd
        set ok 1  ;   # it succeeded if we get here
        } on error { err backtrace } {
        if { [regexp {duplicate column name: ([a-zA-Z0-9])} [string trim $err ] match columnname ] } {
            puts "Error:  $err ... trying again" 
            set cmd [removeAlterTable $cmd $columnname ]
        } else {
            throw DBERROR "$err\n$backtrace"
        }
        }
    }
    }
}
# return sqltext with alter table command with column name removed
# if no matching alter table line found or result is no lines then
# returns ""
proc removeAlterTable { sqltext columnname } {
    set mode skip
    set result [list]
    foreach line [split $sqltext \n ] {
    if { [string first "alter table" [string tolower [string trim $line] ] ] >= 0 } {
        if { [string first $columnname $line ] } {
        set mode add
        continue;
        }
    }
    if { $mode eq "add" } {
        lappend result $line
    }
    }
    if { $mode eq "skip" } {
    puts stderr "Unable to find matching alter table line"
    return ""
    } elseif { [llength $result ] }  { 
    return [ join $result \n ]
    } else {
    return ""
    }
}
               
proc printSchema { } {
    db eval { select * from sqlite_master } x {
    puts "Table: $x(tbl_name)"
    puts "$x(sql)"
    puts "-------------"
    }
}
createSchema sqlArray
printSchema
# run again to see if we get alter table errors 
createSchema sqlArray
printSchema
Table: Notes
CREATE TABLE Notes (
                      id INTEGER PRIMARY KEY AUTOINCREMENT,
                      name text,
                      note text,
                      createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                      updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
                      , dump text)
-------------
Table: sqlite_sequence
CREATE TABLE sqlite_sequence(name,seq)
-------------
Table: Version
CREATE TABLE Version (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        version text,
                        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) )
                        , sql text)
-------------
Table: Tags
CREATE TABLE Tags (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name text,
        tag text,
        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
        )
-------------
Error:  duplicate column name: dump ... trying again
Error:  duplicate column name: sql ... trying again
Table: Notes
CREATE TABLE Notes (
                      id INTEGER PRIMARY KEY AUTOINCREMENT,
                      name text,
                      note text,
                      createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                      updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
                      , dump text)
-------------
Table: sqlite_sequence
CREATE TABLE sqlite_sequence(name,seq)
-------------
Table: Version
CREATE TABLE Version (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        version text,
                        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) )
                        , sql text)
-------------
Table: Tags
CREATE TABLE Tags (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name text,
        tag text,
        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
        )
-------------
SELECT CASE (SELECT count(*) FROM pragma_table_info(''product'') c WHERE c.name = ''purchaseCopy'') WHEN 0 THEN ALTER TABLE product ADD purchaseCopy BLOB END
select * from sqlite_master where type = 'table' and tbl_name = 'TableName' and sql like '%ColumnName%'
select * from sqlite_master where type = 'table' and tbl_name = 'MyTable' and sql like '%`MyColumn` TEXT%'
IDbCommand command = dbConnection.CreateCommand();
            command.CommandText = @"SELECT count(*) FROM pragma_table_info('Candidat') c WHERE c.name = 'BirthPlace'";
            IDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                try
                {
                    if (int.TryParse(reader[0].ToString(), out int result))
                    {
                        if (result == 0)
                        {
                            command = dbConnection.CreateCommand();
                            command.CommandText = @"ALTER TABLE Candidat ADD COLUMN BirthPlace VARCHAR";
                            command.ExecuteNonQuery();
                            command.Dispose();
                        }
                    }
                }
                catch { throw; }
            }