Comment supprimer ou ajouter une colonne dans SQLITE?


240

Je veux supprimer ou ajouter une colonne dans la base de données sqlite

J'utilise la requête suivante pour supprimer la colonne.

ALTER TABLE TABLENAME DROP COLUMN COLUMNNAME

Mais cela donne une erreur

System.Data.SQLite.SQLiteException: SQLite error
near "DROP": syntax error

Réponses:


353

ALTER TABLE SQLite

SQLite prend en charge un sous-ensemble limité de ALTER TABLE. La commande ALTER TABLE dans SQLite permet à l'utilisateur de renommer une table ou d'ajouter une nouvelle colonne à une table existante. Il n'est pas possible de renommer une colonne, de supprimer une colonne ou d'ajouter ou de supprimer des contraintes d'une table.

Vous pouvez:

  1. créer une nouvelle table comme celle que vous essayez de changer,
  2. copier toutes les données,
  3. laissez tomber la vieille table,
  4. renommer le nouveau.

47
stackoverflow.com/a/5987838/1578528 donne un exemple de base pour effectuer la tâche.
bikram990

4
Avant de faire cette séquence et dans les cas où il existe des tables externes faisant référence à cette table, il faut appeler PRAGMA foreign_keys=OFF. Dans ce cas, après avoir effectué cette séquence, il faut appeler PRAGMA foreign_keys=ONpour réactiver les clés étrangères.
PazO

Comment copiez-vous également la clé et les index freoign?
Jonathan

tant que vous créez d'abord une nouvelle table au lieu d'une création à partir de la sélection, il y aura toutes ces choses.
John Lord

1
Dans les versions plus récentes de SQLite, RENAME COLUMNest prise en charge. 🎉 sqlite.org/releaselog/3_25_0.html
Grogs

46

J'ai écrit une implémentation Java basée sur la méthode recommandée par Sqlite pour ce faire:

private void dropColumn(SQLiteDatabase db,
        ConnectionSource connectionSource,
        String createTableCmd,
        String tableName,
        String[] colsToRemove) throws java.sql.SQLException {

    List<String> updatedTableColumns = getTableColumns(tableName);
    // Remove the columns we don't want anymore from the table's list of columns
    updatedTableColumns.removeAll(Arrays.asList(colsToRemove));

    String columnsSeperated = TextUtils.join(",", updatedTableColumns);

    db.execSQL("ALTER TABLE " + tableName + " RENAME TO " + tableName + "_old;");

    // Creating the table on its new format (no redundant columns)
    db.execSQL(createTableCmd);

    // Populating the table with the data
    db.execSQL("INSERT INTO " + tableName + "(" + columnsSeperated + ") SELECT "
            + columnsSeperated + " FROM " + tableName + "_old;");
    db.execSQL("DROP TABLE " + tableName + "_old;");
}

Pour obtenir la colonne du tableau, j'ai utilisé le "PRAGMA table_info":

public List<String> getTableColumns(String tableName) {
    ArrayList<String> columns = new ArrayList<String>();
    String cmd = "pragma table_info(" + tableName + ");";
    Cursor cur = getDB().rawQuery(cmd, null);

    while (cur.moveToNext()) {
        columns.add(cur.getString(cur.getColumnIndex("name")));
    }
    cur.close();

    return columns;
}

En fait, j'ai écrit à ce sujet sur mon blog, vous pouvez y voir plus d'explications:

http://udinic.wordpress.com/2012/05/09/sqlite-drop-column-support/


1
c'est assez lent mais n'est-ce pas? pour les tableaux de Big Data?
Joran Beasley

2
Il serait préférable que cela soit fait en une seule transaction, plutôt que de permettre potentiellement à un autre code de voir les choses dans un état de transition.
Donal Fellows

Ce code est généralement exécuté lors de la mise à niveau de la base de données, où aucun autre code ne s'exécute simultanément. Vous pouvez créer une transaction et exécuter toutes ces commandes.
Udinic

3
Je suis sûr que si vous utilisez cette solution, les colonnes de votre tableau de résultats seront complètement nues - aucune information de type, PK, FK, valeurs par défaut, contraintes uniques ou de vérification ne resteront. Tout ce qu'il importe dans la nouvelle table est le nom de la colonne. De plus, comme il ne désactive pas les clés étrangères avant de s'exécuter, les données d'autres tables peuvent également être gâchées.
ACK_stoverflow

4
Alternativement, au lieu de faire une INSERTdéclaration, vous pouvez également créer la nouvelle table en faisant un"CREAT TABLE" + tableName + "AS SELECT " + columnsSeperated + " FROM " + tableName + "_old;"
Robert

26

Comme d'autres l'ont souligné

Il n'est pas possible de renommer une colonne, de supprimer une colonne ou d'ajouter ou de supprimer des contraintes d'une table.

source: http://www.sqlite.org/lang_altertable.html

Bien que vous puissiez toujours créer un nouveau tableau, puis supprimer l'ancien. Je vais essayer d'expliquer cette solution de contournement avec un exemple.

sqlite> .schema
CREATE TABLE person(
 id INTEGER PRIMARY KEY, 
 first_name TEXT,
 last_name TEXT, 
 age INTEGER, 
 height INTEGER
);
sqlite> select * from person ; 
id          first_name  last_name   age         height    
----------  ----------  ----------  ----------  ----------
0           john        doe         20          170       
1           foo         bar         25          171       

Vous souhaitez maintenant supprimer la colonne heightde ce tableau.

Créez une autre table appelée new_person

sqlite> CREATE TABLE new_person(
   ...>  id INTEGER PRIMARY KEY, 
   ...>  first_name TEXT, 
   ...>  last_name TEXT, 
   ...>  age INTEGER 
   ...> ) ; 
sqlite> 

Copiez maintenant les données de l'ancienne table

sqlite> INSERT INTO new_person
   ...> SELECT id, first_name, last_name, age FROM person ;
sqlite> select * from new_person ;
id          first_name  last_name   age       
----------  ----------  ----------  ----------
0           john        doe         20        
1           foo         bar         25        
sqlite>

Déposez maintenant la persontable et renommez-la new_personenperson

sqlite> DROP TABLE IF EXISTS person ; 
sqlite> ALTER TABLE new_person RENAME TO person ;
sqlite>

Alors maintenant, si vous faites un .schema, vous verrez

sqlite>.schema
CREATE TABLE "person"(
 id INTEGER PRIMARY KEY, 
 first_name TEXT, 
 last_name TEXT, 
 age INTEGER 
);

Et les références étrangères? Oracle se plaindrait si vous supprimez une table utilisée par une autre table.
Leos Literak

6
Je peux dire que vous êtes un vrai programmeur. Vous avez manqué de noms juste après John Doe et vous êtes allé directement à "foo bar" :)
msouth

1
CREATE TABLE new_person AS SELECT id, first_name, last_name, age FROM person;
Clay



4

Comme d'autres l'ont souligné, la ALTER TABLEdéclaration de sqlite ne prend pas en charge DROP COLUMN, et la recette standard pour ce faire ne préserve pas les contraintes et les indices.

Voici du code python pour le faire de manière générique, tout en conservant toutes les contraintes et index clés.

Veuillez sauvegarder votre base de données avant de l'utiliser! Cette fonction repose sur la falsification de l'instruction CREATE TABLE d'origine et est potentiellement un peu dangereuse - par exemple, elle fera la mauvaise chose si un identifiant contient une virgule ou une parenthèse incorporée.

Si quelqu'un souhaitait contribuer à une meilleure façon d'analyser le SQL, ce serait formidable!

MISE À JOUR J'ai trouvé une meilleure façon d'analyser en utilisant lesqlparsepaquetopen-source. S'il y a un intérêt, je le posterai ici, laissez simplement un commentaire le demandant ...

import re
import random

def DROP_COLUMN(db, table, column):
    columns = [ c[1] for c in db.execute("PRAGMA table_info(%s)" % table) ]
    columns = [ c for c in columns if c != column ]
    sql = db.execute("SELECT sql from sqlite_master where name = '%s'" 
        % table).fetchone()[0]
    sql = format(sql)
    lines = sql.splitlines()
    findcol = r'\b%s\b' % column
    keeplines = [ line for line in lines if not re.search(findcol, line) ]
    create = '\n'.join(keeplines)
    create = re.sub(r',(\s*\))', r'\1', create)
    temp = 'tmp%d' % random.randint(1e8, 1e9)
    db.execute("ALTER TABLE %(old)s RENAME TO %(new)s" % { 
        'old': table, 'new': temp })
    db.execute(create)
    db.execute("""
        INSERT INTO %(new)s ( %(columns)s ) 
        SELECT %(columns)s FROM %(old)s
    """ % { 
        'old': temp,
        'new': table,
        'columns': ', '.join(columns)
    })  
    db.execute("DROP TABLE %s" % temp)

def format(sql):
    sql = sql.replace(",", ",\n")
    sql = sql.replace("(", "(\n")
    sql = sql.replace(")", "\n)")
    return sql

Maintient-il également les clés étrangères de la table?
Lasse V. Karlsen

@ LasseV.Karlsen J'ai fait quelques tests et cela devrait maintenir les contraintes de clé étrangère car celles-ci semblent être appliquées par le nom de la table.
spam_eggs

Comment puis-je l'exécuter à partir de Java?
Leos Literak

4

J'ai réécrit la réponse @Udinic afin que le code génère automatiquement une requête de création de table . Il n'a pas non plus besoin ConnectionSource. Il doit également le faire dans une transaction .

public static String getOneTableDbSchema(SQLiteDatabase db, String tableName) {
    Cursor c = db.rawQuery(
            "SELECT * FROM `sqlite_master` WHERE `type` = 'table' AND `name` = '" + tableName + "'", null);
    String result = null;
    if (c.moveToFirst()) {
        result = c.getString(c.getColumnIndex("sql"));
    }
    c.close();
    return result;
}

public List<String> getTableColumns(SQLiteDatabase db, String tableName) {
    ArrayList<String> columns = new ArrayList<>();
    String cmd = "pragma table_info(" + tableName + ");";
    Cursor cur = db.rawQuery(cmd, null);

    while (cur.moveToNext()) {
        columns.add(cur.getString(cur.getColumnIndex("name")));
    }
    cur.close();

    return columns;
}

private void dropColumn(SQLiteDatabase db, String tableName, String[] columnsToRemove) {
    db.beginTransaction();
    try {
        List<String> columnNamesWithoutRemovedOnes = getTableColumns(db, tableName);
        // Remove the columns we don't want anymore from the table's list of columns
        columnNamesWithoutRemovedOnes.removeAll(Arrays.asList(columnsToRemove));

        String newColumnNamesSeparated = TextUtils.join(" , ", columnNamesWithoutRemovedOnes);
        String sql = getOneTableDbSchema(db, tableName);
        // Extract the SQL query that contains only columns
        String oldColumnsSql = sql.substring(sql.indexOf("(")+1, sql.lastIndexOf(")"));

        db.execSQL("ALTER TABLE " + tableName + " RENAME TO " + tableName + "_old;");
        db.execSQL("CREATE TABLE `" + tableName + "` (" + getSqlWithoutRemovedColumns(oldColumnsSql, columnsToRemove)+ ");");
        db.execSQL("INSERT INTO " + tableName + "(" + newColumnNamesSeparated + ") SELECT " + newColumnNamesSeparated + " FROM " + tableName + "_old;");
        db.execSQL("DROP TABLE " + tableName + "_old;");
        db.setTransactionSuccessful();
    } catch {
        //Error in between database transaction 
    } finally {
        db.endTransaction();
    }


}

3

DB Browser for SQLite vous permet d'ajouter ou de supprimer des colonnes.

Dans la vue principale, onglet Database Structure, cliquez sur le nom de la table. Un bouton Modify Tableest activé, ce qui ouvre une nouvelle fenêtre dans laquelle vous pouvez sélectionner la colonne / le champ et le supprimer.


2

vous pouvez utiliser Sqlitebrowser. En mode navigateur, pour la base de données et la table respectives, sous la structure tab -database, en suivant l'option Modifier la table, la colonne respective peut être supprimée.


2

J'ai amélioré la réponse user2638929 et maintenant elle peut conserver le type de colonne, la clé primaire, la valeur par défaut, etc.

private static void dropColumn(SupportSQLiteDatabase database, String tableName, List<String> columnsToRemove){
    List<String> columnNames = new ArrayList<>();
    List<String> columnNamesWithType = new ArrayList<>();
    List<String> primaryKeys = new ArrayList<>();
    String query = "pragma table_info(" + tableName + ");";
    Cursor cursor = database.query(query);
    while (cursor.moveToNext()){
        String columnName = cursor.getString(cursor.getColumnIndex("name"));

        if (columnsToRemove.contains(columnName)){
            continue;
        }

        String columnType = cursor.getString(cursor.getColumnIndex("type"));
        boolean isNotNull = cursor.getInt(cursor.getColumnIndex("notnull")) == 1;
        boolean isPk = cursor.getInt(cursor.getColumnIndex("pk")) == 1;

        columnNames.add(columnName);
        String tmp = "`" + columnName + "` " + columnType + " ";
        if (isNotNull){
            tmp += " NOT NULL ";
        }

        int defaultValueType = cursor.getType(cursor.getColumnIndex("dflt_value"));
        if (defaultValueType == Cursor.FIELD_TYPE_STRING){
            tmp += " DEFAULT " + "\"" + cursor.getString(cursor.getColumnIndex("dflt_value")) + "\" ";
        }else if(defaultValueType == Cursor.FIELD_TYPE_INTEGER){
            tmp += " DEFAULT " + cursor.getInt(cursor.getColumnIndex("dflt_value")) + " ";
        }else if (defaultValueType == Cursor.FIELD_TYPE_FLOAT){
            tmp += " DEFAULT " + cursor.getFloat(cursor.getColumnIndex("dflt_value")) + " ";
        }
        columnNamesWithType.add(tmp);
        if (isPk){
            primaryKeys.add("`" + columnName + "`");
        }
    }
    cursor.close();

    String columnNamesSeparated = TextUtils.join(", ", columnNames);
    if (primaryKeys.size() > 0){
        columnNamesWithType.add("PRIMARY KEY("+ TextUtils.join(", ", primaryKeys) +")");
    }
    String columnNamesWithTypeSeparated = TextUtils.join(", ", columnNamesWithType);

    database.beginTransaction();
    try {
        database.execSQL("ALTER TABLE " + tableName + " RENAME TO " + tableName + "_old;");
        database.execSQL("CREATE TABLE " + tableName + " (" + columnNamesWithTypeSeparated + ");");
        database.execSQL("INSERT INTO " + tableName + " (" + columnNamesSeparated + ") SELECT "
                + columnNamesSeparated + " FROM " + tableName + "_old;");
        database.execSQL("DROP TABLE " + tableName + "_old;");
        database.setTransactionSuccessful();
    }finally {
        database.endTransaction();
    }
}

PS. J'ai utilisé ici android.arch.persistence.db.SupportSQLiteDatabase, mais vous pouvez facilement le modifier pour l'utiliserandroid.database.sqlite.SQLiteDatabase


2

Je suppose que ce que vous voulez faire, c'est la migration de la base de données. 'Drop'ping une colonne n'existe pas dans SQLite. Mais vous pouvez cependant ajouter une colonne supplémentaire à l'aide de la requête de table ALTER.


1

Vous pouvez utiliser l'administrateur SQlite pour modifier les noms de colonne. Cliquez avec le bouton droit sur le nom de la table et sélectionnez Modifier la table. Vous trouverez ici la structure de la table et vous pouvez facilement la renommer.



1

Comme alternative:

Si vous avez une table avec schéma

CREATE TABLE person(
  id INTEGER PRIMARY KEY,
  first_name TEXT,
  last_name TEXT,
  age INTEGER,
  height INTEGER
);

vous pouvez utiliser une CREATE TABLE...ASinstruction comme CREATE TABLE person2 AS SELECT id, first_name, last_name, age FROM person;, c'est-à-dire laisser de côté les colonnes dont vous ne voulez pas. Déposez ensuite la persontable d' origine et renommez la nouvelle.

Notez que cette méthode produit une table sans PRIMARY KEY et sans contraintes. Pour les conserver, utilisez les méthodes décrites par d'autres pour créer une nouvelle table ou utilisez une table temporaire comme intermédiaire.


1

Cette réponse à une question différente vise à modifier une colonne, mais je pense qu'une partie de la réponse pourrait également fournir une approche utile si vous avez beaucoup de colonnes et que vous ne voulez pas retaper la plupart d'entre elles à la main pour votre instruction INSERT:

https://stackoverflow.com/a/10385666

Vous pouvez vider votre base de données comme décrit dans le lien ci-dessus, puis saisir l'instruction "create table" et un modèle "insert" à partir de ce vidage, puis suivre les instructions de l'entrée de FAQ SQLite "Comment ajouter ou supprimer des colonnes d'un existant table dans SQLite. " (La FAQ est liée ailleurs sur cette page.)


En fait, je viens de réaliser que le vidage n'inclut pas les noms de colonnes dans l'insertion par défaut. Il peut donc être tout aussi bon d'utiliser le pragma .schema pour récupérer les noms de colonne, car vous devrez alors supprimer les déclarations de type dans les deux cas.
burpgrass

1

Mise en œuvre sur la Pythonbase d'informations sur http://www.sqlite.org/faq.html#q11 .

import sqlite3 as db
import random
import string

QUERY_TEMPLATE_GET_COLUMNS = "PRAGMA table_info(@table_name)"
QUERY_TEMPLATE_DROP_COLUMN = """
  BEGIN TRANSACTION;
  CREATE TEMPORARY TABLE @tmp_table(@columns_to_keep);
  INSERT INTO @tmp_table SELECT @columns_to_keep FROM @table_name;
  DROP TABLE @table_name;
  CREATE TABLE @table_name(@columns_to_keep);
  INSERT INTO @table_name SELECT @columns_to_keep FROM @tmp_table;
  DROP TABLE @tmp_table;
  COMMIT;
"""

def drop_column(db_file, table_name, column_name):
    con = db.connect(db_file)
    QUERY_GET_COLUMNS = QUERY_TEMPLATE_GET_COLUMNS.replace("@table_name", table_name)
    query_res = con.execute(QUERY_GET_COLUMNS).fetchall()
    columns_list_to_keep = [i[1] for i in query_res if i[1] != column_name]
    columns_to_keep = ",".join(columns_list_to_keep)
    tmp_table = "tmp_%s" % "".join(random.sample(string.ascii_lowercase, 10))
    QUERY_DROP_COLUMN = QUERY_TEMPLATE_DROP_COLUMN.replace("@table_name", table_name)\
        .replace("@tmp_table", tmp_table).replace("@columns_to_keep", columns_to_keep)
    con.executescript(QUERY_DROP_COLUMN)
    con.close()

drop_column(DB_FILE, TABLE_NAME, COLUMN_NAME)

Ce script crée d'abord une table temporaire aléatoire et insère les données des seules colonnes nécessaires, sauf celle qui sera supprimée. Restaure ensuite la table d'origine en fonction de la table temporaire et supprime la table temporaire.


1

Ma solution, il suffit d'appeler cette méthode.

public static void dropColumn(SQLiteDatabase db, String tableName, String[] columnsToRemove) throws java.sql.SQLException {
    List<String> updatedTableColumns = getTableColumns(db, tableName);
    updatedTableColumns.removeAll(Arrays.asList(columnsToRemove));
    String columnsSeperated = TextUtils.join(",", updatedTableColumns);

    db.execSQL("ALTER TABLE " + tableName + " RENAME TO " + tableName + "_old;");
    db.execSQL("CREATE TABLE " + tableName + " (" + columnsSeperated + ");");
    db.execSQL("INSERT INTO " + tableName + "(" + columnsSeperated + ") SELECT "
            + columnsSeperated + " FROM " + tableName + "_old;");
    db.execSQL("DROP TABLE " + tableName + "_old;");
}

Et méthode auxiliaire pour obtenir les colonnes:

public static List<String> getTableColumns(SQLiteDatabase db, String tableName) {
    ArrayList<String> columns = new ArrayList<>();
    String cmd = "pragma table_info(" + tableName + ");";
    Cursor cur = db.rawQuery(cmd, null);

    while (cur.moveToNext()) {
        columns.add(cur.getString(cur.getColumnIndex("name")));
    }
    cur.close();

    return columns;
}

Cette méthode ne conserve pas le type de colonne, j'ai donc publié une version modifiée de votre code
Berdimurat Masaliev

0
public void DeleteColFromTable(String DbName, String TableName, String ColName){
    SQLiteDatabase db = openOrCreateDatabase(""+DbName+"", Context.MODE_PRIVATE, null);
    db.execSQL("CREATE TABLE IF NOT EXISTS "+TableName+"(1x00dff);");
    Cursor c = db.rawQuery("PRAGMA table_info("+TableName+")", null);
    if (c.getCount() == 0) {

    } else {
        String columns1 = "";
        String columns2 = "";
        while (c.moveToNext()) {
            if (c.getString(1).equals(ColName)) {
            } else {
                columns1 = columns1 + ", " + c.getString(1) + " " + c.getString(2);
                columns2 = columns2 + ", " + c.getString(1);
            }
            if (c.isLast()) {
                db.execSQL("CREATE TABLE IF NOT EXISTS DataBackup (" + columns1 + ");");
                db.execSQL("INSERT INTO DataBackup SELECT " + columns2 + " FROM "+TableName+";");
                db.execSQL("DROP TABLE "+TableName+"");
                db.execSQL("ALTER TABLE DataBackup RENAME TO "+TableName+";");
            }
        }
    }
}

et il suffit d'appeler une méthode

DeleteColFromTable("Database name","Table name","Col name which want to delete");

-1

Vous pouvez également utiliser maintenant le navigateur DB pour SQLite pour manipuler les colonnes


-2

exemple pour ajouter une colonne: -

alter table student add column TOB time;

ici, student est nom_table et TOB est nom_colonne à ajouter.

Il fonctionne et a été testé.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.