Data first many to many for EF Core - sqlite

I have a SQLite DB that I am trying to use with EF Core database first.
It has a table of users, and a table of groups that users can belong to, and it has a mapping table because users can belong to multiple groups.
-- holds users
CREATE TABLE IF NOT EXISTS user (
_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT,
);
-- holds groups users can belong to
CREATE TABLE IF NOT EXISTS group (
_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT,
);
-- holds user group membership
CREATE TABLE IF NOT EXISTS map_group_user (
group_id INTEGER,
user_id INTEGER,
UNIQUE (group_id,user_id) ON CONFLICT REPLACE,
FOREIGN KEY(group_id) REFERENCES group(_id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES user(_id) ON DELETE CASCADE
);
When I scaffold this up I get a warning from dotnet ef scaffold that it could not identify a primary key for map_group_user and it does not generate a model, and neither the User nor Group model contains any reference to the other (expected).

Try adding an explicit primary key to the map_group_user bridge table:
CREATE TABLE IF NOT EXISTS map_group_user (
group_id INTEGER,
user_id INTEGER,
UNIQUE (group_id,user_id) ON CONFLICT REPLACE,
FOREIGN KEY(group_id) REFERENCES "group"(_id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES user(_id) ON DELETE CASCADE,
PRIMARY KEY (group_id, user_id)
);
The logical primary key for the map_group_user table is the combination of group_id and user_id, each combination which should ideally appear only once.
By the way, please avoid naming your tables and columns using reserved SQL keywords, such as group. I don't know if this was giving you an error, but I have placed "group" in double quotes to escape it.

Related

How do I resolve a foreign key mismatch error?

I tried inserting values into my 'JobTypes' table but I keep getting a foreign key mismatch error. I think I've made an error with my foreign keys but I can't quite figure out where exactly as I'm quite new at this, can anyone help me out?
This is what I have so far:
CREATE TABLE Projects (
Proj_ID INTEGER,
Proj_name TEXT,
PRIMARY KEY (Proj_ID)
);
CREATE TABLE Employees (
Emp_ID INTEGER,
Proj_ID INTEGER,
Emp_fname TEXT,
PRIMARY KEY(Emp_ID, Proj_ID),
FOREIGN KEY(Proj_ID) REFERENCES Projects(Proj_ID)
);
CREATE TABLE HourRates (
Job_type TEXT,
Hour_rate TEXT,
PRIMARY KEY(Job_type)
);
CREATE TABLE JobTypes (
Emp_ID INTEGER,
Job_type TEXT,
PRIMARY KEY(Emp_ID)
FOREIGN KEY (Emp_ID) REFERENCES Employees(Emp_ID)
FOREIGN KEY (Job_type) REFERENCES HourRates(Job_type)
);
Every FKEY target must be either a primary key or have an explicit unique index defined. Employees(Emp_ID) does not have a unique constraint or index. If this column is unique, you need to add a unique constraint or define a simple PKEY. Otherwise, you will not be able insert any data into the JobTypes table. The error message generated is confusing, and SQLite should at least issue some kind of warning when you create the JobTypes table, but this is not how it works, perhaps, due to the need for backward compatibility. Anyhow, my guess is that your Employees table actually needs to be split into Employees & Employees_Projects (many-to-many) tables.

How to get the names of foreign key constraints in SQLite?

Does SQLite indeed have a limitation that it is not possible to retrieve the name of a foreign key? I am asking because I couldn't find this limitation mentioned anywhere in their documentation.
For example, I run the following script:
CREATE TABLE
users (
id INTEGER NOT NULL PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL
) ;
CREATE TABLE
orders (
id INTEGER NOT NULL PRIMARY KEY,
user_id INTEGER NOT NULL,
CONSTRAINT fk_users FOREIGN KEY (user_id) REFERENCES users(id)
) ;
Now I would like to check that the key "fk_users" was created indeed, so I run the following PRAGMA:
PRAGMA foreign_key_list(orders);
I would expect to see the name of my foreign key in the first column, but I am seeing some "0" value instead. Moreover, if I create multiple foreign keys with custom names, they are all called either "0" or "1".
Is this indeed a limitation of SQLite, or am I missing something?
There is no mechanism to extract the constraint name.
The table sqlite_master stores a CREATE command in the column "sql". You could query that command and do some parsing to extract the name of the foreign key. An example for a combined foreign key that works for me:
SELECT sql FROM sqlite_master WHERE name = 'song'
yields
CREATE TABLE "song" (
"songid" INTEGER,
"songartist" TEXT,
"songalbum" TEXT,
"songname" TEXT,
CONSTRAINT "fk__song_album" FOREIGN KEY ("songartist", "songalbum") REFERENCES "album" ("albumartist", "albumname")
)
and contains the name "fk__song_album" of the foreign key.
If one alters the foreign key with a query, the content of the sql column is modified/updated:
The text in the sqlite_master.sql column is a copy of the original CREATE statement text that created the object, except normalized as described above and as modified by subsequent ALTER TABLE statements. The sqlite_master.sql is NULL for the internal indexes that are automatically created by UNIQUE or PRIMARY KEY constraints.
https://www.sqlite.org/fileformat2.html
Extra tip:
In order to see the foreign key information in Navicat (Lite) ... right click on a table and choose "Design table". Then select the foreign keys tab.

Am I defining all my keys correctly? Do I need to add an index?

I have a couple tables: Accounts and Employees, and I am trying to store a Rating and Timestamp for any given combination between Accounts and Employees. It should not be possible for two ratings to exist for the same timestamp for a given Account + Employee combination.
This is what I have so far:
CREATE TABLE main_table (
_ID INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
employee_id INTEGER NOT NULL,
rating REAL NOT NULL,
timestamp LONG NOT NULL,
FOREIGN KEY(account_id) REFERENCES ACCOUNTS(_ID),
FOREIGN KEY(employee_id) REFERENCES EMPLOYEES(_ID),
UNIQUE (account_id, employee_id, timestamp));
Is this the correct way to define what I am trying to do? Do I also need to create a separate index?
CREATE INDEX main_table_idx ON main_table (account_id, employee_id, timestamp);
Unique constraints are implemented using a unique index.
Hence, separately creating the index is not necessary.
As explained in the documentation:
In most cases, UNIQUE and PRIMARY KEY constraints are implemented by
creating a unique index in the database. (The exceptions are INTEGER
PRIMARY KEY and PRIMARY KEYs on WITHOUT ROWID tables.)
Based on your description, the unique constraint seems correct.

Can someone give me a PK insert sample?

So I'm making things complicated ...I think. A primary key basically is to make the row unique. Is that correct? Anyone want to show me an insert statement with the values for PK?
The SQLite documentation says:
On an INSERT, if the ROWID or INTEGER PRIMARY KEY column is not
explicitly given a value, then it will be filled automatically with an
unused integer, usually one more than the largest ROWID currently in
use. This is true regardless of whether or not the AUTOINCREMENT
keyword is used.
So, on a table like
CREATE TABLE test(id INTEGER PRIMARY KEY, descr TEXT);
an insert with a valid id could be
INSERT INTO test(descr) VALUES('this is a test');
A primary key, also called a primary keyword, is a key in a relational database that is unique for each record. It is a unique identifier, such as a driver license number, telephone number (including area code), or vehicle identification number (VIN). A relational database must always have one and only one primary key.
if you are using CREATE TABLE, if you are creating the primary key on a single field, you can use:
CREATE TABLE mytable (
field1 TEXT,
field2 INTEGER PRIMARY KEY,
field3 BLOB,
);
Reference more at: https://www.sqlite.org/lang_createtable.html & http://sqlite.org/faq.html#q11

Combination of two columns unique constraint

I created the table t1t2 which connects tables t1 and t2 as follows:
CREATE TABLE t1t2(
id integer primary key,
t1_id integer,
t2_id integer,
foreign key(t1_id) references t1(id),
foreign key(t2_id) references t2(id));
Is it possible to define a constraint (restriction) that enables only unique values of tuple (t1_id, t2_id)? Or should I check this in the application?
CREATE UNIQUE INDEX idx_twocols ON t1t2(t1_id, t2_id)
You will probably need to add NOT NULL to the declarations for each of the two columns.
Alternatively, you could choose to forego the primary key column (if all you're using it for is uniqueness) and create the primary key on the combination of t1_id and t2_id:
CREATE TABLE t1t2(
t1_id integer NOT NULL,
t2_id integer NOT NULL,
PRIMARY KEY (t1_id, t2_id),
foreign key(t1_id) references t1(id),
foreign key(t2_id) references t2(id));
The PRIMARY KEY is a special case of a UNIQUE index. Using the composite PRIMARY KEY saves you one column and one index, but requires your application to know both t1_id and t2_id to retrieve a single row from the table.
You can add a unique constraint to your create table statement.
This does not have to be the primary key.
UNIQUE(t1_id, t2_id),
You could create your UNIQUE primary index with those options to keep your primary key and a unique constraint SQL Lite New Index option

Resources