Laravel query builder, use custom select when calling count() - php

I have three tables, one defining a many to many relationship between the other two.
table : collections
table : collection_question
table : questions
I am running this query to return the number of questions a collection has
$results = DB::table('collections')
->select(array(
DB::raw('count(collection_question.question_id) as question_count'),
'name',
'description'
))->leftJoin(
'collection_question',
'collections.id',
'=',
'collection_question.collection_id'
)->where('question_count', '>', 1)
->orderBy('question_count', 'asc')
->get();
This works fine as the select query is not tampered with by the query builder.
When I swap out get() for count() however the query builder replaces my select clause with select count(*) as aggregate which is understandable, however I loose the binding to question_count in the process and a SQL exception is thrown.
I've looked through the source code for Illuminate\Database\Query\Builder to try and find a solution but other than manually executing the raw query with a custom count(*) alongside my other select clauses I'm at a bit of a loss.
Can anyone spot a solution to this?

Instead of calling count() on a Builder object, I've resorted to creating my own count expression in addition to any other select clauses on the query rather than replacing them.
// This is in a class that uses Illuminate\Database\Query\Expression the clone
// isn't necessary in most instances but for my case I needed to take a snapshot
// of the query in its current state and then manipulate it further.
$query = clone($query);
$query->addSelect(new Expression("count(*) as aggregate"));
$count = (int) $query->first()->aggregate;

Related

Getting count fields of contained models using WHERE conditions

I have three models, Companies, events and assistances, where the assistances table stores the event_id and the company_id. I'd like to get a query in which the total assistances of the company to certain kind of events are stored. Nevertheless, as all these counts are linked to the same table, I don't really know how to build this query effectively. I have the ids of the assistances to each kind of event stored in some arrays, and then I do the following:
$query = $this->Companies->find('all')->where($conditions)->order(['name' => 'ASC']);
$query
->select(['total_assistances' => $query->func()->count('DISTINCT(Assistances.id)')])
->leftJoinWith('Assistances')
->group(['Companies.id'])
->autoFields(true);
Nevertheless, I don't know how to get the rest of the Assistance count, as I would need to count not all the distinct assistance Ids but only those taht fit to certain conditions, something like ->select(['assistances_conferences' => $query->func()->count('DISTINCT(Assistances.id)')])->where($conferencesConditions) (but obviously the previous line does not work. Is there any way of counting different kind of assistances in the query itself? (I need to do it this way because I then plan to use pagination and sort the table taking those fields into consideration).
The *JoinWith() methods accept a second argument, a callback that receives a query builder used for affecting the select list, as well as the conditions for the join.
->leftJoinWith('Assistances', function (\Cake\ORM\Query $query) {
return $query->where([
'Assistances.event_id IN' => [1, 2]
]);
})
This would generate a join statement like this, which would only include (and therefore count) the Assistances with an event_id of 1 or 2:
LEFT JOIN
assistances Assistances ON
Assistances.company_id = Companies.id AND
Assistances.event_id IN (1, 2)
The query builder passed to the callback really only supports selecting fields and adding conditions, more complex statements would need to be defined on the main query, or you'd possibly have to switch to using subqueries.
See also
Cookbook > Database Access & ORM > Query Builder > Filtering by Associated Data

How to write this self joined query with distinct based on the inner join relationship using the query builder?

I have an entity Place that can hold many different types of places, particularly in this case "cities" and "states", where each "city" can hold a reference to the same table pointing to a parent "state".
I have another entity Office with a many-to-one relationship to Place, but because of domain constraints, an office will only be linked to a "city", never to a "state".
I have to write a query to get all states where we have one or more offices, but only those.
In plain SQL, the query works out easyly enough:
SELECT DISTINCT states.*
FROM offices o
INNER JOIN places cities ON cities.id = o.place_id
INNER JOIN places states ON cities.parent_place_id = states.id
WHERE p.place_type = 'city'
This works, and we get exactly what we need.
But I need (I would prefer) to write the query using the query builder. This is "base" query, but we need to conditionally apply several more filters, which would make using the QB much cleaner and manageable.
Currently we are using a Native Query, but this means that we need to manipulate the SQL string before we call em::createNativeQuery(), which is cumbersome.
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('states')->distinct()
->from(PartnerOffice::class, 'o')
->innerJoin('o.place', 'p')
->innerJoin('p.parentPlace', 'states')
->where("p.placeType = 'city'");
But this gives an error about:
SELECT DISTINCT': Error: Cannot select entity through identification variables without choosing at least one root entity alias
How can we make this query work?
Thanks and regards.
It's hard to figure out the query you should build without seeing the entities (at least for me). Eg: do you have a mappedBy field? But I think your problem is that you are fetching no information from PartnerOffice while it is your root entry (from). Either change your ->select() for a ->addSelect but that will fetch the entire PartnerOffice entity for each record and you distinct might not work as expected, or use the Place entity as your root entry and flip the logic.
Let's assume your entities' relations are bidirectional. Something like this should do it (but you might need to change it for the real entities and fields names).
$this->getEntityManager()->createQueryBuilder()
->distinct()
->from(Place::class, 'p')
->join('p.cities', 'c')
->join(PartnerOffice::class, 'po', Join::WITH, 'po.place = p')
->andWhere('p.placeType = \'state\'');
I think you have posted an incomplete base sql query (WHERE AND) and you made an alias p.place_type which is not defined above in the query.
However, from your statement, I think you can try the query below,
$qb->selectRaw('DISTINCT s.states')
->from('offices as o')
->join('places as c', 'c.id', '=', 'o.place_id')
->join('places as s', 's.id', '=', 'c.parent_place_id')
->where('c.placeType', 'city')->get();
By default, ->join() refers to inner join in queryBuilder.
Source:
https://laravel.com/docs/8.x/queries#inner-join-clause

question about laravel relationships and performance

i hope you are having a good time. i am learning laravel and the inscuctor talked about when you load relationships in laravel, like so
public function timeline()
{
$ids = $this->follows()->pluck('id');
$ids->push($this->id);
return Tweet::whereIn('user_id', $ids)->latest()->get();
}
and i have a follows relationship in my model, and he talked about this line
$ids = $this->follows()->pluck('id');
being better for performance than this line
$ids = $this->follows->pluck('id');
my question is, how does laravel pluck the ids in the first case, and how it queries the database
i hope im making sense, thanks for your time, and answer.
the following one executes a select query on database
$this->follows()->pluck('id');
the follows() returns a query builder (which is a not yet executed sql statement) and then on the result select the id column and returns a collection of ids
you can see the query by dumping the query builder by $this->follows()->dd()
Whereas in the second option
$this->follows->pluck('id')
up until $this->follows laravel executes a query and returns all the records as a collection instance, You will be able to see all the attributes on each of the records. And then ->pluck('id') is getting executed on the laravel collection class, which will do an operation I assume similar to the array_column function does and returns only the id column.
as you can easily see in the second operation the whole data set was retrieved first from the DB and then selected the required attribute/column (2 distinct and heavy operations). Where as in the first option we directly told eloquent to select only the required column, which is only one lighter operation compared to the second option.

Trying to avoid Subquery in the Select Statement : Laravel 5.1

I am using Laravel 5.1. below is my Database query
\App\Models\Project\Bids\ProjectBid_Model
::with(['Project' => function($query){
$query->where('WhoCreatedTheProject', 16);
}])
->where('ProjectBidID', 1)
->where('FreelancerAwardedProjectStatusID', \App\ProjectStatus::InProgress)
->first()
What's the Question ?
In the below part, if there exists no record, then the Project Object will be null. This seems a sub query in the select statement. Is there any way to return overall no record if 'WhoCreatedTheProjectvalue does not match in the database ? I meant I am looking forInner Join`.
::with(['Project' => function($query){
$query->where('WhoCreatedTheProject', 16);
}])
You can do this this way (you will have one query):
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('projectbid_table.*')
->join('projects_table','projectbid_table.project_id','=','project_table.project_id')
->where('WhoCreatedTheProject',16);
->where('ProjectBidID', 1)
->where('FreelancerAwardedProjectStatusID', \App\ProjectStatus::InProgress)
->first();
You need to of course alter table names in join to match your values.
But answering the question, in your example any subquery won't be run. You are getting all the project bids (this is 1st query) and later in separate query you get all the projects for this bids for which WhoCreatedTheProject = 16 (this is 2nd query). So this what you showed is something different what you want to probably achieve.

Laravel Query Builder where max id

How do I accomplish this in Laravel 4.1 Query Builder?
select * from orders where id = (select max(`id`) from orders)
I tried this, working but can't get the eloquent feature.
DB::select(DB::raw('select * from orders where id = (select max(`id`) from orders)'));
Any idea to make it better?
You should be able to perform a select on the orders table, using a raw WHERE to find the max(id) in a subquery, like this:
\DB::table('orders')->where('id', \DB::raw("(select max(`id`) from orders)"))->get();
If you want to use Eloquent (for example, so you can convert your response to an object) you will want to use whereRaw, because some functions such as toJSON or toArray will not work without using Eloquent models.
$order = Order::whereRaw('id = (select max(`id`) from orders)')->get();
That, of course, requires that you have a model that extends Eloquent.
class Order extends Eloquent {}
As mentioned in the comments, you don't need to use whereRaw, you can do the entire query using the query builder without raw SQL.
// Using the Query Builder
\DB::table('orders')->find(\DB::table('orders')->max('id'));
// Using Eloquent
$order = Order::find(\DB::table('orders')->max('id'));
(Note that if the id field is not unique, you will only get one row back - this is because find() will only return the first result from the SQL server.).
Just like the docs say
DB::table('orders')->max('id');
For Laravel ^5
Orders::max('id');
I used it is short and best;
No need to use sub query, just Try this,Its working fine:
DB::table('orders')->orderBy('id', 'desc')->pluck('id');
Laravel 5+:
DB::table('orders')->orderBy('id', 'desc')->value('id');
For objects you can nest the queries:
DB::table('orders')->find(DB::table('orders')->max('id'));
So the inside query looks up the max id in the table and then passes that to the find, which gets you back the object.
You can get the latest record added to the Orders table you can use an eloquent method to retrieve the max aggregate:
$lastOrderId = Order::max('id');
To retrieve a single row by the id column value, use the find method:
$order = Order::find(3);
So combining them, to get the last model added to your table you can use this:
$lastOrder = Order::find(Order::max('id'));

Categories