Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

orest's avatar
Level 13

can't use where clause after union of 2 queries

i have 2 queries and i apply union and finally use a where clause to filter the final query. This doesn't seem to work though. Is this normal ? Or i'm missing something ?

I have to mention that the columns are the same so the union doesn't fail, is just that i cannot apply any where clause after i union 2 queries.

This doesn't work

$query1 = 'some query';
$query2 = 'some query';

$finalQuery = $query1->union($query2);

$finalQuery->where('some condition');

However, if i apply the where clause before i union the 2 queries then everything works.

$query1 = 'some query';
$query2 = 'some query';
$query1->where('some condition');
$query2->where('some condition');

$finalQuery = $query1->union($query2);

0 likes
15 replies
alanholmes's avatar

Hi @orest

That is down to MYSQL rather than Laravel.

As you can see from the example here, if you want to do a where after a union, it needs to be done as a sub query. https://stackoverflow.com/questions/5452233/where-statement-after-a-union-in-sql (that is why your second example works, you are doing the where before the union)

I know you can do sub queries in Laravel, but not played about with them enough know the syntax enough to advise.

Some articles here on sub queries, but not sure if it is enough to get you going. https://reinink.ca/articles

orest's avatar
Level 13

Thank you.

I tried to figure out how to do with as a subquery but the only thing that i found is

$finalQuery = $query1->union($query2);

$unionQuery = DB::table(DB::raw("({$finalQuery->toSql()}) as activities"))->select('*')->mergeBindings($finalQuery->getQuery())

The problem with this one is that all the relationships that i eager loaded in $query1 and $query2 are lost and additionally i can't load any relationship with this query

This doesn't work

$unionQuery->with('relationship')->get();

Any thoughts ?

alanholmes's avatar

Hi @orest

Yeah I would expect that, as using DB means that you are no longer using eloquent, so you dont have access to relationships.

I've not really dug into using unions and relationships, so not sure what I could offer on that.

One thing I would ask, is does it need to be a union query? (not sure of the details as to why its a union), or could it be accomplished with a single query with a more complex where statement? (just this way you would still be within eloquent)

orest's avatar
Level 13

so far the only way to make it work is using union.

i'm still trying to figure out if i can do it in 1 query and avoid union

Tray2's avatar

When doing unions I that needs to be filtered I do something like this

SELECT column1, 'books'
FROM books
UNION ALL
SELECT column1, 'games'
FROM games

Now I can either put my where clause in both to filter out the desired value like this

SELECT column1, 'books'
FROM books
WHERE released > sysdate
UNION ALL
SELECT column1, 'games'
FROM games
WHERE released > sysdate

Or I wrap the two queries into one like this

SELECT * FROM
	(SELECT column1, 'books', released
	FROM books
	UNION ALL
	SELECT column1, 'games', released
	FROM games)
WHERE released > sysdate

In most cases I most likely end up with the second approach since I then can order and or limit the data from both tables.

I hope this helps you solve your issue.

orest's avatar
Level 13

@tray2

Thanks!

I can filter out the queries individually before using union, however i'm trying to avoid this approach and instead follow your second approach.

But I'm trying to figure out how i can apply your second approach using laravel builder because i'm also eager loading relationships and i haven't found a solution yet.

Tray2's avatar

The Eloquent way so to speak isn't always the most eloquent way to do stuff unfortunately. When it comes to more complex queries like the one below (taken from my mediabase project).

SELECT DISTINCT (SELECT GROUP_CONCAT(a.id ORDER BY a.id SEPARATOR ',')
                     FROM authors a, author_books ab
                     WHERE a.id = ab.author_id
                     AND ab.book_id = b.id) author_id,
                    (SELECT GROUP_CONCAT(concat(a.last_name, ', ', a.first_name)
                     ORDER BY a.last_name, a.first_name SEPARATOR ' & ')
                     FROM authors a, author_books ab
                     WHERE ab.author_id = a.id
                     AND ab.book_id = b.id) author_name,
                     b.id book_id,
                     b.title,
                    (SELECT ROUND(AVG(s.score), 1) 
			FROM scores s 
			WHERE s.item_id = b.id AND media_type_id = 1) rating,
                    b.series,
                    b.part,
                    b.released,
                    g.id genre_id,
                    g.genre,
                    f.id format_id,
                    f.format,
                    CASE series
                    	WHEN 'Standalone' THEN b.released
                    	ELSE (SELECT MIN(bi.released) FROM books bi WHERE bi.series = b.series)
                   	END series_started,
                    bc.user_id
                FROM authors a,
                    books b,
                    genres g,
                    formats f,
                    book_collections bc
                WHERE b.genre_id = g.id
                AND   b.format_id = f.id
                AND   b.id = bc.book_id

As you can see it uses five tables and a couple of inline views. To create something like this in the query builder would be way more complex. So what I like to do is create a database view to pull in the data I need from the tables I need. To create one of these database views is very simple. A migration could look lile this.

class CreateBookCollectionViews extends Migration
{
    public function up()
    {
            DB::statement(
                "CREATE OR REPLACE VIEW book_collection_views AS
                SELECT DISTINCT (SELECT GROUP_CONCAT(a.id ORDER BY a.id SEPARATOR ',')
                     FROM authors a, author_books ab
                     WHERE a.id = ab.author_id
                     AND ab.book_id = b.id) author_id,
                    (SELECT GROUP_CONCAT(concat(a.last_name, ', ', a.first_name)
                     ORDER BY a.last_name, a.first_name SEPARATOR ' & ')
                     FROM authors a, author_books ab
                     WHERE ab.author_id = a.id
                     AND ab.book_id = b.id) author_name,
                     b.id book_id,
                     b.title,
                    (SELECT ROUND(AVG(s.score), 1) FROM scores s WHERE s.item_id = b.id AND media_type_id = 1) rating,
                    b.series,
                    b.part,
                    b.released,
                    g.id genre_id,
                    g.genre,
                    f.id format_id,
                    f.format,
                    CASE series
                    	WHEN 'Standalone' THEN b.released
                    	ELSE (SELECT MIN(bi.released) FROM books bi WHERE bi.series = b.series)
                   	END series_started,
                    bc.user_id
                FROM authors a,
                    books b,
                    genres g,
                    formats f,
                    book_collections bc
                WHERE b.genre_id = g.id
                AND   b.format_id = f.id
                AND   b.id = bc.book_id"
            );
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        DB::statement('DROP VIEW IF EXISTS book_collection_views');
    }
}

Then just create your model like normal and then you can filter, sort and do whatever you like with the data. The good thing about doing it this way is that you can tri, the SQL for performance and will run just one query in you controller/view.

I also keep my Eloquent eloquent so to say.

BookCollectionView::whereUserId($user->id)
            ->orderBy('author_name')
            ->orderBy('series_started')
            ->orderBy('part')
            ->orderBy('released')
            ->orderBy('title')
            ->get(),
            'user' => $user]);
orest's avatar
Level 13

That is an interesting approach.

However in my case i use nested eager loading.

In your case as far as i understand you can use eager loading on BookCollectionView, but can you load nested relationships ?

To better explain this, in my case i have the activities table with a polymorphic relationship

activities
- subject_id
- subject_type

Now when i eager load the subject relationship, i want also to load other nested relationships which can also be polymorphic.

This is one of the queries that i want to eventually union it with other queries.

$query->whereHasMorph('subject', ['App\Reply'], function ($builder) {
    $builder->onlyReplies();
})->addSelect(
    [
        'replies_count' => Thread::select('replies_count')
            ->whereRaw('threads.id=(SELECT repliable_id from replies where replies.id=activities.subject_id)'),
    ]
)->with(['subject' => function ($builder) {
    $builder->with(['repliable' => function (MorphTo $morphTo) {
        $morphTo->morphWith([
            Thread::class => ['poster', 'category'],
            ProfilePost::class => ['profileOwner'],
        ]);
    }])->addSelect(
        [
            'replies_count' => Thread::select('replies_count')
                ->whereColumn('threads.id', 'replies.repliable_id'),
        ]);
}]);

As you can see i load the subject relationship which is a reply in this case, and then i want to load the repliable relationship which can be either a Thread or a ProfilePost, and then i load a relationship for each one of the aforementioned. Finally i'm adding an extra column named replies_count on both subject and reply model.

As a conclusion, if i'm not mistaken, i cannot use your approach with the database view because i don't want to just load a relationship on the final model ( the BookCollectionView in your case )

For example

BookCollectionView->with('relationship');

but instead i need to use nested relationships.

Tray2's avatar

I put all the relations in the database model inside the database view so I would never need to use any kind of eagar loading on my view.

I'm not sure exactly what you are trying to achieve in your query since I'm more of a SQL developer than query builder person.

Without knowing your database model and the expected result it's hard to grasp what you are trying to achieve.

Eagar loading is just a fancy way of doing this

SELECT t1.*, t2.* 
FROM table1 t1,
           table2 t2
WHERE t1.id = t2.table1_id;

// The same as in a one to one relation//

Table1::with('table2')->get()

I might go with either approach when it's just one or two tables. If it's more complex I always go with a database view since it's so much easier to sort and filter on a single model.

If you have a one to many then you can't use the database view technique, you will need at least two queries.

orest's avatar
Level 13

Sorry for the confusion but when i said eager loading i had laravel in my mind which creates a collection with the eager loaded relationship.

So if i do

SELECT t1.*, t2.* 
FROM table1 t1,
           table2 t2
WHERE t1.id = t2.table1_id;

Then the result will be

table1.id
table1.created_at
table2.id
table2.created_at

But when you use

Table1::with('table2')->get()

the result is

table1.id
table1.created_at
table2 => [
	table2.id
	table2.created_at
]

So i guess that technically yes with the database view you do eager load the relationships that you need but are not nested like in a laravel collection structure like the example above.

I understand that it's hard to grasp what i'm trying to do without giving more information but if i do then i would have to write a lot and i don't know if anybody would be willing to read all that information.

Using eloqunt in my case the ideal solution would be to have access to thread and reply and apply where clauses and add select on them.

->with(['subject' => function (MorphTo $morphTo) {
                $morphTo->morphWith([
                     Thread::class => ['poster', 'category'],
                     Reply::class => ['repliable' => function

So from the code above what i would need ideally would be

->with(['subject' => function (MorphTo $morphTo) {
                $morphTo->morphWith([
                     Thread::class => function($builder){
                        $builder->where('some condiiton')
                            ->addSelect('some column')
                            ->with('some relationship')
                    },
                    Reply::class => function($builder){
                        $builder->where('some condiiton')
                            ->addSelect('some column')
                            ->with('some relationship')
                    }
                    ],
Tray2's avatar

That is what I meant with a one to one relation, There you can use a database view but with a one to many or many to many you can't since you will get "Subquery returns more than one row".

Unless of course if you concatinate the result from that subquery.

Ken96's avatar

Spent quite a lot of my time and finally realize I can use subquery to achieve filter after union, not sure about the other version, but works for Laravel 10 with PHP 8.2: $query1 = 'some query'; $query2 = 'some query';

$finalQuery = $query1->union($query2);

$finalQuery = DB::query()->fromSub($finalQuery , 'final_query'); $unionedFilter = $finalQuery->where("final_query.some_field", 'some condition');

2 likes

Please or to participate in this conversation.