Вставить и связать повторяющиеся данные в таблице

Я разрабатываю простую ассоциацию базы данных тегов вопросов в postgresql. Вопрос может иметь несколько тегов, и один тег может быть связан с несколькими вопросами. Вот почему у меня есть таблица М-М, вопросительные знаки.
Используя php, я хочу создать вопрос и проверить, не существует ли данных тегов в таблице тегов. Если они существуют:

  • Не добавлять мой тег в таблицу тегов
  • Свяжите предварительно существующий тег с вопросом в моей таблице меток

Если тег не существует, я хочу добавить его в таблицу тегов, а затем создать ассоциацию.

Для этого я пытался что-то вроде этого:

function update_tags($questionid, $tags) {
global $conn;

//Check if question already exists. If Yes, delete it from the array -> //EDIT PROPOSES
$questiontags = get_all_tags();
$existant_tags = [];

foreach ($questiontags as $tag_idx => $tag) {
if(in_array($tag['name'], $tags)){
$key = array_search($tag['name'], $tags);
unset($tags[$key]);
$existant_tags[] = $tag['tagid'];
associate_only_tag($tag['tagid'], $questionid);

}
$questiontags[$tag_idx] = $tag['tagid'];
}

foreach ($tags as $tag) {
associate_tag($tag, $questionid);
}

$tags_to_delete = array_diff($questiontags, $existant_tags);

foreach ($tags_to_delete as $tagid) {
delete_tag_from_question($tagid, $questionid);
}
}

function get_all_tags() {

global $conn;

$query=$conn->prepare("SELECT tags.tagid, tags.name FROM tags ");
$query->execute();

return $query->fetchAll();

}

function get_tags_from_question($questionid) {
global $conn;

$query=$conn->prepare("SELECT tags.tagid, tags.name FROM tags
INNER JOIN questiontags
ON tags.tagid = questiontags.tagid
WHERE questiontags.questionid = :question
");
$query->execute(['question' => $questionid]);

return $query->fetchAll();
}

function insert_tag($tag)
{
global $conn;
$stmt = $conn->prepare("INSERT INTO tags (name) VALUES(:tag)");
$stmt->execute([$tag]);
return (int)$conn->lastInsertId();
}

function associate_tag($tag, $questionid)
{
global $conn;
$tagid = insert_tag($tag);
$stmt = $conn->prepare("INSERT INTO questiontags (questionid, tagid) VALUES(:question, :tag)");
$stmt->execute(['question' => $questionid, 'tag' => $tagid]);
}

function associate_only_tag($tagid, $questionid)
{
global $conn;
$stmt = $conn->prepare("INSERT INTO questiontags (questionid, tagid) VALUES(:question, :tag)");
$stmt->execute(['question' => $questionid, 'tag' => $tagid]);
}

function delete_tag_from_question($tagid, $questionid) {
global $conn;

$query = $conn->prepare("DELETE FROM questiontags WHERE questionid = :question AND tagid = :tag");
$query->execute(['question' => $questionid, 'tag' => $tagid]);
}

Проблема в том, что это работает только для нового вопроса, а не когда я обновляю вопрос. Когда я делаю associate_only_tag Мне нужно что-то, что проверяет, существует ли вопрос, а затем обновлять вместо того, чтобы пытаться создать новую строку questiontags, После многих усилий я не могу понять это.

Есть ли способ сделать это?

1

Решение

Я предлагаю один запрос с INSERT ... ON CONFLICT DO NOTHING в CTE (но сделайте это правильно!).
И если вы хотите обернуть это в функцию, используйте функцию SQL на стороне сервера с VARIADIC входной параметр для удобства:

CREATE OR REPLACE FUNCTION update_tags(_questionid int, VARIADIC _tags text[])
RETURNS void AS
$func$
WITH ins_tags AS (
INSERT INTO tags (name)
SELECT * FROM unnest(_tags)
ON     CONFLICT (name) DO NOTHING
RETURNING tagid
)
INSERT INTO questiontags (questionid, tagid)
SELECT _questionid, i.tagid FROM ins_tags i
UNION  ALL
SELECT _questionid, t.tagid FROM tags t
WHERE  t.name = ANY(_tags)
ON     CONFLICT (questionid, tagid) DO NOTHING;
$func$  LANGUAGE sql;

Это создает все теги в массиве (или списке), которые еще не существовали. И это связывает данный вопрос со всеми из них — если они уже не связаны.

Требуется уникальное (или PK) ограничение на name в таблице tags и на (questionid, tagid) в таблице questiontags, Оба обычно существуют в реализация многих ко многим. Остальное создай друг.

И это предполагает tags.tagid быть serial колонка. Также должно быть дело.

Вызов:

SELECT update_tags(123, 'foo', 'bar');

Или же:

SELECT update_tags(123, VARIADIC '{foo,bar}'::text[]);

Это все еще может потерпеть неудачу под одновременная загрузка записи (даже если очень маловероятно). Если у вас есть, используйте ON CONFLICT ... DO UPDATE вместо. Детальное объяснение:

Около VARIADIC:

0

Другие решения

Других решений пока нет …