暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

子查询返回多列







作者: 李晓光(黑哥)


子查询返回多列可以使用join的方式,但有些需求join效率不如在子查询中返回多列那么好。具体使用子查询中返回多列还是join请根据需求和执行计划决定。


快速阅读请从第3节开始。









01

创建测试表


sql

    drop table if exists deppeoples;
    drop table if exists departments;
    drop type if exists deppeoples01;
    /****************************************************************************************
    部门表
    ****************************************************************************************/
    create table departments(
    objectid serial not null, --唯一编号
    parentid integer not null, --上级部门,指向本表的objectid,0表示最顶级部门
    name text not null, --部门名称
    describe text, --部门备注
    generate timestamptz default(now()) not null, --创建时间
    state integer default(2) not null, --状态.0已无效,(1<<1)正常
    constraint pk_departments_objectid primary key(objectid) with (fillfactor=80)
    ) with (fillfactor=80);
    create index idx_departments_parentid on departments(parentid) with (fillfactor=80);




    /****************************************************************************************
    部门人员表
    ****************************************************************************************/
    create table deppeoples(
    objectid bigserial not null, --唯一编号
    depid integer not null, --部门编号,外键(departments->objectid,级联删除)
    name text not null, --姓名
    title integer not null, --职务.来自字典,测试数据用1-10分别表示,数据越大职务越高
    tel text not null, --联系电话
    sex integer not null, --性别.来自字典,测试数据随机生成
    national integer not null, --民族.来自字典
    mail text, --邮箱
    describe text, --备注
    generate timestamptz default(now()) not null, --创建时间
    state integer default(2) not null, --状态.0已无效,(1<<1)正常
    constraint pk_deppeoples_objectid primary key(objectid) with (fillfactor=80),
    constraint fk_deppeoples_contid foreign key(depid) references departments(objectid) on delete cascade
    ) with (fillfactor=80);
    create index idx_deppeoples_depid on deppeoples(depid,title) with (fillfactor=80);
    ```


    02

    测试数据


    2.1 部门测试数据
    创建"xxxxxx集团公司",在"xxxxxx集团公司"下创建100个部门,部门名称在数据1514736000-1546272000之间随机生成。

    sql

      insert into departments(parentid,name) values(0,'xxxxxx集团公司');
      insert into departments(parentid,name)
      select
      1 as parentid,
      (random()*(1546272000-1514736000)+1514736000)::bigint as name
      from generate_series(1,999);
      ```


      2.2 部门人员测试数据

      创建100万部门人员,随机分布在各个部门,人员名称在数据1514736000-1546272000之间随机生成。同时每个部门的最高领导只有1人。

      sql

        do $$
        declare
        v_start bigint;
        v_end bigint;
        begin
        for i in 1..1000 loop
        v_start := (i-1)*1000 + 1;
        v_end := v_start + 999;




        insert into deppeoples(depid,name,title,tel,sex,national)
        select
        (random()*(100-1)+1)::integer as depid,
        (random()*(1546272000-1514736000)+1514736000)::bigint as name,
        (random()*(9-1)+1)::integer as title,
        (random()*(13999999999-13000000000)+13000000000)::bigint as tel,
        (random()*(3-1)+1)::integer as sex,
        (random()*(54-1)+1)::integer as national
        from generate_series(v_start,v_end);
        raise notice '%,%', v_start,v_end;
        end loop;
        end;
        $$;
        ```


        2.3 生成每个部门的最高领导

        sql

          insert into deppeoples(depid,name,title,tel,sex,national)
          select
          objectid as depid,
          (random()*(1546272000-1514736000)+1514736000)::bigint as name,
          10 as title,
          (random()*(13999999999-13000000000)+13000000000)::bigint as tel,
          (random()*(3-1)+1)::integer as sex,
          (random()*(54-1)+1)::integer as national
          from departments;
          ```

          2.4 测试数据vacuum

          为保证测试的准确性,生成完成后运行vacuum。

          sql

            vacuum  freeze verbose analyze departments;
            vacuum freeze verbose analyze deppeoples;
            ```

            03

            查询各部门的最高领导



            3.1 join方式

            sql


              --禁用并行
              set max_parallel_workers_per_gather=0;


              explain (analyze,verbose,costs,buffers,timing)
              select
              t1.name as department,
              t2.name,t2.title,t2.tel
              from departments as t1
              left join deppeoples as t2 on t2.depid=t1.objectid
              where t2.title=10;
              ```
              执行5次,取最后一次,可以看到left join共扫描了3012页,用时3.296 ms
              ```bash
              QUERY PLAN
              ---------------------------------------------------------------------------------------
              --------------------------------------------------------------
              Nested Loop (cost=0.42..7796.00 rows=1 width=38) (actual time=3.257..3.257 rows=0 loops=1)
              Output: t1.name, t2.name, t2.title, t2.tel
              Buffers: shared hit=3012
              -> Seq Scan on public.departments t1
              (cost=0.00..21.00 rows=1000 width=15)
              (actual time=0.009..0.200 rows=1000 loops=1)
              Output: t1.objectid, t1.parentid, t1.name, t1.describe, t1.generate, t1.state
              Buffers: shared hit=11
              -> Index Scan using idx_deppeoples_depid on public.deppeoples t2
              (cost=0.42..7.76 rows=1 width=31)
              (actual time=0.003..0.003 rows=0 loops=1000)
              Output: t2.objectid, t2.depid, t2.name, t2.title, t2.tel, t2.sex, t2."national",
              t2.mail, t2.describe, t2.generate, t2.state
              Index Cond: ((t2.depid = t1.objectid) AND (t2.title = 10))
              Buffers: shared hit=3001
              Planning Time: 0.314 ms
              Execution Time: 3.296 ms
              (12 rows)
              ```

              3.2 子查询返回多列的方式

              sql

                --重点创建row类型,定义输入的列名称和类型
                drop type if exists deppeoples01;
                create type deppeoples01 as (name1 text,title1 integer,tel1 text);


                explain (analyze,verbose,costs,buffers,timing)
                with cte as(
                select
                t1.name as department,
                (select row(name,title,tel)::deppeoples01 as r from deppeoples as t2 where
                t2.depid=t1.objectid and title=10 order by objectid desc limit 1)
                from departments as t1
                )select department,(r).name1,(r).title1,(r).tel1 from cte;
                ```
                执行5次,取最后一次,可以看到子查询共扫描了3012页,用时8.827 ms
                ```bash
                QUERY PLAN
                --------------------------------------------------------------------------------------------------
                -------------------------------------------------------------------------
                CTE Scan on cte (cost=8481.00..8501.00 rows=1000 width=100) (actual time=0.042..8.584 rows=1000 loops=1)
                Output: cte.department, (cte.r).name1, (cte.r).title1, (cte.r).tel1
                Buffers: shared hit=3012
                CTE cte
                -> Seq Scan on public.departments t1 (cost=0.00..8481.00 rows=1000 width=43) (actual time=0.036..7.363 rows=1000 loops=1)
                Output: t1.name, (SubPlan 1)
                Buffers: shared hit=3012
                SubPlan 1
                -> Limit (cost=8.46..8.46 rows=1 width=40) (actual time=0.006..0.006 rows=0 loops=1000)
                Output: (ROW(t2.name, t2.title, t2.tel)::deppeoples01), t2.objectid
                Buffers: shared hit=3001
                -> Sort (cost=8.46..8.46 rows=1 width=40) (actual time=0.005..0.005 rows=0 loops=1000)
                Output: (ROW(t2.name, t2.title, t2.tel)::deppeoples01), t2.objectid
                Sort Key: t2.objectid DESC
                Sort Method: quicksort Memory: 25kB
                Buffers: shared hit=3001
                -> Index Scan using idx_deppeoples_depid on public.deppeoples t2
                (cost=0.42..8.45 rows=1 width=40)
                (actual time=0.003..0.003 rows=0 loops=1000)
                Output: ROW(t2.name, t2.title, t2.tel)::deppeoples01, t2.objectid
                Index Cond: ((t2.depid = t1.objectid) AND (t2.title = 10))
                Buffers: shared hit=3001
                Planning Time: 0.237 ms
                Execution Time: 8.827 ms
                (22 rows)
                ```

                04

                小结


                - 在本例中没有过多的优化,主要说明子查询返回多列效果,用join时如果有多个最高部门领导的话效率不如子查询;
                - 在本列3.2节中介绍了子查询返回多列的用法。重点为定义类型,然后用row把输出列包装起来,然后转换为定义的类型,定义的类型要和row中输出列类型完全一至,类型名称可以和列名称可以相同也可以不同;
                - 网上比较普遍的声音说是不要使用子查询,实际应该结合自己的需求和执行计划决定采用哪种;
                - 不要偏听偏信,关键业务SQL需要不同表达与关键配置调整的对比验证,适合自己的才是最好的。

                I Love PG

                关于我们

                中国开源软件推进联盟PostgreSQL分会(简称:PG分会)于2017年成立,由国内多家PG生态企业所共同发起,业务上接受工信部产业发展研究院指导。PG分会致力于构建PG产业生态,推动PG产学研用发展,是国内一家PG行业协会组织。



                欢迎投稿

                做你的舞台,show出自己的才华 。

                投稿邮箱:partner@postgresqlchina.com

                                               

                                                 ——愿能安放你不羁的灵魂


                技术文章精彩回顾




                PostgreSQL学习的九层宝塔
                PostgreSQL职业发展与学习攻略
                搞懂PostgreSQL数据库透明数据加密之加密算法介绍
                一文读懂PostgreSQL-12分区表
                PostgreSQL源码学习之:RegularLock
                Postgresql源码学习之词法和语法分析
                PostgreSQL buffer管理
                最佳实践—PG数据库系统表空间重建
                PostgreSQL V12中的流复制配置
                2019,年度数据库舍 PostgreSQL 其谁?
                PostgreSQL使用分片(sharding)实现水平可扩展性
                一文搞懂PostgreSQL物化视图
                PostgreSQL原理解析之:PostgreSQL备机是否做checkpoint
                PostgreSQL复制技术概述

                PG活动精彩回顾




                见证精彩|PostgresConf.CN2019大会盛大开幕
                PostgresConf.CN2019大会DAY2|三大分论坛,精彩不断
                PostgresConf.CN2019培训日|爆满!Training Day现场速递!
                「PCC-Training Day」培训日Day2圆满结束,PCC2019完美收官
                创建PG全球生态!PostgresConf.CN2019大会盛大召开
                首站起航!2019“让PG‘象’前行”上海站成功举行
                走进蓉城丨2019“让PG‘象’前行”成都站成功举行
                中国PG象牙塔计划发布,首批合作高校授牌仪式在天津举行
                PostgreSQL实训基地落户沈阳航空航天大学和渤海大学,高校数据库课改正当时
                群英论道聚北京,共话PostgreSQL
                相聚巴厘岛| PG Conf.Asia 2019  DAY0、DAY1简报
                相知巴厘岛| PG Conf.Asia 2019 DAY2简报
                相惜巴厘岛| PG Conf.Asia 2019 DAY3简报
                独家|硅谷Postgres大会简报
                全球规模最大的PostgreSQL会议等你来!

                PG培训认证精彩回顾




                关于中国PostgreSQL培训认证,你想知道的都在这里!
                首批中国PGCA培训圆满结束,首批认证考试将于10月18日和20日举行!
                中国首批PGCA认证考试圆满结束,203位考生成功获得认证!
                中国第二批PGCA认证考试圆满结束,115位考生喜获认证!
                请查收:中国首批PGCA证书!
                重要通知:三方共建,中国PostgreSQL认证权威升级!
                一场考试迎新年 | 12月28日,首次PGCE中级认证考试开考!
                近500人参与!首次PGCE中级、第三批次PGCA初级认证考试落幕!


                最后修改时间:2020-03-24 08:59:31
                文章转载自开源软件联盟PostgreSQL分会,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论