【问题标题】:Native Oracle Query to fetch One to Many Relationship Structure用于获取一对多关系结构的本机 Oracle 查询
【发布时间】:2023-07-14 03:24:01
【问题描述】:

谁能帮我查询一下oracle中的一对多关系

表结构为:

Table: STUD_NAME                       Table:CLASS                  

STUD_No    STUD_Name                     Class_ID       STUD_No        CLASS_NAME
-------------------                    -------------------------------------------     
1         Sam                            1                1             PHYSICS
2         Michael                        2                1             MATHEMATICS 
3         Patrick                        3                2             PHYSICS
5         Leena                          4                2             CHEMISTRY

我尝试使用连接概念,因此它在 STUD_NAME 表中返回重复的行,例如

  1         Sam  PHYSICS
  1         Sam  CHEMISTRY

我需要一个类似的输出

1 Sam  PHYSICS,MATHEMATICS
2 Michael  PHYSICS,CHEMISTRY

【问题讨论】:

  • 您可以为您的表格(和列)使用别名。所以它会像SELECT A.STUD_NO, A.STUD_NAME, B.CLASS_ID, B.CLASS_NAME FROM STUD_NAME A INNER JOIN CLASS_NAME B ON A.STUD_NO = B.STUD_NO
  • 嗨 Luiggi,我试过这样,但也许它作为名称 SAM 的两行返回,我需要在他正在学习的课程中作为名称 SAM 的单行返回,谢谢
  • 哦,好的,但是您不能通过查询轻松地做到这一点。对于这种输出,您将需要 2 个游标:第一个将获取所有学生的姓名(我假设上面带有过滤器),然后迭代此游标以打开第二个游标,该游标将接收实际学生的 STUD_NO 并将获取他/她拥有的所有类,然后迭代此光标并将所有类的名称连接到 VARCHAR2(some_size) 中,这将完成您的第一个光标的值。
  • 好的,非常感谢您的想法,让我也尝试一下

标签: sql oracle oracle9i string-aggregation


【解决方案1】:
CREATE OR REPLACE TYPE t_string_agg AS OBJECT
(
  g_string  VARCHAR2(32767),

  STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
    RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                       value  IN      VARCHAR2 )
     RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                         returnValue  OUT  VARCHAR2,
                                         flags        IN   NUMBER)
    RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                     ctx2  IN      t_string_agg)
    RETURN NUMBER
);
/
SHOW ERRORS


CREATE OR REPLACE TYPE BODY t_string_agg IS
  STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
    RETURN NUMBER IS
  BEGIN
    sctx := t_string_agg(NULL);
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                       value  IN      VARCHAR2 )
    RETURN NUMBER IS
  BEGIN
    SELF.g_string := self.g_string || ',' || value;
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                         returnValue  OUT  VARCHAR2,
                                         flags        IN   NUMBER)
    RETURN NUMBER IS
  BEGIN
    returnValue := RTRIM(LTRIM(SELF.g_string, ','), ',');
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                     ctx2  IN      t_string_agg)
    RETURN NUMBER IS
  BEGIN
    SELF.g_string := SELF.g_string || ',' || ctx2.g_string;
    RETURN ODCIConst.Success;
  END;
END;
/
SHOW ERRORS


CREATE OR REPLACE FUNCTION string_agg (p_input VARCHAR2)
RETURN VARCHAR2
PARALLEL_ENABLE AGGREGATE USING t_string_agg;
/
SHOW ERRORS

然后执行

select stud_name, string_agg(class_name)
from stud_name s, class c
where s.stud_no = c.stud_no
group by stud_name

到 asktom.oracle.com 的道具

【讨论】:

    【解决方案2】:

    9i 使用:

    select   S.STUD_NO, STUD_NAME, WM_CONCAT(CLASS_NAME)
    from     STUD_NAME S, CLASS C
    where    S.STUD_NO = C.STUD_NO
    group by S.STUD_NO, STUD_NAME
    

    从 11g 开始,您可以使用 listagg 代替 wm_concat。请参阅 here 了解字符串聚合技术的精彩列表。

    【讨论】:

      【解决方案3】:

      您应该能够使用 SYS_CONNECT_BY_PATH 函数执行此操作。对于您的示例,以下内容应该可以工作:

      select
         stud_no,
         stud_name,
         substr(SYS_CONNECT_BY_PATH(class_name, ', '),2) classes
      from
         (
         select     
           cn.stud_no,
           sn.stud_name,
           cn.class_name,
           count(*) OVER ( partition by cn.stud_no ) cnt,
           ROW_NUMBER () OVER ( partition by cn.stud_no order by cn.class_name) seq
         from
           class_name cn
          ,stud_name sn
         where
           sn.stud_no = cn.stud_no
         group by cn.stud_no, sn.stud_name, cn.class_name)
      where
         seq=cnt
      start with
         seq=1
      connect by prior
         seq+1=seq
      and prior
         stud_no=stud_no; 
      

      分解这个...

      内部查询

         select     
           sn.stud_no,
           sn.stud_name,
           cn.class_name,
           count(*) OVER ( partition by cn.stud_no ) cnt,
           ROW_NUMBER () OVER ( partition by cn.stud_no order by cn.class_name) seq
         from
           class_name cn
          ,stud_name sn
         where
           sn.stud_no = cn.stud_no
         group by cn.stud_no, sn.stud_name, cn.class_name
      

      会给出这样的结果集:

          STUD_NO  STUD_NAME  CLASS_NAME  CNT  SEQ
          001      SAM        CHEMISTRY   2    1
          001      SAM        PHYSICS     2    2
          002      MICHAEL    ART         3    1
          002      MICHAEL    HISTORY     3    2
          002      MICHAEL    PHYSICS     3    3
      

      请注意,结果集是有序的,以便每个学生的班级记录根据 stud_no 分组在一起。 CNT 表示记录所属的组(分区)中的记录总数(通过包括具有相同 stud_no 的所有记录的总数),而 SEQ 表示该组内的唯一序列/排名(在这种情况下,基于按类名的字母排序)。

      然后,查询的其余部分通过使用 CNT 和 SEQ 值遍历结果集,为每个学生构建一个以逗号分隔的班级名称列表,如下所示:

      substr(SYS_CONNECT_BY_PATH(class_name, ', '),2) classes
      -- builds a list of class names separated by a comma 
      -- (the substr function is just there to remove the first delimiter)
      
      where seq=cnt -- this condition indicates the last record in each group/list
      
      start with seq=1 
      -- the starting point for each group (i.e. start a new list every time a seq 
      -- value of 1 is encountered while traversing the result set)
      
      connect by prior seq+1=seq and prior stud_no=stud_no 
      -- defines the connection between one list element and the next; the next 
      -- element in a list will have the same stud_no as the prior element AND a 
      -- seq equal to the prior element's seq +1 
      

      结果:

          STUD_NO  STUD_NAME  CLASSES
          001      SAM        CHEMISTRY, PHYSICS
          002      MICHAEL    ART, HISTORY, PHYSICS
      

      这里描述了这种方法和其他一些可能的选项:http://www.dba-oracle.com/t_converting_rows_columns.htm

      希望有帮助!

      【讨论】:

        【解决方案4】:

        问题的格式很糟糕,但让我试着理解一下。

        表:STUD_NAME 列:STUD_No,STUD_Name

        表:类 列:Class_ID、STUD_No、CLASS_NAME

        查询应该返回每个学生和他们正在学习的课程。

        这是我在 SQLite 中做的一个例子...告诉我哪里出了问题...

        sqlite> create table stud_name (stud_no integer, stud_name text);
        sqlite> create table class (class_id integer, stud_no integer, class_name text);
        sqlite> insert into stud_name values (1, 'Sam');
        sqlite> insert into stud_name values (2, 'Mike');
        sqlite> insert into stud_name values (3, 'Pat');
        sqlite> insert into stud_name values (4, 'Leena');
        sqlite> insert into class values (1,1,'Physics');
        sqlite> insert into class values (2,1,'Math');
        sqlite> insert into class values (3,2,'Physics');
        sqlite> select stud_name, class_name from stud_name s, class c
           ...> where s.stud_no = c.stud_no;
        Sam|Math
        Sam|Physics
        Mike|Physics
        

        【讨论】:

        • 是的,你是正经的,我得到了这个输出,但我需要 SAM/MATH,PHYSICS 作为一行,
        • SELECT stud_no , RTRIM ( xmlagg (xmlelement (c, class_name || ',') order by class_name).extract ('//text()') , ',' ) AS classes FROM ( select stud_no, class_name from stud_name s, class c where s.stud_no = c.stud_no) GROUP BY stud_no; --我没有Oracle,所以无法测试...
        • 是的,我试过用这个,结果还是预期的,任何其他解决方案,请
        【解决方案5】:

        好的,在 SQLite 中,我能够通过...完成...

        sqlite> select stud_name, group_concat(class_name)
           ...> from stud_name s, class c
           ...> where s.stud_no = c.stud_no
           ...> group by stud_name
           ...> ;
        Mike|Physics
        Sam|Math,Physics
        

        在 Oracle 9i 中,我相信您可以将上面的 group_concat 替换为 wm_concat。

        告诉我!

        【讨论】:

        • 您好,它显示 ORA-00904:“WM_CONCAT”:无效标识符 00904。00000 - oracle 9i 中的“%s:无效标识符”
        • 同样的问题,ORA-00904:“WMSYS”。“WM_CONCAT”:无效标识符 00904。00000 -“%s:无效标识符” *原因:*操作:第 36 行错误:第 3 列
        • 好的,我们需要创建自己的函数然后......看下一个答案