SQL Pivot 是不是可以进行以下查询?
Posted
技术标签:
【中文标题】SQL Pivot 是不是可以进行以下查询?【英文标题】:Is the following query possible with SQL Pivot?SQL Pivot 是否可以进行以下查询? 【发布时间】:2014-06-09 08:18:23 【问题描述】:假设我有以下表格:
create table student(
id number not null,
name varchar2(80),
primary key(id)
);
create table class(
id number not null,
subject varchar2(80),
primary key(id)
);
create table class_meeting(
id number not null,
class_id number not null,
meeting_sequence number,
primary key(id),
foreign key(class_id) references class(id)
);
create table meeting_attendance(
id number not null,
student_id number not null,
meeting_id number not null,
present number not null,
primary key(id),
foreign key(student_id) references student(id),
foreign key(meeting_id) references class_meeting(id),
constraint meeting_attendance_uq unique(student_id, meeting_id),
constraint present_ck check(present in(0,1))
);
我想要对每个班级进行查询,其中有一列是学生姓名,该班级的每个 class_meeting 有一列,并且对于每个班级会议,单元格将显示当前属性,如果学生在场,则该属性应为 1在那次会议上,如果学生没有参加那次会议,则为 0。这是一张来自excel的图片供参考:
是否可以制作这样的***报告? 通过谷歌搜索,我认为我必须使用 Pivot,但是我很难理解如何在这里使用它。这是我到目前为止的查询:
select * from(
select s.name, m.present
from student s, meeting_attendance m
where s.id = m.student_id
)
pivot(
present
for class_meeting in ( select a.meeting_sequence
from class_meeting a, class b
where b.id = a.class_id )
)
不过,我确定它已经过时了。甚至可以用一个查询来做到这一点,还是我应该使用 pl sql htp 和 htf 包来创建一个 html 表?
这里是相当缺乏经验的 Oracle 开发人员,因此非常感谢任何帮助。
【问题讨论】:
你能提供一些示例数据吗? 这是一个带有data的pastebin 【参考方案1】:花了一段时间才回答,但我不得不把这些都写下来测试一下!
我使用过的数据:
begin
insert into student(id, name) values (1, 'Tom');
insert into student(id, name) values (2, 'Odysseas');
insert into class(id, subject) values (1, 'Programming');
insert into class(id, subject) values (2, 'Databases');
insert into class_meeting (id, class_id, meeting_sequence) values (1, 1, 10);
insert into class_meeting (id, class_id, meeting_sequence) values (2, 1, 20);
insert into class_meeting (id, class_id, meeting_sequence) values (3, 2, 10);
insert into class_meeting (id, class_id, meeting_sequence) values (4, 2, 20);
insert into meeting_attendance (id, student_id, meeting_id, present) values (1, 1, 1, 1); -- Tom was at meeting 10 about programming
insert into meeting_attendance (id, student_id, meeting_id, present) values (2, 1, 2, 1); -- Tom was at meeting 20 about programming
insert into meeting_attendance (id, student_id, meeting_id, present) values (3, 1, 3, 0); -- Tom was NOT at meeting 10 about databases
insert into meeting_attendance (id, student_id, meeting_id, present) values (4, 1, 4, 0); -- Tom was NOT at meeting 20 about databases
insert into meeting_attendance (id, student_id, meeting_id, present) values (5, 2, 1, 0); -- Odysseas was NOT at meeting 10 about programming
insert into meeting_attendance (id, student_id, meeting_id, present) values (6, 2, 2, 1); -- Odysseas was at meeting 20 about programming
insert into meeting_attendance (id, student_id, meeting_id, present) values (7, 2, 3, 0); -- Odysseas was NOT at meeting 10 about databases
insert into meeting_attendance (id, student_id, meeting_id, present) values (8, 2, 4, 1); -- Odysseas was at meeting 20 about databases
end;
PIVOT 就目前而言,不允许以简单的方式动态列数。它只允许使用 XML 关键字进行此操作,从而生成 xmltype 列。 这里有一些优秀的文档。 http://www.oracle-base.com/articles/11g/pivot-and-unpivot-operators-11gr1.php 首先阅读这些总是值得的。
那该怎么做呢? 一旦你开始搜索,你就会发现很多关于同一件事的问题。
动态 SQL
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4471013000346257238 Dynamically pivoting a table Oracle Dynamic Oracle Pivot_In_Clause经典报表可以将返回 sql 语句的函数体作为返回值。交互式报告不能。就目前而言,IR 是不可能的,因为它过于依赖元数据。
例如,在经典报表区域源中使用这些查询/plsql:
静态枢轴
select *
from (
select s.name as student_name, m.present present, cm.meeting_sequence||'-'|| c.subject meeting
from student s
join meeting_attendance m
on s.id = m.student_id
join class_meeting cm
on cm.id = m.meeting_id
join class c
on c.id = cm.class_id
)
pivot ( max(present) for meeting in ('10-Databases' as "10-DB", '20-Databases' as "20-DB", '10-Programming' as "10-PRM", '20-Programming' as "20-PRM") );
-- Results
STUDENT_NAME '10-Databases' 20-DB 10-PRM 20-PRM
Tom 0 0 1 1
Odysseas 0 1 0 1
函数体返回语句
DECLARE
l_pivot_cols VARCHAR2(4000);
l_pivot_qry VARCHAR2(4000);
BEGIN
SELECT ''''||listagg(cm.meeting_sequence||'-'||c.subject, ''',''') within group(order by 1)||''''
INTO l_pivot_cols
FROM class_meeting cm
JOIN "CLASS" c
ON c.id = cm.class_id;
l_pivot_qry :=
'select * from ( '
|| 'select s.name as student_name, m.present present, cm.meeting_sequence||''-''||c.subject meeting '
|| 'from student s '
|| 'join meeting_attendance m '
|| 'on s.id = m.student_id '
|| 'join class_meeting cm '
|| 'on cm.id = m.meeting_id '
|| 'join class c '
|| 'on c.id = cm.class_id '
|| ') '
|| 'pivot ( max(present) for meeting in ('||l_pivot_cols||') )' ;
RETURN l_pivot_qry;
END;
但请注意区域源中的设置。
使用查询特定的列名并验证查询这是标准设置。它将解析您的查询,然后将在查询中找到的列存储在报告元数据中。如果您继续使用上述 plsql 代码创建报告,您可以看到 apex 已解析查询并分配了正确的列。这种方法的问题在于元数据是静态的。报表的元数据不会在每次运行报表时刷新。 这可以很简单地通过向数据添加另一个类来证明。
begin
insert into class(id, subject) values (3, 'Watch YouTube');
insert into class_meeting (id, class_id, meeting_sequence) values (5, 3, 10);
insert into meeting_attendance (id, student_id, meeting_id, present) values (10, 1, 5, 1); -- Tom was at meeting 10 about watching youtube
end;
在不编辑报告的情况下运行页面!编辑和保存会重新生成元数据,这显然不是一种可行的方法。反正数据会变,不能每次都进去保存报表元数据。
--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
使用通用列名(仅在运行时解析查询)
将源设置为此类型将允许您使用更动态的方法。通过将报告的设置更改为这种类型的解析,apex 将仅在其元数据中生成一定数量的列,而不会直接与实际查询相关联。只有“COL1”、“COL2”、“COL3”、... 运行报告。工作正常。现在再次插入一些数据。
begin
insert into class(id, subject) values (3, 'Watch YouTube');
insert into class_meeting (id, class_id, meeting_sequence) values (5, 3, 10);
insert into meeting_attendance (id, student_id, meeting_id, present) values (10, 1, 5, 1); -- Tom was at meeting 10 about watching youtube
end;
运行报告。工作正常。 但是,这里的问题是列名。他们并不是那么有活力,名字很丑。当然,您可以编辑列,但它们不是动态的。没有显示任何类或任何内容,您也不能可靠地将它们的标题设置为一个。这又是有道理的:元数据在那里,但它是静态的。如果您对这种方法感到满意,它可能对您有用。 但是,您可以处理此问题。在报表的“报表属性”中,可以选择“标题类型”。它们都是静态的,当然期待“PL/SQL”!在这里你可以编写一个函数体(或者只是调用一个函数),它会返回列标题!
DECLARE
l_return VARCHAR2(400);
BEGIN
SELECT listagg(cm.meeting_sequence||'-'||c.subject, ':') within group(order by 1)
INTO l_return
FROM class_meeting cm
JOIN "CLASS" c
ON c.id = cm.class_id;
RETURN l_return;
END;
第三方解决方案
https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:4843682300346852395#5394721000346803830 https://***.com/a/16702401/814048 http://technology.amis.nl/2006/05/24/dynamic-sql-pivoting-stealing-antons-thunder/在 APEX 中: 虽然安装后动态支点更加简单,但在 APEX 中的设置与您想要使用动态 SQL 一样。使用具有通用列名的经典报表。 我不打算在这里详细介绍。我没有在 atm 安装这个包。很好,但在这种情况下,它可能没有那么有用。它纯粹允许您以更简洁的方式编写动态枢轴,但在顶点方面并没有太大帮助。正如我在上面所展示的,顶点报告的动态列和静态元数据是这里的限制因素。使用 XML
我自己之前选择使用 XML 关键字。我使用 pivot 来确保我有所有行和列的值,然后用XMLTABLE
再次读取它,然后创建一个XMLTYPE
列,将其序列化为CLOB
。
这可能有点先进,但这是我迄今为止使用过几次的技术,效果很好。它很快,前提是基础数据不是太大,而且只是一个 sql 调用,所以没有很多上下文切换。我也将它与 CUBE 数据一起使用,效果很好。
(注意:我在元素上添加的类对应于主题1中经典报告中使用的类,简单红色)
DECLARE
l_return CLOB;
BEGIN
-- Subqueries:
-- SRC
-- source data query
-- SRC_PIVOT
-- pivoted source data with XML clause to allow variable columns.
-- Mainly used for convenience because pivot fills in 'gaps' in the data.
-- an example would be that 'Odysseas' does not have a relevant record for the 'Watch Youtube' class
-- PIVOT_HTML
-- Pulls the data from the pivot xml into columns again, and collates the data
-- together with xmlelments.
-- HTML_HEADERS
-- Creates a row with just header elements based on the source data
-- HTML_SRC
-- Creates row elements with the student name and the collated data from pivot_html
-- Finally:
-- serializes the xmltype column for easier-on-the-eye markup
WITH src AS (
SELECT s.name as student_name, m.present present, cm.meeting_sequence||'-'||c.subject meeting
FROM student s
JOIN meeting_attendance m
ON s.id = m.student_id
JOIN class_meeting cm
ON cm.id = m.meeting_id
JOIN class c
ON c.id = cm.class_id
),
src_pivot AS (
SELECT student_name, meeting_xml
FROM src pivot xml(MAX(NVL(present, 0)) AS is_present_max for (meeting) IN (SELECT distinct meeting FROM src) )
),
pivot_html AS (
SELECT student_name
, xmlagg(
xmlelement("td", xmlattributes('data' as "class"), is_present_max)
ORDER BY meeting
) is_present_html
FROM src_pivot
, xmltable('PivotSet/item'
passing meeting_xml
COLUMNS "MEETING" VARCHAR2(400) PATH 'column[@name="MEETING"]'
, "IS_PRESENT_MAX" NUMBER PATH 'column[@name="IS_PRESENT_MAX"]')
GROUP BY (student_name)
),
html_headers AS (
SELECT xmlelement("tr",
xmlelement("th", xmlattributes('header' as "class"), 'Student Name')
, xmlagg(xmlelement("th", xmlattributes('header' as "class"), meeting) order by meeting)
) headers
FROM (SELECT DISTINCT meeting FROM src)
),
html_src as (
SELECT
xmlagg(
xmlelement("tr",
xmlelement("td", xmlattributes('data' as "class"), student_name)
, ah.is_present_html
)
) data
FROM pivot_html ah
)
SELECT
xmlserialize( content
xmlelement("table"
, xmlattributes('report-standard' as "class", '0' as "cellpadding", '0' as "cellspacing", '0' as "border")
, xmlelement("thead", headers )
, xmlelement("tbody", data )
)
AS CLOB INDENT SIZE = 2
)
INTO l_return
FROM html_headers, html_src ;
htp.prn(l_return);
END;
在APEX中:好吧,既然已经构建了HTML,这只能是一个PLSQL区域,它调用包函数并使用HTP.PRN
打印它。
(编辑)OTN 论坛上也有这个帖子,它在很大程度上做同样的事情,但不生成标题等,而是使用顶点功能: OTN: Matrix report
PLSQL
或者,您也可以选择走好的 ol' plsql 路线。您可以从上面的动态 sql 中获取主体,对其进行循环,并通过使用 htp.prn
调用输出一个表结构。放出标题,并放出任何你想要的东西。为获得良好效果,请在与您使用的主题相对应的元素上添加类。
【讨论】:
非常感谢您抽出宝贵时间如此彻底地回答!我已经阅读了文档和许多示例,但是我无法弄清楚如何将它们应用于我的特定案例。你的回答是一个巨大的帮助。【参考方案2】:免责声明:我不具体了解 apex。
这是一个正确的数据透视查询,假设您想要的课程的 ID = 1,并且该课程的 meeting_id 是 1、2、3。
select * from(
select s.name, a.present,m.id meeting_id
from student s, meeting_attendance a, class_meeting m, class c
where s.id = a.student_id
and m.id = a.meeting_id
and c.id = m.class_id
and c.id = 1
)
pivot(
sum(present)
for meeting_id in(1,2,3)
);
我不相信您可以使用子查询来返回枢轴的“for in”的值。
【讨论】:
感谢您花时间回答。然而,我最大的问题是会议的数量是可变的,因此枢轴的 for 子句必须是动态的而不是静态的。可以使用 pivot xml 完成,但这不会给我一个可用的顶点报告。以上是关于SQL Pivot 是不是可以进行以下查询?的主要内容,如果未能解决你的问题,请参考以下文章
获取 ROWS 作为 COLUMNS(SQL Server 动态 PIVOT 查询)
SQL Server - 使用 PIVOT 查询比较 2 个表中的字段