I want to fetch a table named supplier_products which has following columns
ID Item_id supplier_id variant_id price lat lng serving_radius(in km) 1 1 2 12 22.00 26.11360000 85.39430000 1 2 1 3 12 44.00 26.11360000 85.39430000 4 3 1 2 13 25.00 26.11360000 85.39430000 4 4 1 3 13 23.00 26.11360000 85.39430000 4
Now for searching supplier products near a latitude and longitude say($lat = 26.1136;$long = 85.3643;)
. i was using this query
SELECT *, (6371 * acos(cos(radians('$lat')) * cos(radians(lat)) * cos( radians(lng) - radians('$long')) + sin(radians('$lat')) * sin(radians(lat)))) AS distance FROM supplier_products HAVING distance <= serving_radius ORDER BY distance")
The above query returns all rows which serves the input $lat & $long
.
But now i want to return all columns of only those rows having distinct variant_id which serves the input $lat & $long
I tried using a GROUP BY
–
SELECT *, (6371 * acos(cos(radians('$lat')) * cos(radians(lat)) * cos( radians(lng) - radians('$long')) + sin(radians('$lat')) * sin(radians(lat)))) AS distance FROM supplier_products GROUP BY variant_id HAVING distance <= serving_radius")
But it eliminates some desired rows as GROUP BY
is being performed before the HAVING
clause. So it eliminates some required rows which are in the serving radius.
I am using PHP
& MYSQL
EDIT- I want this as my output
ID Item_id supplier_id variant_id price lat lng serving_radius(in km) 2 1 3 12 44.00 26.11360000 85.39430000 4 3 1 2 13 25.00 26.11360000 85.39430000 4
As row with ID-1
does not serves the input $lat/$long
But my attempt gave the following result –
ID Item_id supplier_id variant_id price lat lng serving_radius(in km) 3 1 2 13 25.00 26.11360000 85.39430000 4
Because GROUP BY
eliminated the 2nd row
Advertisement
Answer
Your requirement is still incomplete if you want predictable results. You call for choosing just one row from among several. Why did your desired result show id=3 and not id=4? I guess you chose one of the two at random.
This query (https://www.db-fiddle.com/f/USMrhc8gLcRD2rAmuzYzH/0) does that for you.
SET @lat := 26.120888; SET @long := 85.364832; SELECT ANY_VALUE(supplier_id), variant_id, ANY_VALUE(price) FROM ( SELECT ID, (6371 * acos(cos(radians(@lat)) * cos(radians(lat)) * cos( radians(lng) - radians(@long)) + sin(radians(@lat)) * sin(radians(lat)))) AS distance FROM supplier_products ) t WHERE distance < serving_radius GROUP BY variant_id
It has a subquery to show a variant of your table that shows distances from the @lat and @long values supplied. It shows ID and distance. This is it. (https://www.db-fiddle.com/f/guagWYVXXaf7cPkbojj9KD/2)
SELECT *, (6371 * acos(cos(radians(@lat)) * cos(radians(lat)) * cos( radians(lng) - radians(@long)) + sin(radians(@lat)) * sin(radians(lat)))) AS distance FROM supplier_products
It then uses that subquery in an outer query with a GROUP BY, see above.
But, because you want just one row per variant and you haven’t told us how to pick that row if there are multiple rows, the query uses the ANY_VALUE() function to choose one of the available values for each column in your result.
Versions of MySQL older than 8.0 don’t need the ANY_VALUE() functions, because of MySQL’s notorious nonstandard handling of GROUP BY: please read this.
Beware: if you let MySQL use this ANY_VALUE() stuff, whether implicit or explicit, you will drive your testers and your users crazy. They’ll sometimes get different results today than they got last week, and they’ll wonder what they did wrong. Please don’t do it.
And one other thing: your distance formula is notorious for throwing exceptions on very small distances. Make sure it never tries to use ACOS() with a value greater than 1 by modifying it like this to use LEAST(). (https://www.db-fiddle.com/f/USMrhc8gLcRD2rAmuzYzH/1)
(6371 * acos(LEAST(1.0,cos(radians(@lat)) * cos(radians(lat)) * cos( radians(lng) - radians(@long)) + sin(radians(@lat)) * sin(radians(lat)))))