Giter Club home page Giter Club logo

laravel-eloquent-join's Introduction

FORK to support Laravel 10

Tests status

Laravel Eloquent Join

This package introduces the join magic for eloquent models and relations.

Introduction

Eloquent is a powerful ORM but its join capabilities are very poor.

First Eloquent Problem (sorting)

With laravel you can't perform sorting of the relationship fields without manually joining related table which is very awkward. Let me give you a few reasons why. If you have a table with posts and related categories your code might look like this:

$posts = Post::select('posts.*')
    ->join('categories', 'categories.id', '=', 'posts.category_id')
    ->groupBy('posts.id')
    ->where('categories.deleted_at', '=', null)
    ->orderBy('categories.name');
    
if(request()->get('date')){
    $posts->where('posts.date', $date)
}

$posts = $posts->get();

1.The first problem is that you need to worry about select.

    ->select('posts.*')

Reason : without select id from the category can be selected and hydrated into the Post model.

2.The second problem is that you need to worry about groupBy.

->groupBy('posts.id');

Reason : if the relation is HasOne and there are more than one categories for the post, the query will return more rows for categories.

3.The third problem is that you need to change all other where clauses from :

    ->where('date', $date)

to

    ->where('posts.date', $date)

Reason : a post and category can have "date" attribute and in that case without selecting an attribute with table "ambiguous column" error will be thrown.

4.The fourth problem is that you are using table names(not models) and this is also bad and awkward.

    ->where('posts.date', $date)

5.The fifth problem is that you need to worry about soft deletes for joined tables. If the category is using SoftDeletes trait you must add :

    ->where('categories.deleted_at', '=', null)

This package will take care of all above problems for you. Unlike sorting, you can perform filtering on the relationship fields without joining related tables, but this package will give you the ability to do this easier.

Second Eloquent Problem (subqueries)

With laravel you can perform where on the relationship attribute but laravel will generate subqueries which are more slower than joins. With this package you will be available to perform where on the relationship with joins in an elegant way.

Requirements

Laravel Version Package Tag Supported Development Branch
>= 5.5.0 4.* yes master
< 5.5.0 - no -

Package is also tested for SQLite, MySql and PostgreSql

Installation & setup

1.Install package with composer

composer require fico7489/laravel-eloquent-join

With this statement, a composer will install highest available package version for your current laravel version.

2.Use Fico7489\Laravel\EloquentJoin\Traits\EloquentJoinTrait trait in your base model or only in particular models.

...
use Fico7489\Laravel\EloquentJoin\Traits\EloquentJoin;
use Illuminate\Database\Eloquent\Model;

abstract class BaseModel extends Model
{
    use EloquentJoin;
...

3.IMPORTANT

For MySql make sure that strict configuration is set to false

config/database.php

        'mysql' => [
			...
            'strict'    => false,
			...

and that's it, you are ready to go.

Options

Options can be set in the model :

class Seller extends BaseModel
{
    protected $useTableAlias = false;
    protected $appendRelationsCount = false;
    protected $leftJoin = false;
    protected $aggregateMethod = 'MAX';

or on query :

    Order::setUseTableAlias(true)->get();
    Order::setAppendRelationsCount(true)->get();
    Order::setLeftJoin(true)->get();
    Order::setAggregateMethod(true)->get();

useTableAlias

Should we use an alias for joined tables (default = false)

With true query will look like this :

select "sellers".* from "sellers" 
    left join "locations" as "5b5c093d2e00f" 
	...

With false query will look like this :

select "sellers".* 
	from "sellers" 
	left join "locations"                    
	...

Alias is a randomly generated string.

appendRelationsCount

Should we automatically append relation count field to results (default = false)

With true query will look like this :

select "sellers".*, count(locations.id) AS locations_count
	from "sellers" 
	left join "locations" as "5b5c093d2e00f" 
	...

Each relation is glued with an underscore and at the end _count prefix is added. For example for

->joinRelations('seller.locations')

field would be seller_locations_count

leftJoin

Should we use inner join or left join (default = true)

select "sellers".* 
	from "sellers" 
	inner join "locations"                    
	...

vs

select "sellers".* 
	from "sellers" 
	left join "locations"                    
	...

aggregateMethod

Which aggregate method to use for ordering (default = 'MAX').

When join is performed on the joined table we must apply aggregate functions on the sorted field so we could perform group by clause and prevent duplication of results.

select "sellers".*, MAX("locations" ."number") AS sort
	from "sellers" 
	left join "locations" 
	group by "locations" ."id"
	order by sort
	...

Options are : SUM, AVG, MAX, MIN, COUNT

Usage

Currently available relations for join queries

  • BelongsTo
  • HasOne
  • HasMany

New clauses for eloquent builder on BelongsTo and HasOne relations :

joinRelations($relations, $leftJoin = null)

  • $relations which relations to join
  • $leftJoin use left join or inner join, default left join

orderByJoin($column, $direction = 'asc', $aggregateMethod = null)

  • $column and $direction arguments are the same as in default eloquent orderBy()
  • $aggregateMethod argument defines which aggregate method to use ( SUM, AVG, MAX, MIN, COUNT), default MAX

whereJoin($column, $operator, $value, $boolean = 'and')

  • arguments are the same as in default eloquent where()

orWhereJoin($column, $operator, $value)

  • arguments are the same as in default eloquent orWhere()

whereInJoin($column, $values, $boolean = 'and', $not = false)

  • arguments are the same as in default eloquent whereIn()

whereNotInJoin($column, $values, $boolean = 'and')

  • arguments are the same as in default eloquent whereNotIn()

orWhereInJoin($column, $values)

  • arguments are the same as in default eloquent orWhereIn()

orWhereNotInJoin($column, $values)

  • arguments are the same as in default eloquent orWhereNotIn()

Allowed clauses on BelongsTo, HasOne and HasMany relations on which you can use join clauses on the query

  • Relations that you want to use for join queries can only have these clauses : where, orWhere, withTrashed, onlyTrashed, withoutTrashed.
  • Clauses where and orWhere can only have these variations ** ->where($column, $operator, $value) ** ->where([$column => $value])
  • Closures are not allowed.
  • Other clauses like whereHas, orderBy etc. are not allowed.
  • You can add not allowed clauses on relations and use them in the normal eloquent way, but in these cases, you can't use those relations for join queries.

Allowed relation:

public function locationPrimary()
{
    return $this->hasOne(Location::class)
        ->where('is_primary', '=', 1)
        ->orWhere('is_primary', '=', 1)
        ->withTrashed();
}

Not allowed relation:

public function locationPrimary()
{
    return $this->hasOne(Location::class)
        ->where('is_primary', '=', 1)
        ->orWhere('is_primary', '=', 1)
        ->withTrashed()
        ->whereHas('state', function($query){return $query;}
        ->orderBy('name')
        ->where(function($query){
            return $query->where('is_primary', '=', 1);
        });
}

The reason why the second relation is not allowed is that this package should apply all those clauses on the join clause, eloquent use all those clauses isolated with subqueries NOT on join clause and that is more simpler to do.

You might get a picture that there are too many rules and restriction, but it is really not like that. Don't worry, if you do create the query that is not allowed appropriate exception will be thrown and you will know what happened.

Other

  • If the model uses the SoftDelete trait, where deleted_at != null will be automatically applied
  • You can combine new clauses unlimited times
  • If you combine clauses more times on same relation package will join related table only once
Seller::whereJoin('city.title', '=', 'test')
    ->orWhereJoin('city.title', '=', 'test2');
  • You can call new clauses inside closures
Seller::where(function ($query) {
    $query
        ->whereJoin('city.title', '=', 'test')
        ->orWhereJoin('city.title', '=', 'test2');
});
  • You can combine join clauses e.g. whereJoin() with eloquent clauses e.g. orderBy()
Seller::whereJoin('title', '=', 'test')
    ->whereJoin('city.title', '=', 'test')
    ->orderByJoin('city.title')
    ->get();

See action on real example

Database schema :

Database schema

Models :

class Seller extends BaseModel
{
    public function locations()
    {
        return $this->hasMany(Location::class);
    }
    
    public function locationPrimary()
    {
        return $this->hasOne(Location::class)
            ->where('is_primary', '=', 1);
    }

    public function city()
    {
        return $this->belongsTo(City::class);
    }
class Location extends BaseModel
{
    public function locationAddressPrimary()
    {
        return $this->hasOne(LocationAddress::class)
            ->where('is_primary', '=', 1);
    }
    
class City extends BaseModel
{
    public function state()
    {
        return $this->belongsTo(State::class);
    }
}

Join

Join BelongsTo

Seller::joinRelations('city')

Join HasOne

Seller::joinRelations('locationPrimary')

Join HasMany

Seller::joinRelations('locations')

Join Mixed

Seller::joinRelations('city.state')

Join (mix left join)

Seller::joinRelations('city', true)->joinRelations('city.state', false)

Join (multiple relationships)

Seller::join(['city.state', 'locations'])

Ordering

Order BelongsTo

Seller::orderByJoin('city.title')

Order HasOne

Seller::orderByJoin('locationPrimary.address')

Order HasMany

Seller::orderByJoin('locations.title')

Order Mixed

Seller::orderByJoin('city.state.title')

Ordering (special cases with aggregate functions)

Order by relation count

Seller::orderByJoin('locations.id', 'asc', 'COUNT')

Order by relation field SUM

Seller::orderByJoin('locations.is_primary', 'asc', 'SUM')

Order by relation field AVG

Seller::orderByJoin('locations.is_primary', 'asc', 'AVG')

Order by relation field MAX

Seller::orderByJoin('locations.is_primary', 'asc', 'MAX')

Order by relation field MIN

Seller::orderByJoin('locations.is_primary', 'asc', 'MIN')

Filtering (where or orWhere)

Filter BelongsTo

Seller::whereJoin('city.title', '=', 'test')

Filter HasOne

Seller::whereJoin('locationPrimary.address', '=', 'test')

Filter HasMany

Seller::whereJoin('locations.title', '=', 'test')

Filter Mixed

Seller::whereJoin('city.state.title', '=', 'test')

Relation count

$sellers = Seller::setAppendRelationsCount(true)->join('locations', '=', 'test')
    ->get();
    
foreach ($sellers as $seller){
    echo 'Number of location = ' . $seller->locations_count;
}

Filter (mix left join)

Seller::joinRelations('city', true)
    ->joinRelations('city.state', false)
    ->whereJoin('city.id', '=', 1)
    ->orWhereJoin('city.state.id', '=', 1)

Generated queries

Query :

Order::whereJoin('seller.id', '=', 1)->get();

Sql :

select "orders".* 
    from "orders" 
    left join "sellers" on "sellers"."id" = "orders"."seller_id" 
    where "sellers"."id" = ? 
    and "orders"."deleted_at" is null 
    group by "orders"."id"

Query :

Order::orderByJoin('seller.id', '=', 1)->get();

Sql :

select "orders".*, MAX(sellers.id) as sort
    from "orders" 
    left join "sellers" on "sellers"."id" = "orders"."seller_id" 
    where "orders"."deleted_at" is null 
    group by "orders"."id"
    order by sort asc

Elegance of package

Lets look how first example from documentation now looks like. This code :

$posts = Post::select('posts.*')
    ->join('categories', 'categories.id', '=', 'posts.category_id')
    ->groupBy('posts.id')
    ->where('categories.deleted_at', '=', null)
    ->orderBy('categories.name');
    
if(request()->get('date')){
    $posts->where('date', $date)
}

$posts = $posts->get();

is now :

$posts = Post::orderByJoin('category.name');
    
if(request()->get('date')){
    $posts->where('posts.date', $date)
}

$posts = $posts->get();

Both snippets do the same thing.

Tests

This package is well covered with tests. If you want run tests just run composer update and then run tests with "vendor/bin/phpunit"

Contribution

Feel free to create new issue for :

  • bug
  • notice
  • request new feature
  • question
  • clarification
  • etc...

License

MIT

Free Software, Hell Yeah!

laravel-eloquent-join's People

Contributors

bryce13950 avatar davidjosephhayes avatar delenamalan avatar fico7489 avatar fjrid avatar kennyturner avatar lloy0076 avatar longlevan9x avatar maniekcz avatar mikebronner avatar sharptsa avatar xavadu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-eloquent-join's Issues

Next version input

I have been working on similar things as you for this package over the last several months.

https://github.com/dariusj18/laravel-eloquent-join/commits/master

I would like to present some differences in where you have gone vs where I did.

  1. I tried to do more Laravelish way of using method names to change what type of Join to use. Doing this allows for cross joins as well (I don't use them much, but Laravel has specific cross join methods as well). However there is definitely the issue of reverse compatibility.
  2. I added the BelongsToManyJoin relation, I may have been naive, however it worked for my purposes.
  3. I added orderByJoinPivot, this is a natural extension of the BelongsToManyJoin and is very useful.
  4. I see you also removed the groupBy, however I don't think there should be the extra select added automatically either. If anything, I think it would be best to have an additional method to let the user choose to add the grouping if they think it is needed.
  5. I switch the ExtendRelationsTrait to override the newBelongsTo and newHasOne methods that Laravel added in 5.5 laravel/framework#22617

I would appreciate any feedback on my suggestions.

Mutliple orWhereJoins

In Eloquent, we can do something like this

$builder->where(function ($query) use ($value) {
           $query->where('profile.first_name', 'LIKE', "%{$value}%");
           $query->orWhere('profile.last_name', 'LIKE', "%{$value}%");
           $query->orWhere('profile.address', 'LIKE', "%{$value}%");
});

Since this library doesn't allow closures, I don't know if this is possible. I can change the above to:

$builder->where(function ($query) use ($value) {
           $query->whereJoin('profile.first_name', 'LIKE', "%{$value}%");
           $query->orWhereJoin('profile.last_name', 'LIKE', "%{$value}%");
           $query->orWhereJoin('profile.address', 'LIKE', "%{$value}%");
});

Two issues arise:

  1. The join of the profile table is not included when WhereJoin or OrWhereJoin is used inside this closure.

  2. The parenthesis are not in the correct positions. It seems as though you can only use 2 columns in such a request. If the above code is pulled out of the closure, the parenthesis are still wrong. It produces

where ((`user_profiles`.`first_name` LIKE ? or (`user_profiles`.`last_name` LIKE ?)) or (`user_profiles`.`last_name` LIKE ?))

Is there currently a way to properly do an OrWhereJoin with more than 2 columns in question?

use orWhreJoin inside of query function getting an error.

I have a query which is working with orWhere, but if I use orWhereJoin instead of it getting an error

Query - Laravel (orWhere)

            DeviceModel::where(function ($query) {
                      $query ->orWhere('device_company_name', '=', 'samsung');
                      $query ->orWhere('device_company_name', '=', 'mi');
            })
            ->where('id', '!=', '1')
            ->get();

Query - Laravel-eloquent-join (orWhereJoin)

DeviceModel::where(function ($query) {
                      $query ->orWhereJoin('deviceCompany.name', '=', 'samsung');
                      $query ->orWhereJoin('deviceCompany.name', '=', 'mi');
            })
            ->where('id', '!=', '1')
            ->get();

Collide with awobaz/compoships

I'm facing this error when I try to use EloquentJoin trait:
Trait method 'newBelongsTo' will not be applied, because it collides with 'Compoships'
Is there any way to use both?

Aggregate parameters are not escaped

Hello,

The aggregate parameters are not escaped and if an e is present in the string mysql think that it is a double value writing in e notation.

Mysql error:

[2020-01-21 16:22:28] production.ERROR: SQLSTATE[22007]: Invalid datetime format: 1367 Illegal double '5e2725447144' value found during parsing (SQL: select preset_foo.*, MAX(5e2725447144e.code) as sort from `preset_foo` left join `bar` as `5e2725447144e` on `5e2725447144e`.`id` = `preset_foo`.`country_id` group by `preset_foo`.`id` order by sort asc limit 25 offset 0) 
[...]

If the aggregate is escaped it works as it should be:

MAX(`5e2725447144e`.`code`)

min and max doesn't work anymore with whereJoin

I have upgraded from v3 to v4 and my min and max querys doesn't work anymore.
If I use whereJoin and max() I don't get the max value. This is caused by a "group by" which will be added in v4 by whereJoin().

[BUG] whereJoin is using wrong column for SQL joining

Hi, I've tried a simple query for testing a realation using whereJoin
App\Models\ZoomMeeting::whereJoin('live_class.id', '>', 0)->count()

But it fails showing this:

Illuminate\Database\QueryException with message 'SQLSTATE[42S22]: Column not found: 1054 
Unknown column 'live_classes.live_class_id' in 'on clause' (SQL: select count(*) as aggregate from 
`zoom_meetings` left join `live_classes` on `live_classes`.`live_class_id` = `zoom_meetings`.`live_class_id` 
where `live_classes`.`id` > 0 group by `zoom_meetings`.`live_class_id`)'

The problem is that is using live_classes.live_class_id for joining when it should be using live_classes.id

class ZoomMeeting extends Eloquent
{
    protected $primaryKey = 'live_class_id';
    public $incrementing = false;

    protected $casts = [
        'live_class_id' => 'int'
    ];

    public function live_class()
    {
        return $this->belongsTo(\App\Models\LiveClass::class);
    }
}

class LiveClass extends Eloquent
{
    public function zoom_meeting()
    {
        return $this->hasOne(\App\Models\ZoomMeeting::class);
    }
}

required php 7

plz update your composer.json with this require param "php": "^7.0.0"...

Join not using localKey or ownerKey

Loving this package but I'm having a small issue with it.

The joins are not performed on the localKey for HasOne/HasMany relations nor on the ownerKey for BelongsTo relations.

Example :

class Animal extends BaseModel
{
    public function legs()
    {
        return $this->hasMany(Leg::class,
            'animal_hash', 'hash');
    }
}

class Leg extends BaseModel
{
    public function animal()
    {
        return $this->belongsTo(Animal::class,
            'animal_hash', 'hash');
    }
}

dd(Animal::query()->joinRelations('legs')->toSql());
dd(Leg::query()->joinRelations('animal')->toSql());

This outputs :

"select animals.* from animals inner join legs on legs.animal_hash = animals.id group by animals.id"
"select legs.* from legs inner join animals on animals.id = legs.animal_hash group by legs.id"

When it should be :

"select animals.* from animals inner join legs on legs.animal_hash = animals.hash group by animals.id"
"select legs.* from legs inner join animals on animals.hash = legs.animal_hash group by legs.id"

I'll try to do a PR but I'd like to have your thoughts on this.

'Group by': overkill?

Philip,
I have been playing around a little with your package, but I think there is a fundamental issue expressed in the first example of your documentation (Posts joining Category); in particular the second problem (the 'group by Post.id'). If you join only one Category to a Post (because relation is HasOne), the second problem is clearly not there. However, if you would join more Categories to a Post (relations hasMany, etc.), indeed there is a problem/question as to decide on which of the multiple Categories the sort has to be perfomed.

Now, documentation clearly states that only hasOne and BelongsTo relations are supported. If this is so, there is no need to append a 'group by Post.id' to the generated SQL.
In fact this extra 'group by Post.id' addition is the reason why e.g. executing the query with '->count()' returns 1 (which is b.t.w. an identical result to using leftJoin in stead of whereJoin) .
Without this extra 'group by Post.id' addition, '->count()' would return the exact number of Posts selected.

I.m.h.o. you coud do without the 'group by' in the generated SQL. Love to hear your point of view ...

[Question] Use of left join instead of inner join

Is there any reason EloquentJoinTrait's performJoin method uses left joins instead of inner joins?

In my uses I cannot see a reason to use left joins, especially if you are filtering based on things in the foreign table.

Collision with Eloquence

#When I add the EloquentJoin Trait I have this error :
Trait method newEloquentBuilder has not been applied, because there are collisions with other trait methods.

By removing others traits one by one, I deduced that it came from the Eloquence one, and Indeed, it's the same methods named used.

Do you have any ideas to how I can solved that ?

Also, I wondering if you have planned in the future to add clauses in BelongsTo and HasOne like with ?

Thanks in advance

Order clause causing error

Hi. I'm getting an weird error and I don't know why:

   Exception  : Property [relationClauses] does not exist on the Eloquent builder instance.

  at /var/www/html/projects/platforma/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:1301
    1297|         if ($key === 'orWhere') {
    1298|             return new HigherOrderBuilderProxy($this, $key);
    1299|         }
    1300| 
  > 1301|         throw new Exception("Property [{$key}] does not exist on the Eloquent builder instance.");
    1302|     }
    1303| 
    1304|     /**
    1305|      * Dynamically handle calls into the query instance.

  Exception trace:

  1   Illuminate\Database\Eloquent\Builder::__get("relationClauses")
      /var/www/html/projects/platforma/vendor/fico7489/laravel-eloquent-join/src/Traits/JoinRelationTrait.php:17

  2   Fico7489\Laravel\EloquentJoin\Relations\HasManyJoin::__call("orderBy")
      /var/www/html/projects/platforma/app/CMS/Offers/Offer.php:115

Offer model uses EloquentJoin and has many selectablesPivots.

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function selectablesPivots()
    {
        return $this->hasMany(OfferHasOfferSelectable::class, 'offer_id')->orderBy('order');
    }

And load them as follows

        $baseOffers = Offer::with(['groups.optionsPivots.option', 'selectablesPivots.selectable'])
            ->where('is_template', 1)
            ->get();

orWhereNotInJoin has default $boolean='and'

The method orWhereNotInJoin($column, $values, $boolean = 'and') should not have the $boolean parameter and just call whereIn($column, $values, 'or') like orWhereInJoin($column, $values) does.

I'll do a PR.

Foreign key missing in generated Query

This query was generated:

select user.* from user left join user_type_3 on user_type_3.ut3_id_user = user.`` group by user.`u_id` order by `user_type_3`.`ut3_premium` asc limit 50

And this

public function teacherinfo()
{
       return $this->hasOne(Teacher::class, 'ut3_id_user');
}

and this

public function teacherinfo()
{
     return $this->hasOne('User');
}

are the used relations and this

$users = User::with('subjects', function($query) use ($fach) {
     return $query->where('f_id', $fach->f_id);
})->with('teacherinfo')->orderByJoin('teacherinfo.ut3_premium')->take(50)->get();

was the used function call.

I tried to locate the problem and my guess is it has something to do with line 109 in EloquentJoinTrait.php
$keyRelated = $relatedRelation->getForeignKey();

Do you have an idea, why this could be happening?
Thanks

leftJoin argument only available on orderByJoin.

I still suggest going with the more laravelesque whereJoin, whereLeftJoin, and whereCrossJoin. However currently it seems on the orderByJoin method has your current change adding the $leftJoin argument.

join key error

Thank you for developing this tool.

// ProductGoodsModel
public function activityBase()
    {
        // activityBase.id === productGoods.activityId
        return $this->hasOne(ActivityBase::class,'id','activityId');
    }
// Controller
$result = ProductGoodsModel::joinRelations('activityBase')
// Convert result
select productGoods.* from `productGoods` left join `activityBase` on `activityBase`.`id` = `productGoods`.`id` group by `productGoods`.`id`

id = id
id = activityId

model canNot use hasOne ?
change use belongsTo ok !

Class 'App' not found in EloquentJoinBuilder.php:193 when using standone Eloquent

When using Eloquent without Laravel, an error is thrown because the App class doesn't exist.
As a quick work around I created a dummy class with the version.

 $relatedKey = ((float) \App::version() < 5.8) ? $relatedRelation->getQualifiedForeignKey() : 
......
 $ownerKey = ((float) \App::version() < 5.8) ? $relatedRelation->getOwnerKey() : $relatedRelation->getOwnerKeyName();

You could use method exists instead of the Laravel version.

No WhereIn

No realization of where in. How I can solve this issue? or add this method, please.

Group By when joining HasMany

Hello,
shouldn't the groupBy be added when the relation is HasMany?

From:

if ( ! $this->selected && count( $relations ) > 1 ) {

To:

if ( ! $this->selected && ( count( $relations ) > 1 || $relatedRelation instanceof HasMany ) ) {

Edit:
Or maybe just add a distinct?

Invalid argument supplied for foreach()

I have followed the instructions on how to install this package, but I get "Invalid argument supplied for foreach()" error in validateJoinQuery($relatedModel) method call. Any idea?

Does not work when LaravelArdent installed

Somehow, when I am using https://github.com/laravel-ardent/ardent and extending Ardent instead of Eloquent's "Model" (Ardent also extends Model, so I'd think maybe these could be used together? https://github.com/laravel-ardent/ardent/blob/14660469b9a39b46c0174cb2071ad21f40a988df/src/Ardent/Ardent.php#L25 ) none of the static methods are bound to the class.

Example:
User::joinRelations('emails')
"No such method joinRelations()"

Then I undo the LaravelArdent changes, extend Eloquent's Model instead of Ardent, and it works just as the documentation says.

I'm not sure if laravel-eloquent-join somehow does something very specific with Eloquent's Model to the point where using an extended class doesn't work. (But this is weird, because laravel-eloquent-join points out you can use a BaseModel extending Eloquent's Model, this is pretty much what LaravelArdent is doing.)

No error is seen in Laravel log, it just never adds the static methods like "joinRelations()".

Consider change joinQuery as protected method

In my case, InvalidRelationGlobalScope is being thrown when I am using this package with Clockwork.

So I decided to extend the EloquentJoinBuilder to handle the Clockwork global scope and other custom global scopes built for my application.

However, looks like I have to rewrite the whole thing because both core functions performJoin and joinQuery are marked as private.

Is it better to change it to protected or create a protected method for us to override the global scope applying behaviour?

SQL 1066: Not unique table/alias (when repeating relation)

Description

A table users is mapped to providers with a hasOne relationship, which is then mapped to users again with a belongsTo relationship.

Steps to Reproduce

When I execute orderByJoin('providers.users.name', $type), I get the error

SQL 1066: Not unique table/alias

What I think

This is because the users table is repeated in the mapping, so it needs unique aliases for each instance.

Generated SQL

select count(*) as aggregate from `users` left join `providers` on `providers`.`user_id` = `users`.`id` and `providers`.`deleted_at` is null left join `users` on `users`.`id` = `providers`.`user_id` and `users`.`deleted_at` is null where exists (select * from `lv_roles` inner join `practice_role_user` on `lv_roles`.`id` = `practice_role_user`.`role_id` where `users`.`id` = `practice_role_user`.`user_id` and `name` = participant) and exists (select * from `user_info` where `users`.`id` = `user_info`.`user_id` and `user_info`.`deleted_at` is null) and `users`.`deleted_at` is null group by `users`.`id`)

Issue Concerning Join Columns. What Am I Doing Wrong?

Hello I stumbled across this package and its a great solution for what I need however. The join is not on what is setup on my relationship. I have two Models: Location & Room

A Room hasOne relationship with Location by it's location_id column in database

However when I do... orderByJoin('locationState', 'DESC') the sql query is coming out with a join like...

left join locations on locations.id = rooms.id

not the column I need. How do I fix that

Integrity constraint violation: 1052 Column 'name' in where clause is ambiguous

Post::with('category')
->whereHas('category', function ($q) use ($search) {
if (isset($search) && !empty($search)) {
$q->where('name', 'LIKE', '%' . $search . '%');
}
})
->where(function ($q) use ($search) {
if (isset($search) && !empty($search)) {
$q->where('name', 'LIKE', '%' . $search . '%');
}
})
->setLeftJoin(false)
->orderByJoin('category.name', 'desc')
->paginate();

Showing this error

SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'name' in where clause is ambiguous (SQL: select count(*) as aggregate from posts left join categories on categories.id = posts.category_id where exists (select * from categories where posts.category_id = categories.id and name LIKE %r%) and (name LIKE %r%) group by posts.id)

Error with spatie/laravel-query-builder

when using package with spatie/laravel-query-builder i got this error:

Call to undefined method Spatie\QueryBuilder\QueryBuilder::orderByJoin()

How to fix it?

Missing polymorphic relations

Hi, i would like to use this great package, but it's missing all polymorphic relations such as morphOne, morphMany and morphedByMany

Record is removed when BelongsTo record is null

I have Post model and User model

i have some post where user_id is null

public function user()
{
return $this->belongsTo('App\User');
}

Post::orderByJoin('user.name');

this only return records where user_id is not null.

how can i solve this. i also want that post where user_id is null

Multiple ordering

Hi. Is there a way to order by 2 joins? Example:

Specialization belongs to College that belongs to University.
When listing specializations, I want them ordered by University, College.
I did that with raw queries but don't know if I can with the package.

Thanks.

Duplicate join using aliased tables

Spotted an issue when doing joins with $useTableAlias=true

If you are doing multiple where on the same relation, it creates one join per where condition.
It's because when we check if the table has already been join, it's done on aliases (which are unique) instead of the table name.

Example:

Order::setUseTableAlias(true)
       ->whereJoin('seller.id', '=', 1)
       ->whereJoin('seller.title', '=', 'shop')

select orders.*
from "orders"
         left join "sellers" as "5d542264d39e3" on "5d542264d39e3"."id" = "orders"."seller_id"
         left join "sellers" as "5d542264d3a11" on "5d542264d3a11"."id" = "orders"."seller_id"
where "5d542264d39e3"."id" = ?
  and "5d542264d3a11"."title" = ?
  and "orders"."deleted_at" is null
group by "orders"."id";

instead of

select orders.*
from "orders"
         left join "sellers" as "5d542264d39e3" on "5d542264d39e3"."id" = "orders"."seller_id"
where "5d542264d39e3"."id" = ?
  and "5d542264d39e3"."title" = ?
  and "orders"."deleted_at" is null
group by "orders"."id";

I'll do a PR to fix this and add some tests.

what if i need to order by Json column

what if i need to order by Json column for example
Product::orderByJoin('relation.column->field','desc')
i am getting
Syntax error or access violation: 1064 You have an error in your SQL syntax

Private/public issue within EloquentJoinTrait.php

Great package. However, I had a minor problem.
I had to change the EloquentJoinTrait properties relationNotAllowedClauses and relationWhereClauses from private tot public before it started working.
These two properties are not only used on $this model itself, but also on $relatedModel(s).
This problem also holds for a model's primaryKey, so for correct workings, if a primary key is explicitly defined in a model, it should be done so public-ly.
I'am using Laravel-Eloquent-Join version 2.0.0, so these issues might already have been solved in later version(s), not sure ...

setInvalidJoin called when I call `->first()`

On a belongsTo relationship, I call the first() method which is supposed to retrieve the first instance of the data, and I get an error message:

ERROR: Call to undefined method Illuminate\Database\Query\Builder::setInvalidJoin()

Trace: #0 /cryptdata/var/deploy/deploybot/shared/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(1288): Illuminate\Database\Query\Builder->__call('setInvalidJoin', Array)

I believe it originates from this line

parent::__call('setInvalidJoin', $parametersInvalid);
which executes within an else statement, preventing the code after the else from being executed.

Question: withRelations not joining related models

Having some issues with the basic joinRelations statement. I have eloquent models defined, and the relationship is a one to one, but the results dont include the relationship.

Laravel Version: 6.9
Laravel Eloquent Join: 4.0.9

Lender Model
`use Fico7489\Laravel\EloquentJoin\Traits\EloquentJoin;
use Illuminate\Database\Eloquent\Model;

class Lender extends Model
{
use EloquentJoin;

/**
 * Get the details associated with a lender
 */
public function details()
{
    return $this->hasOne(LenderDetail::class);
}

/**
 * Get the address associated with a lender
 */
public function address()
{
    return $this->hasOne('App\LenderAddress');
}


/**
 * Get the details associated with a lender
 */
public function zipCodes()
{
    return $this->belongsToMany('App\ZipCode', 'lender_zip_codes');
}

}
`

LenderDetail Model
`
use Illuminate\Database\Eloquent\Model;

class LenderDetail extends Model
{
/**
* Get the lender associated with a details
*/
public function lender()
{
return $this->hasOne('App\Lender');
}

}
`

Query
Lender::joinRelationship('details')->get()

Results
Illuminate\Database\Eloquent\Collection {#3016 all: [ App\Lender {#3020 id: 1, created_at: null, updated_at: null, lender: "FIRST COMMONWEALTH FCU", bureau_data: "Verbal - EX", experian: 1, equifax: 1, transunion: 0, nation_wide: 1, infusionsoft_id: 620939, primary_lender: 1, }, ], }
Lender::withRelations('details')
Fico7489\Laravel\EloquentJoin\EloquentJoinBuilder {#3011 +baseBuilder: null, +relationClauses: [], }
Happy to provide anything else to debug

Feature: relation hasMany

First, thanks for this package, help me a lot. Are you working on models with hasMany relationships? Would be amazing if you can do this. Is it posible?

QueryException - Name DB?

"SQLSTATE[42000]: Syntax error or access violation: 1055 'crm_db.cities.name' isn't in GROUP BY..."

Hi,
I check your repository in local and work perfectly but upload to server with same DB, and I get a error "1055 'crm_db.cities.name'" where appear name of database as if it were the name of table and of course mysql don't find the column.

Could you tell me where you print the name of the table? and I try to delete the name of the database directly.

Thank you

MySQL error when ordering by join

Hi. I get the following error:

QLSTATE[42S22]: Column not found: 1054 Unknown column 'sort' in 'order clause' (SQL: select count(*) as aggregate from `cities` left join `counties` on `counties`.`id` = `cities`.`county_id` and `counties`.`deleted_at` is null where `county_id` = 2 and `cities`.`deleted_at` is null group by `cities`.`id` order by sort asc)

This happens on a very simple County->Cities tables relation.

Code running the query:

        $builder = City::query();

        $countyColumnIndex = 1;
        $cityColumnIndex   = 2;
        $countyFilterKey   = "columns.{$countyColumnIndex}.search.value";
        $cityFilterKey     = "columns.{$cityColumnIndex}.search.value";
        $orderKey          = 'order.0';
        $orderColumnKey    = 'order.0.column';
        $orderDirKey       = 'order.0.dir';

        if ($request->has($countyFilterKey) && ($county = (int) $request->input($countyFilterKey))) {
            $builder->where('county_id', $county);
        }

        if ($request->has($cityFilterKey) && ($city = $request->input($cityFilterKey))) {
            $builder->where('name', 'like', "%{$city}%");
        }

        if ($request->has($orderKey)) {
            switch ($request->input($orderColumnKey)) {
                case $countyColumnIndex:
                    $builder->orderByJoin('county.name', $request->input($orderDirKey));

                    break;
                case $cityColumnIndex:
                    $builder->orderBy('name', $request->input($orderDirKey));

                    break;
            }
        } else {
            $builder->orderBy('name', 'asc');
        }

        $filteredCount = $builder->count();

        $cities = $builder->offset((int) $request->get('start'))->limit((int) $request->get('length'))->get();

Add global scope support or allow withoutGlobalScopes() in relations

Hi, I have an error:
"Package allows only SoftDeletingScope global scope."

I don't need soft deleting. My model has global scope

static::addGlobalScope( function($query) {
    $query->where('isEntering', false);
});

Without it all works fine, but I want it to remain.
I tried to add withoutGlobalScopes() to relation, but it shows another error:
"Package allows only following clauses on relation : where, orWhere, withTrashed, onlyTrashed and withoutTrashed."

I watched the sources. Is seems withoutGlobalScopes() doesn't remove $relationBuilder->scopes (maybe) but adds $relationBuilder->relationClauses with $method = 'withoutGloalScopes'... and it is not supported (as second error says).

Why do I need to disable MySQL strict mode?

I read that I need to set 'strict' => false for my MySQL connection to have your package work.
I would prefer to keep strict mode enable so I made a quick test:

  • clone your package
  • change your setting in TestCase.php to have 'strict' => true
  • change ServiceProvider.php to test against MySQL : $_ENV['type'] = 'mysql';
  • launch phpunit

and there is no error reporting by phpunit: all your tests passed (58).

So why do I need to disable strict mode?
In which case is it mandatory?

Please explain.

Regards,
Patrick

Property [relationClauses] does not exist on the Eloquent builder instance.

Similar to #50 I get an error after upgrading to Laravel 5.7.*

This is an extract of one of the Models:

use Fico7489\Laravel\EloquentJoin\Traits\EloquentJoin;
use Illuminate\Database\Eloquent\Model as EloquentModel;

class Asset extends Model
{
    use EloquentJoin;

    public $timestamps = false;

    protected $casts = [
        'date' => 'datetime',
    ];

    public function latestCheckout()
    {
        return $this->hasOne(Checkout::class)->latest('date');
    }
}

Elsewhere:

    Asset::orderByJoin('part.part_no', 'asc')
        ->orderBy('serial', 'desc')
        ->get()

    // and

    $remaining = Asset::whereIn('asset.id', $ids)
        ->whereJoin('part.part_no', '!=', static::EXCLUDE)
        ->get()
        ->map(function ($asset) {
            return $asset->in_stock;
        });

app(Asset::class)->latestCheckout() throws an Exception: "Property [relationClauses] does not exist on the Eloquent builder instance."

#50 (comment) seems to fix the problem.

The issue happens because the Checkout class does not use the EloquentJoin trait, only the Asset class does. If I add the EloquentJoin trait to the Checkout class then the Exception is no longer triggered.

Problems with GroupBy in simple orderByJoin

Hi, thank you for the package, it seems pretty useful.

I'm getting an error caused by the group by from https://github.com/fico7489/laravel-eloquent-join/blob/master/src/Traits/EloquentJoinTrait.php#L145.

For example, It creates a query like:
"select "orders".* from "orders" left join "statuses" on "statuses"."id" = "orders"."statusId" where "orders"."deleted_at" is null group by "orders"."id" order by "statuses"."name" asc"

Which gives me this error in postgresql:
column "statuses.name" must appear in the GROUP BY clause or be used in an aggregate function

I wonder why is this group by there and if I'm doing something wrong, as it seems a very trivial usage of the package.

I'm using the query like:
Order::orderByJoin('status.name');

Thank you.

[Suggestion] Support Oracle Eloquent Driver

I think the only thing preventing it from working is that the relationship between SELECT and GROUP BY works differently in Oracle.

$builder->select($baseTable . '.*')->groupBy($baseTable . '.' . $baseModel->primaryKey);

This won't work. Oracle will error because the list of columns selected is different than the list of columns grouped by.

Some info:
https://stackoverflow.com/questions/25782524/select-group-by-in-oracle

For reference:
https://yajrabox.com/docs/laravel-oci8/master/oracle-eloquent

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.