Laravel Eloquent union models from relationsships - sql

I have a user who can have meetings from different relationships.He can be directly assigned to a meeting with one too many relationships, he can have user groups with many too many relationships that can have meetings with many too many relationships, and he can be responsible for a meeting with a relationship of one to many.
I want to get all meetings for one user. I don't want to merge the collections because I want to be able to use features like where directly in the database. My attempt is to merge all meeting ids and then get the meetings via 'whereIn'.
$ids = $this->meetings()->select('meetings.id')
->union($this->meetingsFromGroups()->select('meetings.id'))
->union($this->responsibleMeetings()->select('meetings.id'));
return Meeting::whereIn('id', $ids)->get();
When I execute this, I get this error:
"SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'union (select `meetings`.`id` from `meetings` inner join `meeting_user_group` on' at line 1 (SQL:...)"
The SQL that is executed:
select *
from `meetings`
where `id` in (
(
select `meetings`.`id`
from `meetings`
inner join `meeting_user` on `meetings`.`id` = `meeting_user`.`meeting_id`
where `meeting_user`.`user_id` = 1
)
union
(
select `meetings`.`id`
from `meetings`
inner join `meeting_user_group` on `meeting_user_group`.`meeting_id` = `meetings`.`id`
inner join `user_groups` on `user_groups`.`id` = `meeting_user_group`.`user_group_id`
inner join `user_user_group` on `user_user_group`.`user_group_id` = `user_groups`.`id`
where `user_user_group`.`user_id` = 1
)
union
(
select `meetings`.`id`
from `meetings`
where `meetings`.`responsible_id` = 1
and `meetings`.`responsible_id` is not null
)
)
The relations:
public function responsibleMeetings(): HasMany {
return $this->hasMany(Meeting::class, 'responsible_id');
}
public function meetings(): BelongsToMany {
return $this->belongsToMany(Meeting::class)->withTimestamps();
}
public function meetingsFromGroups(): HasManyDeep {
return $this->hasManyDeepFromRelations($this->userGroups(), (new UserGroup())->meetings());
}
I'm using the package Staudenmeir\EloquentHasManyDeep for meetingsFromGroups.
I can't use union on the whole meeting, because then I would get
SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns
because of the pivot fields.
How would the SQL have to change for it to work, and how can I do that with Eloquent?

By default whereIn expects array to perform IN() clause on array items but in your case your are sending a union query which is why it is throwing error.
$ids = $this->meetings()->select('meetings.id')
->union($this->meetingsFromGroups()->select('meetings.id'))
->union($this->responsibleMeetings()->select('meetings.id'))
->pluck('id')->all();
return Meeting::whereIn('id', $ids)->get();
Additionally whereIn accepts a closure method also but not sure how would you call these relateables $this->meetings() ,$this->meetingsFromGroups() & $this->responsibleMeetings() inside closure
return Meeting::whereIn('id', function($query){
$query->select('...')
...;
})->get();

Related

"nested" table calls in JPA? (translate SQL to JPA)

I am trying to convert this SQL into a JPA #Query call, and am struggling to understand how to handle multiple table calls in one JPA Query, specifically i am trying to return a Count of all the Users that are assigned to a Program.
The DB structure is shown:
The query I want to translate is:
SELECT *FROM User WHERE id in ( SELECT user FROM UserProjectAssignment WHERE project in ( SELECT id FROM Project WHERE program = programId))
My current Query is:
#Query("SELECT COUNT(u) FROM User u, UserProjectAssignment upa, Project p WHERE u.id = upa.project AND WHERE upa.project = (p.program = :programId)")
Long countUsersAssignedToProgram(#Param("programId") Long programId);
Basically what you need here is query with JOIN s
But one can form the query only if he knows the class structures.
You can change the mysql query like this
Select user from User user join UserProjectAssignment upa on user.id = upa.userid join Project p on upa.projectId = p.id join program pgm on p.programId = pgm.id ;

Combining Cypher results with APOC SQL queries

I am trying to update node properties of a PostgreSQL database that I have running in a virtual environment. I am building a Jazz knowledge base and would like to use the node ids to query additional properties from the PSQL database using APOC.
I have tried unwinding the ids and using them to run the SQL statements with APOC.
This is the code I have tried thus far:
MATCH (a:Artist)
WHERE a.genre = 'jazz'
WITH COLLECT(a.mbid) AS ids
UNWIND ids AS id
CALL apoc.load.jdbc('myDB',
"select DISTINCT a.gid, ar.name as country FROM artist a INNER JOIN area ar on a.area = ar.id WHERE a.gid = ?", [id]) YIELD row
MATCH (a:Artist) WHERE a.mbid = row.gid SET a.country = row.country
RETURN COUNT(a.country)
I am running into an error saying "ERROR: operator does not exist: uuid = character varying". Is there anyway to use the ids from the Cypher query to update each node individually through individual SQL statements with APOC?
Assuming the gid column is a UUID type in your Postgres database and that the error is when executing the query via apoc.load.jdbc, the following explicit casts of your IDs in the SQL should at least have the comparison work and have the query return a string value you can compare on:
MATCH (a:Artist)
WHERE a.genre = 'jazz'
WITH COLLECT(a.mbid) AS ids
UNWIND ids AS id
CALL apoc.load.jdbc('myDB',
"select DISTINCT a.gid::text, ar.name as country FROM artist a INNER JOIN area ar on a.area = ar.id WHERE a.gid = UUID(?)", [id]) YIELD row
MATCH (a:Artist) WHERE a.mbid = row.gid SET a.country = row.country
RETURN COUNT(a.country)

Join on multiple fields

I am trying todo a join with LINQ on multiple fields but I keep getting following error:
Error:
The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
LINQ query:
var query = from s in _dbContext.Samples
join sp in _dbContext.UserSampleTypePurposes on new {SampleTypeId = s.SampleTypeID, PurposeId = s.PurposeID} equals new { SampleTypeId = sp.SampleTypeId, PurposeId = sp.PurposeId}
select s;
SamplteTypeId and and purposeId are foreign keys to the respective tables.
system:
dotnet core 2.2.102
Someone an idea how to fix this? or do it differently?

Refactoring the a subquery within a select statement to a join

I've got a table of products and a table of widgets.
A product is built from a number of widgets and I've got a linking table which shows which widgets are linked to which products.
Widgets can be marked as expired, which means they are not eligible to be added to any new products.
Schema and sample data in the following sqlFiddle -
I want a query that shows all the eligible widgets for a particular product given the following rules:
A widget should not be shown if it is marked as expired.
As an exception to the above rule - If a widget is marked as expired, but is already linked to a product, then it should be shown.
If a widget has already been linked to the product, it should be marked as 'selected'
I've got the following query which works, and satisfies all the above rules:
select
data.WidgetName,
data.expired,
case
when data.ID in (select data_id from Widget_link where productId = 1)
then 1
else 0
end as selected
from Widget_data data
left outer join Widget_link link
on data.ID = link.data_id and data.expired = 1
where (data.expired = 0 or (data.expired = 1 and link.productId = 1))
I'd like to find a way to refactor the sub-query within the select part of the query to some kind of join. I'm trying to create a view which I can filter using just a where clause, rather than having the productId in two places. Is this possible?
On simplification your query can solely be evaluated based on JOIN like below, and there is no need for inner query at all.
select data.WidgetName,
case when link.id is not null then 1 else 0 end as selected
from Widget_data data
left outer join Widget_link link
on data.ID = link.data_id and link.productId = 1
where data.expired=0 or link.id is not null
Demo SQL fiddle
You can use SQL CTE (Common Table Expression) to cover the results from a SELECT list, and use in a single SQL statement
You can even create SQL statements with multiple CTE's in your code
What I suggest is can be summarized visually as follows
WITH cte AS (
SELECT ..... FROM .....
),
other_cte AS (
SELECT ..... FROM .....
)
SELECT *
FROM cte
INNER JOIN other_cte ON...

Referring to a column from a different table in inner join select

I'm trying to join onto an inner query, which is having it's results filtered by a value in the other table, so I can select the top result and use a value in it multiple times in my main select statement, but I get the error as below:
The multi-part identifier "TessSCCall.Call_Num" could not be bound.
See below for the code so far:
SELECT BestAppointmentOffer AS foo -- I'm using this lots of times in different fields
BestAppointmentOffer AS bar -- I'm using this lots of times in different fields
....
FROM TessSCEmploy
INNER JOIN TessSCCall on TessSCEmploy.Employ_Num = TessSCCall.Call_Employ_Num
INNER JOIN
(
SELECT TOP 1 dbo.Aqua_Midnight(AppointmentStartTime)
AS BestAppointmentOffer, CallNumber
FROM AQWEB.[360Tracking].dbo.AppointmentOffers
WHERE CallNumber = TessSCCall.Call_Num
ORDER BY AppointmentStartTime) AS Appointment
on TessSCCall.Call_Num = Appointment.CallNumber
where ....
How can I get this to work, so I can use the value from my query (that I'm currently trying to join) in calculations in multiple fields, without repeating it?
The easiest way to do this would be to use CROSS APPLY but since you are using SQL Server 2000 you don't have that option. You should be able to use an aggregate function to get the top result for each appointment time:
select BestAppointmentOffer AS foo -- I'm using this lots of times in different fields
BestAppointmentOffer AS bar -- I'm using this lots of times in different fields
....
from TessSCEmploy
inner join TessSCCall
on TessSCEmploy.Employ_Num = TessSCCall.Call_Employ_Num
INNER JOIN
(
SELECT min(dbo.Aqua_Midnight(AppointmentStartTime)) AS BestAppointmentOffer,
CallNumber
FROM AQWEB.[360Tracking].dbo.AppointmentOffers
GROUP BY CallNumber
) AS Appointment
on TessSCCall.Call_Num = Appointment.CallNumber
where ....
You need to CROSS APPLY to correlate columns in a derived table
...
TessSCEmploy inner join TessSCCall on TessSCEmploy.Employ_Num = TessSCCall.Call_Employ_Num
CROSS APPLY
(SELECT TOP 1 dbo.Aqua_Midnight(AppointmentStartTime) AS BestAppointmentOffer, CallNumber
FROM AQWEB.[360Tracking].dbo.AppointmentOffers
WHERE CallNumber = TessSCCall.Call_Num
ORDER BY AppointmentStartTime) AS Appointment on TessSCCall.Call_Num = Appointment.CallNumber
where ....
CROSS APPLY is the correct construct anyway for what you are doing, which is a "TOP 1 per Something"