Nous avons récemment eu besoin d'ajouter des colonnes à quelques-unes de nos tables de base de données SQLite existantes. Cela peut être fait avec ALTER TABLE ADD COLUMN
. Bien sûr, si le tableau a déjà été modifié, nous voulons le laisser seul. Malheureusement, SQLite ne prend pas en charge une IF NOT EXISTS
clause sur ALTER TABLE
.
Notre solution de contournement actuelle consiste à exécuter l'instruction ALTER TABLE et à ignorer toutes les erreurs de "nom de colonne en double", tout comme cet exemple Python (mais en C ++).
Cependant, notre approche habituelle pour configurer les schémas de base de données consiste à avoir un script .sql contenant des instructions CREATE TABLE IF NOT EXISTS
et CREATE INDEX IF NOT EXISTS
, qui peuvent être exécutées en utilisant sqlite3_exec
ou l' sqlite3
outil de ligne de commande. Nous ne pouvons pas insérer ALTER TABLE
ces fichiers de script car si cette instruction échoue, tout ce qui suit ne sera pas exécuté.
Je veux avoir les définitions de table au même endroit et ne pas être réparties entre les fichiers .sql et .cpp. Existe-t-il un moyen d'écrire une solution de contournement ALTER TABLE ADD COLUMN IF NOT EXISTS
en SQLite SQL pur?
la source
user_version
? Je suppose zéro, mais ce serait bien de voir cela documenté.IF
etALTER TABLE
n'a pas de conditionnel? Qu'entendez-vous par "SQL pur à 99%"?user_version
, elle semble être 0, mais c'est vraiment une valeur définie par l'utilisateur, vous pouvez donc créer votre propre valeur initiale.user_version
la valeur initiale est pertinente lorsque vous avez une base de données existante et que vous ne l'avez jamais utiliséeuser_version
auparavant, mais que vous souhaitez commencer à l'utiliser, vous devez donc supposer que sqlite l'a définie sur une valeur initiale particulière.Une solution de contournement consiste simplement à créer les colonnes et à détecter l'exception / erreur qui se produit si la colonne existe déjà. Lors de l'ajout de plusieurs colonnes, ajoutez-les dans des instructions ALTER TABLE distinctes afin qu'un doublon n'empêche pas la création des autres.
Avec sqlite-net , nous avons fait quelque chose comme ça. Ce n'est pas parfait, car nous ne pouvons pas distinguer les erreurs sqlite dupliquées des autres erreurs 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)); } }
la source
SQLite prend également en charge une instruction pragma appelée "table_info" qui renvoie une ligne par colonne dans une table avec le nom de la colonne (et d'autres informations sur la colonne). Vous pouvez l'utiliser dans une requête pour rechercher la colonne manquante et, si elle n'est pas présente, modifier la table.
PRAGMA table_info(foo_table_name)
http://www.sqlite.org/pragma.html#pragma_table_info
la source
Si vous faites cela dans une instruction de mise à niveau de la base de données, le moyen le plus simple est peut-être de simplement intercepter l'exception levée si vous essayez d'ajouter un champ qui peut déjà exister.
try { db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN foo TEXT default null"); } catch (SQLiteException ex) { Log.w(TAG, "Altering " + TABLE_NAME + ": " + ex.getMessage()); }
la source
threre est une méthode de PRAGMA est table_info (table_name), elle retourne toutes les informations de table.
Voici la mise en œuvre comment l'utiliser pour vérifier la colonne existe ou non,
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; }
Vous pouvez également utiliser cette requête sans utiliser de boucle,
cursor = db.rawQuery("PRAGMA table_info("+ table +") where name = " + column, null);
la source
we give no shit about performance
:)).SELECT * FROM pragma_table_info(...)
(notez le SELECT et le trait de soulignement entre le pragma et les informations de table). Je ne sais pas dans quelle version ils l'ont réellement ajouté, cela n'a pas fonctionné sur 3.16.0 mais cela fonctionne sur 3.22.0.Pour ceux qui veulent utiliser
pragma table_info()
le résultat dans le cadre d'un SQL plus large.select count(*) from pragma_table_info('<table_name>') where name='<column_name>';
L'essentiel est d'utiliser
pragma_table_info('<table_name>')
au lieu depragma table_info('<table_name>')
.Cette réponse est inspirée de la réponse de @Robert Hawkey. La raison pour laquelle je la poste comme nouvelle réponse est que je n'ai pas assez de réputation pour la publier en commentaire.
la source
Je viens avec cette requête
SELECT CASE (SELECT count(*) FROM pragma_table_info(''product'') c WHERE c.name = ''purchaseCopy'') WHEN 0 THEN ALTER TABLE product ADD purchaseCopy BLOB END
la source
Si vous rencontrez ce problème dans flex / adobe air et que vous vous trouvez ici en premier, j'ai trouvé une solution et je l'ai postée sur une question connexe: AJOUTER LA COLONNE à sqlite db SI PAS EXISTE - flex / air sqlite?
Mon commentaire ici: https://stackoverflow.com/a/24928437/2678219
la source
J'ai pris la réponse ci-dessus en C # /. Net, et je l'ai réécrite pour Qt / C ++, pas trop changé, mais je voulais la laisser ici pour quiconque à l'avenir à la recherche d'une réponse 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; }
la source
Vous pouvez également utiliser l'instruction CASE-WHEN TSQL en combinaison avec pragma_table_info pour savoir si une colonne existe:
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')
la source
Voici ma solution, mais en python (j'ai essayé et je n'ai trouvé aucun article sur le sujet lié à 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)
J'ai utilisé PRAGMA pour obtenir les informations de la table. Il renvoie un tableau multidimensionnel plein d'informations sur les colonnes - un tableau par colonne. Je compte le nombre de tableaux pour obtenir le nombre de colonnes. S'il n'y a pas assez de colonnes, j'ajoute les colonnes à l'aide de la commande ALTER TABLE.
la source
Toutes ces réponses conviennent si vous exécutez une ligne à la fois. Cependant, la question initiale était de saisir un script sql qui serait exécuté par une seule exécution de base de données et toutes les solutions (comme vérifier si la colonne est là à l'avance) nécessiteraient que le programme en cours d'exécution sache quelles tables et des colonnes sont modifiées / ajoutées ou effectuent un prétraitement et une analyse du script d'entrée pour déterminer ces informations. En règle générale, vous n'allez pas l'exécuter en temps réel ou souvent. L'idée d'attraper une exception est donc acceptable, puis de passer à autre chose. C'est là que réside le problème ... comment avancer. Heureusement, le message d'erreur nous donne toutes les informations dont nous avons besoin pour ce faire. L'idée est d'exécuter le sql s'il fait exception sur un appel alter table, nous pouvons trouver la ligne alter table dans le sql et renvoyer les lignes restantes et l'exécuter jusqu'à ce qu'il réussisse ou qu'aucune autre ligne alter table correspondante ne puisse être trouvée. Voici un exemple de code où nous avons des scripts SQL dans un tableau. Nous itérons le tableau exécutant chaque script. Nous l'appelons deux fois pour que la commande alter table échoue, mais le programme réussit car nous supprimons la commande alter table du sql et réexécutons le code mis à jour.
#!/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
production attendue
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 ) ) ) -------------
la source
select * from sqlite_master where type = 'table' and tbl_name = 'TableName' and sql like '%ColumnName%'
Logique: la colonne sql dans sqlite_master contient la définition de la table, elle contient donc certainement une chaîne avec le nom de la colonne.
Lorsque vous recherchez une sous-chaîne, elle a ses limites évidentes. Je suggérerais donc d'utiliser une sous-chaîne encore plus restrictive dans ColumnName, par exemple quelque chose comme ça (sous réserve de tests car le caractère `` 'n'est pas toujours là):
select * from sqlite_master where type = 'table' and tbl_name = 'MyTable' and sql like '%`MyColumn` TEXT%'
la source
Je le résous en 2 requêtes. Ceci est mon script Unity3D utilisant 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; } }
la source