Hromadny zapis a hlidani duplicit

altyn
Člen | 6
+
0
-

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?

  1. 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.
  2. 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)?
  3. Zapisovat kazdy zvlast a odchytavat vyjimku UniqueConstraintViolationException? Tady ale uzavre EntityManager, jak jej nasledne pripadne muzu vyrestartovat nebo obnovit?
  4. 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
+
+1
-

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
+
0
-

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
+
+1
-

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)