Oracle XML ORA-00978: nested group function without GROUP BY - oracle

I am trying to generate XML using Oracle's XML functions but I keep getting the ORA-00978: nested group function without GROUP BY error message.
I am new to XML and also Oracle's XML functions so it seems I am missing something major however when looking at examples online I can't understand what I am doing wrong.
SQL:
select
xmlelement("apiRequest",
xmlelement("orders",
xmlagg(
xmlelement("order",
xmlelement("no", orders.order_no),
xmlelement("date", orders.date),
xmlelement("orderItems",
xmlagg(
xmlelement("orderItem",
xmlelement("position", order_items.item_position)
)
)
)
)
)
)
)
as xml
from
...
Desired output:
<apiRequest>
<orders>
<order>
<no>1</no>
<date>04/03/2010</date>
<orderItems>
<orderItem>
<position>1</position>
</orderItem>
<orderItem>
<position>2</position>
</orderItem>
<orderItem>
<position>3</position>
</orderItem>
<orderItem>
<position>4</position>
</orderItem>
</orderItems>
</order>
<order>
<no>2</no>
<date>04/03/2010</date>
<orderItems>
<orderItem>
<position>1</position>
</orderItem>
<orderItem>
<position>2</position>
</orderItem>
<orderItem>
<position>3</position>
</orderItem>
<orderItem>
<position>4</position>
</orderItem>
</orderItems>
</order>
</orders>
</apiRequest>

When you do a nested aggregation, you need a GROUP BY clause. The first level of aggregation is at the GROUP BY level, then the result of that is aggregated again.
To illustrate, I used the ORDERS and ORDER_ITEMS tables from the OE schema that Oracle provides.
select
xmlelement("apiRequest",
xmlelement("orders",
xmlagg(
xmlelement("order",
xmlelement("no", order_id),
xmlelement("date", oe.orders.order_date),
xmlelement("orderItems",
xmlagg(
xmlelement("orderItem",
xmlelement("position", oe.order_items.line_item_id)
)
)
)
)
)
)
)
as xml
from oe.orders join oe.order_items using(order_id)
group by order_id, order_date;
The other answer uses a scalar subquery, which I would avoid in this case because it executes one recursive SQL for each order instead of simply joining.

You could put second xmlagg in a subquery, it gives desired result:
select
xmlelement("apiRequest",
xmlelement("orders",
xmlagg(
xmlelement("order",
xmlelement("no", orders.order_no),
xmlelement("date", orders.order_date),
xmlelement("orderItems",
(select xmlagg(
xmlelement("orderItem",
xmlelement("position", order_items.item_position)
)
)
from order_items where order_no = orders.order_no)
))))) as xml
from orders
dbfiddle

Related

How to bulk insert in MariaDB?

I want to ask about bulk insert of MariaDB
While I'm searching for get information about bulk insert,
I found a solution
That is
INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);
But I'm using list parameter in mybatis. so I can't use this solution
INSERT INTO RaffleData
(
raffleNo, storeNo, storeId, favoriteArea, modelId,
size, personName, personId, createDate_join1, createDate_join2,
createDate_web_join, email, birthDate, isOut, address,
creator, createDate, isDelete,
randomNo, rank2, fileNo
)
That is
<foreach item="item" separator="," collection="raffleDataList">
<![CDATA[
(
#{item.raffleNo}, #{item.storeNo}, #{item.storeId}, #{item.favoriteArea}, #{item.modelId},
#{item.size}, #{item.personName}, #{item.personId}, #{item.createDate_join1}, #{item.createDate_join2},
#{item.createDate_web_join}, #{item.email}, #{item.birthDate}, #{item.isOut}, #{item.address},
#{item.creator}, NOW(), false,
#{item.randomNo}, #{item.rank2}, #{item.fileNo}
)
]]>
I want to apply for extended bulk insert for optimize SQL query
But I don't know how to apply list parameters.
I'm not used to writing English Writing.
If you know about this problem, please solution to me

Contains() in XPATH both Tag name and value of attribute

I have the an XML Column and it is similiar to the one mentioned below.
<appl>
<sftp_job name = "test1_ftp">
<ftp_job name = "test2_ftp">
<link name="testlink1">
<tag name ="task1_ftp">
<unix_job name="test123_ftp">
</appl>
My requirement is to get the value of all the atrributes that ends with "_ftp" like mentioned below.
test1_ftp
test2_ftp
task1_ftp
test123_ftp
I have the below SQL statement and it is working fine.
SELECT JOB_NAME
FROM
(SELECT q.jobname AS JOB_NAME
FROM DFTAB1
LEFT JOIN xmltable('/appl/*[contains(name(),"_job") or
contains(name(),"link") or
contains(name(),"task") ]'
PASSING XMLTYPE(DEF) columns jobname VARCHAR2(20) path '#name')q ON (1=1)
)
WHERE JOB_NAME LIKE '%_ftp%'
and this is working.
Is there a way that I can do in a single SELECT statement.
You can combine boolean operators inside your XPath query:
SELECT x.job_name
FROM DFTAB1 d
CROSS JOIN XMLTable(
'/appl/*[(contains(name(),"_job")
or contains(name(),"link")
or contains(name(),"task"))
and (ends-with(#name, "_ftp"))]'
PASSING XMLTYPE(d.DEF)
COLUMNS job_name VARCHAR2(20) path '#name'
) x;
I've used a cross join instead of a left outer join, which was essentially an inner join anyway, and effectively a cross join because of the dummy condition. I've also just called the generated column what you want it to end up as, so you don't need the alias.
Demo with your sample XML (with tag closures)
with DFTAB1 (DEF) as (
select '<appl>
<sftp_job name = "test1_ftp" />
<ftp_job name = "test2_ftp" />
<link name="testlink1" />
<tag name ="task1_ftp" />
<unix_job name="test123_ftp" />
</appl>' from dual
)
SELECT x.job_name
FROM DFTAB1 d
CROSS JOIN XMLTable(
'/appl/*[(contains(name(),"_job")
or contains(name(),"link")
or contains(name(),"task"))
and (ends-with(#name, "_ftp"))]'
PASSING XMLTYPE(d.DEF)
COLUMNS job_name VARCHAR2(20) path '#name'
) x;
JOB_NAME
--------------------
test1_ftp
test2_ftp
test123_ftp
which is the same result your original query gets; task1_ftp isn't included because tag doesn't match your original contains checks.
There is a slight difference, but I think from the question wording this is actually correct and your original wasn't quite. When you do LIKE '%_ftp%' the underscore is also a single-character wildcard, so that would match say abcftp, which I don't think you wanted. The equivalent would be escaped, as LIKE '%\_ftp%' ESCAPE '\'.
A more significant change, as MTO mentioned, is that your query looks for _ftp (or, as mentioned above, in fact just ftp) anywhere in the job name. Your question actually says 'ends with "_ftp"' so your description and query don't match. If you do want it anywhere in the attribute value, just use contains again instead of ends-with:
SELECT x.job_name
FROM DFTAB1 d
CROSS JOIN XMLTable(
'/appl/*[(contains(name(),"_job")
or contains(name(),"link")
or contains(name(),"task"))
and (contains(#name, "_ftp"))]'
PASSING XMLTYPE(d.DEF)
COLUMNS job_name VARCHAR2(20) path '#name'
) x;
though the result is the same with your sample.
SELECT q.jobname AS JOB_NAME
FROM DFTAB1 d
CROSS JOIN
XMLTABLE(
'/appl/*[contains(name(),"_job") or
contains(name(),"link") or
contains(name(),"task") ]'
PASSING XMLTYPE(d.DEF)
columns jobname VARCHAR2(20) path '#name'
) q
WHERE q.JOBNAME LIKE '%_ftp%'

How can I find out, why a Liquibase-migration with Preconditions was MARK_RAN instead of EXECUTED?

I've ran into a problem with a liquibase changeset. The changeset is somehow like that:
<changeSet id="recreate-cool-dbobject" author="WTF">
<preConditions onFail="MARK_RAN">
<tableExists tableName="table_a" />
<tableExists tableName="table_b" />
<tableExists tableName="table_c" />
</preConditions>
<comment>…should be executed if Feature X is available</comment>
<sqlFile
path="resources/view-definition-v2.sql"
relativeToChangelogFile="true"
dbms="oracle"
stripComments="false"
endDelimiter="-- /"
/>
<rollback>
<sqlFile
path="resources/view-definition-v1.sql"
relativeToChangelogFile="true"
dbms="oracle"
stripComments="false"
endDelimiter="-- /"
/>
</rollback>
</changeSet>
I absolutely know that table_a, table_b and table_c do exists… but still all the time the view-definition-v1.sql is executed, and I several times checked if the SQL inside the linked file is valid. If I issue that SQL directly it's executed without any complain.
SQL inside the view-definition-v1.sql file is somewhat like
CREATE OR REPLACE VIEW view_tab_abc
(
col_a
, col_b
, col_c
, CREATED_AT
, UPDATED_AT
) AS
SELECT a.id AS col_a
, b.id AS col_b
, c.id AS col_c
, a.created_at AS CREATED_AT
, a.updated_at AS UPDATED_AT
FROM tab_a a
JOIN tab_b b on b.id=a.tab_b_id
JOIN tab_c c on c.id=b.tab_c_id
WITH READ ONLY
SQL inside the view-definition-v2.sql file is somewhat like
CREATE OR REPLACE VIEW view_tab_abc
(
col_a
, col_b
, col_c
, CREATED_AT
, UPDATED_AT
) AS
SELECT a.id AS col_a
, a.tab_b_id AS col_b
, a.tab_c_id AS col_c
, a.created_at AS CREATED_AT
, a.updated_at AS UPDATED_AT
FROM tab_a a
WITH READ ONLY
I already tried tableName="table_a"or tableName="TABLE_A", still it's like it is not working at all. I got several other changesets that don't show this problem. Does someone has any clue??
Used Liquibase version is 3.4.2, Database is Oracle 12cR1 StandardEdition1
Thanks for any hint and best regards,
B
One possibility would be that it's not looking in the right schema for your tables. Try changing your precondition to have something like this:
<tableExists schemaName="my_schema" tableName="table_a" />
for each of the checks.
The other thing is that it's always worth saving the generated SQL and running that, using the updateSQL parameter if you are using the liquibase command. That way you can be sure that the SQL you run is the same SQL that Liquibase runs.

Filter cte xpath query

Given the following XPath CTE I want to filter on Field2. How can that be accomplished? I'm just learning XPath. Using SQL Server 2008 R2.
WITH ConvertedToXML AS
(
SELECT TOP 10
xml_msg AS AsVarchar,
CAST(xml_msg AS XML) AS AsXml
FROM
Table
ORDER BY
date_received desc
)
SELECT
ConvertedToXML.AsXml.value('(//Field1)[1]', 'varchar(max)') AS Field1,
ConvertedToXML.AsXml.value('(//Field2)[1]', 'Varchar(10)') as Field2,
ConvertedToXML.AsVarchar,
ConvertedToXML.AsXml
Into #TempXML
FROM ConvertedToXML;
If I understand the question correctly, you want to filter rows having Field2 value equals certain value, foo for example, you can do this way :
SELECT
......
......
Into #TempXML
FROM ConvertedToXML
WHERE ConvertedToXML.AsXml.value('(//Field2)[1]', 'Varchar(10)') = 'foo';
Or using XPath predicate to get Field2 elements having value equals "foo", and use exists() method to execute the XPath :
SELECT
......
......
Into #TempXML
FROM ConvertedToXML
WHERE ConvertedToXML.AsXml.exists('(//Field2[.="foo"])[1]', 'Varchar(10)') = 1;

How can I perform a SELECT DISTINCT on all fields except a BLOB?

I'm trying to perform a SELECT DISTINCT query in Oracle, like this:
SELECT
MOVIES.TITLE, CERTIFICATIONS.ID, PROJECTION.DAY, TIME_SLOTS.SLOT,
PROJECTION.REMAINING_SEATS, IMAGES.IMAGE
FROM
[...]
It doesn't work because the column "IMAGES.IMAGE" is a BLOB. I would like to exclude this field from the DISTINCT (because I don't need it to be unique), and I don't know how. I also tried with a GROUP BY clause, but if I try to GROUP on all the fields except the BLOB, Oracle returns this error:
ORA-00979: not a GROUP BY expression
And if I add the field in the GROUP BY clause, then Oracle returns this:
ORA-00932: inconsistent datatypes: expected - got BLOB
What can I do?
SELECT DISTINCT MOVIES.TITLE, CERTIFICATIONS.ID, PROJECTION.DAY, TIME_SLOTS.SLOT, PROJECTION.REMAINING_SEATS
FROM [...]
Distinct is applied to all columns from the SELECT list. And yes, you cannot use LOBs in GROUP BY, UNION, DISTINCT etc because Oracle doesn't know how to compare different LOBs
If you want to retrieve BLOB as well you may try something like this:
SELECT MOVIES.TITLE, CERTIFICATIONS.ID,
PROJECTION.DAY, TIME_SLOTS.SLOT, PROJECTION.REMAINING_SEATS, IMAGES.IMAGE
FROM (
SELECT MOVIES.TITLE, CERTIFICATIONS.ID,
PROJECTION.DAY, TIME_SLOTS.SLOT, PROJECTION.REMAINING_SEATS, IMAGES.IMAGE,
row_number() over (partition by MOVIES.TITLE, CERTIFICATIONS.ID, PROJECTION.DAY, TIME_SLOTS.SLOT, PROJECTION.REMAINING_SEATS
order by PROJECTION.DAY, TIME_SLOTS.SLOT) RW
FROM [...]
) WHERE RW = 1;
But you should understand what are you looking for. For example, the query above group all the columns except a BLOB column, order them by some two columns and assign a row number to each row in the group. The resulting query retrieves only the first row in each group

Resources