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

jgbneatdesign's avatar

Hey guys what's the best way to query a many to many relationship existence?

I have this Playlist class that has many tracks through a pivot table. The inverse is true for the Track class as well. I want to query a list of playlists that have at least one track.

<?php

  namespace App\Models;

  use Illuminate\Database\Eloquent\Relations\BelongsTo;
  use Illuminate\Database\Eloquent\Relations\BelongsToMany;

  class Playlist extends BaseModel
  {
    protected $guarded = [];

    public function scopeHasTracks($query)
    {
      return $query->has('tracks');
    }

    public function user(): BelongsTo
    {
      return $this->belongsTo(User::class);
    }

    public function tracks(): BelongsToMany
    {
      return $this->belongsToMany(Track::class)
        ->withTimestamps()
        ->orderBy('playlist_track.created_at');
    }
  }```

 I've tried with `has` but it's not working. It keeps saying:

Illuminate/Database/QueryException with message 'SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'created_at' in order clause is ambiguous (SQL: select * from playlists where exists (select * from tracks inner join playlist_track on tracks.id = playlist_track.track_id where playlists.id = playlist_track.playlist_id order by created_at desc, created_at desc) order by created_at desc limit 1)'

I have no idea what's wrong.
0 likes
9 replies
tisuchi's avatar

@jgbneatdesign

Can you check with this?

public function tracks(): BelongsToMany
    {
      return $this->belongsToMany(Track::class)
        ->withTimestamps()
        ->latest('playlist_track.created_at');
    }
jgbneatdesign's avatar

It works without it. But I'm pulling the list of playlists from the API through an app. I don't want to display empty playlist. So that's why I need this option.

tisuchi's avatar

@jgbneatdesign

Have you tried with has() in your controller?

$lists = Playlist::has('tracks')->get();

If it works, then you can easily avoid the scope.

jgbneatdesign's avatar
<?php

  namespace App\Models;

  use Laravel\Scout\Searchable;
  use App\Traits\HelperTrait;
  use Carbon\Carbon;
  use Illuminate\Database\Eloquent\Relations\BelongsTo;
  use Illuminate\Database\Eloquent\Relations\BelongsToMany;
  use Storage;

  class Track extends BaseModel
  {
    use Searchable;
    use HelperTrait;

    private $default_poster_url = "https://img-storage-prod.mp3pam.com/placeholders/track-placeholder.jpg";

    protected $guarded = [];

    protected $casts = [
      'allowDownload' => 'boolean',
    ];

    public function playlists(): BelongsToMany
    {
      return $this->belongsToMany(Playlist::class)
        ->withTimestamps()
        ->orderBy('playlist_track.created_at');
    }

    public function artist(): BelongsTo
    {
      return $this->belongsTo(Artist::class);
    }

    public function user(): BelongsTo
    {
      return $this->belongsTo(User::class);
    }

    public function genre(): BelongsTo
    {
      return $this->belongsTo(Genre::class);
    }

    public function album(): BelongsTo
    {
      return $this->belongsTo(Album::class);
    }

    public function setDetailAttribute($detail)
    {
      $this->attributes['detail'] = nl2br($detail);
    }

    public function setLyricsAttribute($lyrics)
    {
      $this->attributes['lyrics'] = nl2br($lyrics);
    }


    public function scopeByHash($query, $hash)
    {
      return $query->where('hash', $hash);
    }

    public function scopeByGenre($query, $genre_slug)
    {
      $genre = Genre::whereSlug($genre_slug)->first();

      $query->where('genre_id', $genre->id);
    }

    public function scopeRand($query)
    {
      $query->orderByRaw('RAND()');
    }

    public function scopeRelated($query, $obj)
    {
      $query
        ->whereGenreId($obj->genre_id)
        ->where('id', '!=', $obj->id)
        ->orderByRaw('RAND()'); // get random rows from the DB
//        ->published()
    }

    public function scopeLastMonth($query)
    {
      $today     = Carbon::today();
      $lastMonth = Carbon::today()->subMonth();

      $query
        ->where('created_at', '<', $today)
        ->where('created_at', '>', $lastMonth);
    }

    public function scopePopular($query)
    {
      $query
        ->orderBy('download_count', 'desc')
        ->orderBy('play_count', 'desc');
    }

    public function scopeFeatured($query)
    {
      $query->whereFeatured(1);
    }

    public function scopePublished($query)
    {
      $query->wherePublish(1);
    }

    public function isPublished()
    {
      return $this->publish === 1;
    }

    public function scopeByPlay($query)
    {
      $query->orderBy('play_count', 'desc');
    }

    public function toSearchableArray()
    {
      extract($this->toArray());

      return compact('id', 'title', 'detail', 'lyrics');
    }
  }

Here it it @staudenmeir

jgbneatdesign's avatar
jgbneatdesign
OP
Best Answer
Level 1

hey guys I got it working by creating another class just for the pivot table and using the scope with that relation instead directly.

My Playlist class now has a trackList method:

 public function trackList(): HasMany
    {
      return $this->hasMany(PlaylistTrack::class);
    }

and the related pivot class

<?php

  namespace App\Models;

  use Illuminate\Database\Eloquent\Relations\BelongsTo;

  class PlaylistTrack extends BaseModel
  {
    protected $table = 'playlist_track';
    protected $guarded = [];


    public function playlist(): BelongsTo
    {
      return $this->belongsTo(Playlist::class);
    }
  }

And with that I have all the playlists that have at least one track.

Thanks to @tisuchi and @staudenmeir for your time!

Please or to participate in this conversation.