SQL for Data Science › Chapter 2

Chapter 2

A CRUD app with Python and MySQL

3:00 → 50:00XAMPP · phpMyAdminmysql-connector-pythonLive demo on this page

Before any theory, Nitish builds a tiny register / login program in Python backed by a MySQL database. The aim is to show you what a database is for: every real application (Google, Facebook, any app you use) stores its data in one and talks to it from code.

The mindset

Don't treat databases as a boring subject. Once you work at a company you realise they are an interesting topic, and they are what lets you build real-world software.

Setup: what you need

1 · XAMPP

Free software that runs a database server (MySQL / MariaDB) and Apache on your own machine, so you don't need a cloud database. Start Apache and MySQL from the XAMPP control panel.

2 · phpMyAdmin

Comes with XAMPP. A web GUI for your local database at localhost/phpmyadmin: create databases and tables, insert, browse, edit, delete rows.

3 · An editor

Nitish uses PyCharm; any Python IDE works.

4 · A connector library

mysql-connector-python lets Python code connect to MySQL and run SQL.

pip install mysql-connector-python

The project has two files:

  • main.py: the application (menus, user input, what to show).
  • dbhelper.py: everything that talks to the database.

Databases hold tables, tables hold rows

An important idea: you never store data directly "in a database". Inside a database you create tables, and data lives in a table's rows and columns.

MySQL server (XAMPP) Database: my_app_db Table: users user_id | name | email | … 1 | Nitish | nitish@gmail… | … Another database its own tables… phpmyadmin, test… system / sample DBs
Server → databases → tables → rows & columns.

In phpMyAdmin he creates a new database and, inside it, a users table for registration with four columns:

ColumnTypeWhy
user_idINT, A_I (auto-increment), primary keyA unique number per user, like a roll number in college: names can repeat, roll numbers can't.
nameVARCHAR(255)Text, up to 255 characters.
emailVARCHAR(255)Used to log in.
passwordVARCHAR(255)Used to log in.
Auto-increment

With A_I ticked you never supply the id yourself. You tell the database: "check what the last user's id was, add one, and fill it in for me." Primary keys are covered in detail in Chapter 4.

The equivalent SQL (you will learn to write this yourself in Chapter 7):

CREATE TABLE users (
    user_id  INT PRIMARY KEY AUTO_INCREMENT,
    name     VARCHAR(255),
    email    VARCHAR(255),
    password VARCHAR(255)
);

CRUD: the only four things you ever do with data

Whatever project you build with a database, every interaction is one of four operations:

Createtap to flip
Add new data.

e.g. you send a WhatsApp message, register a new account.
INSERT
Read / Retrievetap to flip
Fetch existing data.

e.g. logging in: the app checks your email and password against stored rows.
SELECT
Updatetap to flip
Change existing data.

e.g. editing a comment you posted on a friend's photo.
UPDATE
Deletetap to flip
Remove data.

e.g. deleting a message you sent.
DELETE

You use 50 apps a day; imagine any action in any of them and it is always one of these four. Thinking "divide and rule" this way makes working with databases much easier.

Mini-game · Which CRUD operation?
Tap an action, then tap the operation it belongs to.
Logging in with email + password Signing up for a new account Editing a comment Deleting a sent message Posting a photo Searching for a friend's profile Changing your password Removing an item from your cart Scrolling your feed Placing an order
Create
Read
Update
Delete

In phpMyAdmin you can do all four by hand: Insert a row (leaving user_id blank, since auto-increment fills it), Browse/Search (read), Edit (update) and Delete. But real applications don't open phpMyAdmin; they do the same four operations through code, in Python or any other language. That's what we build next.

Connecting Python to MySQL (dbhelper.py)

Create a DBHelper class. Its constructor connects to the database once; afterwards every method uses that connection.

import mysql.connector
import sys

class DBHelper:

    def __init__(self):
        # connect to the database once, when the object is created
        try:
            self.conn = mysql.connector.connect(
                host="localhost",
                user="root",
                password="",          # XAMPP's default root password is empty
                database="my_app_db"
            )
            self.mycursor = self.conn.cursor()
        except:
            print("Some error occurred. Could not connect to database")
            sys.exit(0)
        else:
            print("Connected to database")
  • Connection object (self.conn): your live link to the database.
  • Cursor (self.mycursor): the object you use to execute SQL and fetch results.
  • try / except: many things can go wrong (wrong host, wrong password, a database name that doesn't exist, MySQL not running). Nitish demonstrates several of these failures live; on any of them, print a message and exit with sys.exit(0) instead of crashing later.
Coming later

You can also create the database itself from code (Python or plain SQL) instead of clicking in phpMyAdmin. That's covered in the DDL chapter.

Create: registration

In main.py, a menu-driven program asks the user what to do. Nitish builds the menu with plain input() (a GUI would need more setup):

import sys
from dbhelper import DBHelper

class Flipkart:

    def __init__(self):
        self.db = DBHelper()       # connect once
        self.menu()

    def menu(self):
        user_input = input("""
        1. Enter 1 to register
        2. Enter 2 to login
        3. Anything else to leave
        """)
        if user_input == "1":
            self.register()
        elif user_input == "2":
            self.login()
        else:
            sys.exit(1000)

    def register(self):
        name = input("Enter the name: ")
        email = input("Enter the email: ")
        password = input("Enter the password: ")

        response = self.db.register(name, email, password)
        if response:
            print("Registration successful")
        else:
            print("Registration failed")
        self.menu()

obj = Flipkart()

The database work lives in DBHelper.register. It builds an INSERT query, executes it, and commits:

    def register(self, name, email, password):
        try:
            self.mycursor.execute("""
            INSERT INTO users (user_id, name, email, password)
            VALUES (NULL, '{}', '{}', '{}')
            """.format(name, email, password))
            self.conn.commit()     # make the change permanent
        except:
            return -1
        else:
            return 1
  • NULL for user_id: auto-increment fills it in.
  • conn.commit(): without it, the insert isn't saved. (Commit belongs to TCL, see Chapter 5.)
  • Return 1 / -1 so the caller can print success or failure.
Design pattern: keep the layers separate

Database code goes in one place, application logic in another, and the part the user sees (the GUI) in a third. This is the idea behind MVC (Model–View–Controller), which you'll meet in software development. Here the "view" is just input()/print(), so there are two files instead of three.

Read: login

Login is a read: find a row whose email and password match. fetchall() returns a list of matching rows (each a tuple).

    def search(self, email, password):
        self.mycursor.execute("""
        SELECT * FROM users WHERE email LIKE '{}' AND password LIKE '{}'
        """.format(email, password))
        data = self.mycursor.fetchall()
        return data
    def login(self):
        email = input("Enter the email: ")
        password = input("Enter the password: ")

        data = self.db.search(email, password)
        if len(data) == 0:
            print("Incorrect email/password")
            self.login()
        else:
            print("Hello", data[0][1])   # row 0, column 1 = name
            # next: a logged-in menu (view / edit profile, delete account…)

How do we decide whether the login worked? Simply by whether the query returned any rows. An empty list means no user has that email + password.

Try it: the same app, running in your browser

This demo runs a real SQL engine in the page (SQLite compiled to WebAssembly, close enough to MySQL for these queries). Register a few users, log in, and watch the exact SQL each button generates.

Live demo · register & login
1 · Register (INSERT)
2 · Login (SELECT)
-- the generated SQL appears here

The users table right now:

My addition: don't ship this code as-is

The course keeps things simple on purpose. In real code, two things change:

  • Never build SQL with .format() from user input. Someone can type ' OR '1'='1 as a password and log in as anyone (SQL injection). Use parameterised queries, where the driver escapes values for you:
    cursor.execute("SELECT * FROM users WHERE email = %s AND password = %s", (email, password))
  • Never store plain-text passwords. Store a salted hash (for example with bcrypt) and compare hashes.

Use = rather than LIKE for exact matches too; LIKE treats % and _ as wildcards.

Update and delete: the logged-in menu

After a successful login, the app shows a second menu (for example: view profile, edit profile, delete account, log out). Each option is one more CRUD method on DBHelper:

-- Update: edit profile
UPDATE users SET name = 'Nitish Singh' WHERE user_id = 1;

-- Delete: delete account
DELETE FROM users WHERE user_id = 1;

In Python, the same pattern applies: execute(...) then conn.commit(). The full syntax of INSERT, SELECT, UPDATE and DELETE is the subject of the DML chapter.

A note from the class: should data ever really be deleted?

When data is related to other data (a user's orders, comments, payments), deleting it can break those relationships. That's why many real systems avoid hard deletes. This connects to referential integrity in Chapter 3.

Recap

connwhat is it?
The connection object from mysql.connector.connect(host, user, password, database).
cursorwhat is it?
Runs SQL with execute() and returns rows with fetchall().
commit()when?
After any change (INSERT / UPDATE / DELETE), so it's saved permanently.
A_Iwhat does it do?
Auto-increment: the DB assigns the next id itself. Pass NULL (or skip the column).

Conceptual questions

These check whether you understand why, not just what. Several go one step beyond the video.

Why does DBHelper connect once in its constructor instead of connecting inside every method?

Connecting involves network round trips and authentication. Create it once and reuse it (real apps use a connection pool).

What is the cursor's role, as opposed to the connection's?

Connection = the pipe; cursor = what you send SQL through and read rows back from.

Why is keeping database code in dbhelper.py, separate from menu/UI code, a good idea?

Separation of concerns: the idea behind MVC.

A user enters the password ' OR '1'='1 and gets logged in as someone else. Which part of the course's code made this possible?

String-formatting user input into SQL allows SQL injection. Parameterised queries prevent it.