Nested wherehas with different relation in Eloquent - postgresql

Here is some relation between Eloquent classes in a Laravel 5 application:
B belongs to A
B has one C
Table are built with the correct foreign keys.
Here is a scope method of A:
public function scopeMyScope(Builder $query)
{
return $query->whereHas('B.C', function($q) {
$q->whereRaw("1=1");
});
}
If we call A::myScope()->get(), it results in an SQL error, because laravel built this query:
select * from "a" where (select count(*) from "b" where "a"."a.b_id" = "b"."id" and (select count(*) from "c" where "c"."b_id" = "b"."id" and 1=1) >=1 ) >= 1
The error is:
Illuminate\Database\QueryException with message 'SQLSTATE[42703]: Undefined column: 7 ERROR: column c.b_id does not exist
So, the query built is wrong because there is no c.b_id column (because B hasOne C, so the join column is in C). Am I doing something wrong or is it a bug in laravel's query builder?

From what I can understand, you need to do a nested whereHas, so:
public function scopeMyScope(Builder $query)
{
// b is the name of the relationship...
return $query->whereHas('b', function($q)
{
// c is the name of the relationship...
$q->whereHas('c', function()
{
$q->where(1, 1);
});
}
}
Does this solve your problem?

Related

Nested Eloquent Relation - HasOneThrough Issue

I have below setup for tables:
Product_variant Table
Product_variant -> id, name, code, image
Warehouse table
Warehouse -> id, name, address
Product Variant Stock table
Product_Variant_stock -> stock_id, warehouse_id, variant_id, stock_qty
Now, what i need to get information is about in which Warehouse, variant has been stored, when i try to access product variant information.
What i have tried in ProductVariation model:
public function warehouseName()
{
return $this->hasOneThrough(Warehouse::class, ProductVariantStock::class, 'warehouse_id', 'id');
}
Above is not working as expected. Any help is appreciated.
laravel hasOneThrough works like this
class ModelA extends Model
{
...
public function cModel()
{
return $this->hasOneThrough(
ModelC::class,
ModelB::class,
'model_a_id', // Key on B that relates to A
'model_c_id', // Key on C that relates to B
'a_id', // Key on A that relates to B
'b_id', // Key on B that relates to C
);
}
}
so your code will be
public function warehouseName()
{
return $this->hasOneThrough(Warehouse::class, ProductVariantStock::class, 'variant_id', 'id', 'id', 'warehouse_id');
}

TypeORM / Postgres - Include all in relation where at least one meets requirement

I am very new to SQL/TypeORM in general and I'm currently facing a problem where I want to load match participants related to a match where at least one participant has a passed userId. The query could be thought of as "Load all my matches with my opponents". I have three tables:
public.match << OneToMany >> public.match_participant << ManyToOne >> public.user
So far I have gone about doing:
select * from public.match m
left join public.match_participant mp on mp."matchId" = m.id
left join public.user u on u.id = mp."userId"
where u.id = 3
and in typeORM
repository
.createQueryBuilder('match')
.leftJoinAndSelect('match.participants', 'participants')
.leftJoinAndSelect('participants.user', 'user')
.where('user.id=:id')
.setParameter('id', 1)
.getMany();
Which of course loads all matches, participants and users for that particular userId but other participants are not included. I believe something like a "subquery" could be of use but I can't seem to figure it out.
Any help is greatly appreciated!
Thanks in advance.
After a bunch of trial and error I learned how to convert a pure query to the builder. The solution was the following:
matchRepository
.createQueryBuilder('match')
.innerJoin(
query => {
return query
.from(MatchParticipant, 'p')
.select('p."matchId"')
.where('p."userId" = :id');
},
'selfMatch',
'"selfMatch"."matchId" = match.id',
)
.leftJoinAndSelect('match.participants', 'participants')
.leftJoinAndSelect('participants.user', 'user')
.setParameter('id', id)
.getMany();
I don't think you even need the query builder for this.
#Entity()
class Match {
#OneToMany(...)
participants: MatchParticipant[];
}
#Entity()
class MatchParticipant {
#ManyToOne(...)
match: Match;
#ManyToOne(...)
participant: Participant;
}
#Entity()
class User {
#OneToMany(...)
matches: MatchParticipant[];
}
// ...
repository.manager.find(MatchParticipant, { where: { match: { participants: { participant: { id } } } } });

Laravel Eloquent Many-To-Many Query Producing Extra null WHERE clause

I'm using Eloquent to produce results from a query comprised of three tables:
photos (id)
photos_to_photosets (photo_id, photoset_id)
photosets (id)
My models have their many-to-many relationships defined as:
class Photo extends Model
{
public function photosets()
{
return $this->hasMany(PhotoSet::class, 'photos_to_photo_sets');
}
}
And
class PhotoSets extends Model
{
public function photos()
{
return $this->belongsToMany(Photo::class, 'photos_to_photo_sets');
}
}
Now, to fetch results I'm forced to use the following Eloquent code:
$photoData = $this->photoSets->photos()
->orWhere('photo_set_id', '=', $id)
->get();
This produces the following query:
SELECT * FROM `photos`
INNER JOIN `photos_to_photo_sets`
ON `photos`.`id` = `photos_to_photo_sets`.`photo_id`
WHERE `photos_to_photo_sets`.`photo_set_id` is null
OR `photo_set_id` = ?
This query works, but I can't seem to remove WHERE `photos_to_photo_sets`.`photo_set_id` is null from the query.
I've tried to just use ->where('photo_set_id', '=', $id) but the null clause still remains; even worse it produces the following WHERE clause:
... WHERE `photos_to_photo_sets`.`photo_set_id` IS NULL
AND `photo_set_id` = ?
Is there any way, utilizing Eloquent, to remove this null WHERE clause segment?
Ideally, I'd like to end up with the following query:
SELECT * FROM `photos`
INNER JOIN `photos_to_photo_sets`
ON `photos`.`id` = `photos_to_photo_sets`.`photo_id`
WHERE `photo_set_id` = ?
Thank you in advance for any help!
UPDATE
Based off #Jonas Staudenmeir's answer, the null WHERE clause has been removed.
To achieve this, I set the photoSet's model ID prior to running the query. The resulting code was:
$this->photoSets->id = $photoset_id;
$photoData = $this->photoSets->photos()->get();
Which produced:
SELECT * FROM `photos`
INNER JOIN `photos_to_photo_sets`
ON `photos`.`id` = `photos_to_photo_sets`.`photo_id`
WHERE `photo_set_id` = ?
The injected model doesn't have an id:
$this->photoSet->id = $id;
Then you don't need the additional constraint:
$photoData = $this->photoSets->photos;

LINQ to Entities does not recognize the method 'Object getMethod' method

I have the following entity code where i want to return a formatted string based on a custom a function that return a string:
var query =
from a in db.Authors
join b in db.Books
on a.id equals b.atuhorId into ab
from item in ab.DefaultIfEmpty()
select new
{
id = a.id,
authorName= a.name,
bookName = b.name,
formatted_book = item.id != null ? model.getFormatedBook(item.id) : "N/A",
},
};
But i got an error like mentioned in my post title.
Is there a cast that i can put inside my entity code for the getFormatedBook(item.id) ? I tried getFormatedBook(item.id).ToString() but with no success
Thanks
LINQ to Entity is unable to call outside methods as a part of a query. Take a look at this answer: https://stackoverflow.com/a/12641286/1202275 for possible workarounds.

joining a table with more than one field in same table

I try to create a join query in Linq. I want to join a table more than one field with same
table. Please see my code below.
var roles = (from ords in _orderRepository.Table
join customers in _customerRepository.Table on ords.CustomerId equals customers.Id
join ordprvrnts in _orderProductVariantRepository.Table on ords.Id equals ordprvrnts.OrderId
join prdvrnts in _productVariantRepository .Table on ordprvrnts.ProductVariantId equals prdvrnts.Id
**join cstevntrle in _customerEventRoleRepository.Table on
new{ customers.Id equals cstevntrle.CustomerId } &&
new { cstevntrle.EventId == model.Event}**
orderby customers.Email ascending
select new CustomerEventRolesModel
{
Customer = customers.Email,
CUstomerId =customers.Id
});
I try to filter customerEventRoleRepository.Table with CustomerId and EventId
how can i do this in this join query.
Please Help.
you have boolean comparisons in your anonymous type definitions...
change your on clause to the following:
join cstevntrle in _customerEventRoleRepository.Table on
new { CustomerId = customers.Id, EventId = model.Event.EventId } equals
new { CustomerId = cstevntrle.CustomerId, EventId = cstevntrle.EventId }
I don't see "model" defined anywhere, so I'm not sure this is going to work, but it should be enough to demonstrate how joins based on multiple fields works - each anonymous class contains the fields from one "side" of the join.