tags:
- SQL
mysql -u root -h 127.0.0.1 -P 3306 -p
-u
indicates the user. We provide the user we want to connect to the database as — root
(synonymous with database admin, in this case).127.0.0.1
is the address of local host on the internet (our own computer).3306
is the port we want to connect to, and this is the default port where MySQL is hosted. Think of the combination of host and port as the address of the database we are trying to connect to!-p
at the end of the command indicates that we want to be prompted for a password when connecting.SHOW DATABASES;
This returns some default databases already in the server.CREATE DATABASE `mbta`;
Instead of quotation marks, we use backticks to identify the table name and other variables in our SQL statements.mbta
:
USE `mbta`;
cards
TableTINYINT
, SMALLINT
, MEDIUMINT
, INT
or BIGINT
based on the size of the number we want to store. The following table shows us the size and range of numbers we can store in each of the integer types.
cards
using an INT
data type for the ID column. Since an INT
can store a number up to 4 billion, it should be big enough for our use case!
CREATE TABLE `cards` (
`id` INT AUTO_INCREMENT,
PRIMARY KEY(`id`)
);
Note that we use the keyword AUTO_INCREMENT
with the ID so that MySQL automatically inserts the next number as the ID for a new row.Should the ID column not be an unsigned integer? How can we denote that?
UNSIGNED
while creating the integer.stations
TableSHOW TABLES;
DESCRIBE
command.
DESCRIBE `cards`;
CHAR
— a fixed width string, and VARCHAR
— a string of variable length. MySQL also has a type TEXT
but unlike in SQLite, this type is used for longer chunks of text like paragraphs, pages of books etc. Based on the length of the text, it could be one of: TINYTEXT
, TEXT
, MEDIUMTEXT
and LONGTEXT
. Additionally, we have the BLOB
type to store binary strings.ENUM
and SET
. Enum restricts a column to a single predefined option from a list of options we provide. For example, shirt sizes could be enumerated to M, L, XL and so on. A set allows for multiple options to be stored in a single cell, useful for scenarios like movie genres.stations
table in MySQL.
CREATE TABLE `stations` (
`id` INT AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL UNIQUE,
`line` ENUM('blue', 'green', 'orange', 'red') NOT NULL,
PRIMARY KEY(`id`)
);
VARCHAR
for the station name because names might be an unknown length. The line that a station is on, however, is one of the existing subway lines in Boston. Since we know the values this could take, we can use an ENUM
type.UNIQUE
and NOT NULL
in the same way as we did with SQLite.Key
field, the primary key is recognized by PRI
and any column with unique values is recognized by UNI
. The NULL
field tells us which columns allow NULL
values, which none of the columns do for the stations
table.Can we use a table as the input to
ENUM
?
SELECT
statement but this might not be a good idea if the values within the table change over time. It may be best to explicitly state values as the options for ENUM
.If we do not know how long a piece of text will be and use something like
VARCHAR(300)
to represent it, is that okay?
swipes
TableDATE
, YEAR
, TIME
, DATETIME
and TIMESTAMP
(for more precise times) to store our date and time values. The last three allow an optional parameter to specify the precision with which we want to store the time.REAL
data type. Here, our options are FLOAT
and DOUBLE PRECISION
as shown in the table below.
swipes
table.
CREATE TABLE `swipes` (
`id` INT AUTO_INCREMENT,
`card_id` INT,
`station_id` INT,
`type` ENUM('enter', 'exit', 'deposit') NOT NULL,
`datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`amount` DECIMAL(5,2) NOT NULL CHECK(`amount` != 0),
PRIMARY KEY(`id`),
FOREIGN KEY(`station_id`) REFERENCES `stations`(`id`),
FOREIGN KEY(`card_id`) REFERENCES `cards`(`id`)
);
DEFAULT CURRENT_TIMESTAMP
to indicate that the timestamp should be auto-filled to store the current time if no value is provided.Key
field has a new value, MUL
(multiple) for the foreign key columns, indicating that they could have repeating values since they are foreign keys.When we add constraints to a column, is there a precedence with which they take effect?
Does MySQL have type affinities?
INT
and VARCHAR
but unlike SQLite, it will not allow us to enter data of a different type and try to convert it.ALTER TABLE `stations`
MODIFY `line` ENUM('blue', 'green', 'orange', 'red', 'silver') NOT NULL;
line
column and change its type, such that the ENUM
now includes silver as an option.MODIFY
in addition to the ALTER TABLE
construct we are familiar with from SQLite.collections
in the MFA database. A view current_collections
displayed all the collections not marked as deleted. We will now use a stored procedure in MySQL to do something similar.USE `mfa`;
collections
table, we see that the deleted
column is not present and needs to be added to the table.
ALTER TABLE `collections`
ADD COLUMN `deleted` TINYINT DEFAULT 0;
Given that the deleted
column only has values of 0 or 1, it is safe to use a TINYINT
. We also assign the default as 0 because we want to keep all the collections already in the table.;
to something else. Unlike SQLite, where we could type in multiple statements between a BEGIN
and END
(which we need for a stored procedure here) and end them with a ;
, MySQL prematurely ends the statement when it encounters a ;
.
delimiter //
CREATE PROCEDURE `current_collection`()
BEGIN
SELECT `title`, `accession_number`, `acquired`
FROM `collections`
WHERE `deleted` = 0;
END//
Notice how we use empty parantheses next to the name of the procedure, perhaps reminiscent of functions in other programming languages. Similar to functions, we can also call stored procedures to run them.;
.
delimiter ;
collections
table because we haven’t soft-deleted anything yet.
CALL current_collection();
UPDATE `collections`
SET `deleted` = 1
WHERE `title` = 'Farmers working at dawn';
Can we add parameters to stored procedures, ie, call them with some input?
Can we call one procedure from another, like with functions?
Can you leave any notes or comments in tables in MySQL?
schema.sql
file describing the intent behind different parts of the schema, but there may be ways to add comments in SQL tables as well.transactions
to log artwork being bought or sold, which we can create here as well.
CREATE TABLE `transactions` (
`id` INT AUTO_INCREMENT,
`title` VARCHAR(64) NOT NULL,
`action` ENUM('bought', 'sold') NOT NULL,
PRIMARY KEY(`id`)
);
collections
because it is being sold, we would also like to update this in the transactions
table. Usually, this would be two different queries but with a stored procedure, we can give this sequence one name.
delimiter //
CREATE PROCEDURE `sell`(IN `sold_id` INT)
BEGIN
UPDATE `collections` SET `deleted` = 1
WHERE `id` = `sold_id`;
INSERT INTO `transactions` (`title`, `action`)
VALUES ((SELECT `title` FROM `collections` WHERE `id` = `sold_id`), 'sold');
END//
delimiter ;
The choice of the parameter for this procedure is the ID of the painting or artwork because it is a unique identifier.CALL `sell`(2);
We can display data from the collections
and transactions
tables to verify that the changes were made.sell
on the same ID more than once? There is a danger of it being added multiple times to the transactions
table. Stored procedures can be considerably improved in logic and complexity by using some regular old programming constructs. The following list contains some popular constructs available in MySQL.
cards
, let’s see what data types are available to us in PostgreSQL.
psql postgresql://postgres@127.0.0.1:5432/postgres
We can log in as the default Postgres user or the admin.\l
and it pulls up a list.CREATE DATABASE "mbta";
\c "mbta"
.\dt
. Right now, though, we will see no tables within the database.cards
table, as proposed. We use a SERIAL
data type for the ID column.
CREATE TABLE "cards" (
"id" SERIAL,
PRIMARY KEY("id")
);
\d "cards"
. On running this, we see some information about this table but in a slightly different format from MySQL.How do you know in PostgreSQL if your query is resulting in an error?
stations
table is created in a similar manner to MySQL.
CREATE TABLE "stations" (
"id" SERIAL,
"name" VARCHAR(32) NOT NULL UNIQUE,
"line" VARCHAR(32) NOT NULL,
PRIMARY KEY("id")
);
We can use VARCHAR
in the same way as in MySQL. To keep things simple, we say that the "line"
column id also of the VARCHAR
type.swipes
table next. Recall that the swipe type can mark entry, exit or deposit of funds in the card. Similar to MySQL, we can use an ENUM
to capture these options, but do not include it in the column definition. Instead, we create our own type.
CREATE TYPE "swipe_type" AS ENUM('enter', 'exit', 'deposit');
TIMESTAMP
, DATE
, TIME
and INTERVAL
to represent date and time values. INTERVAL
is used to capture how long something took, or the distance between times. Similar to MySQL, we can specify the precision with these types.DECIMAL
type is called NUMERIC
.swipes
table as the following.
CREATE TABLE "swipes" (
"id" SERIAL,
"card_id" INT,
"station_id" INT,
"type" "swipe_type" NOT NULL,
"datetime" TIMESTAMP NOT NULL DEFAULT now(),
"amount" NUMERIC(5,2) NOT NULL CHECK("amount" != 0),
PRIMARY KEY("id"),
FOREIGN KEY("station_id") REFERENCES "stations"("id"),
FOREIGN KEY("card_id") REFERENCES "cards"("id")
);
For the default timestamp, we use a function provided to us by PostgreSQL called now()
thst gives us the current timestamp!\q
.CREATE USER 'carter' IDENTIFIED BY 'password';
SHOW DATABASES;
This only displays some of the default databases in the server.rideshare
database with a rides
table. In this table, we stored the names of riders, which is personally identifiable information (PII). We created a view called analysis
which anonymized the names of the riders, intending to share only this view with an analyst or other user.analysis
view with the user we just created, we would do the following while logged in as the root user.
GRANT SELECT ON `rideshare`.`analysis` TO 'carter';
USE `rideshare`;
analysis
view. We can now see the data in this view, but not from the original rides
table! We just demonstrated the benefit of MySQL’s access control: we can have multiple users accessing the database but only allow some to access confidential data.SELECT `id` FROM `users`
WHERE `user` = 'Carter' AND `password` = 'password';
SELECT `id` FROM `users`
WHERE `user` = 'Carter' AND `password` = 'password' OR '1' = '1';
bank
database.accounts
table is this.
SELECT * FROM `accounts`
WHERE `id` = 1 UNION SELECT * FROM `accounts`;
PREPARE `balance_check`
FROM 'SELECT * FROM `accounts`
WHERE `id` = ?';
The question mark in the prepared statement acts as a safeguard against the unintended execution of SQL code.SET @id = 1;
EXECUTE `balance_check` USING @id;
In the above code, imagine the SET
statement to be procuring the user’s ID through the application! The @
is a convention for variables in MySQL.SET @id = '1 UNION SELECT * FROM `accounts`';
EXECUTE `balance_check` USING @id;
This also gives us the same results as the previous code — it shows us the balance of the user with ID 1 and nothing else! Thus, we have prevented a possible SQL injection attack.In this example of prepared statement, does it take into account only the first condition from the variable?
Is this similar to the reason we shouldn’t use formatted strings in Python to execute an SQL query?