web-dev-qa-db-fra.com

ALTER TABLE ADD COLUMN SI PAS EXISTS dans SQLite

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 la table a déjà été modifiée, nous voulons la laisser seule. Malheureusement, SQLite ne supporte pas un IF NOT EXISTS clause sur ALTER TABLE.

Notre solution actuelle consiste à exécuter l'instruction ALTER TABLE et à ignorer les erreurs de type "nom de colonne en double", comme this Python (mais en C++).

Cependant, notre approche habituelle pour configurer des schémas de base de données consiste à utiliser un script .sql contenant CREATE TABLE IF NOT EXISTS et CREATE INDEX IF NOT EXISTS instructions, qui peuvent être exécutées avec sqlite3_exec ou la sqlite3 outil en ligne de commande. Nous ne pouvons pas mettre ALTER TABLE dans ces fichiers de script, car si cette instruction échoue, toute action ultérieure ne sera pas exécutée.

Je veux avoir les définitions de table dans un endroit et pas divisé entre les fichiers .sql et .cpp. Est-il possible d'écrire une solution de contournement à ALTER TABLE ADD COLUMN IF NOT EXISTS en SQLite SQL pur?

75
dan04

J'ai une méthode de SQL pur à 99%. L'idée est de mettre à jour votre schéma. Vous pouvez le faire de deux manières:

  • Utilisez la commande pragma 'user_version' ( PRAGMA user_version ) pour stocker un numéro incrémentiel pour la version de votre schéma de base de données.

  • Stockez votre numéro de version dans votre propre table définie.

De cette manière, lorsque le logiciel est démarré, il peut vérifier le schéma de la base de données et, si nécessaire, exécuter votre ALTER TABLE requête, puis incrémenter la version stockée. C'est de loin préférable à diverses tentatives de mise à jour "à l'aveugle", surtout si votre base de données s'agrandit et change plusieurs fois au fil des ans.

50
MPelletier

Une solution de contournement consiste à simplement créer les colonnes et à récupérer les exceptions/erreurs qui se produisent si la colonne existe déjà. Lors de l'ajout de plusieurs colonnes, ajoutez-les dans des instructions ALTER TABLE séparées afin qu'un duplicata 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 en double 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));
    }
}
28
angularsen

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 (ainsi que d'autres informations sur la colonne). Vous pouvez l'utiliser dans une requête pour rechercher la colonne manquante. Sinon, modifiez la table.

PRAGMA table_info(foo_table_name)

http://www.sqlite.org/pragma.html#pragma_table_info

25
Robert Hawkey

Si vous faites cela dans une instruction de mise à niveau de base de données, le moyen le plus simple consiste peut-être simplement à attraper l'exception levée si vous essayez d'ajouter un champ qui existe peut-être déjà.

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

voici une méthode de PRAGMA appelée table_info (nom_table), elle renvoie toutes les informations de la table.

Voici l'implémentation, comment l'utiliser pour vérifier que 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);
11
Krunal Shah

J'ai pris la réponse ci-dessus dans C # /. Net et je l'ai réécrite pour Qt/C++, cela n'a pas trop changé, mais je voulais la laisser ici pour quiconque à la recherche d'une réponse 'ish' en 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;
}
0
Kevin B Burns

Si vous rencontrez ce problème dans flex/Adobe air et que vous vous trouvez d'abord ici, j'ai trouvé une solution et je l'ai postée sur une question connexe: AJOUTER COLONNE à sqlite db SI EXISTE - flex/air sqlite?

Mon commentaire ici: https://stackoverflow.com/a/24928437/2678219

0
stevesweets