I am trying to use Laravel’s hasOne(Model::class)->ofMany('relationship')
but I would also like to additionally filter the considered results.
My problem in a bit more detail:
I have these two simplified tables that I use in a WYSIWYG style authoring tool:
documents:
id (PK),...
document revisions table:
id (PK),document_id (FK),revision_id,is_autosave,...
In the revisions table the combination document_id, revision_id, is_autosave
is unique meaning that a document can have the same revision_id for an autosaved and a manually saved revision.
I am trying to relate the latest manually saved revision with the document model using the code below:
public function mainRevision() {
return $this->hasOne(DocumentRevision::class)
->where('autosave', false)
->ofMany('revision_id', 'max');
}
I can call the above e.g. like Documents::with('mainRevision')->where(...)->get()
.
The intention is to eager-load the latest manual revision and only the latest manual revision for all matched document rows.
However this generates the following query:
select
`document_revisions`.*
from
`document_revisions` inner join (
select
max(`document_revisions`.`id`) as `id_aggregate`,
min(`document_revisions`.`revision_id`) as `revision_id_aggregate`,
`document_revisions`.`document_id`
from
`document_revisions`
inner join (
select max(`document_revisions`.`revision_id`) as `revision_id_aggregate`,`document_revisions`.`document_id`
from `document_revisions`
where `document_revisions`.`document_id` in (`<list of matched ids from the 1st query>`)
group by
`document_revisions`.`document_id`
) as `mainVersion`
on `mainVersion`.`revision_id_aggregate` = `document_revisions`.`revision_id` and `mainVersion`.`document_id` = `document_revisions`.`document_id`
group by `document_revisions`.`document_id`) as `mainVersion`
on `mainVersion`.`id_aggregate` = `document_revisions`.`id`
and `mainVersion`.`revision_id_aggregate` = `document_revisions`.`revision_id`
and `mainVersion`.`document_id` = `document_revisions`.`document_id`
where
`autosave` = 0
While I can see the intention here, there is a problem in that the where autosave=0
is added outside the joined table. This results in the situation where if the latest revision_id happens to only correspond to an autosave, then it is not picked up.
I would ideally like the query to be like:
select
`document_revisions`.*
from
`document_revisions` inner join (
select
max(`document_revisions`.`id`) as `id_aggregate`,
min(`document_revisions`.`revision_id`) as `revision_id_aggregate`,
`document_revisions`.`document_id`
from
`document_revisions`
inner join (
select max(`document_revisions`.`revision_id`) as `revision_id_aggregate`,`document_revisions`.`document_id`
from `document_revisions`
where `autosave` = 0 --<<----------- change here
and `document_revisions`.`document_id` in (`<list of matched ids from the 1st query>`)
group by
`document_revisions`.`document_id`
) as `mainVersion`
on `mainVersion`.`revision_id_aggregate` = `document_revisions`.`revision_id` and `mainVersion`.`document_id` = `document_revisions`.`document_id`
group by `document_revisions`.`document_id`) as `mainVersion`
on `mainVersion`.`id_aggregate` = `document_revisions`.`id`
and `mainVersion`.`revision_id_aggregate` = `document_revisions`.`revision_id`
and `mainVersion`.`document_id` = `document_revisions`.`document_id`
Does anyone know if this is possible?
2
Answers
Try the following with the
autosave = 0
condition applied within the subquery that determines the latest revision. Here is the revised codeAccording to the documentation, you can add conditions to the
ofMany()
query by passing a closure as the second parameter. This also requires the aggregate columns to be passed in the first parameter using the array notation. Example below:Unrelated, but Laravel also has another way of defining "one of many" you may prefer, by converting the existing
hasMany()
relationship to ahasOne()
. There is no difference between this and what you have, just a preference of style. Assuming you havehasMany()
relationship defined asdocumentRevisions()
, you can call theone()
method on it to convert it to ahasOne()
relationship: