SQL syntax error - Haversine formula - mysql

I'm trying to get nearest places from a WordPress database using Haversine formula
My table structure below
posts
+--------------+
| Field |
+--------------+
| ID |
| post_author |
| post_title |
| post_type |
+--------------+
postmeta
+--------------+
| Field |
+--------------+
| meta_id |
| post_id |
| meta_key |
| meta_value |
+--------------+
and have records with meta_key values latitude and longitude
See the answer to my previous question for the SQL I've used to get the latitude and longitude.
SELECT p.ID,
p.post_title,
p.post_author,
max(case when pm.meta_key='latitude' then pm.meta_value end) latitude,
max(case when pm.meta_key='longitude' then pm.meta_value end) longitude
FROM `wp_posts` p
LEFT JOIN `wp_postmeta` pm
on p.ID=pm.post_id
WHERE p.post_type='place'
AND (pm.meta_key='latitude' OR pm.meta_key='longitude')
GROUP BY p.ID, p.post_title, p.post_author
ORDER BY p.ID ASC
Now I want to incorporate above query into answer for this question
SELECT item1, item2,
( 3959 * acos( cos( radians(37) )
* cos( radians( lat ) )
* cos( radians( lng )
- radians(-122) )
+ sin( radians(37) )
* sin( radians( lat ) )
)
) AS distance
FROM geocodeTable
HAVING distance < 25
ORDER BY distance LIMIT 0 , 20;
Below is by combined query
SELECT ID,
post_title,
post_author,
max(case when meta_key='latitude' then meta_value end) latitude,
max(case when meta_key='longitude' then meta_value end) longitude,
( 3959 * acos( cos( radians(18.204540500000) )
* cos( radians( latitude ) )
* cos( radians( longitude )
- radians(-66.450958500000) )
+ sin( radians(18.204540500000 )
* sin( radians( latitude ) )
)
) AS distance
FROM `wp_posts`
LEFT JOIN `wp_postmeta`
on ID=post_id
WHERE post_type='place'
AND (meta_key='latitude' OR meta_key='longitude')
GROUP BY ID, post_title, post_author
ORDER BY ID ASC
But this yields syntax error
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AS distance FROM `wp_posts` LEFT JOIN `wp_postmeta` on ID=post_id WHERE po' at line 13

You are missing a closing ) for the first sin()
( 3959 * acos( cos( radians(18.204540500000) )
* cos( radians( latitude ) )
* cos( radians( longitude )
- radians(-66.450958500000) )
+ sin( radians(18.204540500000 ) ) /* <--- here */
* sin( radians( latitude ) )
)
) AS distance
Though it is difficult to spot visually, I found this by copying your code into a text editor that supports brace matching. It is highly recommended to use one, if not for query development and testing, then at least for debugging.

Try this:
SELECT *, ( 3959 * ACOS( COS( RADIANS(18.204540500000) )
* COS( RADIANS( latitude ) )
* COS( RADIANS( longitude )
- RADIANS(-66.450958500000) )
+ SIN( RADIANS(18.204540500000 ) )
* SIN( RADIANS( latitude ) )
)
) AS distance
FROM
(SELECT ID,
post_title,
post_author,
MAX(CASE WHEN meta_key='geo_latitude' THEN meta_value END) latitude,
MAX(CASE WHEN meta_key='geo_longitude' THEN meta_value END) longitude
FROM `wp_posts`
LEFT JOIN `wp_postmeta`
ON ID=post_id
WHERE post_type='place'
AND (meta_key='geo_latitude' OR meta_key='geo_longitude')
GROUP BY ID, post_title, post_author
ORDER BY ID ASC) AS A

Related

How to write SQL JOIN query with geolocation in php

I am new to sql.
Sample of offerings Table
offering_id | offering_meal_id | offering_name | offering_profile_id | Offering_Expiration_Date_Time | Lat | Lon
Sample of profile Table
profile_id | fullname
I am passing following parameters $latitude , $longitude, $meal_id
I want to write a query which will
get all data from offerings table in basis of latitude and longitude and Offering_Expiration_Date_Time is greater than or equal to today's date. For this I have following calculation which is working.
SELECT *, ( 6371 * ACOS( COS( RADIANS( ".$latitude." ) ) *
COS( RADIANS( Lat ) ) * COS( RADIANS( Lon ) - RADIANS( ".$longitude." ) ) + SIN( RADIANS( ".$latitude." ) )
* SIN( RADIANS( Lat ) ) ) ) AS distance FROM offerings HAVING distance < 3 AND Offering_Expiration_Date_Time>='".date('Y-m-d h:i:s')."'
Next Select only those offering which meal_id = $meal_id
Join profile table and get fullname from it where offerings.offering_profile_id = profile.profile_id
order it by offering_id
EDIT
SELECT offerings.*,profile.fullname,
( 6371 * ACOS( COS( RADIANS( 16.691120 ) ) * COS( RADIANS( Lat ) ) * COS( RADIANS( Lon ) - RADIANS( 74.219978 ) ) + SIN( RADIANS( 16.691120 ) ) * SIN( RADIANS( Lat ) ) ) )
AS distance
FROM offerings
INNER JOIN profile
ON offerings.offering_profille_id = profile.profile_id
WHERE distance < 3
AND offerings.offering_meal_id = 2
AND Offering_Expiration_Date_Time>='2017-08-23 15:43:00'
ORDER BY offerings.offering_id
LIMIT 10
Before your having clause, join to the profile table like so:
Inner join profile on offerings.offering_profile_id = profile.profile_id
Then include at end:
Order by offerings.offering_id
Also make sure to include in your select statement
Profile.fullname
Also change having to where and add this into your where condition:
And offerings.offering_meal_id = $meal_id

Search by alias without showing the alias

I have a table of categories and a table of items.
Each item has latitude and longitude to allow me to search by distance.
What I want to do is show each category and how many items are in that category, within a distance chosen.
E.g. Show all TVs in Electronics category within 1 mile of my own latitude and longitude.
Here's what I'm trying but I cannot have two columns within an alias, obviously, and am wondering if there is a better way to do this?
Here is a SQL fiddle
Here's the query:
SELECT *, ( SELECT count(*),( 3959 * acos( cos( radians(52.993252) )
* cos( radians( latitude ) )
* cos( radians( longitude ) - radians(-0.412470) )
+ sin( radians(52.993252) )
* sin( radians( latitude ) ) ) ) AS distance
FROM items
WHERE category = category_id group by item_id
HAVING distance < 1 ) AS howmanyCat,
( SELECT name FROM categories WHERE category_id = c.parent ) AS parname
FROM categories c ORDER BY category_id, parent
First, start with the distance calculation for each item, then join in the category information and aggregate and filter
select c.*, count(i.item_id) as numitems
from category c left outer join
(SELECT i.*, ( 3959 * acos( cos( radians(52.993252) ) * cos( radians( latitude ) )
* cos( radians( longitude ) - radians(-0.412470) ) + sin( radians(52.993252) )
* sin( radians( latitude ) ) )
) AS distance
FROM items i
) i
on c.category_id = i.category_id and distance < 1
group by category_id;
Is this what you're looking for:
SELECT categories.name, count(items.item_id) as cnt
FROM items
JOIN categories
ON categories.category_id=items.category
WHERE ( 3959 * acos( cos( radians(52.993252) )
* cos( radians( latitude ) )
* cos( radians( longitude ) - radians(-0.412470) )
+ sin( radians(52.993252) )
* sin( radians( latitude ) ) ) ) < 1
GROUP BY categories.category_id;
this gives:
Tvs | 1
You can put the expression for computing the distance inside a nested SELECT, and then join the results to the categories table, like this:
SELECT COUNT(*), cc.name FROM (
SELECT
i.item_id
, c.category_id
, ( 3959 * acos( cos( radians(52.993252) )
* cos( radians( latitude ) )
* cos( radians( longitude ) - radians(-0.412470) )
+ sin( radians(52.993252) )
* sin( radians( latitude ) ) ) ) AS distance
FROM items i
JOIN categories c ON c.category_id = i.category
) raw
JOIN categories cc ON raw.category_id = cc.category_id AND raw.distance < 1
GROUP BY cc.name
The nested query pairs up items and categories, and adds the calculated distance column. The outer query then filters the rows by distance, and groups them by category to produce the desired output:
COUNT(*) NAME
-------- ----
1 TVs
Demo on sqlfiddle.

How to solve unknown alias column issue without using nested queries?

I've the query
SELECT ID,
post_title,
post_author,
max(case when meta_key='geo_latitude' then meta_value end) latitude,
max(case when meta_key='geo_longitude' then meta_value end) longitude,
( 3959 * acos( cos( radians(18.204540500000) )
* cos( radians( latitude ) )
* cos( radians( longitude )
- radians(-66.450958500000) )
+ sin( radians(18.204540500000 ) )
* sin( radians( latitude ) )
)
) AS distance
FROM `wp_posts`
LEFT JOIN `wp_postmeta`
on ID=post_id
WHERE post_type='place'
AND (meta_key='geo_latitude' OR meta_key='geo_longitude')
GROUP BY ID, post_title, post_author
ORDER BY ID ASC
which results in
Error Code: 1054. Unknown column 'latitude' in 'field list'
Is there any way to solve this without using inner/nested queries?
Try this:
SELECT ID,
post_title,
post_author,
MAX(CASE WHEN meta_key='geo_latitude' THEN meta_value END) latitude,
MAX(CASE WHEN meta_key='geo_longitude' THEN meta_value END) longitude,
( 3959 * ACOS( COS( RADIANS(18.204540500000) )
* COS( RADIANS( MAX(CASE WHEN meta_key='geo_latitude' THEN meta_value END) ) )
* COS( RADIANS( MAX(CASE WHEN meta_key='geo_longitude' THEN meta_value END) )
- RADIANS(-66.450958500000) )
+ SIN( RADIANS(18.204540500000 ) )
* SIN( RADIANS( MAX(CASE WHEN meta_key='geo_latitude' THEN meta_value END) ) )
)
) AS distance
FROM `wp_posts`
LEFT JOIN `wp_postmeta`
ON ID=post_id
WHERE post_type='place'
AND (meta_key='geo_latitude' OR meta_key='geo_longitude')
GROUP BY ID, post_title, post_author
ORDER BY ID ASC
You can create a view:
create view geo as
select id, post_title, post_author,
max(case when meta_key='geo_latitude' then meta_value end) as latitude,
max(case when meta_key='geo_longitude' then meta_value end) as longitude
from wp_posts
where post_type='place'
and (meta_key='geo_latitude' OR meta_key='geo_longitude')
group by id, post_title, post_author;
and join your query with it instead of wp_posts:
SELECT ID,
post_title,
post_author,
latitude,
longitude,
( 3959 * acos( cos( radians(18.204540500000) )
* cos( radians( latitude ) )
* cos( radians( longitude )
- radians(-66.450958500000) )
+ sin( radians(18.204540500000 ) )
* sin( radians( latitude ) )
)
) AS distance
FROM `geo`
LEFT JOIN `wp_postmeta`
on ID=wp_postmeta.post_id
ORDER BY ID ASC;

SQL: Complicated Select Statement and returned radius

This query is driving me insane and I honestly just don't know how to accomplish it.
I need to grab all USERS and SHOPS longitude and latitude that are within a certain radius, and return back some other information attached to those resulting IDs... the radius part, I have figured out:
( 3959 * acos( cos( radians('48.453541') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('-123.491765') ) + sin( radians('48.453541') ) * sin( radians( lat ) ) ) ) AS distance
^This radians in the statement above are just a random address that I want to query against.
But grabbing results that essentially are two different rows AS ONE result confuses me right out. Both tables are constructed in this manner:
id
meta_key
meta_value
So I need to grab the "meta_value" of two keys for both a USER and a SHOP ... the "meta_key" for the USER is "bid_user_lat" and "bid_user_lng" ... and the "meta_key" for the SHOP are "bid_resource_lat" and "bid_resource_lng"
Ideal results (just showing one of each, but should be multiple, depending on radius):
id - 223
first_name - "Mark"
last_name - "Johnson"
bid_user_lat - 45.0000
bid_user_lng - -150.0000
id - 688
company_name - "Joe's Shop"
bid_resource_lat - 45.0000
bid_resource_lng - -150.0000
I really hope I'm explaining this correctly, because this is just driving me nuts!
My updated query is as follow, yet it's still not producing any results and it should:
SET #lat = '48.453541';
SET #lng = '-123.491765';
SET #radius = '10000';
SELECT
wp_usermeta.user_id,
bid_user_lat.meta_value,
bid_user_lng.meta_value,
( 3959 * acos( cos( radians( #lat ) ) * cos( radians( bid_user_lat.meta_value ) ) * cos( radians( bid_user_lng.meta_value ) - radians( #lng ) ) + sin( radians( #lat ) ) * sin( radians( bid_user_lat.meta_value ) ) ) ) AS distance
FROM
wp_usermeta
JOIN
wp_usermeta bid_user_lat
ON wp_usermeta.user_id = bid_user_lat.user_id
AND wp_usermeta.meta_key = "bid_user_lat"
JOIN
wp_usermeta bid_user_lng
ON wp_usermeta.user_id = bid_user_lng.user_id
AND wp_usermeta.meta_key = "bid_user_lng"
HAVING distance < #radius ORDER BY distance LIMIT 0 , 20;
You can get this information by using self-joins:
SELECT
table.id,
bid_user_lat.meta_value,
bid_user_lng.meta_value
FROM
table
JOIN
table bid_user_lat
ON table.id = bid_user_lat.id
AND table.meta_key = "bid_user_lat"
JOIN
table bid_user_lng
ON table.id = bid_user_lng.id
AND table.meta_key = "bid_user_lng"
WHERE
{ distance clause }
...the distance clause is where you ensure that it's within the proper distance. You can use bid_user_lat.meta_value as the bid_user_lat value, and bid_user_lng.meta_value as the bid_user_lng value in your calculation.
If you want to return the User results in the same query as the Shop results, then you can do the same query as the above one (using Shops instead of Users, of course), and connect them with a UNION or UNION ALL.
Side note:
If you have any control over the database, you may want to re-visit the design. The solution above is not ideal by any means... If a User always has a bid_user_lat and a bid_user_lng field, or even if it's just a common attribute of User, then it really should have its own column in the User table.
Correction:
I was JOINing incorrectly. The ON clause should use bid_user_lng.meta_key = "bid_user_lat" instead of wp_usermeta.meta_key = "bid_user_lat". You also need to add a GROUP BY clause so that you don't have duplicate records. Applying these to your query above, you get:
SET #lat = '48.453541';
SET #lng = '-123.491765';
SET #radius = '10000';
SELECT
wp_usermeta.user_id,
bid_user_lat.meta_value,
bid_user_lng.meta_value,
( 3959 * acos( cos( radians( #lat ) ) * cos( radians( bid_user_lat.meta_value ) ) * cos( radians( bid_user_lng.meta_value ) - radians( #lng ) ) + sin( radians( #lat ) ) * sin( radians( bid_user_lat.meta_value ) ) ) ) AS distance
FROM
wp_usermeta
JOIN
wp_usermeta bid_user_lat
ON wp_usermeta.user_id = bid_user_lat.user_id
AND bid_user_lat.meta_key = "bid_user_lat"
JOIN
wp_usermeta bid_user_lng
ON wp_usermeta.user_id = bid_user_lng.user_id
AND bid_user_lng.meta_key = "bid_user_lng"
GROUP BY wp_usermeta.user_id
HAVING distance < #radius ORDER BY distance LIMIT 0 , 20;
Here's confirmation that it works:
mysql> CREATE TABLE wp_usermeta (user_id INTEGER UNSIGNED, meta_key VARCHAR(255), meta_value VARCHAR(255));
Query OK, 0 rows affected (0.25 sec)
mysql> INSERT INTO wp_usermeta (user_id, meta_key, meta_value)
-> VALUES
-> (1, "bid_user_lat", "45.000"), (1, "bid_user_lng", "-150.000"),
-> (2, "bid_user_lat", "20.000"), (2, "bid_user_lng", "20.000"),
-> (3, "bid_user_lat", "-300.000"), (3, "bid_user_lng", "70.000");
Query OK, 6 rows affected (0.16 sec)
Records: 6 Duplicates: 0 Warnings: 0
mysql> SELECT
-> wp_usermeta.user_id,
-> bid_user_lat.meta_value,
-> bid_user_lng.meta_value,
->
-> ( 3959 * acos( cos( radians( #lat ) ) * cos( radians( bid_user_lat.meta_value ) ) * cos( radians( bid_user_lng.meta_value ) - radians( #lng ) ) + sin( radians( #lat ) ) * sin( radians( bid_user_lat.meta_value ) ) ) ) AS distance
->
-> FROM
-> wp_usermeta
-> JOIN
-> wp_usermeta bid_user_lat
-> ON wp_usermeta.user_id = bid_user_lat.user_id
-> AND bid_user_lat.meta_key = "bid_user_lat"
-> JOIN
-> wp_usermeta bid_user_lng
-> ON wp_usermeta.user_id = bid_user_lng.user_id
-> AND bid_user_lng.meta_key = "bid_user_lng"
->
-> GROUP BY wp_usermeta.user_id
->
-> HAVING distance < #radius ORDER BY distance LIMIT 0 , 20;
+---------+------------+------------+--------------------+
| user_id | meta_value | meta_value | distance |
+---------+------------+------------+--------------------+
| 1 | 45.000 | -150.000 | 1271.3329043047352 |
| 3 | -300.000 | 70.000 | 4905.4310014631055 |
| 2 | 20.000 | 20.000 | 7198.549954690863 |
+---------+------------+------------+--------------------+
3 rows in set (0.05 sec)

Mysql, select value of one cell depending on contents of another cell

My current code
SELECT post_id, ( 3959 * ACOS( COS( RADIANS( 34.09 ) ) * COS( RADIANS( lat ) ) * COS( RADIANS( lng ) - RADIANS( -117.55 ) ) + SIN( RADIANS( 34.09 ) ) * SIN( RADIANS( lat ) ) ) ) AS distance FROM wp_postmeta WHERE `meta_key` LIKE '%location_l%' HAVING distance < 2500 ORDER BY distance LIMIT 0 , 20
Here are two sample row of data (meta_key is not always long or lat):
post_id = 123
meta_key = location_longitude
meta_value = -119.890000
post_id = 123
meta_key = location_latitude
meta_value = 42.170000
How do I modify my query to replace 'lat' and 'lng' in my original query to be the contents of the meta_value listed above? Something like this?
select meta_value where meta_key = location_latitude
If you GROUP BY post_id, you should be able to use MAX() aggregates in place of lat,lng, surrounding CASE statements which determine whether the current row is latitude or longitude. The others will be NULL, and therefore eliminated by the aggregate.
I think this ought to work so you won't need any subselects.
SELECT
post_id,
( 3959 * ACOS( COS( RADIANS( 34.09 ) ) * COS( RADIANS( MAX(CASE WHEN meta_key='location_latitude' THEN meta_value ELSE NULL END) ) ) * COS( RADIANS( MAX(CASE WHEN meta_key='location_longitude' THEN meta_value ELSE NULL END) ) - RADIANS( -117.55 ) ) + SIN( RADIANS( 34.09 ) ) * SIN( RADIANS( MAX(CASE WHEN meta_key='location_latitude' THEN meta_value ELSE NULL END) ) ) ) ) AS distance
FROM wp_postmeta
WHERE `meta_key` IN ('location_latitude','location_longitude')
GROUP BY post_id
HAVING distance < 2500
ORDER BY distance
LIMIT 0 , 20
The moving parts here are:
MAX(CASE WHEN meta_key='location_latitude' THEN meta_value ELSE NULL END)
This translates as: If this row's meta_key is 'location_latitude', return the meta_value, otherwise return NULL. We expect then that since two rows are returned for the post_id (lat,lng), the MAX() value returned above is always the non-null one -- the correct latitude or longitude value from meta_value.
You need to join the table to itself (this is common in databases with EAV structure):
SELECT
post_id,
distance
FROM
( SELECT
lng.post_id,
( 3959 * ACOS( COS( RADIANS( 34.09 ) )
* COS( RADIANS( lat.meta_value ) )
* COS( RADIANS( lng.meta_value )
- RADIANS( -117.55 ) )
+ SIN( RADIANS( 34.09 ) )
* SIN( RADIANS( lat.meta_value ) )
)
) AS distance
FROM wp_postmeta AS lng
JOIN wp_postmeta AS lat
ON lat.post_id = lng.post_id
WHERE lng.meta_key = 'location_longitude'
AND lat.meta_key = 'location_latitude'
) AS tmp
WHERE distance < 2500
ORDER BY distance
LIMIT 0 , 20 ;
One approach is to replace the reference to the table (i.e. FROM wp_postmeta) in your query with an inline view, something like this:
FROM
( SELECT plat.post_id
, plat.meta_value AS lat
, plng.meta_value AS lng
FROM wp_postmeta plat
JOIN wp_postmeta plng
ON plat.post_id = plng.post_id
AND plat.meta_key = 'location_latitude'
AND plng.meta_ley = 'location_longitude'
) wpm
(NOTE: this assumes that there is only one row for each post_id for each of the two meta_key values of interest...)
This uses an "inline view" to combine the latitude and longitude values for each post_id into a single row. (You can run just the query for the inline view to confirm that its returning the results you expect.) We can use an inline view in place of a table reference, in more recent versions of MySQL (version >= 5.0).
SELECT a.post_id,
( 3959 * ACOS( COS( RADIANS( 34.09 ) ) * COS( RADIANS( lat.meta_value ) ) * COS( RADIANS( lng.meta_value ) - RADIANS( -117.55 ) ) + SIN( RADIANS( 34.09 ) ) * SIN( RADIANS( lat.meta_value ) ) ) ) AS distance
FROM wp_postmeta lat, wp_postmeta lng
WHERE lat.post_id = lng.postID
AND lat.meta_key = 'location_latitude'
AND lng.meta_key = 'location_longitude'
HAVING distance < 2500
ORDER BY distance LIMIT 0 , 20