table 已经创建后,如何在 SQLite 数据库中设置主键?

How to set primary key in SQLite database after the table have been already created?

如何在 table 已经创建后在 SQLite 中设置主键?

我正在使用 SQLite 创建一个项目,但我忘记提供用户 ID 的主键,现在我该如何设置它?

下面是我的ContactContractclass

public final class ContactContract {

    private ContactContract(){}


    public static class ContactEntry
    {
        public static final String TABLE_NAME="contact_info";
        public static final String KEY_P = "KEY";
        public static final String CONTACT_ID = "contact_id";
        public static final String NAME="name";
        public static final String EMAIL="email";

    }
}

下面是我的 ContactDbHelper class

public class ContactDbHelper extends SQLiteOpenHelper {

    public static final String DATABASE_NAME = "contact_db";
    public static final int DATABASE_VERSION = 1;

    public static final String CREATE_TABLE="create table "+ContactContract.ContactEntry.TABLE_NAME+
            "(" + ContactContract.ContactEntry.KEY_P +"INTEGER PRIMARY KEY," +ContactContract.ContactEntry.CONTACT_ID+" number,"+ContactContract.ContactEntry.NAME+" text,"+
            ContactContract.ContactEntry.EMAIL+" text);";

使用:-

 public static final String CREATE_TABLE="create table "+ContactContract.ContactEntry.TABLE_NAME+
            "(" + ContactContract.ContactEntry.KEY_P +"INTEGER PRIMARY KEY," +ContactContract.ContactEntry.CONTACT_ID+" number,"+ContactContract.ContactEntry.NAME+" text,"+
            ContactContract.ContactEntry.EMAIL+" text);";

将无法按预期工作,因为:-

  1. 您在列名后没有space和INTEGER PRIMARY KEY,

    • 列名将是 KEYINTEGER 而不是 KEY 并且很可能会导致问题。
    • 该列不是主键,因此如果省略列值(通常是这种情况),则不会自动生成有用的列,该值将为空。
  2. 你不能有一个名为 KEY 的列(就像在 INTEGER 之前添加 space 时的情况一样),因为它是一个关键字。也许使用 public static final String KEY_P = "KEYP"; 来克服这个问题。

这样使用:-

public static final String KEY_P = "KEYP";
  • 或除 = "KEY" 之外的其他适合的不是关键字

以及 :-

 public static final String CREATE_TABLE="create table "+ContactContract.ContactEntry.TABLE_NAME+
            "(" + ContactContract.ContactEntry.KEY_P +" INTEGER PRIMARY KEY," +ContactContract.ContactEntry.CONTACT_ID+" number,"+ContactContract.ContactEntry.NAME+" text,"+
            ContactContract.ContactEntry.EMAIL+" text);";

已更正 1 和 2。然后,如果您可以轻松地重新生成数据,那么您可以 re-install 应用程序来应用架构更改。


但是,因为你说数据库已经填充,如果你不能轻松地重新生成数据,那么可以这样做。

假设数据库当前有数据(例如用于测试以下数据的一部分):-

可以使用以下方法更改架构以添加 PRIMARY KEY 列:-

private void addPrimaryKey() {
    String TAG = "ADDPRMRYKEY";
    Log.d(TAG,"Initiated adding the primary key.");
    SQLiteDatabase db = this.getWritableDatabase();
    Cursor csr = db.query(
            "sqlite_master",
            null,
            "name =? AND instr(sql,'PRIMARY KEY') > 0",
            new String[]{ContactContract.ContactEntry.TABLE_NAME},
            null,null,null
    );
    if (csr.getCount() < 1) {
        Log.d(TAG," PRIMARY KEY clause not found for table " + ContactContract.ContactEntry.TABLE_NAME);
        if (CREATE_TABLE.indexOf("PRIMARY KEY") > 0) {
            Log.d(TAG,"PRIMARY KEY clause located in CREATE TABLE SQL so !!!!ALTERING!!!! table " + ContactContract.ContactEntry.TABLE_NAME);
            db.execSQL("ALTER TABLE " + ContactContract.ContactEntry.TABLE_NAME + " RENAME TO OLD" + ContactContract.ContactEntry.TABLE_NAME);
            Log.d(TAG,"RENAMED TABLE " + ContactContract.ContactEntry.TABLE_NAME + " to OLD" + ContactContract.ContactEntry.TABLE_NAME);
            db.execSQL(CREATE_TABLE);
            Log.d(TAG,"CREATED new version of table " + ContactContract.ContactEntry.TABLE_NAME + " !!!!INSERTING DATA EXTRACTED!!!! from old version");
            db.execSQL("INSERT INTO " + ContactContract.ContactEntry.TABLE_NAME + " SELECT null,* FROM OLD" + ContactContract.ContactEntry.TABLE_NAME);
        } else {
            Log.d(TAG,"PRIMARY KEY clause not found in the CREATE TABLE SQL so doing nothing.");
        }
    } else {
        Log.d(TAG,"PRIMARY KEY clause found for table " + ContactContract.ContactEntry.TABLE_NAME + " - Nothing to do!!!!");
    }
    csr.close();
}
  • 请注意,显然 Log.d 语句将被删除,它们已用于 testing/explanation。

以上作品来自

  1. 询问 sqlite_master table 以提取用于创建 table 的 SQL,如果它包含 PRIMARY KEY 子句。

  2. 如果它执行方法 returns 而没有执行任何操作,因为 PRIMARY KEY 已经被定义。

  3. 潜在的 new/replacement table create SQL 检查是否包含 PRIMARY KEY 子句。

  4. 如果没有则什么都不做。

  5. 当前 table 已重命名。

  6. 根据createtableSQL.

  7. 新建一个原名的table
  8. 数据从原来的table复制到新的table。

    • 注意,您不妨考虑删除重命名的原件table。它没有被删除,因为以防万一它是安全的。

addPrimaryKey 方法已按照 :-

合并到数据库助手的构造函数中,而不是增加版本号
public ContactDbHelper(Context context) {
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
    addPrimaryKey(); //<<<<<<<<<< ADDED to Convert
}
  • addPrimaryKey 方法每次都会 运行 但它只会尝试转换数据,如果
    • table 没有 PRIMARY KEY 子句并且
    • CREATE SQL 包含 PRIMARY KEY 子句
    • 假设 PRIMARY KEY 列是第一个定义的列,如果不是,则行 db.execSQL("INSERT INTO " + ContactContract.ContactEntry.TABLE_NAME + " SELECT null,* FROM OLD" + ContactContract.ContactEntry.TABLE_NAME); 应该相应地更改。

测试

设置

该应用程序 运行 使用(KEY_P 注释掉假定当前状态):-

public static final String CREATE_TABLE = "create table " + ContactContract.ContactEntry.TABLE_NAME +
        "(" +
        //ContactContract.ContactEntry.KEY_P + " INTEGER PRIMARY KEY," + //<<<<<<<<<< comment out this line for first run to generate data
        ContactContract.ContactEntry.CONTACT_ID + " number," +
        ContactContract.ContactEntry.NAME + " text," +
        ContactContract.ContactEntry.EMAIL + " text" +
        ");";

自始至终使用的调用 activity 是 :-

public class MainActivity extends AppCompatActivity {

    ContactDbHelper mDBHlpr;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mDBHlpr = new ContactDbHelper(this);
        addSomeData();
        Cursor csr = mDBHlpr.getWritableDatabase().query(ContactContract.ContactEntry.TABLE_NAME,null,null,null,null,null,null);
        DatabaseUtils.dumpCursor(csr);
        csr.close();

        /*
        DataBaseHelper mDBHlpr = DataBaseHelper.getInstance(this);
        Cursor csr = mDBHlpr.getWritableDatabase().query("sqlite_master",null,null,null,null,null,null);
        DatabaseUtils.dumpCursor(csr);
        */
    }

    /**
     * Add some data
     */
    private void addSomeData() {
        if (DatabaseUtils.queryNumEntries(mDBHlpr.getWritableDatabase(), ContactContract.ContactEntry.TABLE_NAME) > 0 ) return;
        Random rnd = new Random();
        for (int i=0;i < 100; i++) {
            mDBHlpr.insertContact(rnd.nextInt(),"Aname" + String.valueOf(i),"Aname" + String.valueOf(i) + "@email.com");
        }
    }
}

这个:-

  1. 实例化数据库助手
  2. 添加一些数据,但仅当 none 已经存在时(100 行随机生成的 contactID),这只是为了证明它有效。
  3. 提取所有行,按照
  4. 转储游标

:-

2019-07-04 15:52:03.813 7758-7758/aso.so56873021recopydb D/ADDPRMRYKEY: Initiated adding the primary key.
2019-07-04 15:52:03.830 7758-7758/aso.so56873021recopydb D/ADDPRMRYKEY:  PRIMARY KEY clause not found for table contact_info
2019-07-04 15:52:03.830 7758-7758/aso.so56873021recopydb D/ADDPRMRYKEY: PRIMARY KEY clause not found in the CREATE TABLE SQL so doing nothing.
2019-07-04 15:52:03.888 7758-7758/aso.so56873021recopydb I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@791f7af
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: 0 {
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out:    contact_id=-1179778271
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out:    name=Aname0
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out:    email=Aname0@email.com
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: }
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: 1 {
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out:    contact_id=1334348157
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out:    name=Aname1
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out:    email=Aname1@email.com
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: }
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: 2 {
2019-07-04 15:52:03.891 7758-7758/aso.so56873021recopydb I/System.out:    contact_id=1123604651
2019-07-04 15:52:03.891 7758-7758/aso.so56873021recopydb I/System.out:    name=Aname2
2019-07-04 15:52:03.891 7758-7758/aso.so56873021recopydb I/System.out:    email=Aname2@email.com

转化

从上面的输出可以看出,调用了 addPrimaryKey 方法,但由于 D/ADDPRMRYKEY: PRIMARY KEY clause not found in the CREATE TABLE SQL so doing nothing.

它什么也没做

因此,只需将 CREATE SQL 更改为包含新列即可:-

public static final String CREATE_TABLE = "create table " + ContactContract.ContactEntry.TABLE_NAME +
        "(" +
        ContactContract.ContactEntry.KEY_P + " INTEGER PRIMARY KEY," + //<<<<<<<<<< comment out this line for first run to generate data
        ContactContract.ContactEntry.CONTACT_ID + " number," +
        ContactContract.ContactEntry.NAME + " text," +
        ContactContract.ContactEntry.EMAIL + " text" +
        ");";
  • 即KEY_P 列不再被注释掉,因此 new/wanted SQL 开始发挥作用。

结果如下:-

2019-07-04 15:56:47.170 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: Initiated adding the primary key.
2019-07-04 15:56:47.175 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY:  PRIMARY KEY clause not found for table contact_info
2019-07-04 15:56:47.176 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: PRIMARY KEY clause located in CREATE TABLE SQL so !!!!ALTERING!!!! table contact_info
2019-07-04 15:56:47.176 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: RENAMED TABLE contact_info to OLDcontact_info
2019-07-04 15:56:47.177 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: CREATED new version of table contact_info !!!!INSERTING DATA EXTRACTED!!!! from old version
2019-07-04 15:56:47.179 7979-7979/aso.so56873021recopydb I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@791f7af
2019-07-04 15:56:47.179 7979-7979/aso.so56873021recopydb I/System.out: 0 {
2019-07-04 15:56:47.179 7979-7979/aso.so56873021recopydb I/System.out:    KEYP=1
2019-07-04 15:56:47.179 7979-7979/aso.so56873021recopydb I/System.out:    contact_id=-1179778271
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out:    name=Aname0
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out:    email=Aname0@email.com
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: }
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: 1 {
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out:    KEYP=2
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out:    contact_id=1334348157
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out:    name=Aname1
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out:    email=Aname1@email.com
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: }
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: 2 {
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out:    KEYP=3
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out:    contact_id=1123604651
2019-07-04 15:56:47.181 7979-7979/aso.so56873021recopydb I/System.out:    name=Aname2
2019-07-04 15:56:47.181 7979-7979/aso.so56873021recopydb I/System.out:    email=Aname2@email.com

即数据已保留,但已引入新列并分配了值。

在 SQLite 中,没有为现有 table 添加主键的选项。因此,如果您想将主键添加到现有 table,请按照以下步骤操作。

  1. 使用列创建 table 并将该列标记为主键。对于全新安装的应用程序,在 onCreate() 方法中,

    CREATE TABLE cities (
    id INTEGER NOT NULL PRIMARY KEY,
    name TEXT NOT NULL
    

    );

  2. 对于现有数据库,在onUpgrade()方法中

     BEGIN TRANSACTION; //db.beginTransaction();
    
     ALTER TABLE cities RENAME TO old_cities;
    
     CREATE TABLE cities (
        id INTEGER NOT NULL PRIMARY KEY,
        name TEXT NOT NULL
     );
    
     INSERT INTO cities SELECT * FROM old_cities;
    
     DROP TABLE old_cities;
    
     COMMIT; //db.setTransactionSuccessful();
    
  3. 在里面编码try/catch,终于阻塞结束交易。

     finally {
      db.endTransaction();
     }
    
  4. 不要忘记增加数据库版本。