【问题标题】:get result same time in multiple table在多个表中同时获得结果
【发布时间】:2015-11-30 05:14:03
【问题描述】:

我有如下表,我试图找到如何 得到的结果是文章 id 数组或文章行, 同时匹配ArticleTag0ArticleTag1 表中的标签'hiphop'、'rock'、'single'。

现在我使用下面的代码,
获取以不同标签类型标记的每篇文章 id 数组,

例如,如果文章 x Tag0 中标记的文章 ID 行是 [0, 2, 4, 6] 并且文章 x Tag1 是 [0, 4, 9],
然后比较每个数组,得到上面两个数组中的数字 get ,[0, 4]。

我想知道有没有更好的查询一次选择它们?考虑性能

表格

    CREATE TABLE IF NOT EXISTS "Article"(
    "ArticleId" SERIAL NOT NULL,
    "PublishDate" timestamp without time zone,
    "Active" bit NOT NULL,
    PRIMARY KEY ("ArticleId")
    );

    CREATE TABLE IF NOT EXISTS "Tag0"(
    "TagId" SERIAL NOT NULL,
    "Name" varchar,
    "Active" bit NOT NULL,
    PRIMARY KEY ("TagId")
    );

    CREATE TABLE IF NOT EXISTS "Tag1"(
    "TagId" SERIAL NOT NULL,
    "Name" varchar,
    "Active" bit NOT NULL,
    PRIMARY KEY ("TagId")
    );


    CREATE TABLE IF NOT EXISTS "ArticleTag0"(
    "ArticleTagId" SERIAL NOT NULL,
    "ArticleId" integer NOT NULL,
    "TagId" integer NOT NULL,
    FOREIGN KEY ("ArticleId") REFERENCES "Article" ("ArticleId") ON DELETE CASCADE ON UPDATE CASCADE,
    FOREIGN KEY ("TagId") REFERENCES "Tag0" ("TagId") ON DELETE CASCADE ON UPDATE CASCADE,
    PRIMARY KEY ("ArticleTagId")
    );

    CREATE TABLE IF NOT EXISTS "ArticleTag1"(
    "ArticleTagId" SERIAL NOT NULL,
    "ArticleId" integer NOT NULL,
    "TagId" integer NOT NULL,
    FOREIGN KEY ("ArticleId") REFERENCES "Article" ("ArticleId") ON DELETE CASCADE ON UPDATE CASCADE,
    FOREIGN KEY ("TagId") REFERENCES "Tag1" ("TagId") ON DELETE CASCADE ON UPDATE CASCADE,
    PRIMARY KEY ("ArticleTagId")
    );

代码

用户输入参数
inputGenres (Tag0) - [ 'hiphop', 'rock' ]
inputReleaseType (Tag1) - ['single']

    ... 
    // inputGenres
    var inputGenresArticleIdList = [];

    if (inputGenres[0] == 'all') {
      var query = 'SELECT * FROM "Article"';
      var params = [];

      var selectArticle = yield crudDatabase(db,query,params);
      if (typeof selectArticle.error !== 'undefined') {
        response.meta.code = '500';
      } else {
        for (var i = 0; i < selectArticle.result.rows.length; i++) {
          inputGenresArticleIdList.push(selectArticle.result.rows[i].ArticleId);
        }
      }
    } else {
      var query = 'SELECT DISTINCT ON ("ArticleId") * FROM "ArticleTag0" LEFT OUTER JOIN "Tag0" ON ("ArticleTag0"."TagId" = "Tag0"."TagId") WHERE "Name" IN (';
      for (var i = 0; i < inputGenres.length; i++) {
        if (i > 0) {
          query += ',';
        }
        query += '$' + (i + 1);
      }
      query += ')';
      var params = inputGenres;

      var selectArticleTag0 = yield crudDatabase(db,query,params);
      if (typeof selectArticleTag0.error !== 'undefined') {
        response.meta.code = '500';
      } else {
        for (var i = 0; i < selectArticleTag0.result.rows.length; i++) {
          inputGenresArticleIdList.push(selectArticleTag0.result.rows[i].ArticleId);
        }
      }
    }
    console.log(inputGenresArticleIdList);
    // end: inputGenres


    // inputReleaseType
    var inputReleaseTypeArticleIdList = [];

    if (inputReleaseType[0] == 'all') {
      var query = 'SELECT * FROM "Article"';
      var params = [];

      var selectArticle = yield crudDatabase(db,query,params);
      if (typeof selectArticle.error !== 'undefined') {
        response.meta.code = '500';
      } else {
        for (var i = 0; i < selectArticle.result.rows.length; i++) {
          inputReleaseTypeArticleIdList.push(selectArticle.result.rows[i].ArticleId);
        }
      }
    } else {
      var query = 'SELECT DISTINCT ON ("ArticleId") * FROM "ArticleTag4" LEFT OUTER JOIN "Tag4" ON ("ArticleTag4"."TagId" = "Tag4"."TagId") WHERE "Name" IN (';
      for (var i = 0; i < inputReleaseType.length; i++) {
        if (i > 0) {
          query += ',';
        }
        query += '$' + (i + 1);
      }
      query += ')';
      var params = inputReleaseType;

      var selectArticleTag4 = yield crudDatabase(db,query,params);
      if (typeof selectArticleTag4.error !== 'undefined') {
        response.meta.code = '500';
      } else {
        for (var i = 0; i < selectArticleTag4.result.rows.length; i++) {
          inputReleaseTypeArticleIdList.push(selectArticleTag4.result.rows[i].ArticleId);
        }
      }
    }
    console.log(inputReleaseTypeArticleIdList);
    // end: inputReleaseType

    ... then loop each array and compare

【问题讨论】:

    标签: javascript sql node.js postgresql


    【解决方案1】:

    通常最好用一条 SQL 语句来完成这项工作:它不太复杂,它节省了往返服务器的往返行程,它利用了数据库的算法和优化。

    你可以使用下面的语句得到你想要的结果:

    select *
    from   "Article" a
    where  exists (
             select 1
             from   "ArticleTag0" at0,
                    "Tag0" t0
             where  at0."ArticleId" = a."ArticleId"
             and    t0."TagId" = at0."TagId" 
             and    t0."Name" in ('hiphop','rock')
           )
    and    exists (
             select 1
             from   "ArticleTag1" at1, 
                    "Tag1" t1 
             where  at1."ArticleId" = a."ArticleId"
             and    t1."TagId" = at1."TagId" 
             and    t1."Name" in ('single')
           );
    

    当然,您仍然需要更改文字(如'hiphop')来绑定变量($i)。标签的“全部”选项可以通过用true 替换适当的exists(...) 块来完成。


    但我建议对您的架构进行一些重新设计。用数组表示一篇文章的标签怎么样?

    create table article(
      articleId   serial primary key,
      publishDate timestamp without time zone,
      active      boolean, -- clearer that 'bit'
      genres      text[],  -- array of genre tags
      releases    text[]   -- array of release tags
    );
    

    好处:

    1. 文章和标签之间不需要中间表(如ArticleTag0)。
    2. 更简单的查询。
    3. 可以使用具有索引支持的数组重叠操作。

    让我们插入一些值:

    tags=# insert into article(publishDate,active,genres,releases) values ('2015-09-01',true,'{"hiphop"}','{"single"}');
    INSERT 0 1
    tags=# insert into article(publishDate,active,genres,releases) values ('2015-10-01',true,'{"rock","blues"}','{"album"}');
    INSERT 0 1
    tags=# insert into article(publishDate,active,genres,releases) values ('2015-11-01',true,'{"pop"}','{"ep"}');
    INSERT 0 1
    
    tags=# select * from article;
     articleid |     publishdate     | active |    genres    | releases 
    -----------+---------------------+--------+--------------+----------
             1 | 2015-09-01 00:00:00 | t      | {hiphop}     | {single}
             2 | 2015-10-01 00:00:00 | t      | {rock,blues} | {album}
             3 | 2015-11-01 00:00:00 | t      | {pop}        | {ep}
    (3 rows)
    

    现在的查询尽可能简单明了(&amp;&amp; 运算符表示“重叠”):

    tags=# select * from article where genres && '{"hiphop","rock"}' and releases && '{"single"}';
     articleid |     publishdate     | active |  genres  | releases 
    -----------+---------------------+--------+----------+----------
             1 | 2015-09-01 00:00:00 | t      | {hiphop} | {single}
    (1 row)
    

    它还简化了查询文本的构造:使用select * from article where genres &amp;&amp; $1 and releases &amp;&amp; $2; 并为$1$2 生成适当的字符串。

    为了加快查询速度,您可以为数组创建两个支持 &amp;&amp; 运算符的 GIN 索引:

    create index on article using gin(genres);
    create index on article using gin(releases);
    

    【讨论】:

    • 哇非常感谢您的描述,也感谢重新设计架构:)
    • 我有疑问是否遵循重新设计架构使用数组类型列。那么如果我想删除所有文章行中genres每个数组中的所有hiphop值(不删除文章只删除特定标签),该怎么做?并且性能比设计前足够好?
    • update article set genres = array_remove(genres, 'hiphop') where '{"hiphop"}' &lt;@ genres;postgresql.org/docs/9.4/static/arrays.htmlpostgresql.org/docs/9.4/static/functions-array.html
    • 这个可以使用索引,所以我相信性能还可以。虽然你应该测试你的数据。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-18
    • 2022-07-15
    • 1970-01-01
    • 2015-12-16
    • 1970-01-01
    相关资源
    最近更新 更多