i got a big Problem and have no Solution.
i work with WordPress and Custom-Post-Type (CPT) and Advanced Custom Fields (ACF).
I create a new CPT called "projekt" and a ACF "Google Map Field" which saves the Adress, longtitude and latitude. The bad thing is ACF Fields were saved serialzed in the MySQL DB.
i found the following SQL-Query on the Net to calculate the Radius.
$lat = '50.12335';
$lng = '-1.344453';
$radius = 10; // (km)
???....WHERE ( 6371 * acos( cos( radians(" . $lat . ") )
* cos( radians( lat ) )
* cos( radians( lng )
- radians(" . $lng . ") )
+ sin( radians(" . $lat . ") )
* sin( radians( lat ) ) ) ) <= " . $radius . ")"
i don't know how to build the query, because the query needs to get the longtitude and latitude info from the serialized ACF fields.
DB-Field "meta_value":
a:13:{s:7:"address";s:44:"Moskauer Straße 5, Düsseldorf, Deutschland";s:3:"lat";d:51.2199565;s:3:"lng";d:6.8044928;s:4:"zoom";i:14;s:8:"place_id";s:114:"EiNNb3NrYXVlciBTdHIuLCBEw7xzc2VsZG9yZiwgR2VybWFueSIuKiwKFAoSCZV-jR3Ny7hHEVVgUQUBF7q8EhQKEgkBGQXwtsu4RxHgwS1K_GAnBQ";s:4:"name";s:16:"Moskauer Straße";s:11:"street_name";s:16:"Moskauer Straße";s:17:"street_name_short";s:13:"Moskauer Str.";s:4:"city";s:11:"Düsseldorf";s:5:"state";s:19:"Nordrhein-Westfalen";s:11:"state_short";s:3:"NRW";s:7:"country";s:11:"Deutschland";s:13:"country_short";s:2:"DE";}
i can get all my "projekt"'s with:
maybe it's possible to extend the following query
SELECT P.ID, P.post_title, P.post_content, P.post_author, meta_value
FROM wp_posts AS P
LEFT JOIN wp_postmeta AS PM on PM.post_id = P.ID
WHERE P.post_type = 'projekt' and P.post_status = 'publish' and ( meta_key = 'adress' )
ORDER BY P.post_date DESC
result is:
ID|post_title |post_content|post_author|meta_value
55|Test Projekt| |1 |a:13:{s:7:"address";s:44:"Moskauer Straße 5, Düss...
Do you got any Solution for me ?
Thank You!
OK, so this looks pretty nasty but in testing with out adding the radius portion Im able to extract the lat and lng from the serialized data.
So I think you can tack on your radius check as an AND something like below:
SELECT
REPLACE(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(pm.meta_value, 'lat', -1), ';', 2), ':', -1), '"', '') AS latitude,
REPLACE(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(pm.meta_value, 'lng', -1), ';', 2), ':', -1), '"', '') AS longitude
FROM wp_postmeta pm
WHERE pm.meta_key = 'change_to_your_meta_key'
AND ( 6371 * acos( cos( radians(" . $lat . ") )
* cos( radians( latitude ) )
* cos( radians( longitude )
- radians(" . $lng . ") )
+ sin( radians(" . $lat . ") )
* sin( radians( longitude ) ) ) ) <= " . $radius . ")"
i found the solution by myself.
Most People, create extra Tables with lat and long.
I think that's senseless because they are already there in the acf field.
i'm not very skilled in MySQL so some people can shorten the query of course.
i dont know how to use varibales in MySQL so the regex for lat and long can be put into a Variable:
REGEXP_REPLACE(IF(PM.meta_key = 'adress', PM.meta_value, NULL), '.+\"lat\";d:([0-9\.]+).+','\\1') AS latitude
REGEXP_REPLACE(IF(PM.meta_key = 'adress', PM.meta_value, NULL), '.+\"lng\";d:([0-9\.]+).+','\\1') AS longitude
here is now the complete query for Radius Search with WordPress , ACF (Google Map field), where INPUTLAT,INPUTLONG,INPUTKM are the values you deliver:
SELECT P.ID, P.post_title, P.post_content, P.post_author,
IF(PM.meta_key = 'adress', PM.meta_value, NULL) AS adress,
REGEXP_REPLACE(IF(PM.meta_key = 'adress', PM.meta_value, NULL), '.+\"lat\";d:([0-9\.]+).+','\\1') AS latitude,
REGEXP_REPLACE(IF(PM.meta_key = 'adress', PM.meta_value, NULL), '.+\"lng\";d:([0-9\.]+).+','\\1') AS longitude
FROM wp_posts AS P
LEFT JOIN wp_postmeta AS PM on PM.post_id = P.ID
WHERE P.post_type = 'projekt' and P.post_status = 'publish' and IF(PM.meta_key = 'adress', PM.meta_value, NULL) IS NOT NULL and IF(PM.meta_key = 'adress', PM.meta_value, NULL) IS NOT NULL AND
( 6371 * acos( cos( radians(INPUTLAT) )
* cos( radians( REGEXP_REPLACE(IF(PM.meta_key = 'adress', PM.meta_value, NULL), '.+\"lat\";d:([0-9\.]+).+','\\1') ) )
* cos( radians( REGEXP_REPLACE(IF(PM.meta_key = 'adress', PM.meta_value, NULL), '.+\"lng\";d:([0-9\.]+).+','\\1') )
- radians(INPUTLONG) )
+ sin( radians(INPUTLAT) )
* sin( radians( REGEXP_REPLACE(IF(PM.meta_key = 'adress', PM.meta_value, NULL), '.+\"lat\";d:([0-9\.]+).+','\\1') ) ) ) ) <= INPUTKM
ORDER BY P.post_date DESC
This Just work at MariaDB or MySQL 8. MySQL 5 dont know REGEX_REPLACE
Related
How can I exclude rows with show_all = '1' from HAVING clause?
SELECT
ID,
( 6371 * acos( cos( radians('". $lat ."') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('". $long ."') ) + sin( radians('". $lat ."') ) * sin( radians( lat ) ) ) ) AS distance
FROM events
WHERE active = '1'
AND closed = '0'
AND category IN ( '" . implode( "', '" , $styleArr ) . "' )
HAVING distance < 1000 AND distance > 0
ORDER BY events.start_date DESC
I don't want to apply HAVING condition if row's show_all = '1'
Change the HAVING clause to this:
HAVING (distance < 1000 AND distance > 0) OR show_all = '1'
So I have this query:
SELECT P.*,
( 3959 * acos( cos( radians('37.785834') )
* cos( radians( CA.lat ) )
* cos( radians( CA.lng )
- radians('-122.406417') )
+ sin( radians('37.785834') )
* sin( radians( CA.lat ) ) ) ) AS distance
FROM adp2_posts as P
JOIN adp2_offer_details C ON C.merchant_id = P.ID
JOIN adp2_addresses CA ON CA.address_id = C.address_id
WHERE P.post_type = 'merchant'
AND P.post_status = 'publish'
AND CA.mm = "dallasftworth"
AND C.cats RLIKE CONCAT("[[:<:]]", REPLACE(199, ",", "[[:>:]]|[[:<:]]"), "[[:>:]]")
GROUP BY CA.address_id, P.ID
HAVING (distance < 999999999999999999 AND P.coupon_count > 0) ORDER BY distance ASC LIMIT 0 , 20
I expect it to return unique values from LEFT table, but it returns duplicates too.
Results:
I am a bit confused by this one.
Sorry for not providing the full context, its been a long day, but i finally figured it out.
Here's the end query:
SELECT P.*, MIN(( 3959 * acos( cos( radians('37.785834') ) * cos( radians( CA.lat ) ) * cos( radians( CA.lng ) - radians('-122.406417') ) + sin( radians('37.785834') ) * sin( radians( CA.lat ) ) ) )) AS distance FROM adp2_posts as P JOIN adp2_offer_details C ON C.merchant_id = P.ID JOIN adp2_addresses CA ON CA.address_id = C.address_id WHERE P.post_type = 'merchant' AND P.post_status = 'publish' AND CA.mm = "dallasftworth" AND C.cats RLIKE CONCAT("[[:<:]]", REPLACE(199, ",", "[[:>:]]|[[:<:]]"), "[[:>:]]") GROUP BY P.ID HAVING (distance < 999999999999999999 AND P.coupon_count > 0) ORDER BY distance ASC LIMIT 0 , 20
The problem was that, CA.address_id where different and GROUP BY took it as unique values. What i did, is just select P.* with smallest distance(this is exactly what my business logic asks for).
SELECT GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ",")
, SUBSTRING_INDEX(GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),",",1) latitude
, SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),",",2),",",-1) longitude
, ( 6371 * acos( cos( radians(52.2734487789) ) * cos( radians(latitude ) ) * cos( radians(longitude ) - radians(10.5438383386) ) + sin( radians(52.2734487789) ) * sin( radians(latitude ) ) ) ) AS distance
FROM `wp_umkreissuche_posts` AS posts
, `wp_umkreissuche_postmeta` AS postmeta
WHERE posts.post_type = "adresses"
AND posts.ID=postmeta.post_id
AND postmeta.meta_key="latitude"
OR posts.post_type="adresses"
AND posts.ID=postmeta.post_id
AND postmeta.meta_key="longitude"
GROUP
BY postmeta.post_id
HAVING distance < 0.5
ORDER
BY postmeta.post_id
, distance ASC
This is your query, formatted a bit better and with clearer join syntax:
SELECT GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),
SUBSTRING_INDEX(GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),",",1) AS latitude,
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),",",2),",",-1) AS longitude,
( 6371 * acos( cos( radians(52.2734487789) ) * cos( radians(latitude ) ) * cos( radians(longitude ) - radians(10.5438383386) ) + sin( radians(52.2734487789) ) * sin( radians(latitude ) ) ) ) AS distance
FROM `wp_umkreissuche_posts` AS posts JOIN
`wp_umkreissuche_postmeta` AS postmeta
ON posts.ID = postmeta.post_id AND posts.post_type = 'adresses' AND
(postmeta.meta_key IN ('latitude', 'longitude')
)
GROUP BY postmeta.post_id
HAVING distance < 0.5
ORDER BY postmeta.post_id, distance ASC
Your direct problem is that latitude and longitude are defined in the select, so they cannot be used at the same select level. The normal solution is to repeat the expression or to use a subquery, so the alias is known. However, the logic for your query doesn't look write. I suspect it should be something more like this:
SELECT CONCATE_WS, ',', lat.postmeta.meta_value, lng.postmeta.meta_value) ,
lat.postmeta.meta_value AS latitude,
lng.meta_value AS longitude,
. . .
FROM `wp_umkreissuche_posts` posts JOIN
`wp_umkreissuche_postmeta` lat
ON posts.ID = postmeta.post_id AND posts.post_type = 'adresses' AND lat.meta_key = 'latitude' JOIN
`wp_umkreissuche_postmeta` lng
ON posts.ID = postmeta.post_id AND posts.post_type = 'adresses' AND lng.meta_key = 'longitude'
HAVING distance < 0.5
ORDER BY postmeta.post_id, distance ASC;
Note: you can use the having without a group by in MySQL. It then behaves like a where but it allows column aliases.
You cannot use an alias name in another field in the same query unless you use a subquery or join.
Use
(SUBSTRING_INDEX(GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),",",1)
instead of latitude.
Try this way:
SELECT GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),
SUBSTRING_INDEX(GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),",",1) AS latitude,
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),",",2),",",-1) AS longitude,
( 6371 * acos( cos( radians(52.2734487789) ) * cos( radians(latitude ) ) * cos( radians(longitude ) - radians(10.5438383386) ) + sin( radians(52.2734487789) ) * sin( radians((SUBSTRING_INDEX(GROUP_CONCAT(TRIM(postmeta.meta_value) SEPARATOR ","),",",1)) ) ) ) AS distance
FROM wp_umkreissuche_posts AS posts, wp_umkreissuche_postmeta AS postmeta
WHERE posts.post_type="adresses" AND posts.ID=postmeta.post_id AND postmeta.meta_key="latitude" OR posts.post_type="adresses" AND posts.ID=postmeta.post_id AND postmeta.meta_key="longitude"
GROUP BY postmeta.post_id HAVING distance < 0.5
ORDER BY postmeta.post_id, distance ASC
I've wrote a query with DISTINCT but I still get duplicate records returned.
SELECT DISTINCT
`cuisine_types`.`id`,
`cuisine_types`.`name`,
`cuisine_types`.`image`,
(
SELECT group_concat(`tagname` separator ', ')
FROM `cuisine_tags`
WHERE `cuisine_type` = `cuisine_types`.`id`
) AS tags,
(
3959 * acos( cos( radians(" . $dLat . ") ) * cos( radians( gps_lat ) ) * cos( radians( gps_lon ) - radians(" . $dLon . ") ) + sin( radians(" . $dLat . ") ) * sin( radians( gps_lat ) ) )
) AS distance
FROM `company`
LEFT JOIN `cuisine_types`
ON
`company`.`cuisine_type_id` = `cuisine_types`.`id`
HAVING
distance < " .$dMiles
When I try using the GROUP BY function my query isn't working properly.
When I use GROUP BY I place it above my HAVING:
GROUP BY `cuisine_types`.`name`
Examples:
When I use it with these values:
$dLat = '52.779716';
$dLon = '21.84803';
$iKm = '30';
It returns:
id = 1
name = Snackbar
image =
tags = Patat, Snacks
distance = 17.4713944772963
When I use $iKm with 3000 it returns this row as well:
id = 1
name = Snackbar
image =
tags = Patat, Snacks
distance = 722.407714147792
So I get two records.
When I use this with groupby and $iKm = 30; It returns nothing. With a value of 3000 it returns one row. But I have one record with a distance of 17 miles so thats below 30.
This should fix your problem, the issue was the distance calculation blocked the GROUP BY function. By putting the equation in the HAVING itself, the problem seemed to be fixed. Sorry I can't explain it in more detail.
SELECT
`cuisine_types`.`name`,
`cuisine_types`.`id`,
`cuisine_types`.`image`
`c`.`gps_lat` as lat,
`c`.`gps_lon` as lon,
(SELECT group_concat(`tagname` separator ', ') FROM `cuisine_tags` WHERE `cuisine_type`=`cuisine_types`.`id`) as tags FROM `company` as c LEFT JOIN `cuisine_types` ON c.`cuisine_type_id`
= `cuisine_types`.`id` GROUP BY `cuisine_types`.`name` HAVING ( 3959 * acos( cos( radians(52.779716) ) * cos( radians( lat ) ) * cos( radians( lon ) - radians(21.84803) ) + sin( radians(52.779716) ) * sin( radians( lat ) ) ) ) < 2000;
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