Как удалить или добавить столбец в SQLITE?


240

Я хочу удалить или добавить столбец в базе данных sqlite

Я использую следующий запрос для удаления столбца.

ALTER TABLE TABLENAME DROP COLUMN COLUMNNAME

Но это дает ошибку

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

Ответы:


353

ALTER TABLE SQLite

SQLite поддерживает ограниченное подмножество ALTER TABLE. Команда ALTER TABLE в SQLite позволяет пользователю переименовать таблицу или добавить новый столбец в существующую таблицу. Невозможно переименовать столбец, удалить столбец или добавить или удалить ограничения из таблицы.

Ты можешь:

  1. создать новую таблицу как ту, которую вы пытаетесь изменить,
  2. скопировать все данные,
  3. уронить старый стол,
  4. переименуйте новый.

47
stackoverflow.com/a/5987838/1578528 дает базовый пример для выполнения задачи.
bikram990

4
Перед выполнением этой последовательности и в случаях, когда есть внешние таблицы, ссылающиеся на эту таблицу, необходимо вызвать PRAGMA foreign_keys=OFF. В этом случае после выполнения этой последовательности необходимо позвонить PRAGMA foreign_keys=ON, чтобы повторно включить внешние ключи.
PazO

Как также скопировать ключ и индексы freoign?
Джонатан

до тех пор, пока вы сначала создаете новую таблицу, а не создаете из select, у нее будут все эти вещи.
Джон Лорд

1
В более новых версиях SQLite, RENAME COLUMNподдерживается. 🎉 sqlite.org/releaselog/3_25_0.html
Grogs

46

Я написал реализацию Java на основе рекомендованного Sqlite способа сделать это:

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

Чтобы получить столбец таблицы, я использовал «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;
}

Я действительно написал об этом в своем блоге, вы можете увидеть больше объяснений там:

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


1
это довольно медленно, правда? для больших таблиц данных?
Джоран Бизли

2
Было бы лучше, если бы это было сделано в одной транзакции, а не позволяло бы другому коду видеть вещи в переходном состоянии.
Donal Fellows

Этот код я обычно запускаю при обновлении БД, где другой код не работает одновременно. Вы можете создать транзакцию и выполнить все эти команды в ней.
Удинич

3
Я почти уверен, что если вы воспользуетесь этим решением, столбцы в вашей таблице результатов будут полностью пустыми - никаких данных о типе, PK, FK, значениях по умолчанию, уникальных или проверочных ограничениях не останется. Все, что он импортирует в новую таблицу, это имя столбца. Кроме того, поскольку он не отключает внешние ключи перед запуском, данные в других таблицах также могут быть испорчены.
ACK_stoverflow

4
В качестве альтернативы, вместо того, чтобы делать INSERTзаявление, вы также можете создать новую таблицу, выполнив"CREAT TABLE" + tableName + "AS SELECT " + columnsSeperated + " FROM " + tableName + "_old;"
Robert

26

Как уже отмечали другие

Невозможно переименовать столбец, удалить столбец или добавить или удалить ограничения из таблицы.

источник: http://www.sqlite.org/lang_altertable.html

Хотя вы всегда можете создать новую таблицу, а затем отбросить более старую. Я попытаюсь объяснить этот обходной путь на примере.

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       

Теперь вы хотите удалить столбец heightиз этой таблицы.

Создайте другую таблицу с именем new_person

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

Теперь скопируйте данные из старой таблицы

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>

Теперь бросьте personтаблицу и переименуйте new_personвperson

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

Так что теперь, если вы сделаете .schema, вы увидите

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

А как насчет иностранных ссылок? Oracle будет жаловаться, если вы удалите таблицу, которую использует другая таблица.
Леос Литерак

6
Я могу сказать, что вы настоящий программист. У вас закончились имена сразу после Джона Доу, и вы пошли прямо к «Foo Bar» :)
MST

1
CREATE TABLE new_person AS SELECT id, first_name, last_name, age FROM person;
Глина

12

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

Как видно из диаграммы, поддерживается только ADD COLUMN. Однако есть (довольно тяжелый) обходной путь: http://www.sqlite.org/faq.html#q11


«Обходной путь» просто не кажется правильным. Вы теряете типы столбцов, ограничения, индексы, а что нет.
Кенет Джервет


4

Как уже отмечали другие, ALTER TABLEзаявление sqlite не поддерживает DROP COLUMN, и стандартный рецепт для этого не сохраняет ограничений и индексов.

Вот некоторый код на Python, чтобы сделать это в общем, сохраняя все ключевые ограничения и индексы.

Пожалуйста, сделайте резервную копию вашей базы данных перед использованием!Эта функция полагается на исправление оригинального оператора CREATE TABLE и потенциально немного небезопасна - например, она будет делать неправильные вещи, если идентификатор содержит встроенную запятую или скобку.

Если кто-то захочет предложить лучший способ разбора SQL, это было бы здорово!

ОБНОВЛЕНИЕ Я нашел лучший способ анализа с использованиемsqlparseпакетас открытым исходным кодом. Если есть интерес, я опубликую его здесь, просто оставьте комментарий с просьбой об этом ...

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

Поддерживает ли он внешние ключи к таблице?
Лассе В. Карлсен

@ LasseV.Karlsen Я провел несколько тестов, и он должен поддерживать ограничения по внешнему ключу, так как они, похоже, выполняются по имени таблицы.
spam_eggs

Как я могу запустить его с Java?
Леос Литерак

4

Я переписал ответ @Udinic, чтобы код автоматически генерировал запрос на создание таблицы . Это тоже не нужно ConnectionSource. Это также необходимо сделать внутри транзакции .

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 для SQLite позволяет добавлять или удалять столбцы.

В главном представлении tab Database Structureнажмите на название таблицы. Включается кнопка Modify Table, которая открывает новое окно, в котором вы можете выбрать столбец / поле и удалить его.


2

Вы можете использовать Sqlitebrowser. В режиме браузера для соответствующей базы данных и таблицы в структуре tab -database, после опции Modify Table, можно удалить соответствующий столбец.


2

Я улучшил ответ user2638929, и теперь он может сохранять тип столбца, первичный ключ, значение по умолчанию и т. Д.

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. Я использовал здесь android.arch.persistence.db.SupportSQLiteDatabase, но вы можете легко изменить его для использованияandroid.database.sqlite.SQLiteDatabase


2

Я думаю, что вы хотите сделать, это миграция базы данных. «Отбрасывание» столбца не существует в SQLite. Однако вы можете добавить дополнительный столбец, используя запрос таблицы ALTER.


1

Вы можете использовать SQlite Administrator для изменения имен столбцов. Щелкните правой кнопкой мыши по имени таблицы и выберите «Редактировать таблицу». Здесь вы найдете структуру таблицы и сможете легко переименовать ее.



1

Как альтернатива:

Если у вас есть таблица со схемой

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

Вы можете использовать такое CREATE TABLE...ASутверждение, как CREATE TABLE person2 AS SELECT id, first_name, last_name, age FROM person;, например, опустить столбцы, которые вам не нужны. Затем отбросьте исходную personтаблицу и переименуйте новую.

Обратите внимание, что этот метод создает таблицу без PRIMARY KEY и без ограничений. Чтобы сохранить их, используйте методы, описанные другими для создания новой таблицы, или используйте временную таблицу в качестве промежуточной.


1

Этот ответ на другой вопрос ориентирован на изменение столбца, но я считаю, что часть ответа может также дать полезный подход, если у вас много столбцов и вы не хотите перепечатывать большинство из них вручную для вашего оператора INSERT:

https://stackoverflow.com/a/10385666

Вы можете сбросить базу данных, как описано в приведенной выше ссылке, затем извлечь оператор «создать таблицу» и шаблон «вставить» из этого дампа, а затем следовать инструкциям в разделе часто задаваемых вопросов SQLite «Как добавить или удалить столбцы из существующего таблица в SQLite. " (ЧаВо находится в другом месте на этой странице.)


На самом деле, я только что понял, что дамп не включает имена столбцов во вставке по умолчанию. Таким образом, может быть одинаково хорошо просто использовать прагму .schema для захвата имен столбцов, поскольку тогда вам нужно будет удалить объявления типов в любом случае.
отрыжка

1

Реализация Pythonоснована на информации на 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)

Этот скрипт сначала создает случайную временную таблицу и вставляет данные только необходимых столбцов, кроме того, который будет отброшен. Затем восстанавливает исходную таблицу на основе временной таблицы и удаляет временную таблицу.


1

Мое решение, нужно только вызвать этот метод.

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

И вспомогательный метод для получения столбцов:

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

Этот метод не сохраняет тип столбца, поэтому я разместил измененную версию вашего кода
Бердимурат Масалиев

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

и просто вызвать метод

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


-2

Пример добавления столбца: -

alter table student add column TOB time;

здесь student - имя_таблицы, а TOB - имя_столбца для добавления.

Это работает и проверено.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.