Niedawno mieliśmy potrzebę dodania kolumn do kilku naszych istniejących tabel bazy danych SQLite. Można to zrobić za pomocą ALTER TABLE ADD COLUMN
. Oczywiście, jeśli tabela została już zmieniona, chcemy ją zostawić w spokoju. Niestety, SQLite nie obsługuje IF NOT EXISTS
klauzuli on ALTER TABLE
.
Nasze obecne obejście polega na wykonaniu instrukcji ALTER TABLE i zignorowaniu wszelkich błędów „zduplikowanej nazwy kolumny”, tak jak w tym przykładzie w Pythonie (ale w C ++).
Jednak naszym zwykłym podejściem do konfigurowania schematów bazy danych jest posiadanie skryptu .sql zawierającego instrukcje CREATE TABLE IF NOT EXISTS
i CREATE INDEX IF NOT EXISTS
, które można wykonać za pomocą narzędzia sqlite3_exec
lub sqlite3
narzędzia wiersza poleceń. Nie możemy wstawić ALTER TABLE
tych plików skryptów, ponieważ jeśli ta instrukcja zawiedzie, nic po niej nie zostanie wykonane.
Chcę, aby definicje tabel były w jednym miejscu i nie były dzielone między pliki .sql i .cpp. Czy istnieje sposób na napisanie obejścia ALTER TABLE ADD COLUMN IF NOT EXISTS
w czystym SQLite SQL?
źródło
user_version
? Zakładam zero, ale byłoby miło zobaczyć to udokumentowane.IF
iALTER TABLE
nie ma warunku? Co masz na myśli mówiąc „99% czystego SQL”?user_version
, wydaje się, że wynosi 0, ale tak naprawdę jest to wartość zdefiniowana przez użytkownika, więc możesz utworzyć własną wartość początkową.user_version
wartość początkową jest istotne, gdy masz istniejącą bazę danych i nigdy wcześniej jej nieuser_version
używałeś, ale chcesz zacząć jej używać, więc musisz założyć, że sqlite ustawił ją na określoną wartość początkową.Jednym obejściem jest utworzenie kolumn i wychwycenie wyjątku / błędu, który pojawia się, jeśli kolumna już istnieje. Dodając wiele kolumn, należy je dodawać w oddzielnych instrukcjach ALTER TABLE, aby jeden duplikat nie przeszkadzał w utworzeniu pozostałych.
Z sqlite-net zrobiliśmy coś takiego. Nie jest doskonały, ponieważ nie możemy odróżnić powielonych błędów sqlite od innych błędów 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)); } }
źródło
SQLite obsługuje również instrukcję pragma o nazwie „table_info”, która zwraca jeden wiersz na kolumnę w tabeli wraz z nazwą kolumny (i innymi informacjami o kolumnie). Możesz użyć tego w zapytaniu, aby sprawdzić brakującą kolumnę, a jeśli nie, zmienić tabelę.
PRAGMA table_info(foo_table_name)
http://www.sqlite.org/pragma.html#pragma_table_info
źródło
Jeśli robisz to w instrukcji aktualizacji bazy danych, być może najprostszym sposobem jest po prostu przechwycenie zgłoszonego wyjątku, jeśli próbujesz dodać pole, które może już istnieć.
try { db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN foo TEXT default null"); } catch (SQLiteException ex) { Log.w(TAG, "Altering " + TABLE_NAME + ": " + ex.getMessage()); }
źródło
threre to metoda PRAGMA to table_info (nazwa_tabeli), zwraca wszystkie informacje z tabeli.
Oto implementacja, jak go używać, aby sprawdzić, czy kolumna istnieje, czy nie,
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; }
Możesz również użyć tego zapytania bez używania pętli,
cursor = db.rawQuery("PRAGMA table_info("+ table +") where name = " + column, null);
źródło
we give no shit about performance
:)).SELECT * FROM pragma_table_info(...)
(zwróć uwagę na SELECT i podkreślenie między pragmą a informacjami o tabeli). Nie jestem pewien, w której wersji faktycznie go dodali, nie działało na 3.16.0, ale działa na 3.22.0.Dla tych, którzy chcą użyć
pragma table_info()
wyniku jako części większego SQL.select count(*) from pragma_table_info('<table_name>') where name='<column_name>';
Kluczową częścią jest użycie
pragma_table_info('<table_name>')
zamiastpragma table_info('<table_name>')
.Ta odpowiedź jest inspirowana odpowiedzią @Robert Hawkey. Powodem, dla którego publikuję to jako nową odpowiedź, jest to, że nie mam wystarczającej reputacji, aby opublikować ją jako komentarz.
źródło
Wymyślam to zapytanie
SELECT CASE (SELECT count(*) FROM pragma_table_info(''product'') c WHERE c.name = ''purchaseCopy'') WHEN 0 THEN ALTER TABLE product ADD purchaseCopy BLOB END
źródło
Jeśli masz ten problem z flex / adobe air i znajdziesz się tutaj jako pierwszy, znalazłem rozwiązanie i opublikowałem je w powiązanym pytaniu: DODAJ KOLUMNĘ do sqlite db JEŚLI NIE ISTNIEJE - flex / air sqlite?
Mój komentarz tutaj: https://stackoverflow.com/a/24928437/2678219
źródło
Wziąłem powyższą odpowiedź w C # / .Net i przepisałem ją na Qt / C ++, nie zmieniając się zbytnio, ale chciałem zostawić ją tutaj dla każdego, kto w przyszłości szuka odpowiedzi „ish” w 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; }
źródło
Alternatywnie możesz użyć instrukcji CASE-WHEN TSQL w połączeniu z pragma_table_info, aby dowiedzieć się, czy kolumna istnieje:
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')
źródło
Oto moje rozwiązanie, ale w pythonie (próbowałem i nie udało mi się znaleźć żadnego posta na temat związany z pythonem):
# 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)
Użyłem PRAGMA, aby uzyskać informacje o tabeli. Zwraca wielowymiarową tablicę pełną informacji o kolumnach - jedną tablicę na kolumnę. Liczę liczbę tablic, aby uzyskać liczbę kolumn. Jeśli nie ma wystarczającej liczby kolumn, dodaję kolumny za pomocą polecenia ALTER TABLE.
źródło
Wszystkie te odpowiedzi są w porządku, jeśli wykonujesz jedną linię na raz. Jednak pierwotnym pytaniem było wprowadzenie skryptu sql, który byłby wykonywany przez pojedyncze wykonanie bazy danych, a wszystkie rozwiązania (takie jak sprawdzenie, czy kolumna jest tam z wyprzedzeniem) wymagałyby, aby program wykonawczy miał wiedzę o tym, jakie tabele i kolumny są zmieniane / dodawane lub wstępnie przetwarzają i analizują skrypt wejściowy w celu określenia tych informacji. Zazwyczaj nie będziesz uruchamiać tego w czasie rzeczywistym lub często. Tak więc pomysł wyłapania wyjątku jest akceptowalny, a następnie przejść dalej. Na tym polega problem ... jak iść dalej. Na szczęście komunikat o błędzie zawiera wszystkie informacje potrzebne do tego. Chodzi o to, aby wykonać sql, jeśli wyjątek od wywołania alter table, możemy znaleźć wiersz tablicy alter w sql, a następnie zwrócić pozostałe wiersze i wykonać, aż się powiedzie lub nie będzie można znaleźć więcej pasujących wierszy tabeli alter. Oto przykładowy kod, w którym mamy skrypty sql w tablicy. Iterujemy tablicę wykonującą każdy skrypt. Wzywamy to dwukrotnie, aby polecenie alter table zakończyło się niepowodzeniem, ale program się powiódł, ponieważ usuwamy polecenie alter table z sql i ponownie wykonujemy zaktualizowany kod.
#!/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
oczekiwany wynik
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 ) ) ) -------------
źródło
select * from sqlite_master where type = 'table' and tbl_name = 'TableName' and sql like '%ColumnName%'
Logic: sql kolumna w sqlite_master zawiera definicję tabeli, więc z pewnością zawiera ciąg z nazwą kolumny.
Gdy szukasz podciągu, ma on swoje oczywiste ograniczenia. Dlatego sugerowałbym użycie jeszcze bardziej restrykcyjnego podłańcucha w ColumnName, na przykład coś takiego (podlega testom, ponieważ znak `` '' nie zawsze występuje):
select * from sqlite_master where type = 'table' and tbl_name = 'MyTable' and sql like '%`MyColumn` TEXT%'
źródło
Rozwiązuję to w 2 zapytaniach. To jest mój skrypt Unity3D korzystający z 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; } }
źródło