DEĞİŞTİRME TABLOSU SQLite'de YOKSA SÜTUN EKLE


92

Yakın zamanda mevcut SQLite veritabanı tablolarımızdan birkaçına sütun eklememiz gerekti. Bu ile yapılabilir ALTER TABLE ADD COLUMN. Elbette, masa zaten değiştirilmişse, onu yalnız bırakmak istiyoruz. Ne yazık ki, SQLite IF NOT EXISTSon cümlesini desteklemiyor ALTER TABLE.

Şu anki çözümümüz, ALTER TABLE deyimini çalıştırmak ve "yinelenen sütun adı" hatalarını yok saymaktır, tıpkı bu Python örneğinde olduğu gibi (ancak C ++ 'da).

Ancak, veritabanı şemalarını kurmaya yönelik genel yaklaşımımız , veya komut satırı aracı kullanılarak çalıştırılabilen CREATE TABLE IF NOT EXISTSve CREATE INDEX IF NOT EXISTSifadeleri içeren bir .sql betiğine sahip olmaktır . Biz koyamazsınız bu ifade başarısız olursa, ondan sonra şey yürütülmez, çünkü bu komut dosyalarında.sqlite3_execsqlite3ALTER TABLE

Tablo tanımlarının tek bir yerde olmasını ve .sql ve .cpp dosyaları arasında bölünmemesini istiyorum. ALTER TABLE ADD COLUMN IF NOT EXISTSSaf SQLite SQL'de geçici çözüm yazmanın bir yolu var mı ?

Yanıtlar:


65

% 99 saf bir SQL yöntemim var. Buradaki fikir, şemanızı versiyonlamaktır. Bunu iki şekilde yapabilirsiniz:

  • PRAGMA user_versionVeritabanı şema sürümünüz için artımlı bir sayı saklamak için 'user_version' pragma komutunu ( ) kullanın.

  • Sürüm numaranızı kendi tanımlı tablonuzda saklayın.

Bu şekilde, yazılım başlatıldığında, veritabanı şemasını kontrol edebilir ve gerekirse ALTER TABLEsorgunuzu çalıştırabilir ve ardından depolanan sürümü artırabilir . Bu, özellikle veritabanınız yıllar içinde birkaç kez büyür ve değişirse, çeşitli güncellemeleri "kör" denemekten çok daha iyidir.


7
Başlangıç ​​değeri nedir user_version? Sıfır varsayıyorum, ancak bunun belgelendiğini görmek güzel olurdu.
Craig McQueen

Bununla bile, sqlite desteklemediğinden IFve ALTER TABLEkoşullu olmadığına göre , saf SQL'de yapılabilir mi? "% 99 saf SQL" ile neyi kastediyorsunuz?
Craig McQueen

1
@CraigMcQueen Başlangıç ​​değerine gelince user_version, 0 gibi görünüyor, ancak gerçekten kullanıcı tanımlı bir değer, yani kendi başlangıç ​​değerinizi oluşturabilirsiniz.
MPelletier

7
user_versionBaşlangıç ​​değeriyle ilgili soru , mevcut bir veritabanınız olduğunda ve daha önce hiç kullanmadıysanız user_version, ancak kullanmaya başlamak istediğinizde ilgilidir, bu nedenle sqlite'ın onu belirli bir başlangıç ​​değerine ayarladığını varsaymanız gerekir.
Craig McQueen

1
@CraigMcQueen Kabul ediyorum, ancak belgelenmemiş gibi görünüyor.
MPelletier

31

Çözümlerden biri, yalnızca sütunları oluşturmak ve sütun zaten mevcutsa ortaya çıkan istisnayı / hatayı yakalamaktır. Birden fazla sütun eklerken, bunları ayrı ALTER TABLE ifadelerine ekleyin, böylece bir kopya diğerlerinin oluşturulmasını engellemez.

Sqlite-net ile böyle bir şey yaptık. Yinelenen sqlite hatalarını diğer sqlite hatalarından ayırt edemediğimiz için mükemmel değil.

Dictionary<string, string> columnNameToAddColumnSql = new Dictionary<string, string>
{
    {
        "Column1",
        "ALTER TABLE MyTable ADD COLUMN Column1 INTEGER"
    },
    {
        "Column2",
        "ALTER TABLE MyTable ADD COLUMN Column2 TEXT"
    }
};

foreach (var pair in columnNameToAddColumnSql)
{
    string columnName = pair.Key;
    string sql = pair.Value;

    try
    {
        this.DB.ExecuteNonQuery(sql);
    }
    catch (System.Data.SQLite.SQLiteException e)
    {
        _log.Warn(e, string.Format("Failed to create column [{0}]. Most likely it already exists, which is fine.", columnName));
    }
}

27

SQLite ayrıca, sütunun adıyla (ve sütunla ilgili diğer bilgilerle) bir tabloda sütun başına bir satır döndüren "table_info" adlı bir pragma ifadesini destekler. Bunu, eksik sütunu kontrol etmek için bir sorguda kullanabilir ve yoksa tabloyu değiştirebilirsiniz.

PRAGMA table_info(foo_table_name)

http://www.sqlite.org/pragma.html#pragma_table_info


32
Sadece bir bağlantı yerine bu aramayı tamamlayacağınız kodu sağlamış olsaydınız, cevabınız çok daha mükemmel olurdu.
Michael Alan Huff

PRAGMA tablo_bilgisi (tablo_adı). Bu komut, tablo_adı'nın her sütununu sonuçta bir satır olarak listeleyecektir. Bu sonuca göre, sütunun var olup olmadığını belirleyebilirsiniz.
Hao Nguyen

2
Bunu, pragmayı daha büyük bir SQL ifadesinin bir bölümünde birleştirerek yapmanın bir yolu var mı? Sütun yoksa, ancak yoksa yalnızca tek bir sorguda eklenecek şekilde mi?
Michael

1
@Michael. Bildiğim kadarıyla hayır yapamazsın. PRAGMA komutunun sorunu, onu sorgulayamamanızdır. komut verileri SQL motoruna
sunmaz

1
Bu bir yarış durumu yaratmaz mı? Diyelim ki sütun adlarını kontrol ediyorum, sütunumun eksik olduğunu görüyorum, ancak bu arada başka bir işlem sütunu ekliyor. Daha sonra sütunu eklemeye çalışacağım ama zaten mevcut olduğu için bir hata alacağım. Sanırım önce veritabanını kilitlemem mi gerekiyor? Ben sqlite için bir noob'um korkarım :).
Ben Farmer

26

Bunu bir DB yükseltme bildiriminde yapıyorsanız, belki de en basit yol, zaten var olabilecek bir alan eklemeye çalışıyorsanız, atılan istisnayı yakalamaktır.

try {
   db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN foo TEXT default null");
} catch (SQLiteException ex) {
   Log.w(TAG, "Altering " + TABLE_NAME + ": " + ex.getMessage());
}

3
İstisna tarzı programlamayı sevmiyorum, ancak bu inanılmaz derecede temiz. Belki beni biraz etkiledin.
Stephen J

Ben de sevmiyorum ama C ++ şimdiye kadarki en istisna stil programlama dili. Bu yüzden hala "geçerli" olarak görülebileceğini tahmin ediyorum.
tmighty

SQLite için kullanım durumum = Aptalca basit bir şey / diğer dillerde (MSSQL) tek satırlık bir şey için fazladan bir ton kodlama yapmak istemiyorum. İyi cevap ... "istisna tarzı programlama" olmasına rağmen, bir yükseltme fonksiyonunda / izole edilmiş, bu yüzden kabul edilebilir olduğunu düşünüyorum.
maplemale

Diğerleri bundan hoşlanmasa da, bence bu en iyi çözüm lol
Adam Varhegyi

13

there, PRAGMA'nın bir metodudur table_info (tablo_adı), tablonun tüm bilgilerini döndürür.

İşte kontrol sütunu var veya yok için nasıl kullanılacağı,

    public boolean isColumnExists (String table, String column) {
         boolean isExists = false
         Cursor cursor;
         try {           
            cursor = db.rawQuery("PRAGMA table_info("+ table +")", null);
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    String name = cursor.getString(cursor.getColumnIndex("name"));
                    if (column.equalsIgnoreCase(name)) {
                        isExists = true;
                        break;
                    }
                }
            }

         } finally {
            if (cursor != null && !cursor.isClose()) 
               cursor.close();
         }
         return isExists;
    }

Bu sorguyu döngü kullanmadan da kullanabilirsiniz,

cursor = db.rawQuery("PRAGMA table_info("+ table +") where name = " + column, null);

İmleç imleci = db.rawQuery ("tableName'den * seç", null); sütunlar = cursor.getColumnNames ();
Vahe Gharibyan

1
Sanırım imleci kapatmayı unutmuşsunuz :-)
Pecana

@VaheGharibyan, böylece sadece sütun adlarını almak için DB'nizdeki her şeyi seçeceksiniz ?! Basitçe söylediğiniz şey we give no shit about performance:)).
Farid

Son sorgunun yanlış olduğunu unutmayın. Doğru sorgu şudur: SELECT * FROM pragma_table_info(...)(SELECT'e dikkat edin ve pragma ile tablo bilgisi arasındaki alt çizgi). Aslında hangi sürüme eklediklerinden emin değilim, 3.16.0'da çalışmadı, ancak 3.22.0'da çalışıyor.
PressingOnAlways

3

Daha pragma table_info()büyük bir SQL'in bir parçası olarak sonucunu kullanmak isteyenler için .

select count(*) from
pragma_table_info('<table_name>')
where name='<column_name>';

Anahtar kısım pragma_table_info('<table_name>')yerine kullanmaktır pragma table_info('<table_name>').


Bu cevap, @Robert Hawkey'in yanıtından esinlenmiştir. Bunu yeni bir cevap olarak göndermemin nedeni, onu yorum olarak göndermek için yeterli itibarım olmaması.


1

Bu sorgu ile geldim

SELECT CASE (SELECT count(*) FROM pragma_table_info(''product'') c WHERE c.name = ''purchaseCopy'') WHEN 0 THEN ALTER TABLE product ADD purchaseCopy BLOB END
  • İç sorgu, sütun varsa 0 veya 1 döndürür.
  • Sonuca göre sütunu değiştirin

code = Hata (1), message = System.Data.SQLite.SQLiteException (0x800007BF): "ALTER" yakınındaki SQL mantık hatası: System.Data.SQLite.SQLite3.Prepare'de sözdizimi hatası
イ ン コ グ ニ ト ア レ ク セ イ

Dizelerin etrafındaki (product ve PurchCopy) 2 basit alıntıda bir yazım hatası var ama "THEN ALTER TABLE" yüzünden çalışmasını sağlayamıyorum. Bunun mümkün olduğuna emin misin? Bu işe yararsa, kabul edilen cevap olmalıdır.
Neekobus


0

Yukarıdaki cevabı C # /. Net'te aldım ve Qt / C ++ için yeniden yazdım, çok fazla değişmedi, ancak gelecekte C ++ 'ish' cevabı arayan herkes için burada bırakmak istedim.

    bool MainWindow::isColumnExisting(QString &table, QString &columnName){

    QSqlQuery q;

    try {
        if(q.exec("PRAGMA table_info("+ table +")"))
            while (q.next()) {
                QString name = q.value("name").toString();     
                if (columnName.toLower() == name.toLower())
                    return true;
            }

    } catch(exception){
        return false;
    }
    return false;
}

0

Alternatif olarak, bir sütunun var olup olmadığını öğrenmek için CASE-WHEN TSQL ifadesini pragma_table_info ile birlikte kullanabilirsiniz:

select case(CNT) 
    WHEN 0 then printf('not found')
    WHEN 1 then printf('found')
    END
FROM (SELECT COUNT(*) AS CNT FROM pragma_table_info('myTableName') WHERE name='columnToCheck') 

burada masayı nasıl değiştiririz? sütun adı eşleşmesi olduğunda?
user2700767

0

İşte benim çözümüm, ancak python'da (python ile ilgili konuyla ilgili herhangi bir gönderi bulmayı denedim ve bulamadım):

# modify table for legacy version which did not have leave type and leave time columns of rings3 table.
sql = 'PRAGMA table_info(rings3)' # get table info. returns an array of columns.
result = inquire (sql) # call homemade function to execute the inquiry
if len(result)<= 6: # if there are not enough columns add the leave type and leave time columns
    sql = 'ALTER table rings3 ADD COLUMN leave_type varchar'
    commit(sql) # call homemade function to execute sql
    sql = 'ALTER table rings3 ADD COLUMN leave_time varchar'
    commit(sql)

Tablo bilgilerini almak için PRAGMA kullandım. Sütunlar hakkında bilgi içeren çok boyutlu bir dizi döndürür - sütun başına bir dizi. Sütun sayısını elde etmek için dizi sayısını sayıyorum. Yeterli sütun yoksa, ALTER TABLE komutunu kullanarak sütunları eklerim.


0

Her seferinde bir satır uygularsanız tüm bu cevaplar iyidir. Ancak asıl soru, tek bir db çalıştırma tarafından yürütülecek bir sql betiği girmekti ve tüm çözümler (kolonun önceden orada olup olmadığını kontrol etmek gibi), yürütme programının ya hangi tablolar hakkında bilgi sahibi olmasını ve sütunlar değiştiriliyor / ekleniyor veya bu bilgiyi belirlemek için girdi komut dosyasının ön işlemesini ve ayrıştırmasını yapıyor. Genellikle bunu gerçek zamanlı olarak veya sık sık çalıştırmayacaksınız. Dolayısıyla, bir istisna yakalama fikri kabul edilebilir ve sonra devam eder. Sorun burada yatıyor ... nasıl devam edileceği. Neyse ki hata mesajı bize bunu yapmamız için gereken tüm bilgileri veriyor. Buradaki fikir, eğer bir alter tablo çağrısında istisnalar varsa, sql'de alter table satırını bulabilir ve kalan satırları geri döndürebilir ve başarılı oluncaya veya başka eşleşen tablo satırı bulunmayana kadar çalıştırabiliriz. İşte bir dizide sql betiklerimizin olduğu bazı örnek kodlar. Her betiği çalıştıran diziyi yineliyoruz. Alter table komutunun başarısız olmasını sağlamak için iki kez çağırıyoruz ancak program başarılı oluyor çünkü sql'den alter table komutunu kaldırıp güncellenmiş kodu yeniden çalıştırıyoruz.

#!/bin/sh
# the next line restarts using wish \

exec /opt/usr8.6.3/bin/tclsh8.6  "$0" ${1+"$@"}
foreach pkg {sqlite3 } {
    if { [ catch {package require {*}$pkg } err ] != 0 } {
    puts stderr "Unable to find package $pkg\n$err\n ... adjust your auto_path!";
    }
}
array set sqlArray {
    1 {
    CREATE TABLE IF NOT EXISTS Notes (
                      id INTEGER PRIMARY KEY AUTOINCREMENT,
                      name text,
                      note text,
                      createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                      updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
                      );
    CREATE TABLE IF NOT EXISTS Version (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        version text,
                        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) )
                        );
    INSERT INTO Version(version) values('1.0');
    }
    2 {
    CREATE TABLE IF NOT EXISTS Tags (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name text,
        tag text,
        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
        );
    ALTER TABLE Notes ADD COLUMN dump text;
    INSERT INTO Version(version) values('2.0');
    }
    3 {
    ALTER TABLE Version ADD COLUMN sql text;
    INSERT INTO Version(version) values('3.0');
    }
}

# create db command , use in memory database for demonstration purposes
sqlite3 db :memory:

proc createSchema { sqlArray } {
    upvar $sqlArray sql
    # execute each sql script in order 
    foreach version [lsort -integer [array names sql ] ] {
    set cmd $sql($version)
    set ok 0
    while { !$ok && [string length $cmd ] } {  
        try {
        db eval $cmd
        set ok 1  ;   # it succeeded if we get here
        } on error { err backtrace } {
        if { [regexp {duplicate column name: ([a-zA-Z0-9])} [string trim $err ] match columnname ] } {
            puts "Error:  $err ... trying again" 
            set cmd [removeAlterTable $cmd $columnname ]
        } else {
            throw DBERROR "$err\n$backtrace"
        }
        }
    }
    }
}
# return sqltext with alter table command with column name removed
# if no matching alter table line found or result is no lines then
# returns ""
proc removeAlterTable { sqltext columnname } {
    set mode skip
    set result [list]
    foreach line [split $sqltext \n ] {
    if { [string first "alter table" [string tolower [string trim $line] ] ] >= 0 } {
        if { [string first $columnname $line ] } {
        set mode add
        continue;
        }
    }
    if { $mode eq "add" } {
        lappend result $line
    }
    }
    if { $mode eq "skip" } {
    puts stderr "Unable to find matching alter table line"
    return ""
    } elseif { [llength $result ] }  { 
    return [ join $result \n ]
    } else {
    return ""
    }
}
               
proc printSchema { } {
    db eval { select * from sqlite_master } x {
    puts "Table: $x(tbl_name)"
    puts "$x(sql)"
    puts "-------------"
    }
}
createSchema sqlArray
printSchema
# run again to see if we get alter table errors 
createSchema sqlArray
printSchema

beklenen çıktı

Table: Notes
CREATE TABLE Notes (
                      id INTEGER PRIMARY KEY AUTOINCREMENT,
                      name text,
                      note text,
                      createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                      updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
                      , dump text)
-------------
Table: sqlite_sequence
CREATE TABLE sqlite_sequence(name,seq)
-------------
Table: Version
CREATE TABLE Version (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        version text,
                        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) )
                        , sql text)
-------------
Table: Tags
CREATE TABLE Tags (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name text,
        tag text,
        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
        )
-------------
Error:  duplicate column name: dump ... trying again
Error:  duplicate column name: sql ... trying again
Table: Notes
CREATE TABLE Notes (
                      id INTEGER PRIMARY KEY AUTOINCREMENT,
                      name text,
                      note text,
                      createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                      updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
                      , dump text)
-------------
Table: sqlite_sequence
CREATE TABLE sqlite_sequence(name,seq)
-------------
Table: Version
CREATE TABLE Version (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        version text,
                        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
                        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) )
                        , sql text)
-------------
Table: Tags
CREATE TABLE Tags (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name text,
        tag text,
        createdDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) ,
        updatedDate integer(4) DEFAULT ( cast( strftime('%s', 'now') as int ) ) 
        )
-------------

0
select * from sqlite_master where type = 'table' and tbl_name = 'TableName' and sql like '%ColumnName%'

Mantık: sqlite_master'daki sql sütunu tablo tanımını içerir, bu nedenle kesinlikle sütun adıyla dizge içerir.

Bir alt dizeyi ararken, bariz sınırlamaları vardır. Bu nedenle, ColumnName'de daha da kısıtlayıcı alt dizeler kullanmanızı öneririm, örneğin şöyle bir şey ('' 'karakteri her zaman orada olmadığı için teste tabi)

select * from sqlite_master where type = 'table' and tbl_name = 'MyTable' and sql like '%`MyColumn` TEXT%'

0

2 sorguda çözerim. Bu, System.Data.SQLite kullanan Unity3D betiğim.

IDbCommand command = dbConnection.CreateCommand();
            command.CommandText = @"SELECT count(*) FROM pragma_table_info('Candidat') c WHERE c.name = 'BirthPlace'";
            IDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                try
                {
                    if (int.TryParse(reader[0].ToString(), out int result))
                    {
                        if (result == 0)
                        {
                            command = dbConnection.CreateCommand();
                            command.CommandText = @"ALTER TABLE Candidat ADD COLUMN BirthPlace VARCHAR";
                            command.ExecuteNonQuery();
                            command.Dispose();
                        }
                    }
                }
                catch { throw; }
            }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.