Getting the Query Builder object behind fetching a relation in Laravel - php

We have modeled self-referencing relations in our User model using belongsToMany(). Users can be agent or seller for each other - so we definer seller() and agents()
Now we are using https://github.com/Nayjest/Grids which needs a query to the grid.
In the grid we want for example to display a seller's agents.
Currently we are using a hand-crafted query for that - but we would like to pull the logic from the model.
So what we need is the query (as query builder object) which is executed when fetching
$seller = Auth::user(); // or any other instance of User
$seller->agents
We tried
$query = $seller->newQuery()->where('laravel_reserved_1.id', '=', $seller->id);
return $user->agents()->getRelationQuery($query, $query);
without luck.
EDIT
Trying $seller->agents()->getQuery() we get
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in order clause is ambiguous
(SQL: select * from `users` inner join `user_connections` on
`users`.`id` = `user_connections`.`user_id`
where `user_connections`.`related_user_id` = 2 and
`user_connections`.`type` = agent order by `id` asc
limit 15 offset 0)

Turns out it is just
$user->agents()->getQuery()
We had a column "id" in the grid which led to the ambiguous query later in the process.

Related

Group statement being added to query automatically in Laravel

So I'm using Laravel eloquents to access data from my database - it's very similar to other statements in my code which work fine, but for some reason this isn't working:
$data->leftjoin('val_keys', function($leftJoin) use (&$key_words)
{
$leftJoin->on('val_keys.val_id', '=', 'vals.id');
})->whereRaw(DB::raw(sprintf('(LOWER(val_keys.key) LIKE "%%%s%%")', strtolower($key_words))));
I keep getting the following error, which shows that there is a group statement that is somehow being added to my SQL query despite the fact that I'm not putting it there:
Integrity constraint violation: 1052 Column 'id' in group statement is ambiguous (SQL: select count(*) as aggregate from `vals` left join `val_keys` on `val_keys`.`val_id` = `vals`.`id` where (`category_id` = 3780) and (LOWER(val_keys.key) LIKE "%hey%") group by `id`)
I've tried adding my own groupBy() to my query, with vals.id as the input, hoping that this would override the group statement that uses id, but it doesn't:
Integrity constraint violation: 1052 Column 'id' in group statement is ambiguous (SQL: select count(*) as aggregate from `vals` left join `val_keys` on `val_keys`.`val_id` = `vals`.`id` where (`category_id` = 3780) and (LOWER(val_keys.key) LIKE "%hey%") group by `vals`.`id`, `id`)
How do I get rid of this group statement so my query will run as intended? I've already tried changing strict to true. I am using Laravel version 5.2.
Thanks

Select all columns but distinct from database table in Laravel

Bellow SQL command runs perfectly :
select * from `product` group by `owner_name` order by `id` asc
When I translate above code in my Laravel project to get the same result :
Product::select('*')
->orderBy('id','asc')->groupBy('owner_name')
->get();
This laravel code returns me error that
SQLSTATE[42000]: Syntax error or access violation: 1055
'db1.product.id' isn't in GROUP BY (SQL: select * from product group
by owner_name order by id asc)
Problem is I have many duplicated records with slight differences on some of their columns. I need to get them by owner_name and only one time .
Edit your applications's database config file config/database.php
In mysql array, set strict => false to disable MySQL's strict mode
You have don't need to do 'select(*)', by default it will select all columns data.
Try this:
Product::orderBy('id','asc')->groupBy('owner_name')
->get();
And if you want to fetch selected column you can do like this:
Product::select(['column_1', 'column_2'])
->orderBy('id','asc')->groupBy('owner_name')
->get();

Laravel / MYSQL (Eloquent) - Querying large table

I am making an online directory, this directory contains businesses, this is how the current table structure is set out:
1) "Business"
ID (PK)
Name
Phone_Number
Email
2) Tags
id (PK)
tag
3) Business_tags
id (PK)
business_id (FK)
tag_id (FK)
There are over 9k rows inside of the business table, and over 84,269 rows and there are over 29k rows inside the ("Business_tags") table (As a business can have multiple tags).
Inside the business model, is the following:
public function tags()
{
return $this->belongsToMany('App\Tags');
}
The issue is when I am trying to do a search, so for example, let's say that someone wants to search for a "Chinese" then it's takes more time than it probably should to return a value. For example, I am using:
$business = Business::where(function ($business) use ($request) {
$business->whereHas('tags', function ($tag) use ($request) {
});
})->paginate(20);
Searching takes on average: 35 seconds to display the results.
Here is the raw sql:
select * from `businesses` where (exists (select * from `tags` inner join `business_tags` on `tags`.`id` = `business_tags`.`tags_id` where `business_tags`.`business_id` = `businesses`.`id` and `name` in ('chinese')))
This takes on average: 52.4s to run inside Sequel pro (Using the raw SQL statement)
Any ideas how I can improve the performance of this query so that it's a lot faster? I want to have this functionality, but the user is not going to wait this long for a response!
EDIT:
1 PRIMARY businesses NULL ALL NULL NULL NULL NULL 8373 100.00 Using where
2 DEPENDENT SUBQUERY business_tags NULL ALL NULL NULL NULL NULL 30312 10.00 Using where
2 DEPENDENT SUBQUERY tags NULL eq_ref PRIMARY PRIMARY 4 halalhands.business_tags.tags_id 1 10.00 Using where
You're over-complicating this, and not using eloquent relationships correctly. You should be using JOINs instead:
$businesses = Business::join('business_tags', 'business_tags.business_id', '=', 'business.id')
->join('tags', function($join) {
$join->on('business_tags.tag_id', '=', 'tags.id')
->where('tags.name', '=', 'chinese');
})->get();
Or in raw SQL:
SELECT *
FROM `business`
INNER JOIN `business_tags` ON `business_tags`.`business_id` = `business`.`id`
INNER JOIN `tags` ON `business_tags`.`tag_id` = `tags`.`id` AND `tags`.`name` = 'chinese'
(Note that you could put that tags.name = 'chinese' part in the WHERE clause and yield the same effect)
Your current query does an exists subquery to get all the records from the pivot table that match the criteria, then passing that back to the main query. It's an extra step, and it's unnatural.
Eloquent relationships are NOT for complex queries like this, but are rather there to provide additional, related information about a record without having to write another query manually.
For instance, if you want to view a business, you might query with() phone numbers and addresses from other tables. You might want to list out their tags, or sync() them. But eloquent does not build and filter queries, that's what query builder is for.
Let me know if you need more explanation.
As a lot of others are also going to tell you.
Have you run EXPLAIN on your query?
Have you added indexes to your tables?
Because even with the amount of data you have mentioned the query should have been faster than what you have reported.
Also see if a JOIN can work here and if faster?(just a thought)

How to get Laravel Collection from Builder?

I want to get all results from two tables akcii, feeds and after search throw them (now query just returns all records without any conditions for search), here is the query (mysql):
DB::query('SELECT id, title, text FROM (
SELECT id, title, text FROM `akcii` UNION ALL
SELECT id, title, body FROM `feeds`
) temp_table
ORDER BY `id` desc
LIMIT 0, 10')
Results are exacly that I need but after I can't convert it Collection?
If I call ->get() method I'm getting an error:
QueryException in Connection.php line 651:
SQLSTATE[HY000]: General error: 1096 No tables used (SQL: select *)
What am I doing wrong?
DB::query() method does not get any arguments, so the query you pass there is simply ignored. You're calling get() on an empty builder, which does not know what table to run the query on. Generated query is just SELECT *, hence the SQL error.
If you want that to work, you should call DB::select() method, e.g.:
$results = DB::select($your_query);
Still, what you'll get is an array, not a Collection object. If you want to make a collection out of it, do the following:
$collection = new Collection($results);

rename fields in Zend select/join query

I have 2 Tables:
region: region_id,name,state_id
state: state_id,name
I want both names in my result, state.name renamed to statename.
So far I got this:
$select = $select->from(array('r' => 'region'))->join(array('s' => 'state'),
'r.state_id = s.state_id',array("statename" =>"r.name"));
which results in following query:
SELECT `r`.*, `r`.`name` AS `statename` FROM `region` AS `r`
INNER JOIN `state` AS `s` ON r.state_id = s.state_id
So i just need to change r.name AS statename to s.name AS statename.
But i cant get it to work. If i change the last part of the select to array("statename" =>"s.name"), i get an error
Select query cannot join with another table
So how can i rename a field in the joining table?
You have to remove the integrity check.
$table = new self();
$select = $table->select()->setIntegrityCheck(false);
$select = $select->from(array('r' => 'region'))->join(array('s' => 'state'),'r.state_id = s.state_id',array("statename" =>"s.name"));
The integrity check is there to make sure your query is not going to use columns from another table, so Zend_Db_Table_Row objects can be updated, saved or deleted. If you remove the integrity, you're telling Zend that you know what you're doing and you want to use columns from another table.
Here's a brief explanation from documentation:
The Zend_Db_Table_Select is primarily used to constrain and validate
so that it may enforce the criteria for a legal SELECT query. However
there may be certain cases where you require the flexibility of the
Zend_Db_Table_Row component and do not require a writable or deletable
row. for this specific user case, it is possible to retrieve a row or
rowset by passing a FALSE value to setIntegrityCheck(). The resulting
row or rowset will be returned as a ‘locked’ row (meaning the save(),
delete() and any field-setting methods will throw an exception).

Categories