Image from CS50, licensed under CC BY-NC-SA 4.0.

What is SQL?

SQL or structured query language is a declarative language used for relational databases.
It is important to note that everything that is done in SQL could be done in other languages.
SQL has been designed to manipulate data more easily.
One SQL line can replace many lines of python.
Here also, we will not spend time on the syntax but rather on some interesting concepts that we covered in the course.

CRUD
The main actions that we make on a database are :
C: Create / INSERT
R: Read / SELECT
U: Update / ALTER
D: Delete / DROP

Primary key, foreign key and join tables

A Primary Key is a unique identifier for a row in a table. It ensures that each record in the table can be uniquely identified.
A Foreign Key is a field in one table that refers to the Primary Key of another table. It establishes a relationship between the two tables.
Let's consider two tables: Company and Employee.

Here the Company ID serves as the Primary Key, ensuring each company has a unique identifier.

Company ID (PK) Company Name Product Location
1 Tech Corp Software USA
2 InnovateX AI Tools Germany

And here the Company ID is a Foreign Key, linking each employee to a company from the Company Table.

Employee Name Employee Position Company ID (FK)
Alice Developer 1
Bob Manager 2

In a many-to-many relationship, a single record in one table can be associated with multiple records in another table, and vice versa. Since relational databases do not support direct many-to-many relationships, we use a join table (also called a junction table) to bridge the two tables. Consider the following tables:

To track which students are enrolled in which courses, we create a join table that contains foreign keys referencing both tables.

      
      CREATE TABLE student_course (
          student_id INT,
          course_id INT,
          PRIMARY KEY (student_id, course_id),
          FOREIGN KEY (student_id) REFERENCES Student(id) ON DELETE CASCADE,
          FOREIGN KEY (course_id) REFERENCES Courses(id) ON DELETE CASCADE
      );
      
      

To retrieve the names of students who have taken a specific course (e.g., Computer Science), use the following query:

      
      SELECT Student.name 
      FROM Student 
      JOIN student_course ON Student.id = student_course.student_id 
      JOIN Courses ON Courses.id = student_course.course_id 
      WHERE Courses.subject = 'Computer Science';
      
      

Table Summary

Relationship Example Schema Foreign key placement
One to One (1:1) Person → Passport passports.person_id UNIQUE Foreign key in one table, set as unique.
One-to-Many (1:M) Company → Employees employees.company_id Foreign key in the child table (employees).
Many-to-Many (M:M) Students ↔ Courses enrollments(student_id, course_id) Join table with two foreign keys.


Indexes, Race conditions and SQL injections attacks

Indexes
Indexes create a B-tree structure (not binary tree structure) that allows us to access information faster.
They improve performance a lot when we want to read data from the database but might slow down the insertion and deletion ( as we saw with tree like structure that need to stay balanced). So they are great for read heavy sites.

Race conditions
Imagine a picture goes viral and everyone in the world wants to like it at the same time.
How can we make sure we can hanlde the database update in a way that data is not lost?
So there are many techniques to handle this problem but we will focus on one in particular which is transcations and pessimistic locking.
In our example, the transaction will group the reading and the updating of the likes in one operation and the locking will make sure that one transaction is executed at a time.
For more on the topic look ACID properties in database

SQL Injection Attacks
SQL Injection Attacks are malicious inputs that could give access to unauthorized information.
If your API is using f format and not placeholders, a user could enter as username : name'--
The ' would close the first string and the -- would ignore everything after this command.
So basically I user could access the account of someone by only entering the username and not the password.
So use the placeholder instead of the f string.
rows = db.execute (f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'") // Bad
rows = db.execute ("SELECT * FROM users WHERE username = ? AND password = ? ", username , password) // Better