Como excluir ou adicionar coluna no SQLITE?

239

Quero excluir ou adicionar coluna no banco de dados sqlite

Estou usando a consulta a seguir para excluir a coluna.

ALTER TABLE TABLENAME DROP COLUMN COLUMNNAME

Mas dá erro

System.Data.SQLite.SQLiteException: SQLite error
near "DROP": syntax error
Sandip
fonte
1
Possível duplicata da coluna Excluir da tabela SQLite
Ans da comunidade

Respostas:

353

ALTER TABLE SQLite

O SQLite suporta um subconjunto limitado de ALTER TABLE. O comando ALTER TABLE no SQLite permite que o usuário renomeie uma tabela ou adicione uma nova coluna a uma tabela existente. Não é possível renomear uma coluna, remover uma coluna ou adicionar ou remover restrições de uma tabela.

Você pode:

  1. crie uma nova tabela como a que você está tentando alterar,
  2. copie todos os dados,
  3. deixe cair a mesa velha,
  4. renomeie o novo.
Michał Powaga
fonte
47
stackoverflow.com/a/5987838/1578528 fornece um exemplo básico para executar a tarefa.
Bikram990
4
Antes de fazer essa sequência e nos casos em que existem tabelas externas referentes a essa tabela, é preciso chamar PRAGMA foreign_keys=OFF. Nesse caso, após fazer essa sequência, é preciso chamar PRAGMA foreign_keys=ONpara reativar as chaves estrangeiras.
Pazo
Como você também copia a chave freoign e os índices?
Jonathan
contanto que você crie uma nova tabela primeiro, em vez de criar da seleção, ela terá todas essas coisas.
John Lord
1
Nas versões mais recentes do SQLite, RENAME COLUMNé suportado. 🎉 sqlite.org/releaselog/3_25_0.html
Grogs
46

Eu escrevi uma implementação Java com base na maneira recomendada pelo Sqlite de fazer isso:

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;");
}

Para obter a coluna da tabela, usei o "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;
}

Na verdade, eu escrevi sobre isso no meu blog, você pode ver mais explicações aqui:

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

Udinic
fonte
1
isso é bem lento, não é? para tabelas de big data?
Joran Beasley
2
Seria melhor se isso fosse feito em uma única transação, em vez de permitir que outro código veja as coisas em um estado de transição.
Donal Fellows
Normalmente, esse código é executado ao atualizar o banco de dados, onde outro código não está sendo executado simultaneamente. Você pode criar uma transação e executar todos esses comandos nela.
Udinic
3
Tenho certeza de que, se você usar esta solução, as colunas da tabela de resultados ficarão completamente vazias - nenhuma informação de tipo, PK, FK, valores padrão, restrições exclusivas ou de verificação permanecerão. Tudo o que importa para a nova tabela é o nome da coluna. Além disso, como ele não desabilita as chaves estrangeiras antes da execução, os dados em outras tabelas também podem ser danificados.
ACK_stoverflow
4
Como alternativa, em vez de fazer uma INSERTdeclaração, você também pode criar a nova tabela fazendo um #"CREAT TABLE" + tableName + "AS SELECT " + columnsSeperated + " FROM " + tableName + "_old;"
Robert
26

Como outros apontaram

Não é possível renomear uma coluna, remover uma coluna ou adicionar ou remover restrições de uma tabela.

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

Enquanto você sempre pode criar uma nova tabela e soltar a mais antiga. Vou tentar explicar esta solução alternativa com um exemplo.

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       

Agora você deseja remover a coluna heightdesta tabela.

Crie outra tabela chamada new_person

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

Agora copie os dados da tabela antiga

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>

Agora solte a persontabela e renomeie new_personparaperson

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

Então agora, se você fizer um .schema, verá

sqlite>.schema
CREATE TABLE "person"(
 id INTEGER PRIMARY KEY, 
 first_name TEXT, 
 last_name TEXT, 
 age INTEGER 
);
Tasdik Rahman
fonte
E as referências estrangeiras? A Oracle reclamaria se você excluir uma tabela que alguma outra tabela está usando.
Leos Literak
6
Eu posso dizer que você é um verdadeiro programador. Você ficou sem nomes logo após John Doe e foi direto para "foo bar" :)
msouth
1
CREATE TABLE new_person AS SELECT id, first_name, last_name, age FROM person;
Clay
12

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

Como você pode ver no diagrama, apenas ADD COLUMN é suportado. Existe uma solução alternativa (meio pesada): http://www.sqlite.org/faq.html#q11

LeleDumbo
fonte
A "solução alternativa" simplesmente não parece correta. Você está perdendo tipos de colunas, restrições, índices e o que não.
Kenet Jervet 8/09
4

Como outros já apontaram, a ALTER TABLEdeclaração do sqlite não suporta DROP COLUMN, e a receita padrão para fazer isso não preserva restrições e índices.

Aqui está um código python para fazer isso de maneira genérica, mantendo todas as principais restrições e índices.

Faça backup do seu banco de dados antes de usar! Essa função depende de medicar a instrução CREATE TABLE original e é potencialmente um pouco insegura - por exemplo, ela fará a coisa errada se um identificador contiver uma vírgula ou parêntese incorporado.

Se alguém quiser contribuir com uma maneira melhor de analisar o SQL, isso seria ótimo!

ATUALIZAÇÃO Encontrei uma maneira melhor de analisar usando osqlparsepacote decódigo aberto. Se houver algum interesse, vou postá-lo aqui, basta deixar um comentário pedindo ...

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
spam_eggs
fonte
Também mantém chaves estrangeiras na tabela?
Lasse V. Karlsen
@ LasseV.Karlsen Fiz alguns testes e ele deve manter restrições de chave estrangeira, pois elas parecem ser aplicadas pelo nome da tabela.
spam_eggs 02/09
Como posso executá-lo em Java?
Leos Literak
4

Eu reescrevi a resposta @Udinic para que o código gera consulta de criação de tabela automaticamente . Também não precisa ConnectionSource. Ele também precisa fazer isso dentro de uma transação .

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();
    }


}
soshial
fonte
3

O DB Browser for SQLite permite adicionar ou soltar colunas.

Na vista principal, guia Database Structure, clique no nome da tabela. Um botão Modify Tableé ativado, o que abre uma nova janela onde você pode selecionar a coluna / campo e removê-la.

carneiros
fonte
2

você pode usar o Sqlitebrowser. No modo de navegador, para o respectivo banco de dados e a tabela, na estrutura da guia -database, seguindo a opção Modify Table, a respectiva coluna pode ser removida.

Iiamigham
fonte
2

Eu acho que o que você quer fazer é a migração de banco de dados. 'Soltar' uma coluna não existe no SQLite. Mas você pode, no entanto, adicionar uma coluna extra usando a consulta da tabela ALTER.

Subha_26
fonte
1

Você pode usar o SQlite Administrator para alterar os nomes das colunas. Clique com o botão direito do mouse no nome da tabela e selecione Editar tabela. Aqui você encontrará a estrutura da tabela e poderá renomeá-la facilmente.

Tushar Baxi
fonte
1

Como o SQLite possui suporte limitado ao ALTER TABLE, você pode adicionar apenas a coluna no final da tabela OR CHANGE TABLE_NAME no SQLite.

Aqui está a melhor resposta de COMO EXCLUIR A COLUNA DO SQLITE?

visite Excluir coluna da tabela SQLite

Gaurav Singla
fonte
1

Como uma alternativa:

Se você tiver uma tabela com esquema

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

você pode usar uma CREATE TABLE...ASdeclaração como CREATE TABLE person2 AS SELECT id, first_name, last_name, age FROM person;, por exemplo, deixar de fora as colunas que não deseja. Solte a persontabela original e renomeie a nova.

Observe que este método produz uma tabela sem PRIMARY KEY e sem restrições. Para preservá-las, utilize os métodos descritos para criar uma nova tabela ou use uma tabela temporária como intermediária.

karkarlawawa
fonte
1

Esta resposta a uma pergunta diferente é orientada para a modificação de uma coluna, mas acredito que uma parte da resposta também pode fornecer uma abordagem útil se você tiver muitas colunas e não quiser redigitar a maioria delas manualmente para sua instrução INSERT:

https://stackoverflow.com/a/10385666

Você pode despejar seu banco de dados conforme descrito no link acima, pegar a instrução "create table" e um modelo "insert" desse despejo, e seguir as instruções na entrada da SQLite FAQ "Como adicionar ou excluir colunas de um existente tabela no SQLite ". (As perguntas frequentes estão vinculadas a outros lugares desta página.)

burpgrass
fonte
Na verdade, acabei de perceber que o despejo não inclui nomes de colunas na inserção por padrão. Portanto, pode ser igualmente bom usar o pragma .schema para obter nomes de colunas, pois você precisará excluir as declarações de tipo de qualquer maneira.
burpgrass
1

Implementação Pythonbaseada em informações em 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)

Esse script cria primeiro uma tabela temporária aleatória e insere dados apenas das colunas necessárias, exceto a que será descartada. Em seguida, restaura a tabela original com base na tabela temporária e descarta a tabela temporária.

Akif
fonte
1

Minha solução, só preciso chamar esse método.

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;");
}

E método auxiliar para obter as colunas:

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;
}
user2638929
fonte
Este método não manter tipo colunm, então eu postei versão modificada do seu código
Berdimurat Masaliev
1

Eu melhorei a resposta user2638929 e agora pode preservar o tipo de coluna, chave primária, valor padrão 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. Eu usei aqui android.arch.persistence.db.SupportSQLiteDatabase, mas você pode modificá-lo facilmente para usoandroid.database.sqlite.SQLiteDatabase

Berdimurat Masaliev
fonte
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+";");
            }
        }
    }
}

e apenas chame um método

DeleteColFromTable("Database name","Table name","Col name which want to delete");
Sagar Makhija
fonte
-1

exemplo para adicionar uma coluna: -

alter table student add column TOB time;

aqui student é table_name e TOB é column_name a ser adicionado.

Está funcionando e testado.

Demolidor Kallol
fonte
-1

Agora você também pode usar o navegador DB para SQLite para manipular colunas

Jamal Mahroof
fonte