neowaring
@neowaring
Разработчик неизвестных систем

Как записать значения через модель в разные таблицы с одним ID?

При создании категорий я использую 2 таблицы category | category_description
При записи использую следующий код

В модели категории

public function getAutoIncrement() {
        $autoincrement = DB::select("SHOW TABLE STATUS LIKE 'category'");
        return $autoincrement[0]->Auto_increment;
    }


В контроллере:
$AUTOINCREMENT = $this->category->getAutoIncrement();

Category::create([
'category_id' = $AUTOINCREMENT,
'image' => $image,
'parent_id' => $request->input('parent_id'),
'status' => $request->input('status'),
'sort_order' => isset($sort_order) ? $sort_order : '0'
]);

CategoryDesc::create([
'category_id' => $AUTOINCREMENT,
'language_id' => $language->language_id,
'name' => $request->input($name),
'meta_title' => $request->input($meta_title),
'meta_description' => $request->input($meta_description)
]);


Как можно избежать получения auto_increment в начале ? Так как при такой записи он не добавляется. И следующее создание выводит SQL ошибку Duplicate. Требуется записать описание с таким же ID, как и создается категория в таблицу category.
  • Вопрос задан
  • 167 просмотров
Решения вопроса 2
iMedved2009
@iMedved2009
Не люблю людей
Простите а зачем вам такая движуха?

class Category{

...
public function description(){
     return $this->hasOne(CategoryDesc::class);
}
...
}

$category = Category::create([
'image' => $image,
'parent_id' => $request->input('parent_id'),
'status' => $request->input('status'),
'sort_order' => isset($sort_order) ? $sort_order : '0'
]);

$category->description()->create([
'language_id' => $language->language_id,
'name' => $request->input($name),
'meta_title' => $request->input($meta_title),
'meta_description' => $request->input($meta_description)
]);


З.Ы. Не говоря уж о том что в вашем коде то факт того что вы получите реальный аутоинкремент мягко говоря не гарантирован.
Ответ написан
gzhegow
@gzhegow
aka "ОбнимиБизнесмена"
Вы либо прочитали либо нащупали Single Table Inheritance.

Его смысл в том, что центральная модель "агрегат" имеет id - AUTOINCREMENT, а зависимые от неё имеют такой же тип, но НЕ_АВТОИНКРЕМЕНТ. При этом центральная модель часто вообще не содержит информации, а является только регистром айдишек для привязки к ней любого числа таблиц завтра.

Автоинкремент предполагает отсутствие одинаковых id. Если его не поставить - вы сможете это делать, однако вы будете применять "один ко многим", хотя вашей логикой (которую я прочел из названий классов) это не предусмотрено.

В вашем случае нужно поставить на Category.id
UNSIGNED INT
AUTOINCREMENT

а на CategoryDesc.id
UNSIGNED INT
FOREIGN KEY TO Category.id
UNIQUE

Потому что описание у одной категории логически одно.

(формально это можно написать как CategoryDesc.category_id если бы у неё был свой айди, но её отдельно никто никогда не запросит, то есть она "не агрегат", будут запрашивать категорию и всё по ней, а не описание "от чего-то")

Вы все равно не сможете в этом случае создать две записи с одинаковым айди во второй таблице, но айди вы сможете указать какой хотите, кроме того foreign key проверит - есть ли он в исходной таблице и упадет если нету.

Сейчас вы получаете ошибку потому, что AUTOINCREMENT не предполагает что вы сами ставите значение id, оно просто берется "следующее".

Дальше едем. Вы подняли проблему последовательности записи. Её можно делать двумя путями.

1) После выполнения запроса проверить поле полученной модели. Ларавель записывает айдишку на свое место после того как запрос выполнился.

$category = Category::create();

$categoryDesc = new CategoryDesc();
$categoryDesc->category_id = $category->id;
$categoryDesc->save();


2) Вы можете сами назначить айди для целой пачки записей ещё до того, как пойдет запрос в базу. Читайте это как "дать своё айди прямо в коде". Его конечно не запишешь потом в INT AUTOINCREMENT, но можно создать поле `uid` и туда его вручную записывать и не использовать автоинкремент совсем. Что бы это дало? Например batch insert (вставку пачками), который запишет сразу целую пачку, если это возможно. Не то чтобы он невозможен и с auto_increment, но вот разделение логики и работы с БД будет невозможно, следующий шаг отталкивается от прошлого, который еще не сделан и не может быть сделан. Без неё - запишет первую половину, потом ляжет на ошибке, а потом надо удалять, и заниматься оборачиванием половины кода в транзакции, хотя они предназначены не для этого. Точнее так, для этого тоже, но они медленны, и сделав их везде - производительность получится нулевая, а ларавель не предоставляя нам удобного пулинга заставляет нас с этим сражаться. Эта проблема появится не сразу, но позже вы поймете, вы нащупали её. То есть транзакция по хорошему одна на вызов скрипта, даже не так - на вызов действия бизнес логики, бывают действия которые многое делают и там будет несколько, но сначала логика, потом запись в бд, потом дальше что-то делаем. А ларавель либо заставит обернуть всю программу в транзакцию, увеличив её время и вызвав блокировки в будущем, либо заставит вас удалять всё руками.

Кое что о пуллинге (пул - бассейн). Пуллинг - это слежение за несколькими соединениями поочередно переключаясь между ними. Реализация для него чем-то похожа на страшное слово "асинхронка". По сути представляет собой что вы не выполняете команду сразу, а записываете их в массив какой-то, а потом вызываете другую команду flush() и они сохраняя порядок как вызывались в одной транзакции одна за другой выполняются. При этом исходные команды могли быть сделаны в разных соединениях с бд и будут выполнены отдельно в каждом. То есть сначала весь ваш код отработал, сформировал массивы, а потом когда все массивы готовы - выполняются подряд все запросы в БД которые нужно в том порядке как вы их вызывали и откатываются все сразу, если хоть один упал. К сожалению если запустить параллельную запись в разные соединения может случится прикол, когда данные в одно из соединений придут раньше чем в другое (и какой-то системный архитектор ясен хер воткнул в базу триггер, потому что это готовое решение), и какой-то код отработает тогда, когда первый кусок ещё не готов (все ещё идет запись). Поэтому мешать запись в БД с другим кодом нинадо. И триггерами соединять несоединяемое тоже. Нужно дождаться окончания записи (особенно если она параллельная), потом вызвать коммит на всех соединениях, и только потом продолжать дальше.

Команда flush() встречается в Doctrine, только она там делает лютейшую тонну магии под капотом, чтобы не просто выполнить написанные вами запросы, а еще считает сделанные изменения в оперативке и потом записывает "как можно меньше запросов". Если честно мне не нравится такая магия, учитывая их жесткую привязку к их собственному языку запросов, автоматическим ненастраиваемым связям, графу зависимостей, и валидациям прямо в коде объектов-данных, написанных в комментах. Мне нравится видеть что я вызвал запрос - запрос выполнился, не вызвал - не выполнился, а не я вызвал 100 запросов, выполнилось почему-то два, где-то по дороге половина данных не долетела, но всё работает. Поэтому я предпочитаю написать для лары простенький пулинг, чем долбаться с неизвестными мне ошибками доктрины, которые часто возникают чертигде в этой магии, и только через пару часов удается найти место, где я запятую не там поставил, а ложилось где хотело вообще.

3) Либо так, как написал выше Дмитрий, используя связи, которые в ларавеле очень читабельны и понятны, но вопрос последовательности и пуллинга все равно открытый.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы