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

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

Related

MySQL - Using COUNT() to return total results when HAVING is specified

Using the query below, I can search for properties within a given radius and results are returned.
SELECT id, address, ( 3959 * acos( cos( radians( 53.184815 ) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-3.025741) ) + sin( radians(53.184815) ) * sin( radians( lat ) ) ) ) AS distance
FROM properties
WHERE area = 1 HAVING distance <= 1
ORDER BY price DESC, distance ASC
LIMIT 0, 10
However I now want to add pagination, thus the "LIMIT 0, 10" but somehow have the query return the total results. For example, if there are 100 results but we're only limiting to the first 10 results, return the total as 100.
I tried adding "COUNT(*) AS total" after the select but this caused zero results to be returned.
How do I have the query return the total in this way?
I think it will need a subquery to achieve that:
SELECT
id, address, ( 3959 * acos( cos( radians( 53.184815 ) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-3.025741) ) + sin( radians(53.184815) ) * sin( radians( lat ) ) ) ) AS distance,
(SELECT count(*) FROM properties WHERE area = 1 HAVING ( 3959 * acos( cos( radians( 53.184815 ) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-3.025741) ) + sin( radians(53.184815) ) * sin( radians( lat ) ) ) )<= 1) AS total
FROM properties
WHERE area = 1 HAVING distance <= 1
ORDER BY price DESC, distance ASC
LIMIT 0, 10
You either have to use a separate query without limit with count(*) or as splash indicated, use SQL_CALC_FOUND_ROWS in your query and then issue a SELECT FOUND_ROWS(); to get the total number.
You can try to inject the count(*) query as a subquery in your main query, but to me that's only unnecessary complication of your query.

Converting 'WITH CTE' MSSQL to MySQL

I realize that this doesn't directly translate from MSSQL to MySQL but I'm not not sure how to make it work. Any help that you have is appreciated.
;WITH cte As (
SELECT
post_id,
status,
dealer,
distributor,
SUM(
3959 * acos(
cos( radians(%f) ) *
cos( radians( lat ) ) *
cos( radians( lng ) - radians(%f) ) +
sin( radians(%f) ) *
sin( radians( lat ) )
)
)
AS DISTANCE
FROM wp_geodatastore
GROUP BY post_id, status, dealer, distributor
)
SELECT post_id, status, dealer, distributor, DISTANCE
FROM cte WHERE (DISTANCE < %d)
AND status = 'publish' AND dealer = 'on' AND distributor = 'on'
ORDER BY DISTANCE
OFFSET %d ROWS
FETCH NEXT %d ROWS ONLY;
Just make it a subquery:
SELECT post_id, status, dealer, distributor, DISTANCE
FROM (SELECT post_id, status, dealer, distributor,
SUM( 3959 * acos(
cos( radians(%f) ) *
cos( radians( lat ) ) *
cos( radians( lng ) - radians(%f) ) +
sin( radians(%f) ) *
sin( radians( lat ) )
)
) AS DISTANCE
FROM wp_geodatastore
GROUP BY post_id, status, dealer, distributor
) cte
WHERE (DISTANCE < %d) AND
status = 'publish' AND dealer = 'on' AND distributor = 'on'
ORDER BY DISTANCE
OFFSET %d ROWS
FETCH NEXT %d ROWS ONLY;

Merging 2 sql queries into a single one

I have two sql queries which when run independent produces the correct results
Query 1
SELECT id,
(6371 * acos( cos( radians(9.977364864079215) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(76.58620953448485) ) + sin( radians(9.977364864079215) ) * sin( radians( latitude ) ) ) )
AS distance
FROM geodata HAVING distance < 20
ORDER BY distance
LIMIT 0 , 20;
Query 2
SELECT DISTINCT e.* FROM schools e
WHERE (
(e.type = 'preprimary')
)
AND(
e.title LIKE '%government%'
)
LIMIT 0, 10
I want to merge the first query with the second one, so that it should return all "preprimary" type schools with title like "government" located within 20KM radius and the result needs to be ordered by the distance.
How can I merge the two queries? I tried using JOINING the geodata table on the school table. But I dont know the remaining. Sorry, if this is a silly question. I am pretty new to SQL world.
SELECT DISTINCT school.* FROM
( SELECT geodata.id,
(6371 * acos( cos( radians(9.977364864079215) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(76.58620953448485) ) + sin( radians(9.977364864079215) ) * sin( radians( latitude ) ) ) )
AS distance ,school.*
FROM geodata LEFT JOIN school on geodata.id=school.id
WHERE
(school.type = 'preprimary')
AND(
school.title LIKE '%government%'
)
AND school.id IS NOT NULL
HAVING distance < 20 )x
ORDER BY x.distance
LIMIT 0 , 10;
Try this:
SELECT *
From (
SELECT DISTINCT e.* ,
(6371 * acos( cos( radians(9.977364864079215) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(76.58620953448485) ) + sin( radians(9.977364864079215) ) * sin( radians( latitude ) ) )
) as distance
FROM schools e
LEFT JOIN geodata g ON e.id=g.id
WHERE (e.type = 'preprimary')
AND ( e.title LIKE '%government%' )
) as s
Where s.distance < 20
Order by s.distance

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.

SQL syntax error - Haversine formula

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