Slow SELECT with LEFT JOIN over 4 tables - mysql

My database has 4 tables: org_addresses, org_main, zipcodes and cities.
The table org_addresses contains address data for organizations, the table org_main contains the main data for these organizations, the table zipcodes contains city zipcodes, and the table cities contains city names.
org_addresses:
| id | org_id | address_line | zipcode |
org_main:
| id | name |
zipcodes
| id | city_id | zipcode |
cities
| id | name |
In my query, I join all 4 tables using LEFT JOIN together to output complete data as a list with 1600 organizations.
As soon as I join the table zipcodes via LEFT JOIN and then join to the table cities, the request slows down to 4 seconds. Without the table zipcodes, the query takes 0.009 seconds.
SELECT a.org_id
, a.address_line2
, a.zipcode
, m.name
FROM org_addresses a
LEFT
JOIN org_main m
ON a.org_id = m.id
LEFT
JOIN zipcodes z
ON a.zipcode = z.zipcode
LEFT
JOIN cities c
ON z.city_id = c.id
What am I doing wrong?

You should look at the execution plans of the queries to see what is happening.
My guess would be that zipcodes(zipcode) is not indexed (and probably not cities(city). It is curious that you are not joining on the tables id instead.

be sure you have proper index
on table org_addresses composite index on columns (org_id, zipcode)
on table zipcodes column composite index on columns ( city_id, zipcode)
on table cities on column (id)

Related

SQL LEFT-JOIN with 2 foreign keys and merge them into one row (MySQL)

I have following tables
Orders:
id | address1 | address2 | state
1 | 2 | 4 | Delivered
2 | 7 | 1 | Payment
Address:
id | city
1 | New York
2 | Paris
4 | London
7 | Berlin
Now I need a statement to get both cities with order status.
For example order ID 1 should output:
Delivered | Paris | London
I tried the following statement:
SELECT orders.state, address.city FROM orders
LEFT JOIN address
ON orders.address1 = address.id OR orders.address2 = address.id;
It obviously only outputs one address city but I want both.
Any idea how the statement should look like?
You can do the self join:
select o.status, ad.city, ad1.city
from Orders o
left join Address ad on ad.id = o.address1
left join Address ad1 on ad1.id = o.address2;
You want to have aliases to your tables, this makes it readable.
SELECT o1.state, a1.city, a2.city FROM orders o1
LEFT JOIN address a1
ON o1.address1 = a1.id
LEFT JOIN address a2
ON o1.address2 = a2.id
;
Try this
SELECT o.ID, (CASE WHEN o.address1 = a.id THEN a.city) as addy1,
(CASE WHEN o.address2 = a.id THEN a.city) as addy2
FROM Orders as o LEFT JOIN Address as a WHERE ...
We Need to reach to the understanding that we have to deal 2 different tables rather than 1, as we want to have different result set from the same table.
Also we want the exact data , not the extra data thus opting for an Inner Join
Solution :
SELECT O.ID,AD1,A.CITY City1, o.AD2, b.City City2, STATE FROM ORDERS O
JOIN
ADDRS A
ON O.AD1=A.ID
JOIN ADDRS b
on O.AD2=b.ID
In Case you need the DDL and DML Scripts :
create table orders
(id int , ad1 int, ad2 int,state varchar(20))
insert into orders values
(2,7,1,'Payment')
create table addrs(id int,city varchar(20))
insert into addrs values(1,'NY')
insert into addrs values(2,'Paris')
insert into addrs values(4,'London')
insert into addrs values(7,'Berlin')
select * from orders
select * from addrs
Hope this answer focuses more on the concept and less on the Solution

SELECT using three tables w/ 1000+ entries

For transaction listing I need to provide the following columns:
log_out.timestamp
items.description
log_out.qty
category.name
storage.name
log_out.dnr ( Representing the users id )
Table structure from log_out looks like this:
| id | timestamp | storageid | itemid | qty | categoryid | dnr |
| | | | | | | |
| 1 | ........ | 2 | 23 | 3 | 999 | 123 |
As one could guess, I only store the corresponding ID's from other tables in this table. Note: log_out.id is the primary key in this table.
To get the the corresponding strings, int's or whatever back, I tried two queries.
Approach 1
SELECT i.description, c.name, s.name as sname, l.*
FROM items i, categories c, storages s, log_out l
WHERE l.itemid = i.id AND l.storageid = s.id AND l.categoryid = c.id
ORDER BY l.id DESC
Approach 2
SELECT log_out.id, items.description, storages.name, categories.name AS cat, timestamp, dnr, qty
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id
INNER JOIN categories ON log_out.categoryid = categories.id
ORDER BY log_out.id DESC
They both work fine on my developing machine, which has approx 99 dummy transactions stored in log_out. The DB on the main server got something like 1100+ tx stored in the table. And that's where trouble begins. No matter which of these two approaches I run on the main machine, it always returns 0 rows w/o any error *sigh*.
First I thought, it's because the main machine uses MariaDB instead of MySQL. But after I imported the remote's log_out table to my dev-machine, it does the same as the main machine -> return 0 rows w/o error.
You guys got any idea what's going on ?
If the table has the data then it probably has something to do with JOIN and related records in corresponding tables. I would start with log_out table and incrementally add the other tables in the JOIN, e.g.:
SELECT *
FROM log_out;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id
INNER JOIN categories ON log_out.categoryid = categories.id;
I would execute all the queries one by one and see which one results in 0 records. Additional join in that query would be the one with data discrepancy.
You're queries look fine to me, which makes me think that it is probably something unexpected with the data. Most likely the ids in your joins are not maintained right (do all of them have a foreign key constraint?). I would dig around the data, like SELECT COUNT(*) FROM items WHERE id IN (SELECT itemid FROM log_out), etc, and seeing if the returns make sense. Sorry I can't offer more advise, but I would be interested in hearing if the problem is in the data itself.

mysql where multiple fields in

I want to extract unique minlat, minlng based on city and country then want to find all id which have this pair I mean something like
select id from spots
where (minlat,minlng) in
(select s.minlat, s.minlng from spots s, spot_cards sc
where sc.spot_id=s.id and sc.country="italy"
and
(sc.city="perugia" or sc.locality="perugia" or sc.sublocality="perugia")
);
Structure of spots table is:
+----+-----------+-----------+
| id | minlat | minlng |
+----+-----------+-----------+
I created spot_cards table structure as
+---------+-------------+-------------+---------+--------+
| spot_id | sublocality | locality | country | city |
+---------+-------------+-------------+---------+--------+
by executing below query
insert into spot_cards(spot_id)
select id from spots
group by minlat,minlng
order by id
Any help is appreciated.
I think you need something like this:
Join the spots table to get all the records you want, then join again on the spots table to get the coordinates. Do a DISTINCT to get rid of any possible duplicates.
SELECT DISTINCT s2.id from spots s
INNER JOIN spot_cards sc
ON sc.spot_id=s.id
INNER JOIN spots s2
ON s.minlat = s2.minlat AND s.minlng = s2.minlng
WHERE sc.country="italy"
AND (sc.city="perugia" or sc.locality="perugia" or sc.sublocality="perugia")

Free search on multiple tables using JOIN

I have a DB of Students who have a fullname, a location, and a list of schools they frequented.
"student" table
id | fullname | location
------------------------
"location" table
id | zipcode | city
-------------------
"school" table
id | name
---------
"student_school" table (which holds two foreign keys on school and user to create for each user a list of schools)
id | id_student | id_school
---------------------------
I want to perform a search through the students comparing the search term with student.fullname, location.zipcode, location.city, school.name and return all the students matching one (or more) of these conditions.
Note that student can have a null location, so we need an extern join.
Here is example of matching based on regular expressions:
SELECT distinct s.id, s.fullname
FROM student_school ss
JOIN student s ON s.id = ss.id_student
LEFT JOIN LOCATION l ON s.LOCATION = l.id
JOIN school sc ON ss.id_school = sc.id
WHERE (s.fullname RLIKE 'Sasha.*') or
(ifnull(l.zipcode RLIKE '100.*', 0)) or
(ifnull(l.city RLIKE 'New.*', 0)) or
(sc.name RLIKE '.*2')
And here is complete SQL fiddle
So the general idea is to join all student data, filter rows matching at least one criterion and use distinct to group data and avoid duplicating of results.
Update:
To include students with no school records you may use following FROM phrase:
student_school ss
RIGHT JOIN student s ON s.id = ss.id_student
LEFT JOIN LOCATION l ON s.LOCATION = l.id
LEFT JOIN school sc ON ss.id_school = sc.id

mysql join tables with an if condition?

Im trying to join 5 tables that look somewhat like this
post table
ID | product | user-us | make-id | dealer-id | pending | .... 30 other columns ... |
make table
ID | make |
state table
prefix | state | city | zip
members table
ID | name | email | password | zip | dealer-id
dealer table
ID | dealer name | city | state | zip | address | phone
MySql query looks like this
SELECT *
FROM `post` AS p
JOIN (`make` AS m, `state` AS s, `members` AS mb, `dealer` AS d)
ON (p.Make = m.id AND p.state = s.id AND p.id = mb.id AND mb.dealer-id = d.id)
WHERE p.pending != '1'
The problem is this query only returns rows that member.dealer-id = dealer.id, And if i use a LEFT JOIN, it returns all the correct rows, BUT all columns in tables make, state, members and dealer will be NULL. which it shouldn't be because i need the info in those tables.
Is there away i can only join the dealer table if member.dealer-id is > 0? i could add a row in the dealer table with id 0, but there has to be a better way.
Once you code your joins in the normal way, use LEFT JOIN only on the dealer table:
SELECT *
FROM post AS p
JOIN make AS m ON p.Make = m.id
JOIN state AS s ON p.state = s.id
JOIN`members AS mb ON p.id = mb.id
LEFT JOIN dealer AS d ON mb.dealer_id = d.id
WHERE p.pending != '1'
This will automatically only join to dealer if the member.dealer-id is greater than zero.
btw, I have never seen a query join coded like yours before. If I had, I would have assumed it would not execute due to a syntax error - it looks that strange.