I have some case where I need to write a single SQL query but inside that query, I also need to get some data on another table. Let me give some visualization.
The customers table schema
Name | Type | Options |
---|---|---|
id | INT | PRIMARY KEY, AUTO INCREMENT |
VARCHAR(255) | NOT NULL | |
name | VARCHAR(255) | NOT NULL |
I have some case where I need to write a single SQL query but inside that query, I also need to get some data on another table. Let me give some visualization.
The customers table schema
Name | Type | Options |
---|---|---|
id | INT | PRIMARY KEY, AUTO INCREMENT |
VARCHAR(255) | NOT NULL | |
name | VARCHAR(255) | NOT NULL |
The addresses table schema
Name | Type | Options |
---|---|---|
id | INT | PRIMARY KEY, AUTO INCREMENT |
customer_id | INT | FOREIGN KEY REF: customers.id |
address | VARCHAR(255) | NOT NULL |
is_default | SMALL INT | DEFAULT: 0 |
The example case is: I need to get each customer along with their default addresses, if I run this query
SELECT
id,
name,
(
SELECT
address
FROM addresses
WHERE
customer_id = customers.id AND
is_default = 1 LIMIT 1
) AS default_address
FROM customers;
Would it be possible for this query to cause N+1 problem because for each customer it also needs to fetch their default address?
I have write those kind of query on my projects for many times and it just occurs to me "Would it be possible for those query to cause N+1"?
Would expect anyone to give me some explanation about it, is the N+1 query can be happening or not. Thank you!
Share Improve this question edited Feb 17 at 4:36 Dale K 27.3k15 gold badges57 silver badges83 bronze badges asked Feb 17 at 3:53 Dewa AdiDewa Adi 11 bronze badge New contributor Dewa Adi is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 4 |2 Answers
Reset to default 0This is a potential N+1 problem. The subquery inside SELECT executes once per customer. If the database doesn't optimize it well, this means that for N customers, the subquery runs N times (once per row)
Instead of a subquery, use a LEFT JOIN
SELECT
c.id,
c.name,
a.address AS default_address
FROM customers c
LEFT JOIN addresses a
ON c.id = a.customer_id AND a.is_default = 1;
It fetches all data in one go instead of running a subquery per row. If customer_id is indexed, the join will be optimized. Also for a large dataset JOIN queries are more efficient than the subquery
- N+1 refers specifically to executing N+1 separate queries.
- The example correlated subquery case is a single SQL query.
With no proper indexing on the tables, MySQL could possibly run multiple table scans which can be inefficient, but it's still not technically N+1.
MySQL has EXPLAIN
which can show how a query will be optimized.
A cleaner approach, but not necessarily more efficient:
SELECT c.id, c.name, a.address AS default_address
FROM customers c
LEFT JOIN addresses a ON c.id = a.customer_id AND a.is_default = 1
LIMIT
withoutORDER BY
is fairly non sensical. – Tim Biegeleisen Commented Feb 17 at 3:55join
– mr mcwolf Commented Feb 17 at 4:01addresses
table only one row percustomer_id
containsis_default <> 0
? Does that code provides strictly one such row (the presence of such row)? Why this mark which is a customer entity attribute is stored not in customers table but in addresses table? – Akina Commented Feb 17 at 6:56