0

Наблюдаю странное поведение при выборке.

Данные выбираются из таблицы 'article', по колонке 'tag_id' (статьи, у которых есть нужный тег)

ID тегов хранится как отдельное число или как числа, перечисленные через запятую '1,5,10' или '10' или '19,14'

Реализация в модели:

class Tags extends Model {
    use HasFactory;
    protected $table = 'tags';
public function getArticleWithTags() {
    return Article::where('tag_id', $this->id)
                    ->orWhere('tag_id', 'like', $this->id.',%')
                    ->orWhere('tag_id', 'like', '%,'.$this->id)
                    ->orWhere('tag_id', 'like', '%,'.$this->id.',%')
                    ->get();
}

}

Запятую нужно учитывать, для исключения однозначных вхождений в двухзначные, т.е. если использовать where('tag_id', 'like', '%'.$this->id.'%'), то при поиске id=5, в выборку так же попадут id=15,25,51, etc.


Но что то пошло не так...

При нормальном поведении в результаты запроса 'tags.id'='article.tag_id':

public function getArticleWithTags() {
    return Article::where('tag_id', '=', $this->id)->dd();
    //->get();
}

dd()

должны попадать данные с точным соответствием id из двух колонок.

Т.е. из запроса выше select * from 'article' where 'tag_id' = 1 должны вернуться строки из таблицы, где в колонке tag_id стоит 1

Но в выбокру так же попадают значения, у которых в колонке tag_id стоят следующие значения: '1', '1,11', '1,9', '1,Z', '1,qwerty', '1qwerty' при этом, как и положено, игнорируются: '9,1', '19', 'abc,1'.


Условие where('tag_id', $this->id) можно заменить на where('tag_id', 'like', $this->id) Получив корректную работу:

return Article::where('tag_id', 'like', $this->id)
                    ->orWhere('tag_id', 'like', $this->id.',%')
                    ->orWhere('tag_id', 'like', '%,'.$this->id)
                    ->orWhere('tag_id', 'like', '%,'.$this->id.',%')
                    ->get();
}

Так же, конструкция where('tag_id', $this->id) будет корректно работать, если приводить передаваемый аргумент к строке:

return Article::where('tag_id', ''.$this->id)->get();
return Article::where('tag_id', (string)$this->id)->get();
return Article::where('tag_id', strval($this->id))->get();

dd()

Но вопросы остались...

Почему без приведения к строке попадают в выборку странные значения?

Типы колонок:

  • tags.id - bigint(20)
  • article.tag_id - varchar(255)

Получается сравнение идёт по первому символу

select * from 'article' where [varchar(255)='1qwerty'] = [bigint(20)=1]

Точнее из двух сравниваемых значений именно varchar приводится к числу, иначе (при приведении искомого id к строке) и результаты бы такие не появлялись и ручного приведения делать не пришлось бы, да и при явном приведении лишние данные из выборки пропадают

По итогу из varchar берётся первый символ, который видимо преобразуется в число. Или первый символ сравнивается после такой обрезки с id, преобразованным в строку, но тогда почему id не приводится к строке на более раннем этапе.

Для меня странно такое преобразование В документации не видел комментариев по такому поведению.

Какие мысли? Что посмотреть по теме? Буду признателен...

Danis
  • 28
  • 5
  • 2
    Добавлю, что если mysql видит числовое значение то он пытается все приводить к нему для сравнения. Так же добавлю, что хранить значения через запятую и сравнивать как текст крайне не рекомендуется. Возникают вот такие проблемы, да и полный скан таблицы гарантирован. И оперировать этим полем неудобно. Стоит рассмотреть нормализацию данных, вынесения перечислимых значений в отдельную таблицу, отдельными записями – Mike Nov 29 '21 at 18:04
  • Но если с нормализацией проблемы, то рекомендую использовать функцию find_in_set https://ru.stackoverflow.com/a/532322/194569 – Mike Nov 29 '21 at 18:06
  • Благодарю за пояснения. Возможно позднее буду делать нормализацию, понимаю что некрасиво, но вопрос задал, т.к. интересовало именно поведение и преобразование. Не знал, что приводится к числу... Спасибо за ответ. – Danis Nov 29 '21 at 18:19

0 Answers0