Як видалити або додати стовпчик у SQLITE?


240

Я хочу видалити або додати стовпчик у базу даних sqlite

Для видалення стовпця я використовую наступний запит.

ALTER TABLE TABLENAME DROP COLUMN COLUMNNAME

Але це дає помилку

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

Відповіді:


353

АЛЬТЕР ТАБЛИЦЯ 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

Як ви також копіюєте ключ фрейму та індекси?
Джонатан

доки ви спершу створите нову таблицю замість створення з select, вона матиме всі ці речі.
Джон Лорд

1
У нових версіях SQLite RENAME COLUMNпідтримується. 🎉 sqlite.org/releaselog/3_25_0.html
Гроги

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
це досить повільно, але хіба це? для великих таблиць даних?
Joran Beasley

2
Було б краще, якби це було зроблено однією транзакцією, а не потенційним дозволом іншому коду бачити речі в перехідному стані.
Стипендіати доналу

Цей код, як правило, виконується під час оновлення БД, де інший код не працює одночасно. Ви можете створити транзакцію і виконати всі ці команди в ній.
Удиніч

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

4
Крім того, замість того, щоб робити INSERTзаяву, ви також можете створити нову таблицю, зробивши"CREAT TABLE" + tableName + "AS SELECT " + columnsSeperated + " FROM " + tableName + "_old;"
Роберт

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" :)
msouth

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



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?
Leos Literak

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

Браузер БД для SQLite дозволяє додавати або видаляти стовпці.

У головному вікні, на вкладці Database Structure, натисніть на назву таблиці. Modify TableУвімкнена кнопка , яка відкриває нове вікно, де ви можете вибрати стовпчик / поле та видалити його.


2

ви можете використовувати Sqlitebrowser. У режимі браузера для відповідної бази даних та таблиці під структурою бази даних табуляції після опції Змінити таблицю відповідний стовпчик можна буде видалити.


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 для зміни назв стовпців. Клацніть правою кнопкою миші на назві таблиці та виберіть «Редагувати таблицю». Тут ви знайдете структуру таблиці і зможете легко перейменувати її.



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

Цей метод не підтримує типу colunm, тому я опублікував модифіковану версію вашого коду
Бердимурат Масалієв

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;

тут студент - це ім'я таблиці та TOB - ім'я стовпця, яке потрібно додати.

Він працює і перевіряється.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.