Hromadny zapis a hlidani duplicit
- altyn
- Člen | 6
Ahoj,
trosku vaham jak pracovat s Doctrinou. Mam situaci, kde do DB chci zapsat
nazvy, ktere jsou unikatni. Najednou chci zapsat vice nazvu. Jake je nejlepsi
reseni?
- Pro kazdy nazev se ptat do DB jestli jiz nazev existuje a pripadne se podle toho zaridit? Tady se bojim zbytecneho dotazovani do DB pri zapisu napr 1000 nazvu.
- Vyjet si vsechny nazvy (nemelo by jich byt az tolik, max par tisic) v DB a kontrolovat pak kazdy zapis s timto polem (+ do nej pridavat nove zapsane)?
- Zapisovat kazdy zvlast a odchytavat vyjimku UniqueConstraintViolationException? Tady ale uzavre EntityManager, jak jej nasledne pripadne muzu vyrestartovat nebo obnovit?
- Je nejaka moznost nad kazdym zaznamem dat persist() a nasledne pri flush() nechat zapsat co projde a odchytit co neprojde? Teoreticky ale chapu, ze toto funguje jako transakce, takze by to nemelo byt spravne reseni.
Existuje pripadne nejaky lepsi overeny postup? Co pouzivate vy a proc?
Moc diky za vas cas!
- Martk
- Člen | 661
Při cronu mám velmi hodně záznamů, které se musejí zapsat, některé můžou existovat. Svoje id si volím sám, proto jsem zvolil INSERT IGNORE INTO …
$values = array_map(
fn (Entity $entity) => [
$entity->getId(),
$entity->getName(),
],
$array,
);
$count = $this->insertion->insertArray(Entity::class, $values, ['id', 'name'], ignore: true);
public function __construct(
private EntityManagerInterface $em,
)
{
}
public function insertArray(string $entity, array $values, array $columns = [], bool $ignore = false): ?int
{
$insert = $this->sqlValues($values);
$metadata = $this->em->getClassMetadata($entity);
$table = $metadata->getTableName();
if (!$insert) {
return null;
}
if ($columns) {
$columns = implode(', ', array_map(fn (string $column) => $metadata->getColumnName($column), $columns));
$columns = ' (' . $columns . ')';
} else {
$columns = '';
}
$sql = sprintf('INSERT%s INTO %s%s %s', $ignore ? ' IGNORE' : '', $table, $columns, $insert);
return $this->em->getConnection()->executeStatement($sql);
}
private function sqlValues(array $values): ?string
{
$sql = null;
foreach ($values as $items) {
$sql .= sprintf(
'(%s), ',
implode(',', array_map(fn (mixed $value) => $this->escape($value), $items))
);
}
return $sql ? 'VALUES ' . substr($sql, 0, -2) : null;
}
private function escape(mixed $value): mixed
{
$type = ParameterType::STRING;
if (is_int($value)) {
$type = ParameterType::INTEGER;
} elseif (is_bool($type)) {
$type = ParameterType::BOOLEAN;
}
return $this->em->getConnection()->quote($value, $type);
}
U tohoto přístupu jen pozor na auto increment
Editoval Martk (15. 7. 2021 14:44)
- altyn
- Člen | 6
Jo, super … diky!
Ale je u tohoto zpusobu nejaka moznost jak ke kazdemu zaznamu zjistit zda byl proveden, pripadne proc nebyl?
Chtel bych mit kontrolu nad tim co se stalo, ale nezapisovat jako samostatne volani pro kazdy zapis.
Pripadne jak se toto resi?
Vrati mi pocet zapsanych zaznamu, porovnam je s poctem zaznamu co jsem chtel
vlozit, kdyz je pocet stejny je to vse OK. Pokud ne, tak dohledam dalsim
dotazem, ktere zaznamy nebyly vytvoreny a vyhodim si nejaky uzivatelsky
message.
Pripadne nejaka jina moznost?
- Martk
- Člen | 661
Zajímá tě, které položky nebyly zapsané nebo zda některé hodnoty nebyly validní?
Jestli to první, tak bych to udělal jako bod 2.
tzn.
$exists = array_column(
$builder
->select('e.id AS id')
->where('e.id IN(:ids)')
->setParameter('ids', array_map(function (array $vals) => $vals['id'], $values))
->getQuery()
->getScalarResult(),
'id'
);
$exists; // tyto id jsou v databázi, takže je můžeš při insert vynechat a vypsat uživateli, že se jedná o duplicity
Editoval Martk (16. 7. 2021 14:06)