我正在postgresql中开发一个简单的问题标签数据库关联。一个问题可以有多个标记,一个标记可以关联到多个问题。这就是为什么我有一张M-M表,问号。
使用PHP,我想创建一个问题,并验证标签是否在标签表上不存在。如果确实存在:
不要将我的标记添加到标记表中
将预存在标签与问题标签表中的问题相关联
如果标签不存在,我想将它添加到标签表中,然后创建一个ASAACACION。
为此我试着这样做:

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]);
}

问题是,这只适用于新问题,而不是当我更新问题时。当我做“ccc>”时,我需要检查问题是否存在,然后更新,而不是尝试创建一个新的行“cc>”。经过很多努力,我想不出来。
有什么办法可以做到这一点吗?

最佳答案

我建议在CTE中使用INSERT ... ON CONFLICT DO NOTHING的单个查询(但要正确!).
如果要将其包装成函数,请使用带有VARIADIC输入参数的服务器端SQL函数,以方便使用:

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;

这就创建了数组(或列表)中不存在的所有标记。它把给定的问题和所有的问题联系起来——除非已经联系起来。
它需要对表name中的tags和表(questionid, tagid)中的questiontags进行唯一(或PK)约束。两者都存在于many-to-many implementation中。否则创建每个。
它假设tags.tagid是一个serial列。也应该是这样。
呼叫:
SELECT update_tags(123, 'foo', 'bar');

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

在并发写负载下,它仍然可能失败(即使不太可能)。如果有,请改用ON CONFLICT ... DO UPDATE。详细说明:
How to use RETURNING with ON CONFLICT in PostgreSQL?
关于VARIADIC
Pass array of tags to a plpgsql function and use it in WHERE condition

10-01 07:49
查看更多