Copter Labs Copter Labs

Smart Design.

For Smart People.

Hold on... This isn't EnnuiDesign.com — What Gives?

It's been a long time coming, but Jason Lengstorf, formerly of Ennui Design, has expanded his team to include Drew Douglass, Rob MacKay, Henry Moran, and Tom Sturge.

It didn't feel right to keep the same name, so we decided to continue on as Copter Labs. You can expect the same great content under this new name!

Optimizing MySQL Queries with JOINs

DISCLAIMER: I am by no means a MySQL optimization expert, and I can't guarantee that the information provided in this blog entry will take a query from painfully slow to blazing fast. I can, however, guarantee that this entry was written with the best intentions and aims to explore some of the more advanced features of MySQL.

This week, I wanted to share some of my recent experiments with MySQL queries in regard to lowering the number of requests sent to the database server and, hopefully, avoiding the bottleneck that can form on sites that rely heavily on database interaction.

What Are MySQL Joins?

A JOIN in MySQL is a tool that allows developers to combine one or more tables in a query. There are a few flavors of JOIN, but we'll be focusing on LEFT JOIN today.

A LEFT JOIN in MySQL combines rows from two tables. We're able to narrow our searches as usual using a WHERE clause, and we can specify how the tables are joined with a USING clause.

A Simple Example

It's tough to explain JOIN in plain English without getting lost in a bunch of technical jargon (as I'm sure anyone who ventures into the MySQL manual is already aware). It's far easier to provide a concrete example to illustrate the way that LEFT JOIN works.

We'll be using three tables in this example: users, lists, and items. These three tables will be the basis of a theoretical wish list application, with a user's unique ID and name stored in the users table, unique list ID and user ID stored in lists, and individual item IDs, containing list ID, and item text in the items table.

Table: users
+---------+-----------------+
| user_id | user_name       |
+---------+-----------------+
| 1       | Jason Lengstorf |
+---------+-----------------+
| 2       | John Doe        |
+---------+-----------------+

Table: lists
+---------+---------+
| list_id | user_id |
+---------+---------+
| 1       | 2       |
+---------+---------+
| 2       | 1       |
+---------+---------+

Table: items
+---------+---------+----------------------+
| item_id | list_id | item_text            |
+---------+---------+----------------------+
| 1       | 1       | iPod Touch           |
+---------+---------+----------------------+
| 2       | 1       | Chuck Season 2       |
+---------+---------+----------------------+
| 3       | 1       | Nike Free Shoes      |
+---------+---------+----------------------+
| 4       | 2       | F-Stop Maverick Bag  |
+---------+---------+----------------------+
| 5       | 2       | Bon Iver Tickets     |
+---------+---------+----------------------+
| 6       | 2       | Rogue Mocha Porter   |
+---------+---------+----------------------+

Imagine we've been supplied with a user's ID and need to retrieve the items on his wish list. Without using JOIN, we would need two MySQL queries: one to retrieve the list ID associated with the user, and one to retrieve all the items associated with that list ID.

Example 1 (Not Using JOIN)

<?php
	// Establish a MySQL connection
	$link = new PDO(DBINFO, DBUSER, DBPASS);

	// Retrieve the list ID associated with the user ID
	$sql = "SELECT list_id
			FROM lists
			WHERE user_id = ?
			LIMIT 1";
	$stmt = $link->prepare($sql); // Prepare the statement
	if($stmt->execute(array($_POST['user_id'])))
	{
		$row = $stmt->fetch(); // Load the results in an array
		$list_id = $row['list_id'];
		$stmt->closeCursor(); // Free memory used in this query

		// Retrieve the items associated with the list ID
		$sql = "SELECT item_text
				FROM items
				WHERE list_id = $list_id";
		foreach($link->query($sql) as $row) {
			// Output the wish list items
			echo $row['item_text'], "<br />";
		}
	}
?>

Assuming the user has supplied the user id of 1, we would see the following output:

F-Stop Maverick Bag
Bon Iver Tickets
Rogue Mocha Porter

This returns the desired result, but sending two queries just feels inefficient. Fortunately, we can shorten this script to just one query using a JOIN.

Example 1 (Using JOIN)

<?php
	// Establish a MySQL connection
	$link = new PDO(DBINFO, DBUSER, DBPASS);

	// Retrieve the items associated with the user ID
	$sql = "SELECT item_text
			FROM lists
			LEFT JOIN items
			USING (list_id)
			WHERE user_id = ?";
	$stmt = $link->prepare($sql); // Prepare the statement
	if($stmt->execute(array($_POST['user_id'])))
	{

		// Loop through the returned results
		while($row = $stmt->fetch()) {
			// Output the wish list items
			echo $row['item_text'], "<br />";
		}

		$stmt->closeCursor(); // Free memory used in this query
	}
?>

Using a LEFT JOIN, we were able to combine the lists and items tables, grouping entries by their list IDs. Essentially, by using a JOIN, we're temporarily creating a new table that looks like this:

SELECT * FROM lists LEFT JOIN items USING (list_id)
+---------+---------+---------+---------------------+
| list_id | user_id | item_id | item_text           |
+---------+---------+---------+---------------------+
| 1       | 2       | 1       | iPod Touch          |
+---------+---------+---------+---------------------+
| 1       | 2       | 2       | Chuck Season 2      |
+---------+---------+---------+---------------------+
| 1       | 2       | 3       | Nike Free Shoes     |
+---------+---------+---------+---------------------+
| 2       | 1       | 4       | F-Stop Maverick Bag |
+---------+---------+---------+---------------------+
| 2       | 1       | 5       | Bon Iver Tickets    |
+---------+---------+---------+---------------------+
| 2       | 1       | 6       | Rogue Mocha Porter  |
+---------+---------+---------+---------------------+

With all the information available from both tables, we're able to use our user's ID to directly select wish list items.

Going a Step Further

Our wish list isn't very useful if it only shows a list of products. In order to make any sense, we should probably show the user's name to indicate who it is that wants the displayed items.

This presents a problem for us, however, because we now need to pull information from all three tables in order to gather all the necessary data.

Example 2 — Combining Three Tables Using LEFT JOIN

<?php
	// Establish a MySQL connection
	$link = new PDO(DBINFO, DBUSER, DBPASS);

	// Retrieve the user's name and associated list items
	$sql = "SELECT user_name, item_text
			FROM users
			LEFT JOIN lists
			USING (user_id)
			LEFT JOIN items
			USING (list_id)
			WHERE user_id = ?";
	$stmt = $link->prepare($sql); // Prepare the statement
	if($stmt->execute(array($_POST['user_id'])))
	{

		// Loop through the returned results
		while($row = $stmt->fetch()) {

			// Save the user name
			$user_name = $row['user_name'];

			// Create an array of items
			$items[] = $row['item_text'];
		}

		$stmt->closeCursor(); // Free memory used in this query

		// Output the user's name and identify what we're displaying
		echo "$user_name's Wish List<br /><br />n";

		// Loop through the items and output to the browser
		foreach($items as $item) {
			echo $item, "<br />";
		}
	}
?>

With the above query, we effectively retrieve the user's name from the users table, then pull the list ID that matches the user's ID, and finally retrieve all items that match the list ID.

The above script will output the following, assuming user ID 1 was passed:

Jason Lengstorf's Wish List

F-Stop Maverick Bag
Bon Iver Tickets
Rogue Mocha Porter

By combining the queries using LEFT JOIN, we create a much easier to read snippet of code. Also, by reducing the number of queries run, we can hope to see faster execution in our scripts.

Summary

Like I said, I'm not a MySQL optimization expert, but from what I've read and my limited testing, it seems pretty safe to say that using JOIN to reduce the number of queries in our scripts is a good way to not only enhance the readability of our scripts, but also to grease them up for faster execution.

Do you know any tricks for speeding up MySQL queries? How do you feel about using JOIN in queries? Let me know in the comments!

Date. 05/05/2009

Comments. 19

Category. MySQL

Jason Lengstorf

Jason Lengstorf

Jason Lengstorf a turbogeek hailing from Portland, Oregon. He designs and develops websites using PHP, MySQL, JavaScript (jQuery), CSS, and HTML. He's written two books (PHP for Absolute Beginners [2009 Apress] and Pro PHP and jQuery [2010 Apress]), and he's written articles on development and design for Nettuts, CSS Tricks, and Smashing Magazine, among others.

Was This Post Helpful? Pass It On!

Share the Love

If this post taught you something, reminded you of something you had forgotten, or just made you feel good, there's really no better way to say "thank you" than passing it along to your friends.

Don't forget to like us on Facebook, join our newsletter, and/or subscribe to our RSS feed to make sure you hear about new posts first!

Join Our Gaggle of Geeks
* indicates required

Comments.

  1. Gravatar

    When do you use left join versus inner join?

  2. Gravatar

    Haha, nice drawing. Nice post too, and a timely tip in my case. Was just about to cobble together a few complicated MySQL statements, and this is a good reminder to slow down and do it right:

    SQL is your friend! What can be done with one SQL statement (usually) shouldn't be split into many. Speed, atomicity, etc... and most importantly less places for code to break.

    One thing, though: it seems like bad form to plug a Superglobal straight into a DB-query ($_POST['user_id'] in this case). Is the PDO->prepare() statement enough to safeguard against SQL-injection and the like?

  3. Gravatar

    @John Masterson:

    It looks like the difference between LEFT JOIN and INNER JOIN is this:

    LEFT JOIN will include all results that exist in the left table, regardless of whether there is a match in the right table, meaning you could potentially have a NULL result from the right table;

    INNER JOIN will include all results that exist in BOTH tables, so no NULL results will be returned.

    Here's a slightly more in-depth (but still easy to follow) comparison: http://answers.yahoo.com/question/index?qid=20070427082608AA7DwSh

    @Dan Z.:

    Using PDO->prepare() is 100% effective against SQL injection (according to the PHP manual, at least). The prepare() method performs all escaping as part of the preparation process.

    Thanks for the feedback!

  4. Gravatar

    On my site whenever I have needed to handle taking data from multiple tables, I have always used UNION.

    Do you know how it compares with JOIN?

  5. Gravatar

    You really will want to avoid joins as well if your tables start to have a large number of rows. Try using subqueries. So instead of

    SELECT item_text

    FROM lists

    LEFT JOIN items

    USING (list_id)

    WHERE user_id = ?

    SELECT item_text

    FROM lists

    WHERE user_id = (

    SELECT list_Id

    FROM items

    WHERE user_id = ?

    )

  6. Gravatar

    @Will:

    I'm not sure what the performance differences are. One thing I noticed when poking around for information is that UNION will produce duplicate entries if rows meet both query conditions unless you use UNION DISTINCT.

    @CoryMathews:

    Thanks for your input! I'll definitely keep that in mind when working with larger data sets. Do you have a link to any research about the differences between the two approaches?

    I like the look of your blog, by the way. Nice work!

  7. Gravatar

    Hi,

    "You really will want to avoid joins as well if your tables start to have a large number of rows. Try using subqueries."

    While this approach is probably efficient in some cases, it is sometimes *very* inefficient.

    In all cases, you do not have to avoid a join. Just check that you have the right index on your tables. ;)

    On Cory Mathews' example, that would be on item_text.user_id

  8. Gravatar

    What about RIGHT JOIN :)

  9. Gravatar

    @Silky:

    You know, when I wrote this, I probably had a reason in mind for not including RIGHT JOIN, but I can't remember what it was. I probably should have gone over it. There's a really simple explanation of LEFT JOIN vs RIGHT JOIN here: http://phpweby.com/tutorials/mysql/32

  10. Gravatar

    I am very much pleased with the contents you have mentioned.I wanted to thank you for this great article. I enjoyed every little bit part of it and I will be waiting for the new updates.

  11. Gravatar

    Keep up the good work, I'll be coming back to read any of your future articles

  12. Gravatar

    This is so great that I had to comment. I am usually just a lurker, taking in knowledge and nodding my head in quiet approval at the good stuff.

  13. Gravatar

    Hello, Neat post. There is a problem along with your web site in web explorer, could check this?K IE still is the marketplace leader and a good portion of other folks will leave out your fantastic writing

    because of this problem.

  14. Gravatar

    Some try to make a difference.

  15. Gravatar

    Well written article, well researched and useful for me in the future.I am so happy you took the time and effort to make this. See you around

  16. Gravatar

    Nice to be visiting your blog again Herve Leger, it has been months for me. Well this article that i’ve been waited for so long. I need this article to complete my assignment in the college, and it has same topic with your article Herve Leger Dresses. Thanks, great share.

  17. Gravatar

    Since the start of his campaign, Gingrich has Adidas Titan wagered that a display of humility will convince voters that Adidas Shoes the former House speaker who shut down Jeremy Scott Shop the government and earned headlines with provocative Adidas Porsche rhetoric in the 1990s has matured Jeremy Scott Wings into a presidential contender.With only a week before the first round of voting in Iowa’s Jan.

  18. Gravatar

    Have no doubts because of trouble nor be thou discomfited

  19. Gravatar

    This has to be one of the most interesting articles i’ve ever read on a blog! (then again, this is one of the most interesting blogs i’ve ever come across)

Join In.

Have something to say? By all means, speak up!

But first, a few rules:

  • Don’t be a jerk.
  • Use your real name, not your business name - this is a discussion, not a billboard.
  • Only <strong>, <em>, and <code> are allowed tags.
  • Wrap code samples in <code> tags.

Happy commenting!

Add a Comment