【问题标题】:Storing and comparing unique combinations存储和比较独特的组合
【发布时间】:2015-06-26 06:42:03
【问题描述】:

我需要一个网站上的搜索功能,除此之外,您应该能够选择多个类别。搜索将存储在数据库中,但搜索参数的每个唯一组合只能存储一次,这也包括所选类别的唯一组合。

问题是我不知道如何存储所选类别的组合。我查看了数组并找到了这个http://blog.2ndquadrant.com/postgresql-9-3-development-array-element-foreign-keys/,但显然该功能从未实现过。

因此,如果我需要使用多个表,我想我可以为组合创建一个表,其中每个唯一组合都有一个 id,可以轻松引用和比较,然后另一个表将类别链接到组合。但是通过这种方法,我如何检查组合是否已经存在?我能想到的唯一方法是遍历所有现有组合并检查其中是否有任何一个等于比较组合。

我觉得这不是一个不常见的问题,但我找不到任何这样做的例子。我也觉得我的方法可能不是最好的。非常欢迎任何建议。

I have these two tables currently:

Categories
- CategoryId (int)
- Name (string)

Searches
- SearchId (int)
- Keywords (string)
- ExampleOption1 (bool)
- ExampleOption2 (bool)
- CategoriesCombinationId (int) -- this would represent the unique combination of categories and links to the combination table

这就是我可能尝试解决问题的方法(如果有一种好方法可以检查组合是否已经存在):

CategoriesCombinations -- unique combinations
- CombinationId (int)

CombinedCategories
- CombinationId (int) -- links to id in combinations table
- CategoryId (int) -- links to id in categories table

【问题讨论】:

  • 组合中的绝对最大数量类别是多少?请问你的 Postgres 版本是什么?
  • @Erwin Brandstetter Postgresql 9.4,确实没有最大数量,但目前大约有 200 个类别,所以这些类别的任何可能组合。
  • 每个组合中的类别是否唯一? ('{1,1,1,3,5}' 是否允许?
  • 是的,每个类别在每个组合中只能存在一次。

标签: sql arrays postgresql database-design unique-constraint


【解决方案1】:

对于您当前的方法,您可以使用string_agg 在每个CategoriesCombinations 中拥有所有Categories 的字符串表示形式,并针对新搜索进行检查:

SELECT CombinationId
FROM CombinedCategories
WHERE string_agg(CategoryId, ',') = '84,95,102'
GROUP BY CombinationId

但更简单的方法是根据所有参数为每次搜索计算一个唯一哈希,并将其存储在 Searches 表中,并将新搜索的哈希与搜索历史进行比较。

【讨论】:

    【解决方案2】:

    存储为数组(非规范化)

    我会考虑额外的模块intarray,它提供了方便(和快速)的功能uniq()sort()。在典型的现代 Postgres 安装中,它很简单:

    CREATE EXTENSION intarray;
    

    使用这些,一个简单的CHECK 约束可以强制ascending 具有distinct 元素的数组。

    CHECK (uniq(sort(cat_arr)) = cat_arr)
    

    您可以另外(可选地)拥有一个自动规范化数组值ON INSERT OR UPDATE 的触发器。然后,您可以传递 any 数组(可能未排序且带有欺骗),一切正常。喜欢:

    CREATE OR REPLACE FUNCTION trg_search_insup_bef()
      RETURNS trigger AS
    $func$
    BEGIN
       NEW.cat_arr := uniq(sort(NEW.cat_arr);
       RETURN NEW;
    END
    $func$ LANGUAGE plpgsql;
    
    CREATE TRIGGER insup_bef
    BEFORE INSERT OR UPDATE OF cat_arr ON search
    FOR EACH ROW
    EXECUTE PROCEDURE trg_search_insup_bef();
    

    附加模块intarray是可选的,还有其他方式:

    但是 intarray 函数提供了卓越的性能。

    那么你可以在数组列上创建一个UNIQUE约束来强制整个数组的唯一性。

    UNIQUE (cat_arr)
    

    就在两天前,我在这个相关答案中写了更多关于将(非常严格和可靠的)约束与(不太可靠但更方便)触发器结合起来的优点:

    如果对于每个组合,您需要为每个类别存储的只是 ID(而不是其他信息),这应该足够了。
    但是,参照完整性不容易确保这边走。数组元素没有外键约束(目前)——比如documented in your link:如果其中一个类别被删除或您更改了 ID,则引用中断...

    规范化架构

    如果您需要存储更多内容,或者您​​希望使用规范化架构来强制引用完整性或出于某种原因,您也可以这样做,并添加一个触发器来填充手工制作的物化视图(多余的表)并以类似的方式强制唯一性:

    CREATE TABLE search (
      search_id serial PRIMARY KEY
    , ... more columns
    );
    
    CREATE TABLE cat (
      cat_id serial PRIMARY KEY
    , cat text NOT NULL
    );
    
    CREATE TABLE search_cat (
      search_id int REFERENCES search ON DELETE CASCADE
    , cat_id    int REFERENCES cat
    , PRIMARY KEY (search_id, cat_id)
    );
    

    演示触发器的相关答案(不是针对独特组合,而是针对独特元素):

    【讨论】:

    • 这可行,但我会跳过模块并对数组进行排序并在比较之前删除应用程序中的重复项,除非使用它有一些隐藏的优势。
    • @trist:intarray 函数的优势主要在于性能。使用CHECK,您可以始终依赖列中的一致数据,并且结合UNIQUE 约束,也可以在整个表中使用。如果您仅在应用程序中实现约束,则可靠性仅限于通过应用程序进行独占访问(即使那样它的可靠性也低得多)。 RDBMS 针对这样的工作进行了优化。应用程序代码无法与之竞争。我添加了更多的指针和链接。
    • 只是为了澄清这如何解决我的问题。我意识到您的答案实际上可能是两种可能的解决方案,但我打算同时使用这两种解决方案。该数组允许我轻松检查唯一性,并且该表存储具有外键约束的类别列表。由于该数组仅用于唯一性,因此删除一个类别并将其 id 保留在数组中并不重要,因为该表始终用于获取选定的类别。即使无法再次选择该类别,包含已删除类别的搜索仍将是唯一的。
    猜你喜欢
    • 2010-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-17
    • 2017-02-23
    相关资源
    最近更新 更多