Use result of substring function to compute values for multiple columns - mysql

I am trying to run to find the closest point with a long and lat. Which works fine is i have separate value stored in my db, however i have a single piped string, which i am able to split with a substring index. However when i try and combine these functions in my select query i am not getting any joy.
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 1), '|', -1) as 'lat',
SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 2), '|', -1) as 'lng',
title,
( 6371 * acos( cos( radians(51.527351) ) * cos( radians( 'lat') ) * cos( radians( 'lng' ) - radians(0.765544) ) + sin( radians(51.527351) ) * sin( radians( 'lat' ) ) ) ) AS distance
FROM locations HAVING distance < 500
ORDER BY distance
LIMIT 0 , 20
Can anyone shed some light?

radians( 'lat')
In this part of the query, 'lat' is a string, not a column name or similar. When passing that string to radians, it will be converted to a number, in this case to zero. So you're converting 0 to radians.
You cannot use one column as the input to the computation of the value of another column. You either have to repeat the expression, or use a subquery like this:
SELECT lat, lng, title,
(6371 * acos(cos(radians(51.527351)) *
cos(radians(lat)) * cos(radians(lng) - radians(0.765544)) +
sin(radians(51.527351)) * sin(radians(lat)))
) AS distance
FROM
(SELECT title,
SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 1), '|', -1) + 0 as lat,
SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 2), '|', -1) + 0 as lng
FROM locations
) AS sub
HAVING distance < 500
ORDER BY distance
LIMIT 0 , 20

Here was my solution which repeats the query. I haven;t had a chance to test of benchmark the differing solutions, but the subquery solution is more pleasant
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 1), '|', -1) as 'latitude',
SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 2), '|', -1) as 'longitude',
(
3959 * acos(
cos(radians({MYLAT})) * cos(radians(SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 1), '|', -1)))
* cos(radians(SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 2), '|', -1))) - radians({MYLONG}) + sin(radians({MYLAT}))
* sin( radians(SUBSTRING_INDEX(SUBSTRING_INDEX(longlat, '|', 1), '|', -1)))
)
)
AS distance,
title
FROM locations
ORDER BY distance
LIMIT 0 , 20
Turns out i'm going to be using this equation everywhere now, so glad to get it nailed :)
I'm using the averaged 3959 as the earth's radius, however i wonder if i might be better to use some kind regional latitude for different global locations.

Related

WordPress ACF Google Maps Radius Search

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

Mysql group by conflict records with order by

A table contains multiple addresses (latitude and longitude) of a dealers but I want to get nearest dealers
Here is query which return works fine with order by and where dealer=1 of a single dealer but I need multiple unique dealers.
check query and first image result
SELECT
*, ROUND(
(
3959 * ACOS(
COS(RADIANS(41.355724)) * COS(
RADIANS(adzip.dealer_zipcode_latitude)
) * COS(
RADIANS(adzip.dealer_zipcode_longitude) - RADIANS(- 87.607332)
) + SIN(RADIANS(41.355724)) * SIN(
RADIANS(adzip.dealer_zipcode_latitude)
)
)
), 2
) AS dealer_distance
FROM
ad_dealers_zipcodes AS adzip
WHERE adzip.dealer_zipcode_dealer_id = 1 #GROUP BY adzip.dealer_zipcode_dealer_id
ORDER BY dealer_distance ASC
when I add group by adzip.dealer_zipcode_dealer_id query returns 4th record but I need the 1st record of result which dealer_distance is 13.80
Actually, I think you should add a level to your query for the group by statement to work as you intend, like this:
SELECT *, min(dealer_distance) AS min_dealer_distance FROM
(
SELECT
*, ROUND(
(
3959 * ACOS(
COS(RADIANS(41.355724)) * COS(
RADIANS(adzip.dealer_zipcode_latitude)
) * COS(
RADIANS(adzip.dealer_zipcode_longitude) - RADIANS(- 87.607332)
) + SIN(RADIANS(41.355724)) * SIN(
RADIANS(adzip.dealer_zipcode_latitude)
)
)
), 2
) AS dealer_distance
FROM
ad_dealers_zipcodes AS adzip
WHERE adzip.dealer_zipcode_dealer_id = 1 #GROUP BY adzip.dealer_zipcode_dealer_id
ORDER BY dealer_distance ASC
) as t1
GROUP BY dealer_zipcode_dealer_id
ORDER BY min_dealer_distance ASC

MySQL check if row with category exists

I'm using the Haversine formula with this query and it works until. The goal is to check if each row has certain categories in an value of $deflin which looks like $deflin = category1, category2, category3. The results will show rows within 50km and if it contains any of the categories defined by $deflin. Not sure how to approach this either with WHERE IN or LIKE. Any help is appreciated.
MySQL for query
$awaka = "SELECT *,
( 6371 * acos( cos( radians(?) ) * cos( radians(job_latitude) ) *
cos(radians(?) - radians(job_longitude) ) + sin( radians(?) ) *
sin( radians(job_latitude) ) ) ) AS distance FROM job, users
WHERE job.listee_id = users.user_id AND job.job_category LIKE ?
HAVING distance < 50";
$result = $this->db->query($awaka, array($conlat, $conlong, $conlat, $deflin));
I don't think you like to use the LIKE operator since it only searches for patterns within a column and you only wan't to return rows that really has one of your categories. Instead you should use an IN clause to check if a job has one of your categories:
// contains the id for each category
$categories = array(1, 4, 5, 6);
$deflin = implode(',', $categories);
// if job_category is a text column you could do like this instead
$categories = array('category1', 'category2', 'category3');
$deflin = implode(',', $categories);
$awaka = "SELECT *,
( 6371 * acos( cos( radians(?) ) * cos( radians(job_latitude) ) *
cos(radians(?) - radians(job_longitude) ) + sin( radians(?) ) *
sin( radians(job_latitude) ) ) ) AS distance FROM job, users
WHERE job.listee_id = users.user_id AND job.job_category IN ($deflin)
HAVING distance < 50";

SQL UNION not working with 2 HAVING clauses

I want to use UNION to join two SQL SELECT queries. I need the final data to use the HAVING clause to filter the entire query. Here is my statement:
SELECT CLIENT,
BIZNAME,
BIZSTREET,
BIZCITY,
BIZSTATE,
BIZZIP,
BIZPHONE,
URL,
LAT,
LNG,
CONSOLIDATED,
( 3959 * ACOS(COS(RADIANS('%s')) * COS(RADIANS(LAT)) * COS(
RADIANS(LNG) - RADIANS('%s'))
+ SIN
(RADIANS('%s')) * SIN(RADIANS(LAT))) ) AS distance
FROM BizInfo
INNER JOIN WebSites
ON WebSites.CUSTOMER = BizInfo.CUSTOMER
WHERE BizInfo.CLIENT = 'GCB'
AND WebSites.STATUS <> 'Cancel'
AND WebSites.STATUS <> 'In Progress'
AND WebSites.STATUS <> 'Review'
AND WebSites.STATUS <> 'Testing'
UNION
SELECT CLIENT,
BIZNAME,
BIZSTREET,
BIZCITY,
BIZSTATE,
BIZZIP,
BIZPHONE,
'http://www.abc-site.com',
LAT,
LNG,
'0',
( 3959 * ACOS(COS(RADIANS('%s')) * COS(RADIANS(LAT)) * COS(
RADIANS(LNG) - RADIANS('%s'))
+ SIN
(RADIANS('%s')) * SIN(RADIANS(LAT))) ) AS distance
FROM BizInfo
WHERE CLIENT = 'GCB'
AND BIZNAME = 'Acme'
HAVING DISTANCE < '%s'
ORDER BY DISTANCE
LIMIT 0, 200
I read on this site http://www.really-fine.com/SQL_union.html (GROUP BY and HAVING clauses can be used only within individual queries and cannot be used to affect the final results set. ), but I don't understand how to implement this or if it is correct.
How do I properly write this SQL query?
You can probably solve that very easily by wrapping everything into a subquery. Something like :
SELECT
*
FROM
(
SELECT Client, BizName, BizStreet, BizCity, BizState, BizZip, BizPhone, url, lat, lng, Consolidated,
( 3959 * acos( cos( radians('%s') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('%s') ) + sin( radians('%s') ) * sin( radians( lat ) ) ) )
AS distance FROM BizInfo
INNER JOIN WebSites ON WebSites.Customer = BizInfo.Customer
WHERE BizInfo.Client = 'GCB'
AND WebSites.Status <> 'Cancel' AND WebSites.Status <> 'In Progress' AND WebSites.Status <> 'Review' AND WebSites.Status <> 'Testing'
UNION SELECT Client, BizName, BizStreet, BizCity, BizState, BizZip, BizPhone, 'http://www.abc-site.com', lat, lng, '0',
( 3959 * acos( cos( radians('%s') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('%s') ) + sin( radians('%s') ) * sin( radians( lat ) ) ) )
AS distance FROM BizInfo WHERE Client = 'GCB' AND BizName = 'Acme'
) AS ClientInfo
WHERE
distance < '%s'
ORDER BY
distance
LIMIT 0 , 200
This is the fastest path to what you want but it is not very clean.
Also please tell me that all those parameters are not vulnerable to SQL injection...?
HAVING can only be used in an aggregate query with a GROUP BY clause. Your queries do not. Use WHERE instead.
Those are scalar functions, not aggregate functions. So to filter on the result, use the WHERE clause, not the HAVING clause. You are filtering horizontally, not vertically.
You cannot use a column alias in the WHERE clause, so you have to restate the functions used in the SELECT list (I've done that in the query below).
Try the following. There are 2 issues at play.
SELECT Client,
BizName,
BizStreet,
BizCity,
BizState,
BizZip,
BizPhone,
url,
lat,
lng,
Consolidated,
(3959 * acos(cos(radians('%s')) * cos(radians(lat)) *
cos(radians(lng) - radians('%s')) +
sin(radians('%s')) * sin(radians(lat)))) AS distance
FROM BizInfo
INNER JOIN WebSites
ON WebSites.Customer = BizInfo.Customer
WHERE BizInfo.Client = 'GCB'
AND WebSites.Status <> 'Cancel'
AND WebSites.Status <> 'In Progress'
AND WebSites.Status <> 'Review'
AND WebSites.Status <> 'Testing'
UNION
SELECT Client,
BizName,
BizStreet,
BizCity,
BizState,
BizZip,
BizPhone,
'http://www.abc-site.com',
lat,
lng,
'0',
(3959 * acos(cos(radians('%s')) * cos(radians(lat)) *
cos(radians(lng) - radians('%s')) +
sin(radians('%s')) * sin(radians(lat)))) AS distance
FROM BizInfo
WHERE Client = 'GCB'
AND BizName = 'Acme'
and (3959 * acos(cos(radians('%s')) * cos(radians(lat)) *
cos(radians(lng) - radians('%s')) +
sin(radians('%s')) * sin(radians(lat)))) < '%s'
ORDER BY distance LIMIT 0, 200

How to improve this huge SQL query?

I have a big SQL query (for MySQL) that is slow. It's a union of two select statements. I have tried different things, but any slight variance gives me a different result set from the original. Any help with improving it will be greatly appreciated. Thanks. Here is the SQL:
(SELECT
CONCAT(city_name,', ',region) value,
latitude,
longitude,
id,
population,
( 3959 * acos( cos( radians($latitude) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($longitude) ) + sin( radians($latitude) ) * sin( radians( latitude ) ) ) )
AS distance,
CASE region
WHEN '$region' THEN 1
ELSE 0
END AS region_match
FROM `cities`
$where and foo_count > 5
ORDER BY region_match desc, foo_count desc
limit 0, 11)
UNION
(SELECT
CONCAT(city_name,', ',region) value,
latitude,
longitude,
id,
population,
( 3959 * acos( cos( radians($latitude) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($longitude) ) + sin( radians($latitude) ) * sin( radians( latitude ) ) ) )
AS distance,
CASE region
WHEN '$region' THEN 1
ELSE 0
END AS region_match
FROM `cities`
$where
ORDER BY region_match desc, population desc, distance asc
limit 0, 11)
limit 0, 11
The SQL does take some interpolated values (prefixed with the dollar sign($)).
The following might give the same result (I'm not sure about how the maximum/minimum functions are called in SQL, but you should get an idea -- you need two fields derived from foo_count which separate the items of the first part of your UNION from those of the second one and allow ordering within the first part without disturbing the order in the second part) -- of course, you later need a second query to throw the additional fields out again:
SELECT
CONCAT(city_name,', ',region) value,
latitude,
longitude,
id,
population,
( 3959 * acos( cos( radians($latitude) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($longitude) ) + sin( radians($latitude) ) * sin( radians( latitude ) ) ) )
AS distance,
min ( 6, max (foo_count, 5)) AS group_discriminator,
max ( 6, foo_count) AS rank_for_use_in_first_group,
CASE region
WHEN '$region' THEN 1
ELSE 0
END AS region_match
FROM `cities`
$where
ORDER BY group_discriminator desc, region_match desc, rank_for_use_in_first_group desc, population desc, distance asc
limit 0, 11
EDIT: Improvements