Introduction
A PHP application often needs more than variables, arrays, and text files. Once an application must store structured data, search it, update it, and retrieve only the rows that match a condition, a relational database becomes a much better tool.
MySQL is a relational database management system commonly used with PHP applications. A relational database stores data in tables. A table has rows and columns. Each row usually represents one real-world item, such as a member, customer, booking, product, or article. Each column stores one attribute of that item, such as a name, reference number, city, status, or year.
This post explains the practical MySQL commands a PHP developer needs before connecting PHP code to a database. The focus is not advanced database design. The goal is to understand the command patterns that create, change, read, and combine database data safely.
Context and Scope
Imagine a small web application that manages club members. The application needs to store details such as:
- A unique member number
- First name and last name
- Gender code
- Job title
- City of birth
- Year of birth
- Number of children
A relational database table is a natural fit for this data because each member has the same general shape. Each member is one row, and each property is one column.
A simplified table might look like this:
members
member_no | first_name | last_name | gender | job_title | birth_city | birth_year | children_count
----------|------------|-----------|--------|------------|------------|------------|---------------
1201 | Lena | Moore | F | nurse | Oslo | 1994 | 1
1202 | Omar | Kay | M | mechanic | Berlin | 1981 | 2
1203 | Nina | Shah | F | developer | Madrid | 1990 | 0
The commands in this post cover the main work you will do with that kind of table:
- Create a database and choose it as the current database.
- Understand table names, field names, field types, and primary keys.
- Insert new records.
- Delete records carefully.
- Change table structure with
ALTER TABLE. - Update existing records.
- Retrieve records with
SELECT. - Sort, filter, limit, count, and summarize results.
- Combine related tables with joins.
- Inspect databases and tables with
SHOWandDESCRIBE.
Relational Database Terms You Must Know
Before writing commands, it helps to make the terminology clear.
| Term | Practical meaning |
|---|---|
| Database | A container for one or more related tables. |
| Table | A two-dimensional structure made from rows and columns. |
| Row | One record, such as one member. |
| Record | Another name for a row. |
| Column | One field in the table, such as first_name. |
| Field | Another name for a column. |
| Field type | The kind of value the column stores, such as integer or variable-length text. |
| Primary key | A field, or group of fields, that uniquely identifies each row. |
| Query or command | An instruction sent to MySQL, such as inserting, selecting, updating, or deleting data. |
The most important idea is this: a table is not just a visual grid. MySQL also stores metadata about the table, including the names and types of its fields. That structure controls what kind of data can be stored and how MySQL treats it.
Creating and Selecting a Database
A database is the container where your tables live. Creating an empty database is simple:
CREATE DATABASE club_records
After creating it, you usually select it as the current database. That means later commands can refer to tables directly, without repeating the database name every time.
USE club_records
After this, a command such as SELECT * FROM members means "select from the members table inside the current database".
In a PHP-driven application, selecting the database is usually handled when the PHP code opens the database connection. Still, understanding USE is useful when learning MySQL directly or working in a database administration tool.
Naming Databases, Tables, and Fields
Database names, table names, and field names should be boring and predictable. Stick to letters, digits, underscores, and clear lowercase names when possible.
Good examples:
club_records
members
member_no
first_name
birth_year
children_count
Avoid names that contain spaces, hyphens, or unusual punctuation. MySQL can support names like that when they are quoted with backticks, but they make scripts easier to break and harder to read.
Prefer this:
SELECT first_name, last_name FROM members
Avoid designing tables that force this style:
SELECT `first name`, `last-name` FROM `club members`
There is also a practical case-sensitivity rule to remember. Field names are not case-sensitive in MySQL, so first_name and FIRST_NAME refer to the same field. Database and table name case-sensitivity can depend on the operating system used by the server. A safe habit is to choose one naming style and use it consistently everywhere.
Understanding Field Types
Each field has a type. You do not need every MySQL type to start building useful applications, but you should understand the idea.
For the members example, a table might contain fields like this:
| Field | Example type | Purpose |
|---|---|---|
member_no |
integer | Unique reference number. |
first_name |
variable-length text | Member first name. |
last_name |
variable-length text | Member last name. |
gender |
enumeration | Limited set of values, such as M or F. |
job_title |
variable-length text | Occupation or role. |
birth_city |
variable-length text | City name. |
birth_year |
year value | Four-digit year. |
children_count |
small integer | Small numeric value. |
Field types matter because MySQL treats numbers, text, dates, and special values differently. For example, text values normally need quotes in commands, while numeric values usually do not.
Choosing a Primary Key
A primary key is the value that lets MySQL identify one row exactly.
For a members table, last_name would be a poor primary key because many people can share the same last name. first_name plus last_name is better, but still not safe. Two members might have the same full name.
A dedicated member number is safer:
ALTER TABLE members ADD PRIMARY KEY (member_no)
The primary key should be stable, unique, and available for every row. It becomes especially useful when updating or deleting records because you can target one row without accidentally touching several.
If you set the wrong primary key, you can remove it and add a better one:
ALTER TABLE members DROP PRIMARY KEY
ALTER TABLE members ADD PRIMARY KEY (member_no)
In real projects, this kind of change should be handled carefully, especially once the database contains production data.
Inserting Records
The INSERT INTO command creates a new row.
A full insert can name every field explicitly:
INSERT INTO members
(member_no, first_name, last_name, gender, job_title, birth_city, birth_year, children_count)
VALUES
(1201, 'Lena', 'Moore', 'F', 'nurse', 'Oslo', 1994, 1)
This style is clear because the field list explains exactly what each value means. It is also safer when a table has many columns.
If you provide values for every field in the table's default order, MySQL lets you omit the field list:
INSERT INTO members
VALUES (1202, 'Omar', 'Kay', 'M', 'mechanic', 'Berlin', 1981, 2)
That is shorter, but it depends on the exact column order. If the table changes later, this form can become harder to maintain.
You can also insert only selected fields:
INSERT INTO members
(member_no, first_name, last_name, job_title, birth_city)
VALUES
(1203, 'Nina', 'Shah', 'developer', 'Madrid')
Fields not supplied by the command receive their default value, an empty value, or NULL, depending on how the table was defined.
Quotes Around Values
Use straight single quotes around text values:
INSERT INTO members
(member_no, first_name, last_name)
VALUES
(1204, 'Aron', 'Stone')
Do not forget quotes around text values. Without quotes, MySQL may treat the word as a field name rather than as text.
Numbers usually do not need quotes:
INSERT INTO members
(member_no, birth_year, children_count)
VALUES
(1205, 1978, 3)
For text fields that happen to contain digits, keep the quotes because the value is still text.
Inserting Several Records in One Command
When several rows use the same fields, you can insert them together:
INSERT INTO members
VALUES
(1206, 'Maja', 'Eriksen', 'F', 'teacher', 'Copenhagen', 1988, 2),
(1207, 'Leo', 'Grant', 'M', 'designer', 'Dublin', 1995, 0),
(1208, 'Sara', 'Novak', 'F', 'engineer', 'Prague', 1983, 1)
This is more compact than writing one command per row. It is especially convenient for seed data, examples, and controlled imports.
Replacing a Record by Primary Key
REPLACE INTO behaves like INSERT INTO, with one important difference. If a row already exists with the same primary key, MySQL removes the old row and inserts the new one.
REPLACE INTO members
VALUES (1208, 'Sara', 'Novak', 'F', 'systems engineer', 'Prague', 1983, 1)
This can be useful, but it should be used carefully. Replacing is not the same as changing selected fields. It can remove existing values that are not included in the new row. For normal edits, UPDATE is usually more explicit.
Deleting Records Safely
To delete a row, use DELETE FROM with a WHERE clause.
DELETE FROM members
WHERE member_no = 1207
The WHERE clause is the protection. Without it, the command affects every row in the table:
DELETE FROM members
That means "delete all member records". It is rarely what you want during normal application work.
When deleting by primary key, adding LIMIT 1 is a useful habit:
DELETE FROM members
WHERE member_no = 1207
LIMIT 1
Because member_no is the primary key, only one row can match. LIMIT 1 makes that intent clear and prevents MySQL from continuing to search for more matches after the row is found.
Changing Table Structure
ALTER TABLE changes the structure of a table. You can add fields, remove fields, change field names, change field specifications, and change primary keys.
To add a field at the end of a table:
ALTER TABLE members ADD passport_code VARCHAR(12)
To add a field after a specific existing field:
ALTER TABLE members ADD passport_code VARCHAR(12) AFTER job_title
To add a field as the first column:
ALTER TABLE members ADD passport_code VARCHAR(12) FIRST
To remove a field:
ALTER TABLE members DROP passport_code
The word COLUMN can also be used, but it is optional:
ALTER TABLE members DROP COLUMN passport_code
To rename a field and change its type at the same time:
ALTER TABLE members CHANGE children_count child_count TINYINT
A table structure change can affect stored data. For example, changing a text field into an integer field may destroy text values that cannot be converted into numbers. Treat structure changes as significant operations.
Emptying a Table Without Deleting Its Structure
There is a major difference between removing rows and removing the whole table.
To remove all rows but keep the table structure:
TRUNCATE members
To delete the table itself:
DROP TABLE members
Use TRUNCATE when you want an empty table with the same columns and field definitions. Use DROP TABLE only when the table is no longer needed.
Updating Existing Records
UPDATE changes values in existing rows.
To update one member using the primary key:
UPDATE members
SET gender = 'M', job_title = 'chef', birth_year = 1979, child_count = 99
WHERE member_no = 1203
LIMIT 1
The SET part can include one or more field = value pairs separated by commas.
The WHERE clause is as important here as it is for DELETE. Without it, every row in the table will be updated.
For example, this command updates every record:
UPDATE members
SET job_title = 'unknown'
That may be correct when you deliberately want a table-wide change, but it is dangerous if you intended to edit only one row.
You can also update many rows that match a condition:
UPDATE members
SET status_note = 'review'
WHERE gender = 'F' AND child_count > 2 AND child_count != 99
The extra child_count != 99 condition matters if 99 is being used as a special code for "unknown". Without that condition, rows with unknown child counts could be treated as if they had more than two children.
Using Field Values in Updates
The value assigned in an UPDATE command does not have to be a constant. It can be calculated from existing fields.
For example, to store the year after each member was born:
UPDATE members
SET review_year = birth_year + 1
To calculate a simple age-like value for a fixed year:
UPDATE members
SET calculated_value = 2020 - birth_year
To join first and last names into a single text value, MySQL provides CONCAT:
UPDATE members
SET display_name = CONCAT(first_name, ' ', last_name)
This copies the joined value into the field at that moment. It does not behave like a spreadsheet formula. If first_name or last_name changes later, display_name will not automatically change unless you run another update.
Understanding NULL
NULL means there is no value. It is not the same as zero, and it is not the same as an empty string.
0is a real numeric value.''is a real text value with no characters.NULLmeans the value does not exist or is unknown.
You can assign NULL:
UPDATE members
SET status_note = NULL
WHERE birth_year < 1980
To test for it, use IS NULL or IS NOT NULL:
SELECT first_name, last_name
FROM members
WHERE status_note IS NULL
SELECT first_name, last_name
FROM members
WHERE status_note IS NOT NULL
Do not use normal equality for NULL checks. NULL needs its own comparison form.
Reading Data with SELECT
The main reason to store data is to retrieve it later. SELECT returns rows from a table.
To select every field from every row:
SELECT * FROM members
The * means all fields.
In application code, you usually select only the fields you need:
SELECT first_name, last_name, gender, child_count
FROM members
This makes the result smaller and clearer. It also documents which fields the application actually uses.
When MySQL commands are issued from PHP, most non-SELECT commands report success or failure. A valid SELECT command returns a result object from which rows and columns can be extracted. The application can then display those values, format them, email them, or use them in later logic.
Sorting Results with ORDER BY
Do not rely on MySQL returning rows in the order they were inserted. If the order matters, state it explicitly.
Sort by primary key:
SELECT * FROM members
ORDER BY member_no
Ascending order is the default, but you can write it explicitly:
SELECT * FROM members
ORDER BY member_no ASC
Descending order uses DESC:
SELECT * FROM members
ORDER BY member_no DESC
You can sort by multiple fields:
SELECT * FROM members
ORDER BY last_name, first_name DESC
This sorts by last_name first. If two rows have the same last name, those rows are sorted by first_name in descending order.
Custom Sort Orders
Alphabetical order is not always the right order. Suppose a field contains values such as small, medium, and large. Alphabetical sorting would not represent the logical order.
MySQL supports a custom order with FIELD:
SELECT * FROM packages
ORDER BY FIELD(package_size, 'small', 'medium', 'large', 'extra large')
A custom order is useful when the values have a business meaning that does not match alphabetical or numeric order.
Filtering Results with WHERE
A WHERE clause selects only the rows that satisfy a condition.
SELECT last_name, first_name, birth_year
FROM members
WHERE gender = 'M' AND child_count > 0 AND child_count != 99
ORDER BY last_name, first_name
A simple condition follows this pattern:
field operator value
Common comparison operators include:
| Operator | Meaning |
|---|---|
= |
Equal to |
!= |
Not equal to |
<> |
Not equal to |
< |
Less than |
<= |
Less than or equal to |
> |
Greater than |
>= |
Greater than or equal to |
Conditions can be combined with AND, OR, and NOT:
SELECT * FROM members
WHERE gender = 'F'
AND (birth_year > 1985 OR child_count != 99)
AND NOT (job_title = 'doctor' OR job_title = 'engineer')
Parentheses make the intended logic easier to read and safer to maintain.
Filtering Ranges with BETWEEN
BETWEEN checks whether a value is inside a range.
For numeric or year values:
SELECT * FROM members
WHERE birth_year BETWEEN 1980 AND 2000
ORDER BY birth_year
For text values, the range follows alphabetical ordering:
SELECT * FROM members
WHERE last_name BETWEEN 'Hill' AND 'Stone'
ORDER BY last_name
When combining clauses, remember the order: WHERE comes before ORDER BY.
Selecting Values That Are Not Direct Fields
SELECT can return calculated or combined values, not only stored columns.
For example, this query returns a full name built from two fields:
SELECT CONCAT(first_name, ' ', last_name), gender
FROM members
WHERE child_count > 1 AND child_count != 99
ORDER BY last_name, first_name
This is useful for display output. You can keep data normalized in separate fields but still produce friendly output when needed.
Limiting Results
LIMIT restricts how many rows are returned.
To list the four youngest members by birth year:
SELECT * FROM members
ORDER BY birth_year DESC
LIMIT 4
LIMIT 1 is also useful when selecting by primary key:
SELECT first_name, last_name, birth_year
FROM members
WHERE member_no = 1201
LIMIT 1
If only one row can match, there is no need to continue looking after it is found.
MySQL also supports an offset:
SELECT * FROM members
ORDER BY birth_year DESC
LIMIT 2, 4
The first number is the offset. The second number is how many rows to return. Because counting starts at zero, LIMIT 2, 4 skips the first two rows and then returns four rows.
This pattern is useful for paging through ordered results.
Summarizing Data with Functions
MySQL can summarize field values directly.
Useful functions include:
MIN, to find the smallest valueMAX, to find the largest valueAVG, to calculate an average
Example:
SELECT MIN(birth_year), MAX(birth_year), MIN(last_name), MAX(last_name), AVG(child_count)
FROM members
MIN and MAX can work with numbers and text. With text, they use alphabetical ordering.
Be careful with special codes. If 99 means "unknown" for child_count, including it in an average will produce misleading output. Filter it out:
SELECT MIN(child_count), MAX(child_count), AVG(child_count)
FROM members
WHERE child_count != 99
Counting Rows
To count every row in a table:
SELECT COUNT(*) FROM members
To count rows where a field has a non-null value:
SELECT COUNT(birth_city) FROM members
To count rows that satisfy a condition, you can use COUNT with IF:
SELECT COUNT(IF(birth_year > 1985 AND gender = 'F', 1, NULL))
FROM members
The idea is that matching rows produce a countable value, while non-matching rows produce NULL, which is not counted.
Finding Distinct Values
DISTINCT returns only unique values.
SELECT DISTINCT gender
FROM members
This is useful when exploring data. For example, a title field might contain inconsistent values such as Mrs, Mrs., and MRs. Before cleaning the data, you need to know which values exist.
You can also find distinct combinations:
SELECT DISTINCT gender, child_count
FROM members
This returns each unique pair only once.
Combining Tables with Joins
Relational databases become more useful when information is split into related tables and combined when needed.
Imagine two tables:
pets
pet_id | pet_name | legs | can_fly
-------|----------|------|--------
1 | dog | 4 | no
2 | parrot | 2 | yes
3 | salmon | 0 | no
4 | lizard | 4 | no
pet_types
type_name | category
----------|----------
dog | mammal
parrot | bird
salmon | fish
rabbit | mammal
Both tables contain an animal name, but the field names are different: pet_name in one table and type_name in the other.
Inner Join
An inner join returns rows that have matching values in both tables.
SELECT *
FROM pets INNER JOIN pet_types
ON pets.pet_name = pet_types.type_name
ORDER BY pet_name
This returns only animals found in both tables. In this example, dog, parrot, and salmon match. lizard does not appear because it is missing from pet_types. rabbit does not appear because it is missing from pets.
Left Outer Join
A left outer join returns every row from the first table and adds matching data from the second table when available.
SELECT *
FROM pets LEFT OUTER JOIN pet_types
ON pets.pet_name = pet_types.type_name
ORDER BY pet_name
If a pet has no matching type, the fields from pet_types are returned as null values.
Use a left outer join when the first table is your main list and the second table is optional supporting information.
Right Outer Join
A right outer join returns every row from the second table and adds matching data from the first table when available.
SELECT *
FROM pets RIGHT OUTER JOIN pet_types
ON pets.pet_name = pet_types.type_name
ORDER BY type_name
If a type has no matching pet, the fields from pets are returned as null values.
Use a right outer join when the second table is the main list you want to preserve.
Join Checklist
When writing joins, check these points:
- Which table should provide the full list of rows?
- Which fields are being compared?
- Do both compared fields contain the same kind of values?
- Can either side contain duplicates?
- Do you want only matches, all rows from the left table, or all rows from the right table?
- Are field names duplicated across tables? If yes, qualify them with table names.
Auto-Increment Fields
Sometimes a table does not naturally contain a safe primary key. Names, cities, years, and categories can repeat. In that case, an auto-increment field can provide a unique value automatically.
An auto-increment field is usually an integer field. MySQL gives it the next sequential value whenever a new row is inserted. It commonly starts at 1 and increases by 1 for each new record.
A table can have only one auto-increment field. It is usually used as the primary key.
If an auto-increment field is not the last field in the table and you insert a full row, use an empty string placeholder for the value that MySQL should generate:
INSERT INTO members
VALUES ('', 1210, 'Tara', 'Mills', 'F', 'analyst', 'Rome', 1992, 0)
To change the next auto-increment value:
ALTER TABLE members AUTO_INCREMENT = 100
This can be useful when you want generated identifiers to begin at a chosen value.
Inspecting Databases and Tables
MySQL includes commands for inspecting structure and system information.
List available databases:
SHOW DATABASES
List tables in the current database:
SHOW TABLES
List tables in a specific database:
SHOW TABLES IN club_records
Show the fields in a table:
SHOW FIELDS FROM members
Equivalent forms are:
SHOW COLUMNS FROM members
DESCRIBE members
The field description output normally includes information such as:
- Field name
- Field type
- Whether
NULLis allowed - Whether the field is indexed or is a primary key
- Default value
- Extra information, such as auto-increment behavior
To inspect system variables:
SHOW VARIABLES
To display the MySQL version:
SHOW VARIABLES LIKE 'version'
These commands are useful when debugging environment differences or verifying table structure before writing application code.
Renaming and Removing Tables or Databases
To rename a table, you can use either form:
ALTER TABLE members RENAME TO archived_members
RENAME TABLE members TO archived_members
To delete a table:
DROP TABLE archived_members
To delete a database and all its tables:
DROP DATABASE club_records
These commands are destructive. In a real project, use them with care and make sure you understand exactly which database is selected.
Comments in MySQL Commands
MySQL commands can include comments. A comment is ignored by MySQL and is meant for humans.
Single-line comments can start with #:
DROP DATABASE old_test_db # remove unused test database
They can also start with two minus signs followed by a space:
DROP DATABASE old_test_db -- remove unused test database
Block comments use /* and */:
DROP DATABASE /* remove unused test database */ old_test_db
Comments can make maintenance scripts easier to read, but avoid placing destructive commands in shared scripts unless the purpose is completely clear.
Practical Workflow for Building Confidence
When learning these commands, do not start with a production database. Use a small practice database and a table with a few rows.
A safe learning workflow looks like this:
- Create a practice database.
- Select it with
USE. - Work with an existing table structure.
- Add a primary key.
- Insert a few records.
- Run simple
SELECTcommands. - Add
WHEREfilters. - Add
ORDER BYclauses. - Try
LIMITwith and without an offset. - Update one row using the primary key.
- Delete one row using the primary key.
- Add and remove a temporary column.
- Use
SHOW FIELDSorDESCRIBEto confirm structure. - Practice joins with two small related tables.
The goal is to understand how each command changes either the data or the table structure.
Common Mistakes to Watch For
Forgetting WHERE in DELETE or UPDATE
This is the most dangerous beginner mistake.
UPDATE members SET job_title = 'unknown'
This changes every row. Use a WHERE clause when you mean to target selected rows.
Using a non-unique field as a primary key
A surname, city, title, or job is rarely unique. Prefer a stable identifier such as a reference number or an auto-increment field.
Confusing NULL with zero or an empty string
NULL, 0, and '' mean different things. Use IS NULL and IS NOT NULL when testing for missing values.
Assuming row order without ORDER BY
MySQL is not required to return rows in insertion order. Use ORDER BY whenever order matters.
Relying too much on short INSERT commands
INSERT INTO members VALUES (...) is compact, but it depends on column order. Naming fields is often clearer and safer.
Changing field types without thinking about existing data
Converting a text field to a numeric field can destroy values that cannot be converted. Structure changes should be treated as careful operations.
Mixing special codes with real data
Using 99 to mean "unknown" can work in examples, but it can also distort calculations. Always filter special codes out of averages and business rules.
Developer Checklist
Use this checklist when writing MySQL commands for a PHP-backed application:
- Choose clear lowercase names for databases, tables, and fields.
- Use a primary key that uniquely identifies one row.
- Prefer explicit field lists in
INSERT INTOcommands. - Quote text values with straight single quotes.
- Do not quote ordinary numeric values.
- Always review
DELETEandUPDATEcommands before running them. - Use
WHEREfor targeted changes. - Use
LIMIT 1when targeting a single row by primary key. - Use
ORDER BYwhen output order matters. - Use
DISTINCTto explore inconsistent field values. - Use
COUNT,MIN,MAX, andAVGfor summaries, but filter out special codes. - Use joins only after confirming which table should control the result set.
- Use
SHOW FIELDSorDESCRIBEto verify table structure. - Treat
DROP TABLEandDROP DATABASEas destructive commands.
Conclusion
MySQL becomes much easier to understand when you separate its work into two categories: commands that change data or structure, and commands that retrieve information.
INSERT INTO, REPLACE INTO, UPDATE, DELETE, ALTER TABLE, TRUNCATE, and DROP change something. SELECT, SHOW, and DESCRIBE help you read data or inspect structure. Clauses such as WHERE, ORDER BY, and LIMIT make those commands precise.
For PHP developers, this foundation is essential. PHP can send commands to MySQL, receive results, and turn those results into web output. Before adding PHP code, make sure you understand what each MySQL command does on its own. That makes your database-backed applications safer, easier to debug, and easier to maintain.