can put alias in fulltext query? - full-text-search

This is FULLTEXT query, searching food from restaurants-menu
$query = "SELECT *, MATCH(food_row) AGAINST('cheese' IN BOOLEAN MODE) AS yyy
FROM table_menu
WHERE MATCH(food_row) AGAINST('cheese' IN BOOLEAN MODE)
AND restaurant IN (SELECT id FROM table_restaurant
WHERE country IN ('FR') AND town='paris')";
I want to modify. Considering Max distance = 5km. this is working.
latitude and longitude are of user (google geocode), lat and lng of restaurants in mysql (they are in table_restaurant)
$xxx = " (((acos(sin((".$latitude."*pi()/180)) *
sin((`lat`*pi()/180))+cos((".$latitude."*pi()/180)) *
cos((`lat`*pi()/180)) * cos(((".$longitude."- `lng`)
*pi()/180))))*180/pi())*60*1.1515*1.609344) ";
$query .= " AND ($xxx <= 5 )"; // 5 = the max distance
yes working, but I can't write each restaurant distance in the output.
I don't know how insert 'AS distance' If not FULLTEXT, would be easy.
$query = "SELECT *, $xxx as distance FROM table2 WHERE ... ORDER BY distance ";
output: distance = row['distance']
how can I do? Sorry for my english, tanks

I assume the reason you're modifying the query is because the distance test is optional.
Try it like this:
if ($max_dist) {
$dist = " (((acos(sin((".$latitude."*pi()/180)) *
sin((`lat`*pi()/180))+cos((".$latitude."*pi()/180)) *
cos((`lat`*pi()/180)) * cos(((".$longitude."- `lng`)
*pi()/180))))*180/pi())*60*1.1515*1.609344) ";
$having = "HAVING distance < $max_dist";
} else {
$dist = " NULL ";
$having = '';
}
$query = "SELECT m.*, MATCH(food_row) AGAINST('cheese' IN BOOLEAN MODE) AS yyy,
$dist AS distance
FROM table_menu m
JOIN table_restaurant r ON m.restaurant = r.id
WHERE MATCH(food_row) AGAINST('cheese' IN BOOLEAN MODE)
AND country IN ('FR') AND town='paris'
$having";

Related

Order by distance on POINT in Laravel 5.1 via associated tables

I'm attempting to order events by their distance from a user submitted postcode and distance.
I've attached a sample of my database tables and their relationships, as you can see geom is associated with multiple addresses via postcode and addresses can be associated to multiple tables (in this instance the events table).
I'm taking a postcode from the end user as well as a radius in miles to retrieve appropriate events, here is a sample of how I am achieving this in Eloquent.
/**
* Extend locale method which initially only gets lat/long for given postcode to search
*
* #param \Illuminate\Database\Eloquent\Builder $query The query builder
* #param \App\Http\Requests\SearchRequest $request The search request
* #return void
*/
protected function locale(Builder $query, SearchRequest $request)
{
$postcode = $this->formatPostcode($request->postcode);
$geom = Geom::query()->where('postcode', $postcode)->first();
if (! $geom || Cache::has('postcodeAPIFailed')) {
return;
}
$lat = $geom->geo_location['lat'];
$long = $geom->geo_location['long'];
// Top-left point of bounding box
$lat1 = $lat - ($request->within / 69);
$long1 = $long - $request->within / abs(cos(deg2rad($lat)) * 69);
// Bottom-right point of bounding box
$lat2 = $lat + ($request->within / 69);
$long2 = $long + $request->within / abs(cos(deg2rad($lat)) * 69);
$query->whereHas('address', function (Builder $query) use ($request, $lat, $long, $lat1, $long1, $lat2, $long2) {
$query->whereHas('geom', function (Builder $query) use ($request, $lat, $long, $lat1, $long1, $lat2, $long2) {
$query->whereRaw('st_within(geo_location, envelope(linestring(point(?, ?), point(?, ?))))', [$long1, $lat1, $long2, $lat2]);
});
});
}
In the controller after we have retrieved the search results, we calculate the distances for each of the results.
if ($request->has('postcode')) {
$postcodeDistances = $this->getDistances($results, $request);
}
This produces an array with a key of postcode and value of distance, i.e $postcodeDistances['L1 0AA'] = '3';, we send this array to the view.
In the view we then use the following logic to display distance on a record where applicable
#if($postcodeDistances)
<span>
{{ $postcodeDistances[$result->address->postcode] }}
mile{{ $postcodeDistances[$result->address->postcode] != 1 ? 's' : '' }} away
</span>
#endif
I've tried a few methods but I've been unable to update my function locale() to do the ordering by distance. I've considered maybe I can attached the distance to the collection and use a Laravel method to order the collections that way but achieving this from the database layer would be ideal if the latter is even possible.
My first attempt was to addSelect a distance field after whereHas('geom') and order by the new field
$query->addSelect(\DB::raw("ST_DISTANCE_SPHERE(geo_location, POINT({$long}, {$lat})) AS distance"));
I receive the following error:
SQLSTATE[21000]: Cardinality violation: 1241 Operand should contain 2 column(s) (SQL: select count(*) as aggregate from `event` where (select count(*) from `address` where `address`.`addressable_id` = `event`.`id` and `address`.`addressable_type` = event and (select count(*), ST_DISTANCE_SPHERE(geo_location, POINT(-2.717472, 53.427078)) AS distance from `geom` where `geom`.`postcode` = `address`.`postcode` and st_within(geo_location, envelope(linestring(point(-3.6903924055016, 52.847367855072), point(-1.7445515944984, 54.006788144928))))) >= 1) >= 1 and (select count(*) from `organisation` where `event`.`organisation_id` = `organisation`.`id` and `status` = 1) >= 1 and `event_template_id` is not null and `date_start` >= 2018-07-31 00:00:00 and `status` in (1, 5))
I also attempted to use orderByRaw in the same place instead, whilst I did not receive an error the results were not ordered accordingly.
$query->orderByRaw('ST_DISTANCE_SPHERE(geo_location, POINT(?, ?)) ASC', [$long, $lat]);
I'll have a go for solving your issue. But. As I mentioned in a comment, you will have to split up geo_location to lat and lng.
When that is done, the formula is as follows. This will calculate distance in km.
$distance = 50; //max distance in km
$limit = 100; //the amount of selected records
$earthRadiusKm = 6371;
$earthRadiusMiles = 3959;
$postcode = $this->formatPostcode($request->postcode);
$geom = Geom::query()->where('postcode', $postcode)->first();
$lat = $geom->lat;
$lng = $geom->lng;
//assuming your Geom model db name is geoms and the 'id' is id
$postcodeDistances = \DB::table('geoms')->selectRaw("
geoms.id, ( $earthRadiusKm * acos( cos( radians($lat) ) * cos( radians( geoms.lat ) )
* cos( radians( geoms.lng ) - radians($lng) ) + sin( radians($lat) ) *
sin(radians(geoms.lat)) ) ) AS distance, lat, lng
")->havingRaw("distance < $distance")->orderBy('distance')->limit($limit)->get();
For your interest, if you want the results in imperial miles, I have added the earth radius in both metrics. As the formula does make use of the earth's radius, longer distances will be more accurate.
The result (json) should look like this (2 records result, the first coord is always the starting point). Fyi, the coords are in the Philippines.
all: [
{#3627
+"id": 1128,
+"distance": 0.0,
+"lat": "15.6672998",
+"lng": "120.7349950",
},
{#3595
+"id": 1535,
+"distance": 9.564007130831,
+"lat": "15.6732128",
+"lng": "120.6458749",
},
]

How not to use DB in the following query having a subquery

I'm having the following function to calculate distances between two points using Haversine formula:
public static function getByDistance($distance)
{
$pubsFiltered = DB::select(
'SELECT * FROM
(SELECT *, (6371 * acos(cos(radians(40.4169473)) * cos(radians(latitude)) *
cos(radians(longitude) - radians(-3.7035285)) +
sin(radians(40.4169473)) * sin(radians(latitude))))
AS distance
FROM pubs) AS distances
WHERE distance < ' . $distance . '
ORDER BY distance
;
');
return $pubsFiltered;
}
This is returning an array instead of a collection, that is what I need.
If I change DB to Pub it returns an error because of the subquery where I calculate "distance" using my table "pubs".
How can I change the whole function or simply the subquery to have an instance of my Pub model?...Do I have to use set/declare variables in mysql?
Thanks a lot!!
$pubs = Pubs::hydrate($pubsFiltered)
For now, I am using this query to obtain nearby records:
public function scopeNearby($query, $lat, $long, $distance = 10)
{
if($lat AND $long) {
// 3959 for miles, 6371 for kilometers
$distanceQuery = '(6371 * acos(cos(radians(?)) * cos(radians(geo_lat)) * ' .
'cos(radians(geo_long) - radians(?)) + sin(radians(?)) ' .
'* sin(radians(geo_lat))))';
return $query->select('*', DB::raw("{$distanceQuery} as distance"))
->addBinding([$lat, $long, $lat], "select")
->whereNotNull('geo_lat')
->whereNotNull('geo_long')
->whereRaw("{$distanceQuery} < ?")
->addBinding([$lat, $long, $lat, $distance], "where")
->orderBy('distance', "ASC");
}
}
You may notice that I have used the $distanceQuery for both SELECT and WHERE clauses. I am currently having issues when this scope is used alongside with the paginate() method. This might need some refactoring, but for now you can do the same.

Performing a sum subquery using linq

Please could someone assist with the below query. I'm trying to select the Sum of all receipts per Client. But the 'Total =' sub query in my select section is not working and I'm getting the following error:
Unable to create a constant value of type 'AppName.Domain.Entities.AccountingEntry'. Only primitive types or enumeration types are supported in this context.
I've already tried doing a ToList() after that query but this results in the same problem. What is the standard way of selecting a Sum as a subquery using linq?
var receipts = (from ae in repo.AccountingEntries
join c in repo.Clients on ae.ClientId equals c.ClientId
join m in repo.Memberships on c.MembershipId equals m.MembershipId
where
(ae.EntryDate <= start) &&
(ae.ClientId != null) &&
(ae.AccountingEntryTypeId == (byte)Shared.AccountingEntryTypes.Receipt)
select new AppName.Reports.Clients.AgeAnalysis.Receipt
{
ClientId = (Guid)ae.ClientId,
Client = c.FirstName + " " + c.LastName,
Membership = c.Membership.Name,
Total = (from ae2 in repo.AccountingEntries where ae2.ClientId == ae.ClientId select ae2.Total).Sum()
});
Thanks,
Gary
This seems overly complex and unnecessary. If your entity model is set up properly, you can simplify by removing the joins and using a "group by" clause. Something along the lines of:
from ae in c.AccountingEntries
where
(ae.EntryDate <= start) &&
(ae.ClientId != null ) &&
(ae.AccountingEntryTypeId == (byte)Shared.AccountEntyrTypes.Receipt)
group by
ae.Client into g
select
new AppName.Reports.Clients.AgeAnalysis.Receipt
{
ClientId = (Guid)g.Key.ClientId,
Client = g.Key.FirstName + " " + g.Key.LastName,
Membership = g.Key.Membership.Name,
Total = g.Sum( p => p.Total )
}

display results on query basis in Codeigniter with Pagination

I have a table of products. I need to display all the products. I am fetching results on different conditions.
create table `products` (
`id` double ,
`category_id` double ,
`subcategory_id` double ,
`product_name` varchar (765),
`product_description` varchar (765),
`product_viewed` varchar (765),
`sale_wanted` tinyint (2),
`added_date` datetime ,
`updated_date` datetime ,
);
I need to diplay the results like this
1. The latest products (use of added date)
2. Most Wanted (Sorting by sale_wanted 1 for sale , 2 for wanted)
3. Most Viewed (Sorting by product_viewed)
4. Sorting by Specific Subcategory
All the results should display with pagination. This is all right if i first get the result. But if i walk with pagination links all the condition data is lost and the query fetches the results without any condition. How can i manage This situation. Please i dont need Code i need hints and suggestions. The other thing is that i am using Codeigniter's pagination class.
EDITED
Here is my Model Method i am using
public function getProductsList($per_page=5,$page=0)
{
$info = $this->input->post();
if(isset($info['type']))
{
$type = $info['type'];
if($type == 'most_wanted'){
$where = " AND sale_wanted = 1";
$order_by = " ORDER BY ldc.added_date desc";
}else if($type == 'most_viewed'){
$where = " ";
$order_by = " ORDER BY ldc.product_viewed desc";
}else{
$where = " ";
$order_by = " ORDER BY ldc.added_date desc";
}
}else if(isset($info['sale_wanted']) AND isset($info['subcategory_id'])){
$sale_wanted = $info['sale_wanted'];
$subcategory_id = $info['subcategory_id'];
$where = " AND sale_wanted = $sale_wanted AND ldc.subcategory_id = $subcategory_id";
$order_by = " ORDER BY ldc.added_date desc";
}else if(isset($info['keyword'])){
$keyword = $info['keyword'];
$search_type = $info['search_type'];
$where = " AND ldc.$search_type like '$keyword%'";
$order_by = " ";
}else{
$where = " ";
$order_by = " ";
}
$num = 0;
if($page != 0){
$num = ($page * $per_page) - $per_page;
}
$sql_query = "
SELECT
ldc.id,
ldc.product_name,
ldc.product_viewed,
DATE_FORMAT(ldc.added_date, '%m/%d/%Y') as added_date,
ifnull(dc.name,'Unknown') as category,
dpi.product_image
FROM default_products AS ldc
LEFT JOIN default_manufacturers as dm ON dm.id = ldc.manufacturer
LEFT JOIN default_category as dc ON dc.category_id = ldc.category_id
LEFT JOIN ((select product_id , product_image from default_product_images group by product_id) as dpi)
ON dpi.product_id = ldc.id
WHERE approved = 1
$where
$order_by
LIMIT $num,$per_page
";
$query = $this->db->query($sql_query);
return $query->result();
}
i would highly recommend these two Free tutorials on codeigniter pagination -
video tutorials, working sample code ( might need to update code to CI 2.1 ), and even some helpful info in the comments.
CodeIgniter from Scratch: Displaying & Sorting Tabular Data
http://net.tutsplus.com/tutorials/php/codeigniter-from-scratch-displaying-sorting-tabular-data/
CodeIgniter from Scratch: Search Results without Query Strings
http://net.tutsplus.com/tutorials/php/codeigniter-from-scratch-search-results-without-query-strings-2/

How to speed this query up?

I'm using yii framework to create a website takes care of poems and words and stuff...
the problem is that the database has more than 250,000 record of messages and more than 500,000 records of keywords and relationships :S
what I'm trying to say that I want to make an optimal query to get all related messages of a specific message depending on key-tags..
what I did so far is caching the query, but it's still slow when opening it for the first time and that's a big problem!
here is my code:
public function getRelatedMessages()
{
$id = $this->id;
$dependency = "select `messages`.id,count(id) as tag_count from messages left join `messages_tags` on `messages`.`id` = `messages_tags`.mid
where `messages_tags`.`tagid` in (select `tags`.`id` from tags left join `messages_tags` on tags.id = `messages_tags`.tagid
where `messages_tags`.`mid` = {$id}) and id <> {$id} group by id order by tag_count desc";
$dependency = Messages::model()->cache(900)->findAllBySql($dependency);
foreach ($dependency as $dependency) {
$dependency1[] = $dependency['id'];
}
$dependency = implode(", ", $dependency1);
$sql = "select * from messages where id in ({$dependency}) limit 4";
$relateds = Messages::model()->findAllBySql($sql);
$db = array();
foreach($relateds as $related) {
$db[] = $related;
}
if(!$relateds || count($db)<4) {
$limit = 4-count($db);
$sql = "select `messages`.id, `messages`.url_key, `messages`.photo_url, `messages`.message from messages order by rand(), likes desc, comments desc, impressions desc, reported asc limit {$limit};";
$relateds = Messages::model()->cache(900)->findAllBySql($sql);
foreach($relateds as $related) {
$db[] = $related;
}
}
return $db;
}
the code above selects only 4 records from the related messages after filtering them and ordering them and stuff.. the problem is with "order by" but I need it :S
sorry if that was too long..
thank you :)
You could improve the first query by not using findAllBySql() and instantiating all the ActiveRecord classes. You don't need them, so use plain sql query like this (query stays the same but code around has changed):
// ...
$dependency = $this->dbConnection->createCommand("
select `messages`.id, count(id) as tag_count
from messages left join `messages_tags` on `messages`.`id` = `messages_tags`.mid
where
`messages_tags`.`tagid` in
(select `tags`.`id` from tags left join `messages_tags` on tags.id = `messages_tags`.tagid where `messages_tags`.`mid` = {$id})
and id <> {$id}
group by id order by tag_count desc")->queryColumn();
$dependency = implode(", ", $dependency);
$sql = "select * from messages where id in ({$dependency}) limit 4";
$relateds = Messages::model()->findAllBySql($sql);
// ...

Resources