SQLITEで列を削除または追加する方法


239

SQLiteデータベースの列を削除または追加したい

次のクエリを使用して列を削除しています。

ALTER TABLE TABLENAME DROP COLUMN COLUMNNAME

しかし、それはエラーを与えます

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

回答:


353

ALTER TABLE SQLite

SQLiteは、ALTER TABLEの限定されたサブセットをサポートします。SQLiteのALTER TABLEコマンドを使用すると、ユーザーはテーブルの名前を変更したり、既存のテーブルに新しい列を追加したりできます。列の名前を変更したり、列を削除したり、テーブルに制約を追加または削除したりすることはできません。

あなたはできる:

  1. 変更しようとしているテーブルとして新しいテーブルを作成し、
  2. すべてのデータをコピーし、
  3. 古いテーブルを落とす
  4. 新しい名前を変更します。

47
stackoverflow.com/a/5987838/1578528は、タスクを実行するための基本的な例を示しています。
bikram990 2014年

4
このシーケンスを実行する前に、このテーブルを参照する外部テーブルがある場合は、を呼び出す必要がありますPRAGMA foreign_keys=OFF。この場合、このシーケンスを実行した後PRAGMA foreign_keys=ON、外部キーを再度有効にするために呼び出す必要があります。
PazO

どのようにして、freoignキーとインデックスもコピーしますか?
ジョナサン

create from selectの代わりに最初にcreate tableを実行する限り、テーブルにはそれらすべてが含まれます。
John Lord、

1
新しいSQLiteバージョンでRENAME COLUMNは、がサポートされています。🎉sqlite.org /
成長

46

私はこれを行うためにSqliteの推奨される方法に基づいてJava実装を作成しました:

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 2013

2
これが単一のトランザクションで行われた方が、他のコードが遷移状態にあるものを見ることができるようになるよりも良いでしょう。
ドナルフェロー

このコードは通常、他のコードが同時に実行されていないDBのアップグレード時に実行されます。トランザクションを作成し、その中ですべてのコマンドを実行できます。
Udinic 2013年

3
このソリューションを使用すると、結果テーブルの列が完全にむき出しになると確信しています。タイプ情報、PK、FK、デフォルト値、一意制約、またはチェック制約は残りません。新しいテーブルにインポートされるのは列名だけです。さらに、実行前に外部キーを無効にしないため、他のテーブルのデータも台無しになる可能性があります。
ACK_stoverflow 2014年

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_personperson

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は文句を言うでしょう。
Leos Literak 2016

6
あなたは本物のプログラマーだと言えます。
ジョンドーの直後に名前が足り

1
CREATE TABLE new_person AS SELECT id, first_name, last_name, age FROM person;
クレー



4

他の人が指摘したように、sqliteのALTER TABLEステートメントはサポート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

テーブルへの外部キーも維持しますか?
Lasse V. Karlsen、2014

@ LasseV.Karlsen私はいくつかのテストを行いましたが、これらはテーブル名によって強制されているように見えるため、外部キー制約を維持する必要があります。
spam_eggs 14

どうすればJavaから実行できますか?
Leos Literak 2016

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のDBブラウザーでは、列を追加または削除できます。

メインビューのタブDatabase Structureで、テーブル名をクリックします。ボタンModify Tableが有効になり、列/フィールドを選択して削除できる新しいウィンドウが開きます。


2

Sqlitebrowserを使用できます。ブラウザーモードでは、それぞれのデータベースとテーブルについて、[データベース構造]タブの下で、[テーブルの変更]オプションの後に、それぞれの列を削除できます。


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 FAQエントリの「既存の列を追加または削除するにはどうすればよいですか?」 SQLiteのテーブル。」(FAQはこのページの他の場所にリンクされています。)


実際、デフォルトでは、ダンプの挿入に列名が含まれていないことに気づきました。したがって、.schemaプラグマを使用して列名を取得することも同様に適切です。これは、どちらの方法でも型宣言を削除する必要があるためです。
burpgrass

1

http://www.sqlite.org/faq.html#q11のPython情報に基づいた実装。

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

この方法では列のタイプが維持さないため、コードの変更バージョンを投稿しまし
Berdimurat Masaliev

1

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


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

-1

列を追加する例:-

alter table student add column TOB time;

ここで、studenttable_nameTOBは追加するcolumn_nameです

動作し、テストされています。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.