137

I have a problem: I need to delete a column from my SQLite database. I wrote this query

alter table table_name drop column column_name 

but it does not work. Please help me.

Manngo
  • 14,066
  • 10
  • 88
  • 110
sandy
  • 1,908
  • 3
  • 19
  • 23

11 Answers11

260

Update: SQLite 2021-03-12 (3.35.0) now supports DROP COLUMN.


From: http://www.sqlite.org/faq.html:

(11) How do I add or delete columns from an existing table in SQLite.

SQLite has limited ALTER TABLE support that you can use to add a column to the end of a table or to change the name of a table. If you want to make more complex changes in the structure of a table, you will have to recreate the table. You can save existing data to a temporary table, drop the old table, create the new table, then copy the data back in from the temporary table.

For example, suppose you have a table named "t1" with columns names "a", "b", and "c" and that you want to delete column "c" from this table. The following steps illustrate how this could be done:

BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
CREATE TABLE t1(a,b);
INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT;
jpseng
  • 1,618
  • 6
  • 18
MeBigFatGuy
  • 28,272
  • 7
  • 61
  • 66
  • 12
    + Always read SQLite documentation. You'll notice too many limitations and differences in SQL grammar when you get errors. SQLite Documentation is very easy to understand. Don't worry about it. – ahmet alp balkan May 13 '11 at 08:00
  • 6
    You need to perform the VACUUM command after removing the columns for security; without vacuuming, the database file still contains the data of deleted columns. – jj1bdx Feb 14 '17 at 05:03
  • @jj1bdx I don't think it still contains the data, but "the unused disk space is added to an internal "free-list" and is reused the next time you insert data. The disk space is not lost. But neither is it returned to the operating system." as quoted from the sqlite3 website. – Guilherme Salomé Aug 23 '17 at 00:49
  • As I used multiple column removals in one transaction this worked only when I removed `TEMPORARY` from `CREATE TABLE`. – ephemerr Dec 20 '17 at 12:53
  • 1
    There is my implementation using Qt's QSqlQuery: https://gist.github.com/ephemerr/568d0d41bc389ec78f9fb7d1f015a82a – ephemerr Dec 20 '17 at 13:04
  • 4
    FAQ still isn't updated but you can check the ALTER TABLE syntax which already contains the description of the DROP COLUMN feature: https://www.sqlite.org/lang_altertable.html#altertabdropcol – user1171760 Jun 09 '21 at 11:00
64

Instead of dropping the backup table, just rename it...

BEGIN TRANSACTION;
CREATE TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
ALTER TABLE t1_backup RENAME TO t1;
COMMIT;
Duda
  • 1,673
  • 17
  • 22
  • 10
    It will not work when you have foregin key connected to `t1`. – ephemerr Dec 20 '17 at 12:54
  • @ephemerr does this mean INSERT INTO inserts the foreign key connection to the new table, but applying RENAME to a table with foreign keys connected to it destroys the foreign key connections? – Mark E. Jan 31 '21 at 00:21
45

For simplicity, why not create the backup table from the select statement?

CREATE TABLE t1_backup AS SELECT a, b FROM t1;
DROP TABLE t1;
ALTER TABLE t1_backup RENAME TO t1;
user4086833
  • 549
  • 5
  • 3
  • 3
    This approach seems to preserve the data types of the columns, whereas something like [the accepted answer](https://stackoverflow.com/a/5987838/107625) seems to result in all columns to be of type `TEXT`. – Uwe Keim Jul 10 '17 at 15:05
  • 2
    These statement should also be wrapped in a transaction. – Georg Schölly Nov 16 '17 at 09:38
  • 11
    Note that this not preserve the primary key and sqlite does not support altering tables to add primary key. So if primary key is important, this is not what you should use – Tim Feb 02 '18 at 11:19
  • 2
    This also doesn't preserve NOT NULL. – FutureShocked Oct 26 '18 at 17:49
  • 1
    the accepted answer works fine. You are supposed to specify the data types when you create the table. Sigh. – John Lord Jun 21 '19 at 18:26
16

This option works only if you can open the DB in a DB Browser like DB Browser for SQLite.

In DB Browser for SQLite:

  1. Go to the tab, "Database Structure"
  2. Select you table Select Modify table (just under the tabs)
  3. Select the column you want to delete
  4. Click on Remove field and click OK
MagTun
  • 5,619
  • 5
  • 63
  • 104
3

=>Create a new table directly with the following query:

CREATE TABLE table_name (Column_1 TEXT,Column_2 TEXT);

=>Now insert the data into table_name from existing_table with the following query:

INSERT INTO table_name (Column_1,Column_2) FROM existing_table;

=>Now drop the existing_table by following query:

DROP TABLE existing_table;
Shahood ul Hassan
  • 745
  • 2
  • 9
  • 19
3
PRAGMA foreign_keys=off;

BEGIN TRANSACTION;

ALTER TABLE table1 RENAME TO _table1_old;

CREATE TABLE table1 (
( column1 datatype [ NULL | NOT NULL ],
  column2 datatype [ NULL | NOT NULL ],
  ...
);

INSERT INTO table1 (column1, column2, ... column_n)
  SELECT column1, column2, ... column_n
  FROM _table1_old;

COMMIT;

PRAGMA foreign_keys=on;

For more info: https://www.techonthenet.com/sqlite/tables/alter_table.php

Nexus242
  • 529
  • 4
  • 7
1

I've made a Python function where you enter the table and column to remove as arguments:

def removeColumn(table, column):
    columns = []
    for row in c.execute('PRAGMA table_info(' + table + ')'):
        columns.append(row[1])
    columns.remove(column)
    columns = str(columns)
    columns = columns.replace("[", "(")
    columns = columns.replace("]", ")")
    for i in ["\'", "(", ")"]:
        columns = columns.replace(i, "")
    c.execute('CREATE TABLE temptable AS SELECT ' + columns + ' FROM ' + table)
    c.execute('DROP TABLE ' + table)
    c.execute('ALTER TABLE temptable RENAME TO ' + table)
    conn.commit()

As per the info on Duda's and MeBigFatGuy's answers this won't work if there is a foreign key on the table, but this can be fixed with 2 lines of code (creating a new table and not just renaming the temporary table)

Dom
  • 19
  • 1
  • 3
0

For SQLite3 c++ :

void GetTableColNames( tstring sTableName , std::vector<tstring> *pvsCols )
{
    UASSERT(pvsCols);

    CppSQLite3Table table1;

    tstring sDML = StringOps::std_sprintf(_T("SELECT * FROM %s") , sTableName.c_str() );



    table1 = getTable( StringOps::tstringToUTF8string(sDML).c_str() );

    for ( int nCol = 0 ; nCol < table1.numFields() ; nCol++ )
    {
        const char* pch1 = table1.fieldName(nCol);  

        pvsCols->push_back( StringOps::UTF8charTo_tstring(pch1));
    }
}


bool ColExists( tstring sColName )
{
    bool bColExists = true;

    try
    {
        tstring sQuery = StringOps::std_sprintf(_T("SELECT %s FROM MyOriginalTable LIMIT 1;") , sColName.c_str() );

        ShowVerbalMessages(false);

        CppSQLite3Query q = execQuery( StringOps::tstringTo_stdString(sQuery).c_str() );

        ShowVerbalMessages(true);
    }
    catch (CppSQLite3Exception& e)
    {
        bColExists = false;
    }

    return bColExists;
}

void DeleteColumns( std::vector<tstring> *pvsColsToDelete )
{
    UASSERT(pvsColsToDelete);

    execDML( StringOps::tstringTo_stdString(_T("begin transaction;")).c_str() );


    std::vector<tstring> vsCols;
    GetTableColNames( _T("MyOriginalTable") , &vsCols );


    CreateFields( _T("TempTable1") , false );

    tstring sFieldNamesSeperatedByCommas;

    for ( int nCol = 0 ; nCol < vsCols.size() ; nCol++ )
    {

        tstring sColNameCurr = vsCols.at(nCol);

        bool bUseCol = true;

        for ( int nColsToDelete = 0; nColsToDelete < pvsColsToDelete->size() ; nColsToDelete++ )
        {
            if ( pvsColsToDelete->at(nColsToDelete) == sColNameCurr )
            {
                bUseCol = false;
                break;
            }
        }

        if ( bUseCol )
            sFieldNamesSeperatedByCommas+= (sColNameCurr + _T(","));

    }

    if ( sFieldNamesSeperatedByCommas.at( int(sFieldNamesSeperatedByCommas.size()) - 1) == _T(','))
        sFieldNamesSeperatedByCommas.erase( int(sFieldNamesSeperatedByCommas.size()) - 1 );

    tstring sDML;


    sDML = StringOps::std_sprintf(_T("insert into TempTable1 SELECT %s FROM MyOriginalTable;\n") , sFieldNamesSeperatedByCommas.c_str() );
    execDML( StringOps::tstringTo_stdString(sDML).c_str() );

    sDML = StringOps::std_sprintf(_T("ALTER TABLE MyOriginalTable RENAME TO MyOriginalTable_old\n") );
    execDML( StringOps::tstringTo_stdString(sDML).c_str() );

    sDML = StringOps::std_sprintf(_T("ALTER TABLE TempTable1 RENAME TO MyOriginalTable\n") );
    execDML( StringOps::tstringTo_stdString(sDML).c_str() );


    sDML = ( _T("DROP TABLE MyOriginalTable_old;") );   
    execDML( StringOps::tstringTo_stdString(sDML).c_str() );


    execDML( StringOps::tstringTo_stdString(_T("commit transaction;")).c_str() );   
}
Sunny127
  • 265
  • 3
  • 8
0

In case anyone needs a (nearly) ready-to-use PHP function, the following is based on this answer:

/**
 * Remove a column from a table.
 * 
 * @param string $tableName The table to remove the column from.
 * @param string $columnName The column to remove from the table.
 */
public function DropTableColumn($tableName, $columnName)
{
    // --
    // Determine all columns except the one to remove.

    $columnNames = array();

    $statement = $pdo->prepare("PRAGMA table_info($tableName);");
    $statement->execute(array());
    $rows = $statement->fetchAll(PDO::FETCH_OBJ);

    $hasColumn = false;

    foreach ($rows as $row)
    {
        if(strtolower($row->name) !== strtolower($columnName))
        {
            array_push($columnNames, $row->name);
        }
        else
        {
            $hasColumn = true;
        }
    }

    // Column does not exist in table, no need to do anything.
    if ( !$hasColumn ) return;

    // --
    // Actually execute the SQL.

    $columns = implode('`,`', $columnNames);

    $statement = $pdo->exec(
       "CREATE TABLE `t1_backup` AS SELECT `$columns` FROM `$tableName`;
        DROP TABLE `$tableName`;
        ALTER TABLE `t1_backup` RENAME TO `$tableName`;");
}

In contrast to other answers, the SQL used in this approach seems to preserve the data types of the columns, whereas something like the accepted answer seems to result in all columns to be of type TEXT.

Update 1:

The SQL used has the drawback that autoincrement columns are not preserved.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
0

Just in case if it could help someone like me.

Based on the Official website and the Accepted answer, I made a code using C# that uses System.Data.SQLite NuGet package.

This code also preserves the Primary key and Foreign key.

CODE in C#:

void RemoveColumnFromSqlite (string tableName, string columnToRemove) {
 try {
    var mSqliteDbConnection = new SQLiteConnection ("Data Source=db_folder\\MySqliteBasedApp.db;Version=3;Page Size=1024;");
    mSqliteDbConnection.Open ();             
    // Reads all columns definitions from table
    List<string> columnDefinition = new List<string> ();
    var mSql = $"SELECT type, sql FROM sqlite_master WHERE tbl_name='{tableName}'";
    var mSqliteCommand = new SQLiteCommand (mSql, mSqliteDbConnection);
    string sqlScript = "";
    using (mSqliteReader = mSqliteCommand.ExecuteReader ()) {
       while (mSqliteReader.Read ()) {
          sqlScript = mSqliteReader["sql"].ToString ();
          break;
       }
    }
    if (!string.IsNullOrEmpty (sqlScript)) {
       // Gets string within first '(' and last ')' characters
       int firstIndex = sqlScript.IndexOf ("(");
       int lastIndex = sqlScript.LastIndexOf (")");
       if (firstIndex >= 0 && lastIndex <= sqlScript.Length - 1) {
          sqlScript = sqlScript.Substring (firstIndex, lastIndex - firstIndex + 1);
       }
       string[] scriptParts = sqlScript.Split (new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
       foreach (string s in scriptParts) {
          if (!s.Contains (columnToRemove)) {
             columnDefinition.Add (s);
          }
       }
    }
    string columnDefinitionString = string.Join (",", columnDefinition);
    // Reads all columns from table
    List<string> columns = new List<string> ();
    mSql = $"PRAGMA table_info({tableName})";
    mSqliteCommand = new SQLiteCommand (mSql, mSqliteDbConnection);
    using (mSqliteReader = mSqliteCommand.ExecuteReader ()) {
       while (mSqliteReader.Read ()) columns.Add (mSqliteReader["name"].ToString ());
    }
    columns.Remove (columnToRemove);
    string columnString = string.Join (",", columns);
    mSql = "PRAGMA foreign_keys=OFF";
    mSqliteCommand = new SQLiteCommand (mSql, mSqliteDbConnection);
    int n = mSqliteCommand.ExecuteNonQuery ();
    // Removes a column from the table
    using (SQLiteTransaction tr = mSqliteDbConnection.BeginTransaction ()) {
       using (SQLiteCommand cmd = mSqliteDbConnection.CreateCommand ()) {
          cmd.Transaction = tr;
          string query = $"CREATE TEMPORARY TABLE {tableName}_backup {columnDefinitionString}";
          cmd.CommandText = query;
          cmd.ExecuteNonQuery ();
          cmd.CommandText = $"INSERT INTO {tableName}_backup SELECT {columnString} FROM {tableName}";
          cmd.ExecuteNonQuery ();
          cmd.CommandText = $"DROP TABLE {tableName}";
          cmd.ExecuteNonQuery ();
          cmd.CommandText = $"CREATE TABLE {tableName} {columnDefinitionString}";
          cmd.ExecuteNonQuery ();
          cmd.CommandText = $"INSERT INTO {tableName} SELECT {columnString} FROM {tableName}_backup;";
          cmd.ExecuteNonQuery ();
          cmd.CommandText = $"DROP TABLE {tableName}_backup";
          cmd.ExecuteNonQuery ();
       }
       tr.Commit ();
    }
    mSql = "PRAGMA foreign_keys=ON";
    mSqliteCommand = new SQLiteCommand (mSql, mSqliteDbConnection);
    n = mSqliteCommand.ExecuteNonQuery ();
 } catch (Exception ex) {
    HandleExceptions (ex);
 }
}
Naveen Kumar V
  • 2,559
  • 2
  • 29
  • 43
0

In Python 3.8... Preserves primary key and column types.

Takes 3 inputs:

  1. a sqlite cursor: db_cur,
  2. table name: t and,
  3. list of columns to junk: columns_to_junk
def removeColumns(db_cur, t, columns_to_junk):

    # Obtain column information
    sql = "PRAGMA table_info(" + t + ")"
    record = query(db_cur, sql)

    # Initialize two strings: one for column names + column types and one just
    # for column names
    cols_w_types = "("
    cols = ""

    # Build the strings, filtering for the column to throw out
    for r in record:
        if r[1] not in columns_to_junk:
            if r[5] == 0:
                cols_w_types += r[1] + " " + r[2] + ","
            if r[5] == 1:
                cols_w_types += r[1] + " " + r[2] + " PRIMARY KEY,"
            cols += r[1] + ","

    # Cut potentially trailing commas
    if cols_w_types[-1] == ",":
        cols_w_types = cols_w_types[:-1]
    else:
        pass

    if cols[-1] == ",":
        cols = cols[:-1]
    else:
        pass

    # Execute SQL
    sql = "CREATE TEMPORARY TABLE xfer " + cols_w_types + ")"
    db_cur.execute(sql)
    sql = "INSERT INTO xfer SELECT " + cols + " FROM " + t
    db_cur.execute(sql)
    sql = "DROP TABLE " + t
    db_cur.execute(sql)
    sql = "CREATE TABLE " + t + cols_w_types + ")"
    db_cur.execute(sql)
    sql = "INSERT INTO " + t + " SELECT " + cols  + " FROM xfer"
    db_cur.execute(sql)

You'll find a reference to a query() function. Just a helper...

Takes two inputs:

  1. sqlite cursor db_cur and,
  2. the query string: query
def query(db_cur, query):

    r = db_cur.execute(query).fetchall()

    return r

Don't forget to include a "commit()"!