I want to create two tables Category and Product in SQLite.
Category table should be parent and Product table should be a child.
I created two tables in this way, and I do not know it is a good approach:
CREATE TABLE Category (
id BIGINT PRIMARY KEY,
category_name VARCHAR,
category_description VARCHAR,
image_path VARCHAR
):
CREATE TABLE Product (
product_id integer PRIMARY KEY,
product_name VARCHAR,
product_description VARCHAR,
product_numberOfProduct integer,
product_image_path VARCHAR,
product_price integer,
category_id integer NOT NULL,
FOREIGN KEY (category_id) REFERENCES Category(id)
);
I want to create a query for searching all products from category_name and something like that.
Your approach is almost spot on. The major deficiency in your solution is that if a given product can belong to more than one category, then your schema would be duplicating all of that product's metadata for each product-category relationship. The standard way to deal with this is to create a third junction table, which mainly exists to store the relationships between products and their categories. So, your junction table might look something like this:
CREATE TABLE Product_Category (
product_id integer,
category_id integer,
PRIMARY KEY (product_id, category_id)
)
From your current Product table you would remove the reference to the category_id.
As an example query, if you wanted to find out all category names of a given product, you could try:
SELECT
c.category_name
FROM Category c
INNER JOIN Product_Category pc
ON c.id = pc.category_id
INNER JOIN Product p
ON pc.product_id = p.product_id
WHERE
p.product_name = 'some product name';
Related
I have to create a database with a PRODUCTS table and a CATEGORIES table.
Each product has a name, a price, a creation date and may belong to several categories.
Categories have a name and a flag to indicate whether the category is private or public.
Then I have to select all records that belongs to more than 5 public categories.
I've created the tables like this:
CREATE TABLE PRODUCTS (
ID_PROD int NOT NULL PRIMARY KEY,
NAME TEXT(255),
PRICE INTEGER,
CREATION_DATE DATE
);
CREATE TABLE CATEGORIES (
ID_CAT INTEGER NOT NULL PRIMARY KEY,
NAME TEXT(255),
PRIVATE INTEGER
);
CREATE TABLE PROD_CAT (
ID INTEGER NOT NULL PRIMARY KEY,
ID_PROD INTEGER,
ID_CAT INTEGER,
FOREIGN KEY (ID_PROD) REFERENCES PRODUCTS(ID_PROD),
FOREIGN KEY (ID_CAT) REFERENCES CATEGORIES(ID_CAT)
)
I've managed to select all the records that belongs to more than 5 categories but I can't find out how to add the public category condition...
Here's what I've tried:
This works:
SELECT NAME
FROM PRODUCTS
WHERE ID_PROD IN (SELECT ID_PROD FROM PROD_CAT GROUP BY ID_PROD HAVING COUNT(*)>5)
But not this:
SELECT PRODUCTS.NAME
FROM PRODUCTS, CATEGORIES
WHERE ID_PROD IN (SELECT ID_PROD FROM PROD_CAT GROUP BY ID_PROD HAVING COUNT(*)>5)
AND CATEGORIES.PRIVATE = 1
Any help would be appreciated :)
You need a join of PROD_CAT to CATEGORIES:
SELECT NAME
FROM PRODUCTS
WHERE ID_PROD IN (
SELECT pc.ID_PROD
FROM PROD_CAT pc INNER JOIN CATEGORIES c
ON c.ID_CAT = pc.ID_CAT
WHERE c.PRIVATE -- or WHERE NOT c.PRIVATE for public categories
GROUP BY pc.ID_PROD
HAVING COUNT(*) > 5
)
Or, without the operator IN, with joins of all 3 tables:
SELECT p.ID_PROD, p.NAME
FROM PRODUCTS p
INNER JOIN PROD_CAT pc ON pc.ID_PROD = p.ID_PROD
INNER JOIN CATEGORIES c ON c.ID_CAT = pc.ID_CAT
WHERE c.PRIVATE -- or WHERE NOT c.PRIVATE for public categories
GROUP BY p.ID_PROD, p.NAME
HAVING COUNT(*) > 5
Since this looks like homework, I'll give a good hint.
Your first query returns products belonging to more than 5 categories, using a sub-query for the COUNT. The restriction you added in the second query was added to the top-level WHERE-clause, not the sub-query. The sub-query still works on PROD_CAT and still returns the same results, which may include public categories.
I am using SQLite Studio 3.3 to work with a simple video database. These are three of the tables I am working with.
-- Table: Videos
DROP TABLE IF EXISTS Videos;
CREATE TABLE Videos (
id INTEGER PRIMARY KEY ASC AUTOINCREMENT
UNIQUE
NOT NULL,
vid_title TEXT NOT NULL
UNIQUE,
vid_rel_date DATE
);
-- Table: People
DROP TABLE IF EXISTS People;
CREATE TABLE People (
id INTEGER PRIMARY KEY AUTOINCREMENT
NOT NULL
UNIQUE,
person_name_first TEXT NOT NULL,
person_name_last TEXT
);
-- Table: _Videos_People
DROP TABLE IF EXISTS _Videos_People;
CREATE TABLE _Videos_People (
id INTEGER PRIMARY KEY AUTOINCREMENT
UNIQUE
NOT NULL,
vid_id INTEGER REFERENCES Videos (id),
person_id INTEGER REFERENCES People (id)
);
I have these statements to add a video and a person to the database and to get both IDs.
INSERT OR IGNORE INTO Videos (
vid_title,
vid_rel_date
)
VALUES (
'The Call Of The Wild',
'2020'
);
INSERT OR IGNORE INTO People (
person_name_first,
person_name_last
)
VALUES (
'Harrison',
'Ford'
);
SELECT id
FROM Videos
WHERE vid_title = 'The Call Of The Wild'
AND vid_rel_date = '2020';
SELECT id
FROM People
WHERE person_name_first = 'Harrison'
AND person_name_last = 'Ford';
This is where I am stuck. Once I have these two IDs, how do I add them to the _Videos_People join table so that the person is associated with the video?
I found this but I don't know how to use it for my situation (or if I should).
If I am going about this completely wrong, I am open to suggestions. I am new at this so I appreciate any help.
You can use this INSERT statement:
INSERT INTO _Videos_People (vid_id, person_id) VALUES (
(SELECT id FROM Videos WHERE vid_title = 'The Call Of The Wild' AND vid_rel_date = '2020'),
(SELECT id FROM People WHERE person_name_first = 'Harrison' AND person_name_last = 'Ford')
);
See the demo.
Can anyone explain how to implement one-to-one, one-to-many and many-to-many relationships while designing tables with some examples?
One-to-one: Use a foreign key to the referenced table:
student: student_id, first_name, last_name, address_id
address: address_id, address, city, zipcode, student_id # you can have a
# "link back" if you need
You must also put a unique constraint on the foreign key column (addess.student_id) to prevent multiple rows in the child table (address) from relating to the same row in the referenced table (student).
One-to-many: Use a foreign key on the many side of the relationship linking back to the "one" side:
teachers: teacher_id, first_name, last_name # the "one" side
classes: class_id, class_name, teacher_id # the "many" side
Many-to-many: Use a junction table (example):
student: student_id, first_name, last_name
classes: class_id, name, teacher_id
student_classes: class_id, student_id # the junction table
Example queries:
-- Getting all students for a class:
SELECT s.student_id, last_name
FROM student_classes sc
INNER JOIN students s ON s.student_id = sc.student_id
WHERE sc.class_id = X
-- Getting all classes for a student:
SELECT c.class_id, name
FROM student_classes sc
INNER JOIN classes c ON c.class_id = sc.class_id
WHERE sc.student_id = Y
Here are some real-world examples of the types of relationships:
One-to-one (1:1)
A relationship is one-to-one if and only if one record from table A is related to a maximum of one record in table B.
To establish a one-to-one relationship, the primary key of table B (with no orphan record) must be the secondary key of table A (with orphan records).
For example:
CREATE TABLE Gov(
GID number(6) PRIMARY KEY,
Name varchar2(25),
Address varchar2(30),
TermBegin date,
TermEnd date
);
CREATE TABLE State(
SID number(3) PRIMARY KEY,
StateName varchar2(15),
Population number(10),
SGID Number(4) REFERENCES Gov(GID),
CONSTRAINT GOV_SDID UNIQUE (SGID)
);
INSERT INTO gov(GID, Name, Address, TermBegin)
values(110, 'Bob', '123 Any St', '1-Jan-2009');
INSERT INTO STATE values(111, 'Virginia', 2000000, 110);
One-to-many (1:M)
A relationship is one-to-many if and only if one record from table A is
related to one or more records in table B. However, one record in table B cannot be related to more than one record in table A.
To establish a one-to-many relationship, the primary key of table A (the "one" table) must be the secondary key of table B (the "many" table).
For example:
CREATE TABLE Vendor(
VendorNumber number(4) PRIMARY KEY,
Name varchar2(20),
Address varchar2(20),
City varchar2(15),
Street varchar2(2),
ZipCode varchar2(10),
Contact varchar2(16),
PhoneNumber varchar2(12),
Status varchar2(8),
StampDate date
);
CREATE TABLE Inventory(
Item varchar2(6) PRIMARY KEY,
Description varchar2(30),
CurrentQuantity number(4) NOT NULL,
VendorNumber number(2) REFERENCES Vendor(VendorNumber),
ReorderQuantity number(3) NOT NULL
);
Many-to-many (M:M)
A relationship is many-to-many if and only if one record from table A is related to one or more records in table B and vice-versa.
To establish a many-to-many relationship, create a third table called "ClassStudentRelation" which will have the primary keys of both table A and table B.
CREATE TABLE Class(
ClassID varchar2(10) PRIMARY KEY,
Title varchar2(30),
Instructor varchar2(30),
Day varchar2(15),
Time varchar2(10)
);
CREATE TABLE Student(
StudentID varchar2(15) PRIMARY KEY,
Name varchar2(35),
Major varchar2(35),
ClassYear varchar2(10),
Status varchar2(10)
);
CREATE TABLE ClassStudentRelation(
StudentID varchar2(15) NOT NULL,
ClassID varchar2(14) NOT NULL,
FOREIGN KEY (StudentID) REFERENCES Student(StudentID),
FOREIGN KEY (ClassID) REFERENCES Class(ClassID),
UNIQUE (StudentID, ClassID)
);
One-to-many
The one-to-many table relationship looks as follows:
In a relational database system, a one-to-many table relationship links two tables based on a Foreign Key column in the child which references the Primary Key of the parent table row.
In the table diagram above, the post_id column in the post_comment table has a Foreign Key relationship with the post table id Primary Key column:
ALTER TABLE
post_comment
ADD CONSTRAINT
fk_post_comment_post_id
FOREIGN KEY (post_id) REFERENCES post
One-to-one
The one-to-one table relationship looks as follows:
In a relational database system, a one-to-one table relationship links two tables based on a Primary Key column in the child which is also a Foreign Key referencing the Primary Key of the parent table row.
Therefore, we can say that the child table shares the Primary Key with the parent table.
In the table diagram above, the id column in the post_details table has also a Foreign Key relationship with the post table id Primary Key column:
ALTER TABLE
post_details
ADD CONSTRAINT
fk_post_details_id
FOREIGN KEY (id) REFERENCES post
Many-to-many
The many-to-many table relationship looks as follows:
In a relational database system, a many-to-many table relationship links two parent tables via a child table which contains two Foreign Key columns referencing the Primary Key columns of the two parent tables.
In the table diagram above, the post_id column in the post_tag table has also a Foreign Key relationship with the post table id Primary Key column:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_post_id
FOREIGN KEY (post_id) REFERENCES post
And, the tag_id column in the post_tag table has a Foreign Key relationship with the tag table id Primary Key column:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_tag_id
FOREIGN KEY (tag_id) REFERENCES tag
One to one (1-1) relationship:
This is relationship between primary & foreign key (primary key relating to foreign key only one record). this is one to one relationship.
One to Many (1-M) relationship:
This is also relationship between primary & foreign keys relationships but here primary key relating to multiple records (i.e. Table A have book info and Table B have multiple publishers of one book).
Many to Many (M-M): Many to many includes two dimensions, explained fully as below with sample.
-- This table will hold our phone calls.
CREATE TABLE dbo.PhoneCalls
(
ID INT IDENTITY(1, 1) NOT NULL,
CallTime DATETIME NOT NULL DEFAULT GETDATE(),
CallerPhoneNumber CHAR(10) NOT NULL
)
-- This table will hold our "tickets" (or cases).
CREATE TABLE dbo.Tickets
(
ID INT IDENTITY(1, 1) NOT NULL,
CreatedTime DATETIME NOT NULL DEFAULT GETDATE(),
Subject VARCHAR(250) NOT NULL,
Notes VARCHAR(8000) NOT NULL,
Completed BIT NOT NULL DEFAULT 0
)
-- This table will link a phone call with a ticket.
CREATE TABLE dbo.PhoneCalls_Tickets
(
PhoneCallID INT NOT NULL,
TicketID INT NOT NULL
)
I'm developing an iOS app and I have a sqlite database with 2 tables related by 1-to-many relationship.
Now I would like to do a query that retrieve all element by first table and in the same time do a count by second table so I can pass the result into my view.
CREATE TABLE track(
trackid INTEGER,
trackname TEXT,
trackartist INTEGER,
FOREIGN KEY(trackartist) REFERENCES artist(artistid)
);
CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
I would like to create a query that returns all artist name and the count of track for each artist name so I can pass this value to my list.
Is it possible? Any help?
Thanks to Joe, your code works well for my, but it's possibile to add new field for store the result of count?
Sorry and if i would take the also all trackname for each artist in the same query?
SELECT a.artistname, count(*)
FROM track t
INNER JOIN artist a
on t.trackartist = a.artistid
GROUP BY a.artistid
Try this:
SELECT a.artistname,
(SELECT COUNT(*) FROM track t
WHERE t.trackartist = a.artistid)
FROM artist a
I'm working on a SQLite Database. The database is already filled, but I want to refactor it. Here is a sample of what I need to do:
I currently have one table:
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT,
EngineCap FLOAT);
I want to split this into two tables:
CREATE TABLE Vehicles (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT);
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
VehicleID INTEGER CONSTRAINT FK_Cars REFERENCES [Vehicles](ID),
EngineCap FLOAT);
I have figured out to create a temporary table with the Cars table contents, and I can fill up the Vehicles table with the contents of the Cars table:
CREATE TEMPORARY TABLE Cars_temp AS SELECT * FROM Cars;
INSERT INTO Vehicles (Name, TopSpeed)
SELECT Name, TopSpeed FROM Cars_temp;
But I am still looking for a way to go over that same selection, while putting the EngineCap field into the new Cars table and somehow extracting the corresponding ID value from the Vehicles table to put into the VehicleID foreign key field on the Cars table.
I'm open for workaround or alternative approaches.
Thanks.
Since #mateusza did not provide an example, I've made one:
Suppose you have this table:
CREATE TABLE [Customer] (
[name] TEXT,
[street] TEXT,
[city] TEXT);
Now you want to move street and city into a separate table Address, so you'll end up with two tables:
CREATE TABLE [Customer2] (
[name] TEXT,
[addr] INTEGER);
CREATE TABLE [Address] (
[rowid] INTEGER NOT NULL,
[street] TEXT,
[city] TEXT,
PRIMARY KEY ([rowid])
);
(For this example, I'm doing the conversion in the same database. You'd probably use two DBs, converting one into the other, with an SQL ATTACH command.)
Now we create a view (which imitates our original table using the new tables) and the trigger:
CREATE VIEW Customer1 (name, street, city) AS
SELECT C.name, A.street, A.city FROM Customer2 AS C
JOIN Address as A ON (C.addr == A.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Customer1 FOR EACH ROW BEGIN
INSERT INTO Address (street, city) SELECT NEW.street, NEW.city;
INSERT INTO Customer2 (addr, name) SELECT last_insert_rowid(), NEW.name;
END;
Now you can copy the table rows:
INSERT INTO Customer1 (name, street, city) SELECT name, street, city FROM Customer;
The above is a simplified case where you'd only move some data into a single new table.
A more complex (and more general) case is where you want to...
Separate your original table's columns into several foreign tables, and
Have unique entries in the foreign tables (that's usually the reason why you'd refactor your table).
This adds some additional challenges:
You'll end up inserting into multiple tables before you can insert their rowids into the table with the referencing rowids. This requires storing the results of each INSERT's last_insert_rowid() into a temporary table.
If the value already exists in the foreign table, its rowid must be stored instead of the one from the (non-executed) insertion operation.
Here's a complete solution for this. It manages a database of music records, constisting of a song's name, album title and artist name.
-- Original table
CREATE TABLE [Song] (
[title] TEXT,
[album] TEXT,
[artist] TEXT
);
-- Refactored tables
CREATE TABLE [Song2] (
[title] TEXT,
[album_rowid] INTEGER,
[artist_rowid] INTEGER
);
CREATE TABLE [Album] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[title] TEXT UNIQUE
);
CREATE TABLE [Artist] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[name] TEXT UNIQUE
);
-- Fill with sample data
INSERT INTO Song VALUES ("Hunting Girl", "Songs From The Wood", "Jethro Tull");
INSERT INTO Song VALUES ("Acres Wild", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Broadford Bazar", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Statue of Liberty", "White Music", "XTC");
INSERT INTO Song VALUES ("Standing In For Joe", "Wasp Star", "XTC");
INSERT INTO Song VALUES ("Velvet Green", "Songs From The Wood", "Jethro Tull");
-- Conversion starts here
CREATE TEMP TABLE [TempRowIDs] (
[album_id] INTEGER,
[artist_id] INTEGER
);
CREATE VIEW Song1 (title, album, artist) AS
SELECT Song2.title, Album.title, Artist.name
FROM Song2
JOIN Album ON (Song2.album_rowid == Album.rowid)
JOIN Artist ON (Song2.artist_rowid == Artist.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Song1 FOR EACH ROW BEGIN
INSERT OR IGNORE INTO Album (title) SELECT NEW.album;
UPDATE TempRowIDs SET album_id = (SELECT COALESCE (
(SELECT rowid FROM Album WHERE changes()==0 AND title==NEW.album), last_insert_rowid()
) ) WHERE rowid==1;
INSERT OR IGNORE INTO Artist (name) SELECT NEW.artist;
UPDATE TempRowIDs SET artist_id = (SELECT COALESCE (
(SELECT rowid FROM Artist WHERE changes()==0 AND name==NEW.artist), last_insert_rowid()
) ) WHERE rowid==1;
INSERT INTO Song2 (title, album_rowid, artist_rowid) SELECT
NEW.title, (SELECT album_id FROM TempRowIDs), (SELECT artist_id FROM TempRowIDs);
END;
INSERT INTO TempRowIDs DEFAULT VALUES;
INSERT INTO Song1 (title, album, artist) SELECT title, album, artist FROM Song;
DROP TRIGGER TempTrig;
DROP TABLE TempRowIDs;
-- Conversion ends here
-- Print results
SELECT * FROM Song;
SELECT * FROM Song1;
-- Check if original and copy are identical (https://stackoverflow.com/a/13865679/43615)
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT * FROM Song UNION SELECT * FROM Song1)) == (SELECT COUNT() FROM Song) THEN 'Success' ELSE 'Failure' END;
Note that this example has one potential issue: If the constraints on the foreign table are more complex, the SELECT rowid FROM search for the existing entry needs to be updated accordingly. Ideally, SQLite should provide a way to determine the conflicting rowid somehow, but it doesn't, unfortunately (see this related question).
Simple solution without triggers:
create VEHICLES_TEMP table including the CAR_ID
create your new CARS table without the VEHICLES columns you don't want
update CARS with VEHICLE_ID taken from VEHICLES_TEMP (identified by the CAR_ID)
create final VEHICLES table without the CAR_ID
Create a table New_Cars and a INSTEAD OF INSERT trigger, which will insert data to both tables Vehicles and Cars. When inserting to Cars, you can use last_insert_rowid() function to refer to inserted row in Vehicles table.
This can be temporary solution, or you can leave it in your database for further modifications.