This tutorial introduces SQL using mainly MariaDB and MySQL syntax. Database-specific commands and functions may differ in PostgreSQL, SQL Server and other systems.
SQL statements can be categorized into five types:
| Category | Purpose |
|---|---|
| 1. Query statements |
They make the search of the information possible
|
| 2. Data manipulation statements |
Allow you to change the contents of the tables
|
| 3. Data definition statements |
Allow you to change the design of the database
|
| 4. Transaction control statements |
They make the saving or cancelling of the change in the contents of rows possible
|
| 5. Data control statements |
Allow you to change the user privileges
|
Before creating tables, you need a database to store them in. The CREATE DATABASE statement creates a new database:
CREATE DATABASE dbname;
Replace dbname with the name you want to use. Creating a database requires the appropriate privileges on the server.
In MariaDB and MySQL, select the database for the current connection with USE:
USE dbname;
After this, statements such as CREATE TABLE person (...) and SELECT * FROM person use the selected database unless you explicitly specify another database in the table name. The selection applies only to the current connection.
PostgreSQL: CREATE DATABASE dbname; also creates a database, but PostgreSQL does not support USE. Open a connection to the new database in your client, or use \connect dbname in the psql command-line client.
Use -- for a single-line comment. In MariaDB and MySQL, the second dash must be followed by whitespace or a control character. Use /* ... */ for a block comment.
-- A single-line comment /* A comment that spans multiple lines */
Tables are created with the CREATE TABLE statement. After that there will be the name of the new table and inside brackets the column names and their types.
ExampleCREATE TABLE person( id_person SMALLINT PRIMARY KEY, firstname VARCHAR(15) NOT NULL, lastname VARCHAR(15) NOT NULL, city CHAR(10), birthyear SMALLINT, salary DECIMAL(10,2));
You can add constraints and default values to individual column definitions. The following examples each show one column definition to use inside a CREATE TABLE statement.
NOT NULL prevents a column from containing NULL (a missing value). The column must have a value; an empty string is still a value and is not prevented by this constraint.
firstname VARCHAR(15) NOT NULL
CHECK restricts the values allowed in a column. For example, the following definition rejects birth years of 1900 or earlier:
birthyear SMALLINT CHECK (birthyear > 1900)
This CHECK condition still allows NULL. Add NOT NULL if a birth year is also required.
DEFAULT supplies a value when the column is omitted from an INSERT statement or the keyword DEFAULT is used explicitly. For example:
city CHAR(10) DEFAULT 'Helsinki'
If no city is supplied, the database uses 'Helsinki'. An explicitly supplied NULL is not replaced by this default.
A data type defines the kind of value a column can store. The examples below use MariaDB/MySQL syntax; supported types and their limits differ between database systems.
| Data type | Purpose | Example column definition |
|---|---|---|
CHAR(n) | Fixed-length text, such as a two-character country code. | country_code CHAR(2) |
VARCHAR(n) | Variable-length text up to the specified number of characters. | lastname VARCHAR(50) |
SMALLINT | Whole numbers in a smaller range than INT. | birthyear SMALLINT |
INT | Whole numbers. | workhour INT |
DECIMAL(p,s) | Exact decimal values: p total digits, including s digits after the decimal point. | salary DECIMAL(10,2) |
FLOAT, DOUBLE | Approximate floating-point values, such as measurements. DOUBLE generally provides more precision. | temperature DOUBLE |
DATE | A calendar date. | start_date DATE |
TIME | A time of day; MariaDB/MySQL also supports durations. | start_time TIME |
DATETIME | A date and time. | appointment_time DATETIME |
For monetary amounts such as salaries, use DECIMAL to avoid the approximation associated with floating-point types. In DECIMAL(10,2), up to eight digits appear before the decimal point and two after it.
CREATE TABLE person( id_person SMALLINT PRIMARY KEY ...The same is achieved also as follows:
CREATE TABLE person( id_person SMALLINT, ... PRIMARY KEY(id_person) ...
PRIMARY KEY (id_project, id_person)
Referential integrity means that references between tables remain valid: each non-NULL foreign key value in a child table must match an existing key value in the parent table.
A FOREIGN KEY constraint defines this relationship and tells the database to enforce referential integrity. Its ON DELETE and ON UPDATE options specify how changes to referenced parent rows are handled, for example by preventing a deletion or automatically updating the related foreign key values.
In order to create table named hour to MySQL, you can use below code
CREATE TABLE hour (
id_project CHAR(5) NOT NULL,
id_person SMALLINT NOT NULL,
workhour INT,
PRIMARY KEY (id_project,id_person),
CONSTRAINT project_hour
FOREIGN KEY (id_project) REFERENCES project (id_project)
ON DELETE RESTRICT
ON UPDATE CASCADE) ENGINE=InnoDB;
In order to execute the code above, first create a project table with a primary or unique key on id_project and a compatible column definition.
And now if you update some value of id_project int the project table(parent-table), the same changes will be made automatically to hour table(child table). And you can not delete rows from project table if there is child rows in hour table.
An index is a data structure that can help the database find rows without scanning the whole table. Indexes can cover one column or several columns and can help filtering, joins and sorting. In InnoDB, the primary key organizes the table data, and secondary indexes contain the primary key values used to find the corresponding rows.
Indexes take disk space and add work to INSERT, UPDATE and DELETE operations. Create indexes to support your queries, and use EXPLAIN to inspect how the database plans to retrieve data.
When you create an index, you can define whether duplicate values are allowed
or not. Use INDEX to allow duplicate values and UNIQUE INDEX to prevent duplicate values.
CREATE TABLE person( id_person SMALLINT PRIMARY KEY, firstname VARCHAR(15) NOT NULL, lastname VARCHAR(15) NOT NULL, city CHAR(10), birthyear SMALLINT, salary DECIMAL(10,2), email VARCHAR(100) NOT NULL, INDEX lastnameIndex (lastname), UNIQUE INDEX emailIndex (email));
lastnameIndex is not unique: several people can have the same lastname value.emailIndex is unique: two people cannot have the same email value. The NOT NULL constraint also requires an email value for each person.You can also create an index that includes several columns. Add the following index definition inside the CREATE TABLE statement:
INDEX nameIndex (firstname, lastname)
If you don't give any name to the index,
MySQL names the index according to the first index column.
Example INDEX (fi,si) -> index will be named fi.
You can also add indexes later, when the table already exists. Then you can use below code:
CREATE INDEX lastnameIndex ON person(lastname);
You can check what indexes the table has with below command:
SHOW INDEXES FROM person\G
You can delete indexes with below command:
DROP INDEX lastnameIndex ON person;
The SQL ALTER TABLE command is used to modify the definition (structure) of a table by modifying the definition of its columns. The ALTER command is used to perform the following functions.
ALTER TABLE person ADD COLUMN test CHAR(20);
ALTER TABLE person MODIFY COLUMN test CHAR(40);
ALTER TABLE person DROP COLUMN test;
ALTER TABLE person ADD INDEX ind_name (lastname);
DROP TABLE hour;
DROP TABLE hour,person;
A SELECT statement returns a result set. It does not create a stored database view; that requires CREATE VIEW.
Select queries consists of The following elements :
SELECT which columns will be retrieved
FROM from what table/tables or a view/views is it searched for
WHERE which rows will be included in the result set
GROUP BY how is it grouped
HAVING what of the lines which have been created as a result of the grouping are shown
ORDER BY how is it sorted
Examples
-- this will return id_person and lastname SELECT id_person, lastname FROM person;
-- this will return all columns from the table SELECT * FROM person;
You can use DISTINCT statement in the SELECT query to return only those lines as a result which have differences.
SELECT DISTINCT city FROM person;prints all city-names only once.
In MariaDB and MySQL, LIMIT restricts the number of returned rows. Use ORDER BY to define which rows come first; without it, the order is not guaranteed.
SELECT * FROM person ORDER BY id_person LIMIT 2;
This returns up to two rows, ordered by person ID.
SELECT * FROM person ORDER BY id_person LIMIT 3, 2;
The first number is an offset: skip three rows, then return up to two rows (positions 4 and 5 in this ordering). The equivalent, more explicit syntax is:
SELECT * FROM person ORDER BY id_person LIMIT 2 OFFSET 3;
ORDER BY sorts the result set. ASC means ascending order and is the default; DESC means descending order. You can specify several columns to resolve ties.
SELECT id_person, lastname, salary FROM person ORDER BY salary DESC, id_person ASC;
This lists the highest salaries first. People with the same salary are ordered by their unique person ID, giving a defined order even when LIMIT is used.
There are several functions which you can use in the SELECT. Here are some examples, which works in MySQL.
SELECT CURRENT_DATE;Shows today's date. In MariaDB and MySQL,
CURDATE() is another option. DATE(expression) extracts the date part of a supplied value, for example DATE('2026-10-22 14:30:00').
SELECT VERSION();Shows the version of your MySQL-server (in MySQL).
SELECT USER();Shows your username (in MySQL).
If you want to change the heading of the result set, you can use the AS keyword
ExampleSELECT lastname AS Name FROM person;In above example lastname is the column name in the database, but in the result set the heading is changed to Name. (Note: the code works also without the keyword AS, but the code is clearer, when you use it.)
SELECT lastname, 'Some text' AS explanation FROM person;
You can concatenate several columns and literals together
ExampleSELECT CONCAT(firstname, ' ', lastname) AS Name FROM person;
SELECT SUBSTRING(lastname,2,4) FROM person;This will print 4 characters from name starting from the second character.
You can use the WHERE clause to choose which rows will be included in the result.
ExamplesSELECT * FROM person WHERE city ='TURKU';
SELECT * FROM person WHERE NOT city='TURKU';
SELECT * FROM person WHERE city <>'TURKU';
SELECT * FROM person WHERE salary=5000;
If there is several option, it is shorter to use IN query
WHERE city='Turku' OR city='Tampere' OR city='Oulu'is the same as
WHERE city in ('Turku', 'Tampere', 'Oulu')
If you need to return values between some values, you can use BETWEEN
WHERE salary >= 4000 AND salary <= 5000is the same as
WHERE salary BETWEEN 4000 AND 5000
BETWEEN includes both endpoints: 4000 and 5000 are included.
Missing information can be found by using IS NULL-syntax
SELECT * FROM project WHERE place IS NULL;
Example
WHERE city LIKE 'T%'Returns those rows where the city begins with the character T
WHERE city LIKE '_i%'Returns rows with i as the second character in the city column.
WHERE city LIKE '%i%'Returns rows with i anywhere in the city column.
_ : any character (only one character)
% : any character or string (also several characters)
The Aggregate Functions are:
SELECT AVG(salary) FROM person;
SELECT city, count(id_person) FROM person GROUP BY city;Example
SELECT city, GROUP_CONCAT(lastname) AS peoples FROM person GROUP BY city;the following result is obtained:
+----------+----------------------+ | city | peoples | +----------+----------------------+ | HELSINKI | River,Simpson | | TAMPERE | Daniels | | TURKU | Morrison,Smith,Jones | +----------+----------------------+
SELECT place, count(*) FROM project GROUP BY place HAVING COUNT(*) >= 2;
Difference between WHERE and HAVING: WHERE filters rows before grouping, and HAVING filters groups after grouping. HAVING can also be used with an aggregate query without GROUP BY; all selected rows then form a single group.
SELECT COUNT(*) AS person_count FROM person HAVING COUNT(*) > 5;
This returns one row only if the table contains more than five people.
A SQL JOIN combines rows from two or more tables based on a join condition. A declared foreign key is not required, and the joined columns can have different names. You can combine the parent and child table with JOIN and then return columns from both tables. The following sections cover three common JOIN types supported by MariaDB and MySQL:
FROM Table1 INNER JOIN Table2 ON Table1.id=Table2.idExample: Creating an INNER JOIN between tables hour and project.
If you want to view all the rows from one table (usually the parent-table), even when the other table does not have all matching rows, an OUTER JOIN is used.
Types of OUTER JOIN:
Note: The word OUTER is often omitted, so LEFT JOIN and LEFT OUTER JOIN are equivalent.
Example: LEFT JOIN between project and hour tables
You can join multiple tables in a single query by chaining JOIN clauses. This is useful when you need to combine data from three or more related tables.
Example: Joining three tables from the Project database (person, hour, and project)
You can retrieve rows from several tables using a UNION query
Example
The persons which salary is over 40 and/or which belong to the project 102
SELECT lastname as Name,'Salary is over 40' FROM person WHERE salary>40 UNION SELECT lastname, 'Belongs to 102' FROM person INNER JOIN hour ON person.id_person = hour.id_person WHERE id_project='102';The results are
+----------+-------------------+ | Name | Salary is over 40 | +----------+-------------------+ | Morrison | Salary is over 40 | | Jones | Salary is over 40 | | Morrison | Belongs to 102 | | River | Belongs to 102 | +----------+-------------------+You can also make UNION queries like this:
SELECT lastname as Name,'Salary is over 40' FROM person WHERE salary>40 UNION SELECT lastname as Name,'Salary is not over 40' FROM person WHERE salary<=40;The results are
+----------+-----------------------+ | Name | Salary is over 40 | +----------+-----------------------+ | Morrison | Salary is over 40 | | Jones | Salary is over 40 | | Smith | Salary is not over 40 | | River | Salary is not over 40 | | Simpson | Salary is not over 40 | | Daniels | Salary is not over 40 | +----------+-----------------------+You can also replace the previous UNION with IF like this
SELECT lastname as Name, IF(salary>40, 'Salary is over 40', 'Salary is not over 40') AS Explanation FROM person;
SELECT lastname,'Less than 39' FROM person WHERE salary<39 UNION SELECT lastname,'Over 39' FROM person WHERE salary>39 UNION SELECT lastname,'Exactly 39' FROM person WHERE salary=39 ORDER BY 2;And the result is
+----------+--------------+ | lastname | Less than 39 | +----------+--------------+ | Smith | Exactly 39 | | River | Exactly 39 | | Simpson | Less than 39 | | Daniels | Less than 39 | | Morrison | Over 39 | | Jones | Over 39 | +----------+--------------+
Example
SELECT column_name(s) FROM table1
UNION ALL
SELECT column_name(s) FROM table2
In MariaDB and MySQL, the IF() function returns one of two values: IF(condition, value_if_true, value_if_false). For a portable SQL alternative, use CASE WHEN condition THEN value_if_true ELSE value_if_false END.
Example
SELECT pname, IF(priorit<2, 'Very Important', 'Normal priority') as Category FROM project;
Subquery or Inner query or Nested query is a query inside a query. A subquery is usually added in the WHERE Clause of the sql statement.
Most of the time, a subquery is used when you know how to search for a value using a SELECT statement, but do not know the exact value.
Example
All the person are fetched who were born in the same year as Morrison
SELECT lastname FROM person WHERE birthyear= ( SELECT birthyear FROM person WHERE lastname='Morrison');Example
SELECT lastname, salary FROM person WHERE salary >= ( SELECT min(salary)*2 FROM person);A scalar subquery used with = must return at most one row. The Morrison example assumes exactly one matching person; use a unique person ID to avoid ambiguity. To test membership in several returned values, use IN, as in the next example.
SELECT lastname FROM person WHERE id_person IN ( SELECT id_person FROM hour WHERE workhour>=400);
If we use word ANY like this
WHERE salary > ANY (SELECT salary FROM person WHERE city = 'Turku')ANY means at least one: the condition is true if the salary is greater than at least one value returned by the subquery.
and if we use word ALL, like this
WHERE salary > ALL (SELECT salary FROM person WHERE city = 'Turku')In this case, the condition is fulfilled when the salary is greater than all of the return values of the subquery.
Example, we have this data
+----------+--------+ | city | salary | +----------+--------+ | HELSINKI | 39 | | HELSINKI | 36 | | TAMPERE | 34 | | TURKU | 44 | | TURKU | 39 | | TURKU | 44 | +----------+--------+Then with this query
SELECT city, salary FROM person WHERE salary < ANY(SELECT salary FROM person WHERE city='Turku');we will get this data (all rows where salary < 44)
+----------+--------+ | city | salary | +----------+--------+ | TURKU | 39 | | HELSINKI | 39 | | HELSINKI | 36 | | TAMPERE | 34 | +----------+--------+And with this query
SELECT city, salary FROM person WHERE salary < ALL(SELECT salary FROM person WHERE city='Turku');we will get this data (all rows where salary < 39)
+----------+--------+ | city | salary | +----------+--------+ | HELSINKI | 36 | | TAMPERE | 34 | +----------+--------+
Let's create a practical example using subqueries to find projects located in the same city where Jim Morrison lives.
A more complex example: Find the names of people who have worked on the same projects as Jim Morrison.
Delete rows, which meets on a condition
DELETE FROM project WHERE id_project=2145;
UPDATE person SET salary=salary*1.2 WHERE city='TURKU';
INSERT INTO test VALUES(101,'Matt',180);or like this:
INSERT INTO test VALUES(102,'Liisa',NULL);In the previous examples the information must be given in that order as the columns are in the table and to every column a value must be fed(at least NULL.)
INSERT INTO test (name,id)
VALUES('Liisa',103);
You can also read data from one table and insert that data to a new table which does not already exist.
CREATE TABLE ... SELECT derives columns from the query result; it does not clone the full table definition. Indexes and constraints are not automatically copied, and column types or attributes may differ. Define the required structure explicitly when those details matter.
Example
Below code will read data from project-table and create a new table named helsinki with that data.
CREATE TABLE helsinki SELECT * FROM project WHERE place='Helsinki';If you already have table named helsinki and you want to insert data from project, you can use below code
INSERT INTO helsinki SELECT * FROM project WHERE place ='Helsinki';
Stored Procedures are SQL-scripts which will be stored in the database. Every RDBMS has they own syntax for procedures.
The following examples work in MariaDB and MySQL.
DELIMITER is a client command that changes the marker used to identify the end of a statement. The default marker is ;. A procedure contains semicolons inside its body, so DELIMITER // temporarily changes the marker to //. This lets the client send the complete CREATE PROCEDURE statement to the server. After END //, use DELIMITER ; to restore the normal marker.
The MariaDB and MySQL command-line clients support this command. Other SQL clients may handle procedure scripts differently; DELIMITER itself is not SQL sent to the server.
Next example will create an procedure named searchPerson:DELIMITER // CREATE PROCEDURE searchPerson(IN sname CHAR(50)) BEGIN SELECT * FROM person WHERE lastname=sname; END // DELIMITER ;Then we can call this procedure with this command:
CALL searchPerson('Morrison');
We can check what procedures we have in a certain database:
SHOW PROCEDURE STATUS WHERE db='database' \GWe can check the procedure syntax:
SHOW CREATE PROCEDURE searchPerson \GWe can delete the procedure:
DROP PROCEDURE searchPerson;
NOTE : In above example the query was so simple that it is not reasonable to save it as a procedure.
The following examples work in MariaDB and MySQL. In addition to parameters such as sname in the previous example, procedures can use variables to store values:
@ and belong to the current connection (session). Their values remain available after the procedure finishes, until changed or the connection is closed.DECLARE inside a BEGIN ... END block. They are available only within that block and are initialized on each call, using their DEFAULT value or NULL if no default is specified.This procedure looks up a person by primary key and stores their first name in the session variable @chosenFname. Use the person table created earlier on this page.
DELIMITER //
CREATE PROCEDURE chooseFirstName(IN p_id SMALLINT)
BEGIN
SET @chosenFname = (
SELECT firstname FROM person WHERE id_person = p_id
);
END //
DELIMITER ;
After creating the procedure, run these statements in your SQL client using the same connection. Replace 1 with an existing person's ID:
CALL chooseFirstName(1); SELECT @chosenFname AS chosen_firstname;
If person 1 has the first name 'Anna', the SELECT returns Anna. The variable remains available even though the procedure has finished. It is not shared with other connections, even if they use the same username.
The primary key lookup returns at most one row. If the person does not exist, the subquery returns NULL and the SET statement replaces any previous value of @chosenFname with NULL.
This version uses a local variable without @. DECLARE statements must appear at the beginning of the block, before executable statements.
DELIMITER //
CREATE PROCEDURE showFirstName(IN p_id SMALLINT)
BEGIN
DECLARE chosenFname VARCHAR(50) DEFAULT NULL;
SET chosenFname = (
SELECT firstname FROM person WHERE id_person = p_id
);
SELECT chosenFname AS chosen_firstname;
END //
DELIMITER ;
Call the procedure from your SQL client:
CALL showFirstName(1);
The SELECT inside the procedure returns the name as a result set. The local variable itself is no longer accessible after the procedure finishes. It is separate from the session variable @chosenFname and does not change its value.
In MariaDB and MySQL procedures, an IF statement chooses which statements to execute based on a condition. The basic syntax is:
IF condition THEN -- Statements executed when the condition is true ELSE -- Statements executed otherwise END IF;
The ELSE branch is optional. End the statement with END IF;, not just END;. This control-flow statement is different from the IF() function used in SELECT expressions.
The following procedure checks an amount and returns a message. It does not require any tables:
DELIMITER //
CREATE PROCEDURE checkAmount(IN p_amount DECIMAL(10,2))
BEGIN
IF p_amount > 0 THEN
SELECT 'Amount is positive' AS message;
ELSE
SELECT 'Enter an amount greater than zero' AS message;
END IF;
END //
DELIMITER ;
After creating the procedure, test both branches in your SQL client:
CALL checkAmount(10.00); -- Returns: Amount is positive CALL checkAmount(-5.00); -- Returns: Enter an amount greater than zero
The first call executes the THEN branch because 10.00 > 0 is true. The second executes the ELSE branch. Zero or NULL also takes the ELSE branch because the condition is not true.
Stored Procedures have several benefits:
A transaction groups database operations into one unit: either commit the changes together or roll them back. An SQL error does not always roll back the whole transaction automatically. The application or procedure must handle errors and issue ROLLBACK when needed.
START TRANSACTION begins an explicit transaction, COMMIT makes its changes permanent, and ROLLBACK cancels uncommitted changes. These examples require transactional tables, so both tables explicitly use InnoDB. DDL commands such as CREATE TABLE can cause an implicit commit in MariaDB/MySQL and should not be mixed into this transfer transaction.
Create a table with below code
CREATE TABLE bank_account( account_number VARCHAR(20) PRIMARY KEY, balance DECIMAL(10,2) NOT NULL, credit_limit DECIMAL(10,2) NOT NULL ) ENGINE=InnoDB;And another table with below code
CREATE TABLE action ( idaction int PRIMARY KEY AUTO_INCREMENT, account_number VARCHAR(20) NOT NULL, action VARCHAR(45) NOT NULL, action_time timestamp NOT NULL, amount DECIMAL(10,2) NOT NULL ) ENGINE=InnoDB;Then we will create two accounts, which both have balance 500.
INSERT INTO bank_account VALUES('FI001',500,1000);
INSERT INTO bank_account VALUES('FI002',500,2000);
Then create a procedure to transfer a positive amount between two different accounts. These procedures manage their own transactions: call them when the connection has no active transaction. The exception handler rolls back on SQL errors and RESIGNAL reports the error to the caller. A deadlock can still occur during concurrent transfers; the caller should retry the complete transaction when appropriate.
DELIMITER //
CREATE PROCEDURE debit_transfer(
IN first_account VARCHAR(20),
IN second_account VARCHAR(20),
IN transfer_amount DECIMAL(10,2) )
BEGIN
DECLARE test1,test2 INT DEFAULT 0;
START TRANSACTION;
UPDATE bank_account SET balance=balance-transfer_amount WHERE account_number=first_account AND balance >= transfer_amount;
SET test1=ROW_COUNT();
UPDATE bank_account SET balance=balance+transfer_amount WHERE account_number=second_account;
SET test2=ROW_COUNT();
IF (test1 > 0 AND test2 >0) THEN
INSERT INTO action(account_number,action,amount,action_time) VALUES(first_account,'withdrawal',transfer_amount,NOW());
INSERT INTO action(account_number,action,amount,action_time) VALUES(second_account,'deposit',transfer_amount,NOW());
COMMIT;
SELECT 1 AS success;
ELSE
ROLLBACK;
SELECT 0 AS success;
END IF;
END //
DELIMITER ;
Explanation:
CALL debit_transfer('FI001','FI002',100);
SELECT * FROM bank_account;
SELECT * FROM action;
and
CALL debit_transfer('FI001','ACC003',100);
SELECT * FROM bank_account;
SELECT * FROM action;
and
CALL debit_transfer('FI001','FI002',2000);
SELECT * FROM bank_account;
SELECT * FROM action;
Above transaction is suitable for bank_accounts where is not allowed that the balance is negative.
DELIMITER //
CREATE PROCEDURE credit_transfer(
IN first_account VARCHAR(20),
IN second_account VARCHAR(20),
IN transfer_amount DECIMAL(10,2) )
BEGIN
DECLARE test1,test2 INT DEFAULT 0;
START TRANSACTION;
UPDATE bank_account SET balance=balance-transfer_amount WHERE account_number=first_account AND balance + credit_limit >= transfer_amount;
SET test1=ROW_COUNT();
UPDATE bank_account SET balance=balance+transfer_amount WHERE account_number=second_account;
SET test2=ROW_COUNT();
IF (test1 > 0 AND test2 >0) THEN
INSERT INTO action(account_number,action,amount,action_time) VALUES(first_account,'withdrawal',transfer_amount,NOW());
INSERT INTO action(account_number,action,amount,action_time) VALUES(second_account,'deposit',transfer_amount,NOW());
COMMIT;
SELECT 1 AS success;
ELSE
ROLLBACK;
SELECT 0 AS success;
END IF;
END //
DELIMITER ;
The following animation demonstrates how transactions work with the bank account example:
Key concepts demonstrated in the animation:
Triggers are little modules stored on database. These modules are executed automatically, when certain SQL-commands are executed. We can define the triggers start when UPDATE-, INSERT-, or DELETE-command is executed.
ExampleCREATE TABLE Customers( idCustomers INT PRIMARY KEY AUTO_INCREMENT, customerName VARCHAR(20) ); CREATE TABLE Logs( idLogs INT PRIMARY KEY AUTO_INCREMENT, customerName VARCHAR(20), idCustomers INT, updateType VARCHAR(50) DEFAULT NULL, updateTime TIMESTAMP );Then create a trigger that saves the previous customer name to the Logs table whenever a Customers row is updated. Also we will add the time and explanation "update".
DELIMITER $$
CREATE TRIGGER updateCustomer
BEFORE UPDATE ON Customers
FOR EACH ROW
BEGIN
INSERT INTO Logs
SET updateType = 'update',
idCustomers = OLD.idCustomers,
customerName = OLD.customerName,
updateTime = NOW();
END$$
DELIMITER ;
Also we can create trigger, which will save the name of the Customer, to Logs-table, if we delete the Customer.
Also the time and explanation "delete" will be saved.
DELIMITER $$
CREATE TRIGGER deleteCustomer
BEFORE DELETE ON Customers
FOR EACH ROW
BEGIN
INSERT INTO Logs
SET updateType = 'delete',
idCustomers = OLD.idCustomers,
customerName = OLD.customerName,
updateTime = NOW();
END$$
DELIMITER ;
MariaDB and MySQL can schedule SQL statements using events. Events require the EVENT privilege for the database and a running Event Scheduler. Check SHOW VARIABLES LIKE 'event_scheduler';. An administrator can enable it with SET GLOBAL event_scheduler = ON;; to keep it enabled after a restart, configure event_scheduler=ON in the server configuration. The following example updates a value after one minute.
Create a test database and a table with below code
create database eventtest; use eventtest; create table atest( id_atest INT PRIMARY KEY AUTO_INCREMENT, amount int); insert into atest(amount) values(5);And create the event with below code
CREATE EVENT myevent
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
DO
UPDATE atest SET amount=amount + 1;
If you check the data after in a minute, you should see that the value of amount has been updated.
You can check what kind of event your database has with below command
SHOW events;Note! above event will be deleted after it has been executed. If you want to make an event which will be executed every 1 minute, you can use below code
DELIMITER //
CREATE EVENT permanent_event
ON SCHEDULE EVERY 1 MINUTE
DO
BEGIN
UPDATE atest SET amount=amount + 1;
END //
DELIMITER ;
If you now check the data once in a minute, you should see that the value of amount will be updated.
You can remove the event with the command
DROP EVENT permanent_event;
If you are going to make an application (web-application, Java-application etc), you should take care that there is no risk for SQL injection. SQL injection is a code injection technique that exploits security vulnerabilities in an application's database layer.
The first two animation steps illustrate a deliberately vulnerable legacy login that concatenates input and compares plain-text passwords. For this demonstration only, assume person also contains a password column; do not add plain-text passwords to the tutorial's person table. The final step uses a separate users table, password hashes and a parameterized query.
CREATE TABLE users ( id_user INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL ) ENGINE=InnoDB;
During registration, generate the hash with password_hash($password, PASSWORD_DEFAULT) and save it using a parameterized INSERT. During login, retrieve the hash by username and check it with password_verify(). The final animation assumes this table contains a registered user named Morrison.
For more information about SQL injection and security best practices, visit:
OWASP SQL Injection Prevention Cheat Sheet