Elimina colonna dalla tabella SQLite


114

Ho un problema: devo eliminare una colonna dal mio database SQLite. Ho scritto questa domanda

alter table table_name drop column column_name 

ma non funziona. Mi aiuti per favore.

Risposte:


207

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

(11) Come aggiungere o eliminare colonne da una tabella esistente in SQLite.

SQLite ha un supporto ALTER TABLE limitato che puoi usare per aggiungere una colonna alla fine di una tabella o per cambiare il nome di una tabella. Se vuoi apportare modifiche più complesse alla struttura di una tabella, dovrai ricreare la tabella. È possibile salvare i dati esistenti in una tabella temporanea, rilasciare la vecchia tabella, creare la nuova tabella, quindi copiare nuovamente i dati dalla tabella temporanea.

Ad esempio, supponi di avere una tabella denominata "t1" con i nomi delle colonne "a", "b" e "c" e di voler eliminare la colonna "c" da questa tabella. I seguenti passaggi illustrano come eseguire questa operazione:

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;

8
+ Leggi sempre la documentazione di SQLite. Noterai troppe limitazioni e differenze nella grammatica SQL quando ricevi errori. La documentazione di SQLite è molto facile da capire. Non ti preoccupare.
AhmetB - Google

2
È necessario eseguire il comando VACUUM dopo aver rimosso le colonne per motivi di sicurezza; senza passare l'aspirapolvere, il file di database contiene ancora i dati delle colonne eliminate.
jj1bdx

@ jj1bdx Non penso che contenga ancora i dati, ma "lo spazio su disco inutilizzato viene aggiunto a una" lista libera "interna e viene riutilizzato la prossima volta che inserisci i dati. Lo spazio su disco non viene perso. Ma non lo è nemmeno restituito al sistema operativo. " come citato dal sito web sqlite3.
Guilherme Salomé

Poiché ho utilizzato più rimozioni di colonne in una transazione, questo ha funzionato solo quando ho rimosso TEMPORARYda CREATE TABLE.
ephemerr

C'è la mia implementazione usando QSqlQuery di Qt: gist.github.com/ephemerr/568d0d41bc389ec78f9fb7d1f015a82a
ephemerr

56

Invece di eliminare la tabella di backup, rinominala semplicemente ...

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;

6
Non funzionerà quando avrai la chiave foregin collegata a t1.
ephemerr

39

Per semplicità, perché non creare la tabella di backup dall'istruzione select?

CREATE TABLE t1_backup AS SELECT a, b FROM t1;
DROP TABLE t1;
ALTER TABLE t1_backup RENAME TO t1;

3
Questo approccio sembra preservare i tipi di dati delle colonne, mentre qualcosa come la risposta accettata sembra comportare che tutte le colonne siano di tipo TEXT.
Uwe Keim

2
Queste istruzioni dovrebbero anche essere incluse in una transazione.
Georg Schölly

10
Si noti che questo non conserva la chiave primaria e sqlite non supporta la modifica delle tabelle per aggiungere la chiave primaria. Quindi, se la chiave primaria è importante, non è quella che dovresti usare
Tim

2
Anche questo non conserva NOT NULL.
FutureShocked

la risposta accettata funziona bene. Dovresti specificare i tipi di dati quando crei la tabella. Sospiro.
John Lord,

8

Questa opzione funziona solo se è possibile aprire il database in un browser di database come DB Browser per SQLite .

In DB Browser per SQLite:

  1. Vai alla scheda "Struttura del database"
  2. Seleziona la tua tabella Seleziona Modifica tabella (appena sotto le schede)
  3. Seleziona la colonna che desideri eliminare
  4. Fare clic su Rimuovi campo e fare clic su OK

3

=> Crea una nuova tabella direttamente con la seguente query:

CREATE TABLE table_name (Column_1 TEXT,Column_2 TEXT);

=> Ora inserisci i dati in nome_tabella da tabella_esistente con la seguente query:

INSERT INTO table_name (Column_1,Column_2) FROM existing_table;

=> Ora rilascia la tabella_esistente seguendo la query:

DROP TABLE existing_table;

1

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

1

Ho creato una funzione Python in cui inserisci la tabella e la colonna da rimuovere come argomenti:

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

Secondo le informazioni sulle risposte di Duda e MeBigFatGuy, questo non funzionerà se c'è una chiave esterna sul tavolo, ma questo può essere risolto con 2 righe di codice (creando una nuova tabella e non solo rinominando la tabella temporanea)


Cos'è c? Cos'è conn? Questa risposta fa troppe ipotesi sulle variabili disponibili di tipo sconosciuto.
Ivan Castellanos

0

Nel caso in cui qualcuno abbia bisogno di una funzione PHP (quasi) pronta per l'uso, quanto segue si basa su questa risposta :

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

A differenza di altre risposte, l'SQL utilizzato in questo approccio sembra preservare i tipi di dati delle colonne, mentre qualcosa di simile alla risposta accettata sembra dare come risultato che tutte le colonne siano di tipo TEXT.

Aggiornamento 1:

L'SQL utilizzato presenta l'inconveniente che le autoincrementcolonne non vengono preservate.


0

Nel caso in cui potesse aiutare qualcuno come me.

Sulla base del sito Web ufficiale e della risposta accettata , ho creato un codice utilizzando C # che utilizza il pacchetto NuGet System.Data.SQLite .

Questo codice conserva anche la chiave primaria e la chiave esterna .

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

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

Per maggiori informazioni: https://www.techonthenet.com/sqlite/tables/alter_table.php

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.