SQLite - 如何使用外键

Posted

技术标签:

【中文标题】SQLite - 如何使用外键【英文标题】:SQLite - how to use foreign key 【发布时间】:2021-12-14 14:09:14 【问题描述】:

我一直在尝试让外键在我的 android SQLite 数据库中工作。我试过下面的语法但是外键是空的。

这是我的数据库助手:

    public static final String DATABASE_NAME ="popeye.db";
    public static final int DATABASE_VERSION = 1;
    public final Context context;
    public static final String table12 = "users";

    public DatabaseHelper(@Nullable Context context) 
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    

    @Override
    public void onConfigure(SQLiteDatabase db) 
        db.setForeignKeyConstraintsEnabled(true);
        super.onConfigure(db);

    
    @Override
    public void onCreate(SQLiteDatabase db) 
        String bookletinfo = "CREATE TABLE bookletinfo ( booklet_id INTEGER PRIMARY  KEY AUTOINCREMENT NOT NULL, type VARCHAR NOT NULL, province VARCHAR NOT NULL, citymunicipality NOT NULL, barangay VARCHAR NOT NULL, address VARCHAR NOT NULL, respondent_name VARCHAR NOT NULL, household_head VARCHAR NOT NULL, hh_mem_count INTEGER NOT NULL, created_at DATETIME NOT NULL, updated_at TIMESTAMP)";
        String demographiccharacteristics = "CREATE TABLE demographiccharacteristics (id INTEGER PRIMARY  KEY AUTOINCREMENT NOT NULL, Q1 VARCHAR , Q2 VARCHAR , Q3 VARCHAR , Q4 INTEGER , Q5 DATE , Q6 VARCHAR , Q7 VARCHAR , Q8 VARCHAR , Q9 VARCHAR , Q10 VARCHAR , Q11 VARCHAR , Q12 VARCHAR , Q13 VARCHAR , Q14 VARCHAR , currentbrgy VARCHAR , created_at TIMESTAMP  , updated_at TIMESTAMP, booklet_id INTEGER, FOREIGN KEY(booklet_id) REFERENCES bookletinfo(booklet_id))";
       
        db.execSQL(bookletinfo);
        db.execSQL(demographiccharacteristics);
       
       
    @Override
    public void onOpen(SQLiteDatabase db)
        super.onOpen(db);
        db.setForeignKeyConstraintsEnabled(true);
    
    
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 
        db.execSQL("DROP TABLE IF EXISTS bookletinfo");
        db.execSQL("DROP TABLE IF EXISTS demographiccharacteristics");
        onCreate(db);
    

    public boolean addBooklet(String type1,String province,String municipality,String barangay, String address,String nameofrespondent, String householdhead,  String householdtotal, String date,String updated_at) 
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues contentValues = new ContentValues();
        contentValues.put("type",type1);
        contentValues.put("province",province);
        contentValues.put("citymunicipality",municipality);
        contentValues.put("barangay",barangay);
        contentValues.put("address",address);
        contentValues.put("respondent_name",nameofrespondent);
        contentValues.put("household_head",householdhead);
        contentValues.put("hh_mem_count", householdtotal);
        contentValues.put("created_at", date);
        contentValues.put("updated_at", updated_at);
        
        db.insert("bookletinfo",null,contentValues);
        return true;

    public boolean addDemographic(String etQ1,String etQ2,String etQ3,String etQ4,String etQ5,String etQ6,String etQ7,String etQ8,String etQ9,String etQ10,String etQ11,String etQ12,String etQ13,String etQ14, String etbrgy,String created_at,String updated_at) 
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues contentValues = new ContentValues();
        contentValues.put("Q1",etQ1);
        contentValues.put("Q2",etQ2);
        contentValues.put("Q3",etQ3);
        contentValues.put("Q4",etQ4);
        contentValues.put("Q5",etQ5);
        contentValues.put("Q6",etQ6);
        contentValues.put("Q7", etQ7);
        contentValues.put("Q8",etQ8);
        contentValues.put("Q9",etQ9);
        contentValues.put("Q10",etQ10);
        contentValues.put("Q11",etQ11);
        contentValues.put("Q12",etQ12);
        contentValues.put("Q13",etQ13);
        contentValues.put("Q14", etQ14);
        contentValues.put("currentbrgy",etbrgy);
        contentValues.put("created_at",created_at);
        contentValues.put("updated_at", updated_at);

        db.insert("demographiccharacteristics",null,contentValues);
        return true;
    


这是我将数据保存到小册子信息的代码,它工作正常,数据已保存:

private static final String TAG = "home";
Button save, next, back, button;
EditText etprovince, etmunicipality, etbarangay, ethouseholdhead, etnameofrespondent, ettotalhouseholdmember, etaddress;
DatabaseHelper db;
private TextView mDisplayDate,tvdate2;
private DatePickerDialog.OnDateSetListener mDateSetListener;

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);
    db = new DatabaseHelper(home.this);
    etprovince = findViewById(R.id.province);
    etmunicipality = findViewById(R.id.municipality);
    etbarangay = findViewById(R.id.barangay);
    ethouseholdhead = findViewById(R.id.q2);
    etnameofrespondent = findViewById(R.id.respondentname);
    ettotalhouseholdmember = findViewById(R.id.totalmember);
    etaddress = findViewById(R.id.address);
    tvdate2=findViewById(R.id.q5);

    Spinner type = (Spinner) findViewById(R.id.ettype);
    ArrayAdapter<String> myAdapter = new ArrayAdapter<String>(home.this,
            android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.type));
    myAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    type.setAdapter(myAdapter);

    save = (Button) findViewById(R.id.save);
    save.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            String province = etprovince.getText().toString();
            String municipality = etmunicipality.getText().toString();
            String barangay = etbarangay.getText().toString();
            String householdhead = ethouseholdhead.getText().toString();
            String nameofrespondent = etnameofrespondent.getText().toString();
            String householdtotal = ettotalhouseholdmember.getText().toString();
            String address = etaddress.getText().toString();
            String type1 = type.getSelectedItem().toString();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String date = sdf.format(new Date());

            String updated_at = "";
            if (db.addBooklet(type1,province, municipality, barangay,address,nameofrespondent,householdhead, householdtotal,date,updated_at)) 
                Toast.makeText(getApplicationContext(), "Data Inserted", Toast.LENGTH_SHORT).show();
                Intent registerIntent = new Intent(home.this, Survey2.class);
                startActivity(registerIntent);
             else 
                Toast.makeText(getApplicationContext(), "error", Toast.LENGTH_SHORT).show();
            

        
    );

    back = (Button) findViewById(R.id.back);
    back.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            Intent registerIntent = new Intent(home.this, menu.class);
            startActivity(registerIntent);
        
    );

这是另一个添加和保存数据的方法,它正在工作。但是每次我保存数据时,我的 booklet_id 都是空的。我想要做的是 booklet_idis 自动输入并且“booklet_id INTEGER,FOREIGN KEY(booklet_id)REFERENCES bookletinfo(booklet_id)”不起作用。我需要帮助

private static final String TAG = "Survey2";
private TextView mDisplayDate;
private DatePickerDialog.OnDateSetListener mDateSetListener;

DatabaseHelper db;
Button next,save;
TextView tvDate;
EditText Q1,Q4,brgy,Q6,Q7,Q9,Q10,Q14;



@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_survey2);
    db = new DatabaseHelper(Survey2.this);

    brgy =(EditText) findViewById(R.id.brgy);
    
    Q1 = (EditText)findViewById(R.id.q1);
    Q4 = (EditText)findViewById(R.id.q4);
    Q14 = (EditText)findViewById(R.id.q14);
    Q6 = (EditText)findViewById(R.id.q6);
    Q7 = (EditText)findViewById(R.id.q7);
    Q9 = (EditText)findViewById(R.id.q9);
    Q10 = (EditText)findViewById(R.id.q10);
    tvDate=findViewById(R.id.q5);



    //household head spinner
    Spinner relationship = (Spinner) findViewById(R.id.q2);
    ArrayAdapter<String> myAdapter = new ArrayAdapter<String>(Survey2.this,
            android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.relationship));
    myAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    relationship.setAdapter(myAdapter);
    
    //Gender spinner
    Spinner SEX = (Spinner) findViewById(R.id.q3);
    ArrayAdapter<String> sex = new ArrayAdapter<String>(Survey2.this,
            android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.SEX));
    sex.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    SEX.setAdapter(sex);

    //marital spinner
    Spinner marital1 = (Spinner) findViewById(R.id.q8);
    ArrayAdapter<String> marital = new ArrayAdapter<String>(Survey2.this,
            android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.marital));
    marital.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    marital1.setAdapter(marital);

    //education spinner
    Spinner Education = (Spinner) findViewById(R.id.q11);
    ArrayAdapter<String> education = new ArrayAdapter<String>(Survey2.this,
            android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.education));
    education.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    Education.setAdapter(education);

    //enroled? spinner
    Spinner Enrolled = (Spinner) findViewById(R.id.q12);
    ArrayAdapter<String> enrolled = new ArrayAdapter<String>(Survey2.this,
            android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.currentEnrolled));
    enrolled.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    Enrolled.setAdapter(enrolled);

    //spinner educ level
    Spinner level = (Spinner) findViewById(R.id.q13);
    ArrayAdapter<String> Level = new ArrayAdapter<String>(Survey2.this,
            android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.level));
    Level.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    level.setAdapter(Level);
    

    mDisplayDate = (TextView)findViewById(R.id.q5);
    mDisplayDate.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View view) 
            Calendar cal2 = Calendar.getInstance();
            int y2=cal2.get(Calendar.YEAR);
            int m2=cal2.get(Calendar.MONTH);
            int d2=cal2.get(Calendar.DAY_OF_MONTH);
            DatePickerDialog dialog2 = new DatePickerDialog(Survey2.this,
                    android.R.style.Theme_Holo_Light_Dialog_MinWidth,mDateSetListener,y2,m2,d2);
            dialog2.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            dialog2.show();
        
    );
    mDateSetListener =new DatePickerDialog.OnDateSetListener() 
        @Override
        public void onDateSet(DatePicker datePicker, int y2, int m2, int d2) 
            m2 = m2+1;
            Log.d(TAG, "onDateSet: yyy/mm/dd" + y2 + "-" + m2 + "-" + d2 );
            String date2 = y2 + "-" + m2 + "-" +d2;
            mDisplayDate.setText(date2);
        
    ;

   
    
    save = (Button)findViewById(R.id.save);
    save.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            String etQ1 = Q1.getText().toString();
            String etQ2 = relationship.getSelectedItem().toString();
            String etQ3 = SEX.getSelectedItem().toString();
            String etQ4 = Q4.getText().toString();
            String etQ5= tvDate.getText().toString();
            String etQ6 = Q6.getText().toString();
            String etQ7 = Q7.getText().toString();
            String etQ8 = marital1.getSelectedItem().toString();
            String etQ9 = Q9.getText().toString();
            String etQ10 =Q10.getText().toString();
            String etQ11 = Education.getSelectedItem().toString();
            String etbrgy = brgy.getText().toString();
            String etQ12 = Enrolled.getSelectedItem().toString();
            String etQ13 = level.getSelectedItem().toString();
            String etQ14 = Q14.getText().toString();
            String updated_at =  "";
            //current date

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String created_at = sdf.format(new Date());
            
            if (db.addDemographic(etQ1,etQ2,etQ3,etQ4,etQ5,etQ6,etQ7,etQ8,etQ9,etQ10,etQ11,etQ12,etQ13,etQ14,etbrgy,created_at,updated_at)) 
                Toast.makeText(getApplicationContext(), "Data Inserted", Toast.LENGTH_SHORT).show();
             else 
                Toast.makeText(getApplicationContext(), "error", Toast.LENGTH_SHORT).show();
            

        
    );
    
    
    

【问题讨论】:

我想做的是 booklet_idis 自动输入注意是自动完成的。 那我该怎么办?我的外键需要输入吗? 【参考方案1】:

您可能会认为 SQLite 在编写外键子句时会自动将孩子与父母相关联。它没有也不能。

不能是因为在插入子项时,如果没有指示父项的方式,就无法知道子项的父项。 例如说有父母 1 - 10 哪个父母应该是孩子 100 的父母? (修辞)

外键子句的作用是添加一个约束(一条规则),它表示子表中的列的值必须是存在于父表的某一行中的指定列中的值。如果这条规则被打破,那么就会发生冲突,从而表明违反了参照完整性(也就是没有父母的孩子(孤儿))。

应该理解,外键子句与描述数据库时使用的外键术语不同。对于存在的外键,不需要外键子句。而是外键子句支持外键的存在。

你必须确定可以插入什么。

首先,我建议将方法 addBookletaddDemographic 从返回布尔值更改为返回 long 并返回插入结果。

如果插入了行,您始终可以使用 if (addBooklet(....) &gt; 0) 为真。

您还需要允许在插入 Demographic 时提供 booklet_id 并插入提供的值(添加到 ContentValues)

因此可以将 DatabaseHelper 中的方法更改为:-

public /*boolean*/ long addBooklet(String type1,String province,String municipality,String barangay, String address,String nameofrespondent, String householdhead,  String householdtotal, String date,String updated_at) 
    SQLiteDatabase db = this.getWritableDatabase();
    ContentValues contentValues = new ContentValues();
    contentValues.put("type",type1);
    contentValues.put("province",province);
    contentValues.put("citymunicipality",municipality);
    contentValues.put("barangay",barangay);
    contentValues.put("address",address);
    contentValues.put("respondent_name",nameofrespondent);
    contentValues.put("household_head",householdhead);
    contentValues.put("hh_mem_count", householdtotal);
    contentValues.put("created_at", date);
    contentValues.put("updated_at", updated_at);

    return db.insert("bookletinfo",null,contentValues);
    /*return true;*/

    public /*boolean*/ long addDemographic(/* ADDED >>>>> */long booklet_id,String etQ1,String etQ2,String etQ3,String etQ4,String etQ5,String etQ6,String etQ7,String etQ8,String etQ9,String etQ10,String etQ11,String etQ12,String etQ13,String etQ14, String etbrgy,String created_at,String updated_at) 
    SQLiteDatabase db = this.getWritableDatabase();
    ContentValues contentValues = new ContentValues();
    contentValues.put("booklet_id",booklet_id); /*<<<<< ADDED */
    contentValues.put("Q1",etQ1);
    contentValues.put("Q2",etQ2);
    contentValues.put("Q3",etQ3);
    contentValues.put("Q4",etQ4);
    contentValues.put("Q5",etQ5);
    contentValues.put("Q6",etQ6);
    contentValues.put("Q7", etQ7);
    contentValues.put("Q8",etQ8);
    contentValues.put("Q9",etQ9);
    contentValues.put("Q10",etQ10);
    contentValues.put("Q11",etQ11);
    contentValues.put("Q12",etQ12);
    contentValues.put("Q13",etQ13);
    contentValues.put("Q14", etQ14);
    contentValues.put("currentbrgy",etbrgy);
    contentValues.put("created_at",created_at);
    contentValues.put("updated_at", updated_at);

    return db.insert("demographiccharacteristics",null,contentValues);
    /*return true;*/

注意用于指示所做更改的 cmets

您现在可以在添加新小册子时获取 booklet_id,以便在添加 Demographic 孩子时使用。

例如:-

public class MainActivity extends AppCompatActivity 

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

        db = new DatabaseHelper(this);
        long currentBooklet = db.addBooklet("Type1","Province1","municipality1","barangay1","address1","name1","househol1","1","2021-01-01","2021-01-01");
        if (currentBooklet > 0) 
            db.addDemographic(currentBooklet,"Demo1","Q2","Q3","Q4","Q5","Q6","Q7","Q8","Q9","Q10","Q11","Q12","Q13","Q14","etbrgy","2021-01-01","202-01-01");
            db.addDemographic(currentBooklet,"Demo2","Q2","Q3","Q4","Q5","Q6","Q7","Q8","Q9","Q10","Q11","Q12","Q13","Q14","etbrgy","2021-01-01","202-01-01");
        
    

这导致数据库中有以下数据:-

bookletinfo 表(仅显示部分数据):-

人口统计特征表:-

即这两行已插入,因为没有外键冲突。

现在如果你尝试了

db.addDemographic(10,"Demo2","Q2","Q3","Q4","Q5","Q6","Q7","Q8","Q9","Q10","Q11","Q12","Q13","Q14","etbrgy","2021-01-01","202-01-01");

10 不是任何行的 booklet_id 列中的值(只有 1 行且 booklet_id 为 1)* 那么会发生外键冲突。然而,方便的 insert 方法捕获/捕获底层异常insertOrThrow 方法不会)并在没有插入行时返回 -1。

如果您查看日志,您会发现异常已引发但已被捕获。例如上面唯一的代码导致日志包括:-

2021-10-30 09:19:43.455 22029-22029/a.a.so69769911javasqlitefk E/SQLiteDatabase: Error inserting Q1=Demo2 Q2=Q2 Q3=Q3 Q4=Q4 Q5=Q5 Q6=Q6 Q7=Q7 Q8=Q8 currentbrgy=etbrgy Q9=Q9 created_at=2021-01-01 Q11=Q11 Q10=Q10 Q13=Q13 Q12=Q12 updated_at=202-01-01 Q14=Q14 booklet_id=10
    android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
        at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
        at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:796)
        at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
        at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
        at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1564)
        at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1433)
        at a.a.so69769911javasqlitefk.DatabaseHelper.addDemographic(DatabaseHelper.java:120)
        at a.a.so69769911javasqlitefk.MainActivity.onCreate(MainActivity.java:20)

【讨论】:

以上是关于SQLite - 如何使用外键的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 Java 在 SQLite 中强制执行外键约束?

如何与SQLite建立关系(外键)

世博sqlite外键

如何在 Sqlite 中将两列合二为一,同时获取外键的底层值?

如何向现有 SQLite 表添加外键?

在 SQLite 中启用外键约束