In 12cR1 you could do something like:
select jt.studentname,
max(case when jt.classname = 'CS220' then jt.teachername end) as teachername
from mytable mt
cross join json_table (
mt.jsonfield,
'$'
columns (
studentname varchar2(30) path '$.studentName',
nested path '$.classes[*]' columns (
classname varchar2(30) path '$.className',
teachername varchar2(30) path '$.teacherName'
)
)
) jt
group by jt.studentname;
The json_table() splits the JSON into relational columns; the nested path means you get one row per class (per student), with the relevant class names and teacher names.
The select list then uses a case expression to change the teacher name to null for any other classes - so John Smith gets one row with CS220 and Jason Wu, and one row with CS115 and null. Aggregating that with max() collapses those so all the irrelevant teachers are ignored.
With some expanded sample data:
create table mytable (jsonfield clob check (jsonfield is json));
insert into mytable a(jsonfield) values (q'#{
'studentName': 'John Smith',
'classes': [
{
'className': 'CS115',
'teacherName': 'Sally Wilson'
},
{
'className': 'CS220',
'teacherName': 'Jason Wu'
}
]
}#');
insert into mytable a(jsonfield) values (q'#{
'studentName': 'Jane Doe',
'classes': [
{
'className': 'CS115',
'teacherName': 'Sally Wilson'
}
]
}#');
insert into mytable a(jsonfield) values (q'#{
'studentName': 'Ajay Kumar',
'classes': [
{
'className': 'CS220',
'teacherName': 'Robert Kroll'
}
]
}#');
the basic json_table() call gets:
select jt.*,
case when jt.classname = 'CS220' then jt.teachername end as adjusted_teachername
from mytable mt
cross join json_table (
mt.jsonfield,
'$'
columns (
studentname varchar2(30) path '$.studentName',
nested path '$.classes[*]' columns (
classname varchar2(30) path '$.className',
teachername varchar2(30) path '$.teacherName'
)
)
) jt;
STUDENTNAME CLASSNAME TEACHERNAME ADJUSTED_TEACHERNAME
------------------------------ ------------------------------ ------------------------------ ------------------------------
John Smith CS115 Sally Wilson
John Smith CS220 Jason Wu Jason Wu
Jane Doe CS115 Sally Wilson
Ajay Kumar CS220 Robert Kroll Robert Kroll
Adding the aggregation step gets:
select jt.studentname,
max(case when jt.classname = 'CS220' then jt.teachername end) as teachername
from mytable mt
cross join json_table (
mt.jsonfield,
'$'
columns (
studentname varchar2(30) path '$.studentName',
nested path '$.classes[*]' columns (
classname varchar2(30) path '$.className',
teachername varchar2(30) path '$.teacherName'
)
)
) jt
group by jt.studentname;
STUDENTNAME TEACHERNAME
------------------------------ ------------------------------
John Smith Jason Wu
Jane Doe
Ajay Kumar Robert Kroll
In 12cR2 I think thought you might be able to do something like this instead, with a filter inside the JSON path (which isn't allowed in 12cR1):
select jt.*
from mytable mt
cross join json_table (
mt.jsonfield,
'$'
columns (
studentname varchar2(30) path '$.studentName',
nested path '$.classes[*]?(@.className=="CS220")' columns (
teachername varchar2(30) path '$.teacherName'
)
)
) jt;
... but I don't have a suitable DB to test that against.
... but it turns out that gets "ORA-40553: path expression with predicates not supported in this operation" and "Only JSON_EXISTS supports predicates".
json_table()and a nested path with a filter...nested path '$.classes[*]?(@.className=="CS220")'rather than a where-clause filter, but as that isn't valid in release 1 that doesn't help much...