Содержание урока · 12 разделов
- gorm where: строковые условия и плейсхолдеры
- Структурные условия: нулевые поля игнорируются
- Второй аргумент Where: Specify Struct search fields
- IN: список значений одной строкой условия
- LIKE и ILIKE: поиск по шаблону
- Диапазоны и AND-склейка нескольких Where
- Шпаргалка: операторы условий
- Or и Not: где теряются скобки
- Что дальше по курсу
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Шестой урок научил читать записи: First, Take и Find доставали строки, но условия там мелькали только в примерах — одно done = ? и срез ключей. Пора полный арсенал: Where — самый частый метод в живом сервисе, потому что фильтрация есть почти в каждом запросе, и именно здесь сходятся удобство и ответственность. Условия gorm принимают три формы — строковую, структурную и карту — и у каждой свой характер: строковая гибкая и полная, структурная типобезопасная, но слепая к нулям, карта честная ко всему, что вы положили. Одна честная оговорка о формате: СУБД в браузерной песочнице не живёт, поэтому runner-ов в курсе нет — вас проверяют quiz, predict, fill, order и карточки, а примеры рассчитаны на локальный Go с PostgreSQL или SQLite. Рабочие данные — те же пять задач из пятого урока в таблице todo_items; для наглядности считаем, что статусы уже разъехались: у задач 1 и 5 done = true и статус done, у остальных — todo или doing, приоритеты 0, 0, 2, 3, 1. Каждый пример снабжён SQL-следом — его печатает логгер из второго урока в режиме Info.
gorm where: строковые условия и плейсхолдеры#
Основная форма — строковая: db.Where("status = ?", "todo"). Внутри метода Where(query interface{}, args ...interface{}) из chainable_api.go такая пара уходит в Statement.BuildCondition, который видит строку с вопросительным знаком и заворачивает её в clause.Expr — фрагмент SQL плюс список аргументов. При сборке запроса каждый ? заменяется плейсхолдером диалекта ($1 на PostgreSQL, ? на SQLite и MySQL), а значение едет в драйвер отдельно от текста команды. Оператор — любой SQL-ный: = и <> для неравенства, >, >=, <, <= для сравнений, AND и OR прямо внутри строки. Есть и короткая форма без плейсхолдера: db.Where("status", "doing") — строка без пробелов и с единственным аргументом превращается в равенство status = 'doing'. Строка условия — это сырой SQL: GORM не разбирает её на части, он лишь подставляет значения вместо ?. Отсюда два следствия: свобода (любой оператор и выражение, которые поймёт база) и ответственность — с неё и начнём.
Страница Security в документации GORM формулирует правило одной фразой: библиотека строит SQL через аргументные плейсхолдеры database/sql, которые автоматически экранируют значения — так SQL-инъекция отсекается на корню. Пользовательский ввод должен быть только аргументом: db.Where("name = ?", userInput) безопасен, даже если в userInput сидит «jinzhu;drop table users;» — драйвер отвезёт эту строку как значение, и база будет искать имя с точкой с запятой, а не исполнять её. Уязвимость появляется ровно в момент, когда ввод попадает в сам текст условия: db.Where(fmt.Sprintf("name = %v", userInput)) — вот это уже инъекция, потому что Sprintf вклеил строку в SQL до того, как её увидел драйвер. Кавычка внутри ввода закрыла строковый литерал — и хвост исполнился кодом. Плейсхолдеры — это не стиль и не вкусовщина, это граница между данными и кодом; всякий раз, когда соблазн склеить условие строкой «просто чтобы быстрее», вспоминайте этот абзац.
Строковые условия и их SQL-след
GOvar todos []TodoItem
// Каноническая форма: условие с плейсхолдером и значение отдельным аргументом
db.Where("status = ?", "todo").Find(&todos)
// SELECT * FROM "todo_items" WHERE status = 'todo'
// Неравенство и сравнения - операторы обычного SQL
db.Where("priority <> ?", 0).Find(&todos) // WHERE priority <> 0
db.Where("priority >= ?", 2).Find(&todos) // WHERE priority >= 2
// Несколько условий в одной строке - через AND
db.Where("done = ? AND priority >= ?", false, 2).Find(&todos)
// WHERE done = false AND priority >= 2
// Короткая форма: колонка и значение двумя аргументами
db.Where("status", "doing").Find(&todos)
// WHERE status = 'doing'След показывает главное: значение приехало в условие не склейкой, а подстановкой драйвера — в логе GORM печатает уже собранный SQL, но реально аргументы едут bind-переменными. Короткая форма удобна для мгновенных проверок, в рабочем коде чаще пишут с ? — единообразие помогает глазу.
Что сделает такой запрос?
Прочитайте код и выберите, что он выведет:
// title - строка из формы пользователя:
// "хлеб'; DROP TABLE todo_items;--"
q := fmt.Sprintf("title LIKE '%%%s%%'", title)
db.Where(q).Find(&todos)Правило из документации: не доверяйте присланному пользователем — используйте его только как аргумент, а для конечных множеств (например, колонок сортировки) делайте белые списки. При этом список методов, чьи строковые аргументы НЕ экранируются, длинный: Select, Distinct, Pluck, Group, Having, Raw, Exec, Order, Table, Delete, Joins — во всех них строка уезжает в SQL буквально. Условия Not и Or принимают те же формы, что и Where, и их строки — такой же сырой SQL: инъекция через Or или Not возможна точно так же, как через Where. Если колонка или направление сортировки приходит из запроса API — сверяйте его со списком разрешённых значений, а не вклеивайте в строку.
Проверка: где место пользовательского ввода
Поисковая строка из запроса API попадает в переменную q. Какое обращение к базе безопасно?
Структурные условия: нулевые поля игнорируются#
Вторая форма — структурная: db.Where(&TodoItem{Status: "todo"}).Find(&todos). BuildCondition разбирает структуру по схеме модели и на каждое ненулевое поле строит равенство: status = 'todo'. Удобство очевидно — колонки подставлены по схеме, опечатка в имени колонки ловится компилятором. Но у формы есть жёсткое правило, уже показанное в шестом уроке как ловушка: нулевые поля игнорируются. В Go «пустое» поле неотличимо от «не заполнял»: ноль, пустая строка, false — это zero values, и BuildCondition пропускает их через проверку isZero. Поэтому db.Where(&TodoItem{Priority: 0}) не даст WHERE priority = 0 — условие окажется пустым, и Find молча вернёт всю таблицу. Шестой урок обещал объяснить причину и дать второй фикс — вот они. Причина: структурные условия собираются только из ненулевых полей, ноль неотличим от «не заполнил». Первый фикс — карта: db.Where(map[string]interface{}{"priority": 0}) честно включает ноль, потому что карта не знает, что такое «пусто». Второй фикс — второй аргумент Where с перечнем полей, отдельный раздел ниже. Бонус формы: голый срез ключей как условие — db.Where([]uint{2, 4}) — разворачивается в WHERE id IN (2,4), это сахар над первичным ключом.
Структура, карта и срез ключей
GO// Структурное условие: каждое ненулевое поле - равенство
var active []TodoItem
db.Where(&TodoItem{Status: "todo", Priority: 3}).Find(&active)
// WHERE status = 'todo' AND priority = 3
// Ловушка из шестого урока: ноль - значит "не заполнял"
db.Where(&TodoItem{Priority: 0}).Find(&todos)
// WHERE нет вовсе: SELECT * FROM "todo_items" - вся таблица
// Карта включает нули; ключи сортируются по алфавиту
db.Where(map[string]interface{}{"priority": 0, "status": "todo"}).Find(&todos)
// WHERE priority = 0 AND status = 'todo'
// Срез ключей как условие - разворачивается в id IN
db.Where([]uint{2, 4}).Find(&todos)
// WHERE id IN (2,4)Сравните второй и третий запросы: одна и та же логика «найти нулевой приоритет» — и два разных следа. Карта — честный способ искать по нулю. Деталь для отладки: ключи карты GORM сортирует, поэтому колонки в следе идут по алфавиту — след стабилен от запуска к запуску. Срез ключей — эквивалент Find(&todos, []uint{2,4}) из шестого урока.
Проверка: что попадёт в WHERE?
Выполнили db.Where(&TodoItem{Done: false, Status: "todo"}).Find(&todos). Что окажется в WHERE?
Второй аргумент Where: Specify Struct search fields#
Официальное имя приёма — Specify Struct search fields: передавая после структуры перечень полей, вы делаете сразу две вещи. Первая — белый список: в условие едут только перечисленные поля, остальные выпадают, даже если заполнены. Вторая — форс нулей: перечисленное поле едет в условие, даже если его значение нулевое. Документированный пример: db.Where(&User{Name: "jinzhu"}, "name", "Age") даст WHERE name = 'jinzhu' AND age = 0 — Age нулевой, но упомянут, поэтому включён. Имена в перечне можно смешивать: GORM сверяет каждый аргумент и с именем колонки (name), и с Go-именем поля (Age). Обратная сторона белого списка — рестрикт: всё, что не перечислили, игнорируется. Структура с пятью заполненными полями и перечнем из одного поля даст условие ровно по одному полю — забытая позиция в списке молча исчезает из WHERE. Приём стоит держать для точечных случаев «нужен ноль» и для явной фиксации состава условия; как замена обычной структурной форме он слишком коварен.
Второй аргумент на практике
GO// Белый список + форс нулей: оба поля едут, Priority - нулевой
db.Where(&TodoItem{Status: "todo", Priority: 0}, "Priority", "Status").Find(&todos)
// WHERE priority = 0 AND status = 'todo'
// Смешивать можно имя колонки и Go-имя поля
db.Where(&TodoItem{Title: "Купить хлеб", Priority: 2}, "Priority").Find(&todos)
// WHERE priority = 2 - Title заполнен, но в белый список не попал
// Без перечня та же структура дала бы оба условия:
// WHERE title = 'Купить хлеб' AND priority = 2Первый запрос — лечение ловушки с нулём без карты: перечислили Priority — ноль уехал в WHERE. Второй показывает рестрикт: поле вне перечня выпадает, даже если заполнено. Проверяйте след каждого такого запроса: ошибка «забыл поле в списке» не ругается — просто тихо меняет выборку.
IN: список значений одной строкой условия#
Третий кит — оператор IN: заменить лес OR-равенств одним условием со списком. Каноническая форма из документации — db.Where("status IN ?", []string{"todo", "doing"}): после IN идёт плейсхолдер, а значение — срез. При сборке Statement.AddVar разворачивает срез в скобки с bind-переменными — получается WHERE status IN ('todo','doing'), где каждый элемент списка по-прежнему едет экранированным аргументом. Круглые скобки в строке — форма "status IN (?)" — тоже допустимы: GORM поймёт, что скобки уже открыты, и не добавит вторые. Сращивать список в строку через strings.Join или Sprintf не нужно никогда: это и инъекция, если элементы от пользователя, и лишняя работа — срез GORM развернёт сам. Поведение на пустом срезе стоит знать наизусть: пустой срез печатается как IN (NULL), что ложно для любой строки — вернётся ноль записей и ни одной ошибки. Зеркальный оператор NOT IN пишется той же строкой ("status NOT IN ?") или через Not с картой, в которой лежит срез.
IN и NOT IN
GO// Каноническая форма: колонка IN ? и срез значений
var open []TodoItem
db.Where("status IN ?", []string{"todo", "doing"}).Find(&open)
// SELECT * FROM "todo_items" WHERE status IN ('todo','doing')
// Скобки в строке тоже допустимы:
db.Where("id IN (?)", []uint{1, 3, 5}).Find(&open)
// WHERE id IN (1,3,5)
// NOT IN - зеркальная форма той же строки
var notDone []TodoItem
db.Where("status NOT IN ?", []string{"done"}).Find(¬Done)
// WHERE status NOT IN ('done')
// Пустой срез - не ошибка, но и не совпадения:
var none []TodoItem
res := db.Where("id IN ?", []uint{}).Find(&none)
// WHERE id IN (NULL) - ложно для каждой строки
fmt.Println(len(none), res.RowsAffected, res.Error) // 0 0 <nil>IN — самый частый оператор фильтров API: «показать задачи этих статусов». Обратите внимание на последний вызов: IN (NULL) не ошибка и не «вся таблица», а пустая выборка — NULL ни с чем не совпадает. Если пустой список — штатная ситуация, лучше не ходить в базу вовсе: ранний выход до запроса.
Сколько строк вернёт запрос с пустым срезом?
Прочитайте код и выберите, что он выведет:
// Пользователь не выбрал ни одного статуса
statuses := []string{}
var picked []TodoItem
result := db.Where("status IN ?", statuses).Find(&picked)
fmt.Println(len(picked), result.RowsAffected, result.Error)LIKE и ILIKE: поиск по шаблону#
Для поиска по подстроке SQL даёт LIKE с двумя спецсимволами: % — сколько угодно любых символов (в том числе ноль), _ — ровно один. Шаблон — это значение, а не часть условия: db.Where("title LIKE ?", "%хлеб%") — проценты сидят в аргументе, и их можно склеивать в Go: "%" + q + "%" безопасно, потому что склейка происходит над значением, которое драйвер потом экранирует (а вот % и _, пришедшие от пользователя, стоит вырезать или экранировать, если они не должны быть подстановками). С регистром всё зависит от диалекта, и здесь GORM не помощник — он передаёт текст условия базе как есть. PostgreSQL: LIKE регистрозависим, для поиска без учёта регистра у диалекта есть собственное расширение ILIKE — db.Where("title ILIKE ?", "%купить%") найдёт и «Купить хлеб». MySQL: при дефолтных коллациях *_ci LIKE регистронезависим сам по себе. SQLite: LIKE не различает регистр для ASCII-букв. Переносимый вариант руками — lower(title) LIKE lower(?), но функцию по колонке может лишить индекса — об этом разговор восемнадцатого урока.
LIKE, ILIKE и регистр
GO// Подстрока: % - сколько угодно символов
var bread []TodoItem
db.Where("title LIKE ?", "%хлеб%").Find(&bread)
// WHERE title LIKE '%хлеб%'
// _ - ровно один символ: пять знаков после дефиса
db.Where("slug LIKE ?", "buy-_____").Find(&bread)
// WHERE slug LIKE 'buy-_____' - подойдёт buy-bread
// ILIKE - расширение PostgreSQL: регистр не важен
db.Where("title ILIKE ?", "%купить%").Find(&bread)
// WHERE title ILIKE '%купить%' - найдёт и "Купить хлеб"Первый запрос не найдёт «Купить хлеб» на PostgreSQL, если ищете «купить» в нижнем регистре: LIKE в этой базе чувствителен к регистру. ILIKE решает вопрос, но это ключевое слово PostgreSQL — на MySQL и SQLite его нет, там работают другие механизмы (коллации, upper/lower). Проверяйте диалект своей базы, а не надейтесь на универсальность.
Проверка: регистр в поиске
PostgreSQL, в таблице есть задача «Купить хлеб». Какой вызов найдёт её по строке «купить» в нижнем регистре?
Диапазоны и AND-склейка нескольких Where#
Диапазонных операторов немного, и все — из SQL: >, >=, <, <= и BETWEEN ? AND ? с включительными границами. Время фильтруется теми же формами: db.Where("created_at > ?", weekAgo) — и плейсхолдер примет time.Time, драйвер сам переведёт его в таймстамп. Смысловая связка с chaining-ловушкой шестого урока: несколько Where в одной цепочке склеиваются AND — клозы сливаются в один WHERE по мере вызовов (MergeClause дописывает выражения в порядке вызова). Порядок вызовов отражается в SQL-следе, но на смысл чистых AND-условий не влияет — «A AND B» то же самое, что «B AND A»; влияет он только тогда, когда в цепочке появляется Or, о котором ниже. Напоминание одной фразой: инстанс после цепочки хранит уже добавленные условия — сохранить его в переменную можно только через Session(&gorm.Session{}), как показывал шестой урок. И ещё одна фраза на память: если в модели есть поле gorm.DeletedAt, GORM сам допишет к каждому WHERE условие deleted_at IS NULL — мягкое удаление молча сужает всякую выборку, детали девятого урока.
Диапазоны и несколько Where
GO// Диапазон числа: два клоза сравнения
var hot []TodoItem
db.Where("priority >= ?", 2).Where("priority <= ?", 3).Find(&hot)
// WHERE priority >= 2 AND priority <= 3
// BETWEEN - то же самое одной строкой, границы включительно
db.Where("priority BETWEEN ? AND ?", 2, 3).Find(&hot)
// WHERE priority BETWEEN 2 AND 3
// Дата-диапазон: задачи за последнюю неделю
weekAgo := time.Now().AddDate(0, 0, -7)
db.Where("created_at > ?", weekAgo).Find(&hot)
// Несколько Where склеиваются AND в порядке вызовов
db.Where("done = ?", false).Where("status IN ?", []string{"todo", "doing"}).Find(&hot)
// WHERE done = false AND status IN ('todo','doing')BETWEEN — сахар над парой сравнений; выбирайте форму, которая читается лучше. Последний запрос — типовой каркас списка «открытые задачи в работе»: равенство плюс IN, склеенные двумя Where. Промежуточные вызовы без финишера ничего не отправляют базе — запрос уезжает только на Find.
Шпаргалка: операторы условий#
| Задача | Форма условия | SQL-след | Нюанс |
|---|---|---|---|
| Равенство | Where("status = ?", v) | status = 'todo' | ввод пользователя — только аргументом |
| Список значений | Where("status IN ?", срез) | status IN ('todo','doing') | пустой срез — IN (NULL): ноль строк |
| Исключить список | Where("status NOT IN ?", срез) | status NOT IN ('done') | то же даёт Not с картой и срезом |
| Подстрока | Where("title LIKE ?", "%х%") | title LIKE '%х%' | % и _ — спецсимволы, экранируйте из ввода |
| Поиск без регистра | Where("title ILIKE ?", ...) | title ILIKE '%...%' | расширение PostgreSQL, не для всех СУБД |
| Диапазон | Where("priority BETWEEN ? AND ?", 2, 3) | priority BETWEEN 2 AND 3 | границы включаются |
| Отрицание | Not("done = ?", true) | NOT done = true | по структуре даёт name <> value |
Or и Not: где теряются скобки#
Метод Or добавляет к WHERE OR-группу: db.Where("role = ?", "admin").Or("role = ?", "super_admin") даст WHERE role = 'admin' OR role = 'super_admin'. Опасность в том, что склейка выражений плоская: db.Where("done = ?", false).Where("priority >= ?", 2).Or("status = ?", "doing") соберёт строку done = false AND priority >= 2 OR status = 'doing' — а по приоритету операторов SQL это (done = false AND priority >= 2) OR status = 'doing'. Автор цепочки почти всегда хотел иначе: «открытые с высоким приоритетом, плюс всё, что в работе». Вывод: появился Or в цепочке — смотрите след и думайте скобками. Помощь есть: если условие с AND написано одной строкой, GORM сам обернёт его в скобки — db.Where("done = ? AND priority >= ?", false, 2).Or("status = ?", "doing") даст уже (done = false AND priority >= 2) OR status = 'doing'. Для сложных случаев документация предлагает Group Conditions — вложенные db.Where, которые строят настоящие скобки; это территория продвинутой части курса. Not работает зеркально Where: строка получит NOT перед условием, срез в карте превратится в NOT IN, а структурное условие превратит каждое равенство в <>. И да, их строки — такой же сырой SQL с теми же правилами инъекционной гигиены.
Or, Not и их SQL-след
GO// Плоский OR: Where + Where + Or
var flat []TodoItem
db.Where("done = ?", false).Where("priority >= ?", 2).Or("status = ?", "doing").Find(&flat)
// WHERE done = false AND priority >= 2 OR status = 'doing'
// по приоритету SQL это (done = false AND priority >= 2) OR status = 'doing'
// AND внутри одной строки - GORM сам поставит скобки
db.Where("done = ? AND priority >= ?", false, 2).Or("status = ?", "doing").Find(&flat)
// WHERE (done = false AND priority >= 2) OR status = 'doing'
// Not: отрицание строкового условия
var notDone []TodoItem
db.Not("status = ?", "done").Find(¬Done)
// WHERE NOT status = 'done'
// Not со срезом в карте - NOT IN
db.Not(map[string]interface{}{"status": []string{"done"}}).Find(¬Done)
// WHERE status NOT IN ('done')Сравните первые два следа: условия те же, смысл разный — вся разница в скобках. Not в структурной форме даёт пары name <> value — это «все, кто не такой», а не «отрицание всего условия сразу»; для отрицания группы условий нужны скобки, то есть Group Conditions или ручная строка.
Заполните пропуски: три формы условий
Восстановите Where с плейсхолдерами, карту с нулевым полем и регистронезависимый поиск.
Соберите путь WHERE по шагам
Расставьте шаги, которые проходят условия от вызова Where до выполнения запроса.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: условия Where
Что дальше по курсу#
Условия gorm закрыты: три формы условия, плейсхолдеры и гигиена против SQL-инъекции, IN с его нулевым срезом, LIKE и ILIKE, диапазоны, Or и Not — это фундамент, на котором стоят все остальные выборки курса: и фильтры пагинации, и мягкое удаление, и связи. Восьмой урок применит те же условия к записи — обновления Update и Updates принимают WHERE ровно в тех формах, которые вы только что разобрали; текстом говорим заранее, ссылки не даём: урок ещё не написан. Если хочется освежить, откуда взялись пять задач и их SQL-следы, вернитесь к пятому уроку про Create; механику чтения и ловушку chaining — в шестом уроке; полный план из 25 уроков — на странице курса. Домашка ниже собирает весь арсенал в один скрипт — и одну намеренную инъекцию для наглядности.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Строковое условие Where("col = ?", v) — SQL-фрагмент плюс аргументы: значения едут плейсхолдерами database/sql и экранируются; сама строка условия сырая, вклейте в неё ввод — получите SQL-инъекцию.
- Структурное условие собирается только из ненулевых полей: Where(&TodoItem{Priority: 0}) даст пустой WHERE и всю таблицу; нули включает карта или второй аргумент Where с перечнем полей (Specify Struct search fields).
- Второй аргумент Where работает в обе стороны: перечисленные поля едут в условие даже нулевыми, неперечисленные — выпадают.
- Where("status IN ?", срез) разворачивается в скобки с bind-переменными; пустой срез даёт IN (NULL) — ноль строк без ошибки; NOT IN — зеркальная форма или Not с картой.
- LIKE ищет по шаблону с % и _; ILIKE — регистронезависимое расширение PostgreSQL; поведение LIKE с регистром определяется диалектом, а не GORM.
- Диапазоны — >= <= и BETWEEN ? AND ? (границы включительно); несколько Where склеиваются AND в порядке вызовов; порядок не важен для смысла, пока в цепочке не появился Or.
- Or склеивает выражения плоско: A AND B OR C читается SQL-ом как (A AND B) OR C; условие с AND одной строкой GORM берёт в скобки сам; Not даёт NOT, NOT IN и <> по полям структуры.
Домашнее задание
Соберите локальный скрипт поверх таблицы из пятого урока и пройдите весь арсенал: три строковых условия с плейсхолдерами (=, <>, IN) и распечатайте их SQL-следы; структурное условие с нулевым Priority — убедитесь, что след пустой, затем повторите запрос картой и вторым аргументом Where; IN по списку статусов, затем тот же запрос с пустым срезом и проверка следа IN (NULL). Поищите задачу по title через LIKE с шаблоном в аргументе и через ILIKE — сравните регистр. Соберите диапазон priority BETWEEN 2 AND 3 и два Where через AND. Финал: соберите плоский Where+Where+Or, распечатайте result.Statement.SQL и объясните себе, почему выборка шире, чем казалась; почините строкой с AND внутри и сравните скобки. Отдельно сломайте намеренно: соберите условие fmt.Sprintf-ом с вводом «хлеб'; --» и опишите, что видите в следе.
Личные заметкиПусто
Вопросы и ответы по уроку
Можно ли подставить в Where имя колонки из пользовательского ввода?
Нет. Экранируются только значения плейсхолдеров; сама строка условия уезжает в SQL как есть, поэтому имя колонки от пользователя — это инъекция. Держите белый список допустимых имён в коде и выбирайте из него, а ввод используйте лишь как значение: Where("status = ?", userInput).
Как искать по статусу, который может быть пустым списком?
Проверяйте список до запроса: если срез пуст, в базу можно не ходить — Where("status IN ?", срез) на пустом срезе даст WHERE status IN (NULL) и честный пустой результат без ошибки. Для голого среза-условия db.Where([]uint{}) условие исчезнет вовсе и вернётся вся таблица — вторая причина валидировать список заранее.
Как объединить условия скобками, чтобы Or не сломал логику?
Для простой пары напишите AND-условие одной строкой: GORM сам возьмёт его в скобки перед OR-группой. Для произвольных вложений документация предлагает Group Conditions — вложенные вызовы db.Where, строящие настоящие скобки. И храните цепочки с условиями в переменной только через Session(&gorm.Session{}) — без неё условия прилипают к инстансу.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
sqlc: типобезопасный код из SQL-запросов
Курс sqlc: настройка, запросы, параметры, NULL-типы, транзакции, джойны, пагинация, CI и сравнение с ORM. 25 уроков с практикой.
- Настраивать sqlc.yaml для Postgres/MySQL/SQLite
- Писать аннотированные запросы с типизированными параметрами
- Работать с NULL, массивами и JSON-колонками
- Организовывать транзакции через WithTx