DNABeast's avatar

Relationships using LIKE

I've got a Model with an id in three parts

'id' => 'FIRSTNAME_SECONDNAME_TITLE'

I've got a second model where I have matching foreign_ids but the data that goes in there has wildcards.

'name_id'=> '???_SMITH_??'

I want to be able to set up a relationship when all the parts that are filled in match but the wildcards are ignored.

I think I need subQueries in my hasMany method but can't work out how to do it.

In my Name Model

	public function relatedNames(): HasMany
	{
			return $this->hasMany(RelatedName::class, 'name_id', 'id')
			->orWhere(function ($query) {
				$query->where(function ($query) {
					$query->where('firstname', $this->firstname)
						->orWhere('firstname', '!==', '???');
				})
					->where(function ($query) {
						$query->where('lastname', $this->lastname)
							->orWhere('lastname', '!==', '???');
					})
					->where(function ($query) {
						$query->where('title', $this->title)
							->orWhere('title', '!==', '??');
					});
			});
}

In this demo every firstname and last name has the same amount of characters and they are in the database as stored generated columns.

Relationships don't seem to allow orWhere methods on them. How do I made this relationship include these partial matches?

0 likes
4 replies
LaryAI's avatar
Level 58

Certainly! The problem here is that you want to set up a relationship where the "foreign" key can have wildcards (using ?) and you want to match those to the parts of an id column in another model that don't have wildcards—at the database relationship (Eloquent) level if possible.

Relationships in Laravel typically use exact matches, but in your case, you need pattern matching (using LIKE) with support for ? as a single-character wildcard.

Solution Outline

  1. Storing parts in generated columns makes this easier. We'll assume your table has columns for firstname, lastname, title for both models.
  2. You can't use orWhere or an advanced query directly inside an Eloquent relationship's definition. However, you can use a custom relationship with hasMany and add extra constraints using ->where(...) or ->whereRaw(...).
  3. You can't directly use ? as a wildcard in SQL; you must convert ? to _ (underscore), the single-character wildcard in SQL LIKE.

Example Solution

Let's assume your RelatedName model has columns:

  • name_id: string, e.g. '???_SMITH_??'
  • firstname, lastname, title as stored generated columns

Step 1: Add an Accessor to Convert ? to _ in Name Model

public function namePattern()
{
    // Converts THIS model's id into a SQL LIKE pattern, replacing '?' with '_'
    return str_replace('?', '_', $this->id);
}

Step 2: Custom Relationship Definition

Because we want RelatedNames whose pattern matches the Name's ID, but the pattern with _ wildcards, we use whereRaw with a LIKE.

public function relatedNames()
{
    return RelatedName::query()
        ->whereRaw('? LIKE REPLACE(name_id, "?", "_")', [$this->id]);
}

This makes the relationship: return all RelatedNames WHERE the pattern in name_id, with '?' replaced by '_' (SQL wildcard), matches this model's id.

If you want this as an actual relationship property, you can use:

public function relatedNames()
{
    return $this->hasMany(RelatedName::class, 'name_id', 'id')
        ->whereRaw('? LIKE REPLACE(name_id, "?", "_")', [$this->id]);
}

But this will run the exact same query.

Step 3: Usage

$name = Name::find(1);
$related = $name->relatedNames; // Will contain all related names matching the pattern

Summary

  • Store patterns as ???_SMITH_?? in name_id of RelatedName.
  • On the Name model, define the relationship using whereRaw + LIKE, replacing ? with _.
  • This tells Eloquent to match Name's id against the wildcard pattern.

Full Example

In Name model:

public function relatedNames()
{
    return $this->hasMany(RelatedName::class, 'name_id', 'id')
        ->whereRaw('? LIKE REPLACE(name_id, "?", "_")', [$this->id]);
}

Usage:

$relatedNames = $name->relatedNames;  // Now includes wildcard matches

Note: If you have the parts as columns, adjust the whereRaw to build the LIKE condition per part.


Let me know if you want a version that splits and matches by firstname, lastname, and title columns!

DNABeast's avatar

Alas this doesn't work. I don't have identical id to foreign_id matching so any relationship call can only filter down.

I need to replace the relationship check that looks for id = foreign_id with a custom solution that checks for id LIKE partial-id

DNABeast's avatar
DNABeast
OP
Best Answer
Level 27

This was done by creating a custom relationship.

Please or to participate in this conversation.