Недавно у нас возникла необходимость добавить столбцы в некоторые из наших существующих таблиц базы данных SQLite. Это можно сделать с помощью ALTER TABLE ADD COLUMN
. Конечно, если таблица уже была изменена, мы хотим оставить ее в покое. К сожалению, SQLite не поддерживает IF NOT EXISTS
пункт о ALTER TABLE
.
Наш текущий обходной путь - выполнить оператор ALTER TABLE и игнорировать любые ошибки «повторяющееся имя столбца», как в этом примере Python (но на C ++).
Тем не менее, наш обычный подход к созданию схемы базы данных должны иметь .sql скрипта , содержащий CREATE TABLE IF NOT EXISTS
и CREATE INDEX IF NOT EXISTS
заявление, которые могут быть выполнены с использованием sqlite3_exec
или sqlite3
инструмент командной строки. Мы не можем вставить ALTER TABLE
эти файлы сценария, потому что в случае сбоя этого оператора ничего после него не будет выполнено.
Я хочу, чтобы определения таблиц были в одном месте и не разделялись между файлами .sql и .cpp. Есть ли способ написать обходной путь ALTER TABLE ADD COLUMN IF NOT EXISTS
в чистом SQLite SQL?
источник
user_version
? Я предполагаю ноль, но было бы хорошо, если бы это задокументировано.IF
иALTER TABLE
не имеет условного выражения? Что вы имеете в виду под «чистым SQL на 99%»?user_version
, оно похоже на 0, но на самом деле это значение, определяемое пользователем, поэтому вы можете создать собственное начальное значение.user_version
начальном значении актуален, когда у вас есть существующая база данных, и вы никогда не использовали ееuser_version
раньше, но хотите начать ее использовать, поэтому вам нужно предположить, что sqlite установил для нее определенное начальное значение.Один из способов решения проблемы - просто создать столбцы и перехватить исключение / ошибку, возникающую, если столбец уже существует. При добавлении нескольких столбцов добавьте их в отдельные операторы ALTER TABLE, чтобы один дубликат не препятствовал созданию других.
С sqlite-net мы сделали что-то вроде этого. Это не идеально, поскольку мы не можем отличить повторяющиеся ошибки 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)); } }
источник
SQLite также поддерживает инструкцию прагмы под названием «table_info», которая возвращает одну строку для каждого столбца в таблице с именем столбца (и другой информацией о столбце). Вы можете использовать это в запросе для проверки отсутствующего столбца и, если он отсутствует, изменить таблицу.
PRAGMA table_info(foo_table_name)
http://www.sqlite.org/pragma.html#pragma_table_info
источник
Если вы делаете это в операторе обновления БД, возможно, самый простой способ - просто перехватить возникшее исключение, если вы пытаетесь добавить поле, которое может уже существовать.
try { db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN foo TEXT default null"); } catch (SQLiteException ex) { Log.w(TAG, "Altering " + TABLE_NAME + ": " + ex.getMessage()); }
источник
Это метод PRAGMA - table_info (table_name), он возвращает всю информацию таблицы.
Вот реализация, как использовать его для проверки, существует ли столбец,
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);
источник
we give no shit about performance
:)).SELECT * FROM pragma_table_info(...)
(обратите внимание на SELECT и подчеркивание между прагмой и информацией о таблице). Не уверен, в какую версию они добавили его, он не работал на 3.16.0, но работает на 3.22.0.Для тех, кто хочет использовать
pragma table_info()
результат как часть более крупного SQL.select count(*) from pragma_table_info('<table_name>') where name='<column_name>';
Ключевой частью является использование
pragma_table_info('<table_name>')
вместоpragma table_info('<table_name>')
.Этот ответ вдохновлен ответом @Robert Hawkey. Причина, по которой я публикую его как новый ответ, заключается в том, что у меня недостаточно репутации, чтобы опубликовать его как комментарий.
источник
Я пришел с этим запросом
SELECT CASE (SELECT count(*) FROM pragma_table_info(''product'') c WHERE c.name = ''purchaseCopy'') WHEN 0 THEN ALTER TABLE product ADD purchaseCopy BLOB END
источник
Если у вас возникла эта проблема в flex / adobe air и вы сначала оказались здесь, я нашел решение и опубликовал его по связанному с ним вопросу: ДОБАВИТЬ КОЛОНКУ в sqlite db, ЕСЛИ НЕ СУЩЕСТВУЕТ - flex / air sqlite?
Мой комментарий здесь: https://stackoverflow.com/a/24928437/2678219
источник
Я взял приведенный выше ответ на C # /. Net и переписал его для Qt / C ++, не сильно изменив его, но я хотел оставить его здесь для всех, кто в будущем будет искать ответ C ++ 'ish'.
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):
# modify table for legacy version which did not have leave type and leave time columns of rings3 table. sql = 'PRAGMA table_info(rings3)' # get table info. returns an array of columns. result = inquire (sql) # call homemade function to execute the inquiry if len(result)<= 6: # if there are not enough columns add the leave type and leave time columns sql = 'ALTER table rings3 ADD COLUMN leave_type varchar' commit(sql) # call homemade function to execute sql sql = 'ALTER table rings3 ADD COLUMN leave_time varchar' commit(sql)
Я использовал PRAGMA, чтобы получить информацию о таблице. Он возвращает многомерный массив, полный информации о столбцах - по одному массиву на столбец. Я считаю количество массивов, чтобы получить количество столбцов. Если столбцов недостаточно, я добавляю столбцы с помощью команды ALTER TABLE.
источник
Все эти ответы хороши, если вы выполняете по одной строке за раз. Однако исходный вопрос заключался в том, чтобы ввести sql-скрипт, который будет выполняться одним выполнением db, и все решения (например, проверка наличия столбца заранее) потребуют, чтобы выполняющаяся программа либо знала, какие таблицы и столбцы изменяются / добавляются или выполняют предварительную обработку и анализ входного скрипта для определения этой информации. Обычно вы не собираетесь запускать это в реальном времени или часто. Так что идея перехвата исключения приемлема, а затем двигаться дальше. В этом проблема ... как двигаться дальше. К счастью, сообщение об ошибке дает нам всю необходимую для этого информацию. Идея состоит в том, чтобы выполнить sql, если он вызывает исключение при вызове alter table, мы можем найти строку alter table в sql и вернуть оставшиеся строки и выполнить до тех пор, пока он либо не завершится успешно, либо больше не будет найдено соответствующих строк alter table. Вот пример кода, где у нас есть скрипты sql в массиве. Мы перебираем массив, выполняя каждый скрипт. Мы вызываем его дважды, чтобы команда alter table завершилась ошибкой, но программа завершается успешно, потому что мы удаляем команду alter table из sql и повторно выполняем обновленный код.
#!/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 * from sqlite_master where type = 'table' and tbl_name = 'TableName' and sql like '%ColumnName%'
Логика: столбец sql в sqlite_master содержит определение таблицы, поэтому он обязательно содержит строку с именем столбца.
Поскольку вы ищете подстроку, у нее есть очевидные ограничения. Поэтому я бы предложил использовать еще более ограничительную подстроку в ColumnName, например, что-то вроде этого (при условии тестирования, поскольку символ '' 'присутствует не всегда):
select * from sqlite_master where type = 'table' and tbl_name = 'MyTable' and sql like '%`MyColumn` TEXT%'
источник
Решаю за 2 запроса. Это мой скрипт Unity3D, использующий System.Data.SQLite.
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; } }
источник