SQL - 03 Writing

Inserting Data

INSERT INTO is used to insert a row of data into a given table.

INSERT INTO table (column0, ...)
VALUES
(value0, ...);
INSERT INTO "collections" ("id", "title", "accession_number", "acquired")
VALUES (1, 'Profusion', '56.257', '1996-04-12');

This command requires the list of columns in the table that will receive the values to be added to each column in the same order.

We can insert more rows by running the inserting multiple times, but typing out the primary key might result in error. SQLite fills out the primary key automatically by checking for the highest value in id and incrementing it by one. This can be done by leaving out the id column altogether while inserting the row.

Other constraints Cannot insert a similar id which is given UNIQUE constraint. When there is NOT NULL, then NULL values cannot be added.

Inserting Multiple Rows

INSERT INTO can be comma separated to insert multiple rows.

INSERT INTO table (column0, ...)
VALUES
(value0, ...),
(value1, ...),
...;
INSERT INTO "collections" ("title", "accession_number", "acquired") 
VALUES 
('Imaginative landscape', '56.496', NULL),
('Peonies and butterfly', '06.1899', '1906-01-01');

The id is auto incremented so left out, the date is unknown so given as NULL.

Using a CSV file

Data can be stored in a comma-separated-values format where values in each row are separated by a comma and rows are in in separate rows.

s
s

This file can be imported into SQLite database. mfa.csv is the file containing the data needed. If the names of the column matches the names according to our schema in database. We can import the CSV by running a SQLite command. (A SQLite commands do not need " " )

.import --csv --skip 1 mfa.csv collections

--csv indicates we are importing CSV file (to specify they are comma separated) --skip 1 indicated that the first row needs to be skipped or not inserted into the table (as they are column headings which are already present in the table) mfa.csv colections the data from CSV file is copied into the table.

Most of the CSV files will not contain any primary key values like they were present here.

title,accession_number,acquired
Profusion of flowers,56.257,1956-04-12
Farmers working at dawn,11.6152,1911-08-03
Spring outing,14.76,1914-01-08
Imaginative landscape,56.496,
Peonies and butterfly,06.1899,1906-01-01

First we will delete the data in the collections table DELETE FROM "collections"; By leaving out the row data in the end, it deletes all the rows in the table.

The schema has 4 rows and the CSV has 3 rows so we will have to use a temporary table first:

.import --csv mfa.csv temp

Here skip is not used and SQLite will take the first row and convert those to column names of the new temp table.

Next we can select the data from temp without the primary key by a query and move it into collections. The id will be added automatically.

INSERT INTO "collections" ("title", "accession_number", "acquired")
SELECT "title", "accession_number", "acquired" FROM "temp";

To clean up the data base we can drop the temp table DROP TABLE "temp";

INSERT INTO table0 (column0, ...)
SELECT column0, ... FROM table1;

Insert into the table 0 columns the result of the query of table 1. The Columns have to allign.

Deleting Data

DELETE FROM table WHERE condition;

Without the condition all rows will be deleted from the table. DELETE FROM table;

DELETE FROM "collections"
WHERE "title" = 'Spring outing';

DELETE FROM "collections"
WHERE "acquired" IS NULL;

So sprint outing and the other one with acquired date NULL both will be deleted.

To delete the ones which are older than 1909

DELETE FROM "collections"
WHERE "acquired" < '1909-01-01';

Foreign Key Constraints

Complications in deleting can impact the integrity of a database. Foreign key constraints are a good example. A foreign key column references the primary key of a different table. If we were to delete the primary key, the foreign key column would have nothing to reference!

s
s
s
s

Here if we choose to delete the Undefined artist from the artists table, it will cause an error as the id of that is being refereed in the created table under artist_id.

One possibility is delete the corresponding row in created beforehand and then delete the one in the artists table.

DELETE FROM "created"
WHERE "artist_id" = (
	SELECT "id" FROM "artists" WHERE "name" = 'Undefined artist'
);

Once the affiliation is deleted, the artist data can be deleted

DELETE FROM "artists"
WHERE "name" = 'Undefined artist';

Specifying actions

Another possibility of deleting an ID referred by a foreign key is deleted by using ON DELETE followed by action to be taken.

FOREIGN KEY("artist_id") REFERENCES "artists"("id")
ON DELETE ...

ON DELETE RESTRICT: Restricts us from deleting IDs when the foreign key constraint is violated.

ON DELETE NO ACTION: Deletion of IDs that are referenced by a foreign key and nothing happens.

ON DELETE SET NULL: Deletion of IDs that are referenced by a foreign key and sets the foreign key reference to NULL

ON DELETE SET DEFAULT: does the same as the previous, but allows us to set a default value instead of NULL.

ON DELETE CASCADE: allows the deletion of IDs that are referenced by a foreign key and also proceeds to cascadingly delete the referencing foreign key rows. For example, if we used this to delete an artist ID, all the artist’s affiliations with the artwork would also be deleted from the created table. (similar to previous two step approach)

Using these in the schema shows

FOREIGN KEY("artist_id") REFERENCES "artists"("id") ON DELETE CASCADE
FOREIGN KEY("collection_id") REFERENCES "collections"("id") ON DELETE CASCADE

The ON DELETE CASCADE will delete the rows it was referenced also when we run

DELETE FROM "artists"
WHERE "name" = 'Undefined artist';

A deleted ID can be made to be repurposed for a new row, this can be done by using AUTOINCREMENT keyword while creating a column.

UPDATE

To change the data already in the table,

UPDATE table
SET column0 = value0, ...
WHERE condition;
UPDATE "created"
SET "artist_id" = (
	SELECT "id" FROM "artists" WHERE "name" = 'Li Yin'
)
WHERE "collectio_id" = (
	SELECT "id" FROM "collections" WHERE "title" = 'Farmers working at dawn'
);

The value of artist_id is changed where the collection_id was of the given painting.


Cleaning the Data

SELECT "title", COUNT("title") FROM "votes" GROUP BY "title";

To show the unique titles with their counts. (if the names match)

(SQLite Scalar functions to modify the data)

TRIM to remove leading and trailing white space.

UPDATE "votes" SET "title" = trim("title");

UPPER LOWER to change cases

UPDATE "votes" SET "title" = upper("title");

Manually changing using conditions

UPDATE "votes" SET "title" = 'FARMERS'
	WHERE "title" = 'FAMER';

Using the LIKE to update things that looks like a condition, not exactly = to.

UPDATE "votes" SET "title" = 'FARMERS'
	WHERE "title" LIKE 'FA%';

Like is case insensitive.


Triggers

A SQL statement that runs when other statement is run like an Insert, Update, Delete.

Having a collections table and a transactions table. Anything removed from collections appears as a sold item in transactions. Anything added will show up as purchases.

CREATE TRIGGER name
BEFORE INSERT ON table
FOR EACH ROW
BEGIN
	...:
END;

name is used to identify them, similar to a table name. AFTER or BEFORE decides when should the statement run.

BEFORE INSERT ON table
BEFORE DELETE ON table
BEFORE UPDATE OF column ON table

FOR EACH ROW specifies how many times a statement should be run, if two rows are deleted then it should run two times.

The statement or multiple statements that have to be run on a trigger will go between BEGIN and END;


Using Triggers

The current schema .schema

CREATE TABLE "collections" (
	"id" INTEGER,
	"title" TEXT NOT NULL,
	"accession_number" TEXT NOT NULL UNIQUE;
	"acquired" NUMERIC,
	PRIMARY KEY("id")
);

CREATE TABLE "artists" (
	"id" INTEGER,
	"name" TEXT NOT NULL,
	PRIMARY KEY("id")
);

CREATE TABLE "created" (
	"artist_id" INTEGER,
	"collection_id" INTEGER,
	PRIMARY KEY("artist_id", "collection_id"),
	FOREIGN KEY("artist_id") REFERENCES "artists"("id")
		ON DELETE CASCADE,
	FOREIGN KEY("collection_id") REFERENCES "collections"("id") ON DELETE CASCADE
);

Making the transactions table

CREATE TABLE "transactions" (
	"id" INTEGER,
	"title" TEXT,
	"actions" TEXT,
	PRIMARY KEY("id")
);

Making a trigger, to run before deletion happens in collections table, to insert that data into transactions table.

CREATE TRIGGER "sell"
BEFORE DELETE ON "collections"
FOR EACH ROW
BEGIN
	INSERT INTO "transactions"("title", "action")
	VALUES (OLD."title", 'sold');
END;

(This will become part of the schema.) In Triggers there is access to the key words OLD and NEW. OLD is the row we just deleted from. OLD."title" gives access to the title of the row that was deleted.

NEW is the recently added row.

CREATE TRIGGER "buy"
BEFORE INSERT ON "collections"
FOR EACH ROW
BEGIN
	INSERT INTO "transactions"("title", "action")
	VALUES (NEW."title", 'bought');
END;

Soft Deletions

Not completely dropping the row but having it in some other place, Or another way is to just have a column which monitors if the row is deleted or not by using 0 1 The filter can be done to show only those which are not having 1 at deleted.

Adding a new column, with default value 0

ALTER TABLE "collections" ADD COLUMN "deleted" INTEGER DEFAULT 0;

Now the value of deleted can be updated from 0 to 1.

UPDATE "collections" SET "deleted" = 1 WHERE "title" = 'Farmers';

To filter according to deleted

SELECT * FROM "collections" WHERE "deleted" != 1;

SELECT * FROM "collections" WHERE "deleted" = 1;

The advantage is data is not deleted, it can still be accessed but that has other issues when handling user data.