This is a bit of a strange one: I have the following MySQL Stored Proc (or function) and the majority can be ignored safely unless there is a need to understand the full picture. The problem is the ORDER BY clause.
BEGIN
SELECT DISTINCT e.*, ( 3959 * acos( cos( radians(in_latitude) ) * cos( radians( e.address_latitude ) )
* cos( radians(e.address_longitude) - radians(in_longitude)) + sin(radians(in_latitude))
* sin( radians(e.address_latitude)))) AS distanceFromUsersPostcode
FROM event e
INNER JOIN event_organiser eo on e.event_organiser_id = eo.id
WHERE (e.event_name LIKE in_search OR e.address_town LIKE in_search OR e.address_county LIKE in_search OR eo.event_organiser_name LIKE in_search)
AND e.start_date_time >= in_start_date
AND e.start_date_time <= in_end_date
AND e.enabled = true
HAVING distanceFromUsersPostcode < in_maxDistanceFromUser
/*
* 1 *
ORDER BY distanceFromUsersPostcode
* 2 *
ORDER BY IF(in_orderBy='LOCATION', CAST(distanceFromUsersPostcode AS DECIMAL), e.start_date_time) ASC;
*/
ORDER BY
CASE in_orderBy
WHEN 'LOCATION' THEN distanceFromUsersPostcode
ELSE e.start_date_time
END
ASC;
END
Now the issue is this, the current uncommented ORDER BY clause seems to treat the DECIMAL value distanceFromUsersPostcode as a VARCHAR (or string) value.
It orders results in the form :
0.4, 101.9, 102.8, 11.1, 11.9
The same can be said if I used the variant labelled as * 2 *
However, if I revert to the original variant labelled as * 1 *, the results would be ordered as expected :
0.4, 11.1, 11.9, 101.9, 102.8
My guessing was that MySQL treated the distanceFromUsersPostcode variable as a VARCHAR if used inside the IF Function (* 2 *), hence my attempt to cast it to a DECIMAL. This however, has no effect.
Can anybody shed any light on what is happening here?
The following acts as expected but isn't very elegant of course as it duplicates the entire query :
BEGIN
IF in_orderBy='LOCATION' THEN
SELECT DISTINCT e.*, ( 3959 * acos( cos( radians(in_latitude) ) * cos( radians( e.address_latitude ) )
* cos( radians(e.address_longitude) - radians(in_longitude)) + sin(radians(in_latitude))
* sin( radians(e.address_latitude)))) AS distanceFromUsersPostcode
FROM event e
INNER JOIN event_organiser eo on e.event_organiser_id = eo.id
WHERE (e.event_name LIKE in_search OR e.address_town LIKE in_search OR e.address_county LIKE in_search OR eo.event_organiser_name LIKE in_search)
AND e.start_date_time >= in_start_date
AND e.start_date_time <= in_end_date
AND e.enabled = true
HAVING distanceFromUsersPostcode < in_maxDistanceFromUser
ORDER BY distanceFromUsersPostcode;
ELSE
SELECT DISTINCT e.*, ( 3959 * acos( cos( radians(in_latitude) ) * cos( radians( e.address_latitude ) )
* cos( radians(e.address_longitude) - radians(in_longitude)) + sin(radians(in_latitude))
* sin( radians(e.address_latitude)))) AS distanceFromUsersPostcode
FROM event e
INNER JOIN event_organiser eo on e.event_organiser_id = eo.id
WHERE (e.event_name LIKE in_search OR e.address_town LIKE in_search OR e.address_county LIKE in_search OR eo.event_organiser_name LIKE in_search)
AND e.start_date_time >= in_start_date
AND e.start_date_time <= in_end_date
AND e.enabled = true
HAVING distanceFromUsersPostcode < in_maxDistanceFromUser
ORDER BY e.start_date_time;
END IF;
END
Because you use different data types for sorting mysql will try to convert them to something convenient to be able to compare the values. In your particular case it will be conversion to string. So it makes no sense to convert distanceFromUsersPostcode to a number, because it will be converted back to string. You will need to transform the string in a convenient format for number sorting. LPAD function will help you here.
ORDER BY
CASE in_orderBy
WHEN 'LOCATION' THEN LPAD(CAST(distanceFromUsersPostcode as CHAR),6)
ELSE e.start_date_time
END
ASC;
Related
I'm creating a query that get's the total number of customers within a certain distance of each one of our stores using the Haversine Formula.
This formula is fairly big and looks ugly when I use it in a query, so I decided to create a function out of it so that my queries would look cleaner.
The issue is that when I run the query using the function, I get this error: Error Code: 2013. Lost connection to MySQL server during query.
As soon as I use the actual formula instead of the function in my query it runs as expected. Anyone know why this is happening? Is this a time out issue because the query is taking to long to run?
Here is my user created Haversine function:
CREATE DEFINER=`MYADMIN`#`%` FUNCTION `Haversine`(lat1 DECIMAL(15,10), lng1 DECIMAL(15,10), lat2 DECIMAL(15,10), lng2 DECIMAL(15,10)) RETURNS decimal(11,2)
BEGIN
RETURN (3959 * acos( cos( radians(lat1) ) * cos( radians( lat2 ) ) * cos( radians( lng2 ) - radians(lng1) ) + sin( radians(lat1) ) * sin(radians(lat2)) ));
END
This query causes the lost connection error:
SELECT s.StoreID
, count(c.PBID) as customers
, sum(c.Status = 'null') as 'Not Active'
, sum(c.Status = 'Left Message') as 'Left Message'
from console.Stores s
cross
join console.Customers c
where console.Haversine(s.Latitude,s.Longitude,c.Lat,c.Lng) <= 5
and c.DNC = '0'
and c.Status != 'Do Not Call'
group
by s.StoreID;
This is the same query without the function which runs as expected:
SELECT s.StoreID, count(c.PBID) as customers, sum(c.Status = 'null') as 'Not Active', sum(c.Status = 'Left Message') as 'Left Message' from console.Stores s cross join console.Customers c where (3959 * acos( cos( radians(s.Latitude) ) * cos( radians( c.Lat ) ) * cos( radians( c.Lng ) - radians(s.Longitude) ) + sin( radians(s.Latitude) ) * sin(radians(c.Lat)) ) ) <= 5 and c.DNC = '0' and c.Status != 'Do Not Call' group by s.StoreID;
I have a sql which is running perfectly okie. I want to convert it into active record query but don't know how to convert these sub query into active record
below is my sql:
Select A.*, B.*
FROM
( SELECT ( 6371 * acos( cos( radians(24.8964089) ) * cos( radians(RB.`lat`) )
* cos( radians(RB.`lng`) - radians(67.06749)) + sin(radians(24.8964089))
* sin( radians(RB.`lat`)))) AS distance, RB.`id` as BRID , R.`name` as NAME
FROM
`rent_br` RB
JOIN `rent` R ON R.`id` = RB.`restId`
JOIN `rent_category` RC on R.`id` = RC.`restId`
WHERE
TRUNCATE(RB.`lat`, 0) >= TRUNCATE(24.8964089,0)
AND TRUNCATE(RB.`lat`, 0) < TRUNCATE(25.8964089,0)
AND RB.`isdelivery` = '1'
AND RC.`catId` = 11
/*AND ( rent.`tags` like '%burger%' OR rent.`name` like '%burger%' OR info.`name` like '%burger%' )*/
HAVING
`distance` <= 50
ORDER BY
`distance` ASC ) A
JOIN
(select max(radius) as maxRadius, rent_br_delivery_radius.`rest_brId` as bbrId from `rent_br_delivery_radius`
group by bbrId) B on `A`.BRID = B.bbrId
WHERE A.distance <= B.maxRadius
ORDER BY A.NAME
the only thing you have to consider here is the use of the get_compiled_select function of the query builder - once you understand that - its pretty easy. You just have to split your queries and merge them together to one query
the following should do the job
your subselect
$strSubQuery = $this->db
->select('
( 6371 * acos( cos( radians(24.8964089) ) * cos( radians(RB.`lat`) )
* cos( radians(RB.`lng`) - radians(67.06749)) + sin(radians(24.8964089))
* sin( radians(RB.`lat`)))) AS distance, RB.`id` as BRID , R.`name` as NAME
')
->from('rent_br RB')
->join('rent r', 'r.id = RB.restId')
->join('rent_category RC', 'R.id = RC.restId')
->where('TRUNCATE(RB.`lat`, 0) >= TRUNCATE(24.8964089,0)', NULL, false)
->where('TRUNCATE(RB.`lat`, 0) < TRUNCATE(25.8964089,0)', NULL, false)
->where('RB.isdelivery`', 1)
->where('RC.catId', 11)
->having('distance <=', 50)
->order_by('distance', 'ASC')
->get_compiled_select();
your subjoin query
$strSubQueryJoin = $this->db
->select_max('radius', 'maxRadius')
->select('rent_br_delivery_radius.rest_brId AS bbrId')
->from('rent_br_delivery_radius')
->group_by('bbrId')
->get_compiled_select();
and finally we put this pieces together
$query = $this->db
->select('A.*, B.*')
->from($strSubQuery.' A', false)
->join('('.$strSubQueryJoin.') B', 'A.BRID = B.bbrId', 'INNER', false)
->where('A.distance <= B.maxRadius', NULL, false)
->order_by('A.NAME')
->get();
That should pretty much do the job.
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";
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
I'm doing a query on a set of users that returns the users within a specific radius. The users belong to 1 of 3 different types of user groups. I need to sort the return list by showing the users from group 1 (sorted by distance) and then the users from group 2 & 3 combined (sorted by distance). I could easily use an ORDER BY clause, but I don't know how to be sure that groups 2 & 3 are combined together in the results.
Here is the statement that returns the users first by group 1, then 2, then 3 and sorted by distance inside those groups. I need to return group 1, then 2&3, sorted by distance.
Also I'm not sure if I'm using the INNER JOIN properly, should this be a LEFT JOIN?
SELECT
SQL_CALC_FOUND_ROWS
a.*,
b.group_id,
ROUND(( 3959 * acos( cos( radians( $lat ) ) * cos( radians( a.latitude ) ) * cos( radians( a.longitude ) - radians( $lon ) ) + sin( radians( $lat ) ) * sin( radians( a.latitude ) ) ) ), 1) AS distance
FROM `users` AS a
INNER JOIN `user_group_map` AS b
ON a.`id` = b.`user_id`
HAVING distance <= $radius
ORDER BY
b.`group_id` DESC,
distance ASC
You can use an ORDER BY CASE to conditionally apply the order forcing group 1 first. I think this should do the job:
ORDER BY
CASE WHEN b.group_id = 1 THEN 0 ELSE 1 END,
distance ASC
The CASE statement returns 0 for group_id = 1 and 1 for any other value of group_id. The 0 sorts ahead of the 1, and these two subgroups (0 & 1) are then subordered by distance ASC so group_id 2 & 3 sort together.