wim91's avatar

How can I collect values from related tables?

I have a table linked to another one-to-many relationship.

The second table has a field flag "lvl".

I need to get a record from the first table, but not with all the related records from the second table, but only three records with the field value "lvl" = 1, and two records with the field value "lvl" = 2.

Tell me how to implement it. The way I did it before, it no longer works through the model.

It was before:

class MyModel extends Model
{
    use HasFactory;
    public function relation_all()
    {
      return $this->hasMany(MyModel1::class, 't1_id', 'id')->with('next_relation');
    }
    public function part1(){
       return $this-> relation_all()->where('lvl', 1)->inRandomOrder()->take(3);
    }
    public function part2(){
          return $this-> relation_all()->where('lvl', 2)->inRandomOrder()->take(2);
    }
    public function result_relation()
    {
      return $this->part1()->union($this->part2());
    }

And a call in the controller

$res = MyModel::with('result_relation')->where('url', $url)->first();

But for some reason it doesn't work anymore. Now in the latest version of Laravel there is an error: SQLSTATE[HY093]: Invalid parameter number

Please tell me how this can be implemented differently?

0 likes
12 replies
wim91's avatar

@Glukinho Thank you. And if the relation needs to be mixed, how can this be implemented?

I did it like this:

$res = MyModel::with(['result_relation', function($q){
	$q->orderByRaw('RAND()');
})->where('url', $url)->first();

How can this be done in your example?

Glukinho's avatar

@wim91

$collection = MyModel::whereRelation('relation_all', 'lvl', '=', 1)->inRandomOrder()->limit(2)->get();

// Or simply

$collection = $collection->shuffle();

You don't have to do EVERYTHING on database level. Sometimes it's more effective to simply retrieve data and do manipulations in your app. Of course, you should always consider performance issues.

wim91's avatar

@Glukinho Probably yes. But it was convenient for me to get a ready-made collection before, but for some reason in version 12 of Laravel my method stopped working.((

wim91's avatar

@Glukinho I can't do it(. I have records in Table 1, and each has many records in the related Table 2. I need to take all the data of one of the records of Table 1 and only some of the records from Table 2 according to the conditions.

In your example, I don't have the relation field, and only the data of the record from the first table is displayed(

Glukinho's avatar

@wim91 It would be much easier if you give real tables/model names used in your app instead of "Table 1", "MyModel"...

As I understood:

  • you have a MyModel instance
  • you want to have this MyModel to have 5 related randomly taken MyModel1 instances - three with lvl = 1 and two with lvl = 2

Something like this:

MyModel
  id = ...some id...
  relation (collection):
    - MyModel1 / id = 68 / lvl = 1
    - MyModel1 / id = 26 / lvl = 2
    - MyModel1 / id = 7 / lvl = 1
    - MyModel1 / id = 19 / lvl = 1
    - MyModel1 / id = 82 / lvl = 2

Is it right?

wim91's avatar

@Glukinho Yes, you got it right. I am finalizing an old project with Laravel 10, and after updating to 12 I started getting this error, so as not to rewrite all the logic and blade, I would like to get the same format as before.

It should look like this. A set of relation_all by condition in random order.

Glukinho's avatar

@wim91 I tried to reproduce your situation on my app. My models are:

  • Project -> BelongsToMany -> Person
  • Person -> BelongsToMany -> Project

I added lvl column to persons table.

I added some code to my Project model:

// I had it before
public function persons (): BelongsToMany
{
		return $this->belongsToMany(Person::class)->using(PersonProject::class)->withPivot('id', 'role')->withTimestamps();
}

// added now
public function lvl1()
{
	return $this->persons()->where('lvl', '=', 1)->inRandomOrder()->limit(3);
}

public function lvl2()
{
	return $this->persons()->where('lvl', '=', 2)->inRandomOrder()->limit(2);
}

public function result_relation()
{
	return $this->lvl1()->union($this->lvl2());
}

And working code is:

$project = Project::find(1);

$result = $project->result_relation()->get()->shuffle();

foreach($result as $r) {
	echo "model: " . get_class($r) . ", id: {$r->id}, lvl: {$r->lvl}" . "\n";
}

Launched it several times, it seems the result is exactly what you asked: five models, three with lvl = 1, two with lvl = 2, in random order.

c:\nastroim\pipka>php artisan app:test
model: App\Models\Person, id: 5, lvl: 1
model: App\Models\Person, id: 6, lvl: 2
model: App\Models\Person, id: 8, lvl: 1
model: App\Models\Person, id: 1, lvl: 1
model: App\Models\Person, id: 7, lvl: 2

c:\nastroim\pipka>php artisan app:test
model: App\Models\Person, id: 5, lvl: 1
model: App\Models\Person, id: 1, lvl: 1
model: App\Models\Person, id: 7, lvl: 2
model: App\Models\Person, id: 6, lvl: 2
model: App\Models\Person, id: 4, lvl: 1

c:\nastroim\pipka>php artisan app:test
model: App\Models\Person, id: 5, lvl: 1
model: App\Models\Person, id: 2, lvl: 2
model: App\Models\Person, id: 3, lvl: 2
model: App\Models\Person, id: 8, lvl: 1
model: App\Models\Person, id: 4, lvl: 1
1 like
wim91's avatar

@Glukinho Yes. This is the set that is needed! But there is one more question. Is it possible to somehow replace this collection specifically in the connection? It was just like this before, I unloaded a record from 1 table, with a connection, where the necessary collection was already located without unnecessary records. And now it is a separate collection. I am also trying to solve this problem now, I only managed to add a new field to the collection with this set.

Glukinho's avatar

@wim91 I don't understand what "replace collection in connection" means.

Can you show how this set is used in views and other places? Places you don't want to rewrite.

upd: something tells me you're confusing two concepts: a relation between models and a method returning a collection of models with some logic applied.

wim91's avatar

@Glukinho Unfortunately, it won't be possible to show it yet, because the error occurred in one element out of many others, the logic is quite voluminous and interconnected, tied to values from different queries.

I mean that the result that you received in

$result = $project->result_relation()->get()->shuffle();

I would like it to be immediately in

#relations: array:1 [▼
         "all_relation" => []

So far I have only been able to add new data like this

$project = Project::find(1);
$result = $project->result_relation()->get()->shuffle();
$project->all_relation = $result;

I think this is not right. I think if the necessary data from the request came immediately, it would be better and more effective.

Glukinho's avatar

@wim91 you may do directly:

$project->result_relation;

But multiple callings will return the same set. While this:

$project->result_relation()->get()->shuffle();
$project->result_relation()->get()->shuffle();
$project->result_relation()->get()->shuffle();

will return three different randomized sets. I don't know if it fits you.

Please or to participate in this conversation.