oracle-databaseoracle-rest-data-services

Nested stucture in ORDS WS response


We're writing WS with ORDS (Oracle Rest Data Services) and we are able to have simple (flat) json responses. ORDS automatically convert my SYS_REFCURSON in json, for example:

{
  "title": "To Kill a Mockingbird",
  "author": "Harper Lee",
  "yearPublished": 1960,
  "genre": "Fiction",
  "pages": 281
}

Now we want to have some structure, for instance:

{
    "title": "To Kill a Mockingbird",
    "author": {
        "firstName": "Harper",
        "lastName": "Lee"
    },
    "yearFirstPublished": 1960,
    "genre": "Fiction",
    "editions": [
        {
            "yearPublished": 1960,
            "publisher": "J.B. Lippincott & Co.",
            "isbn": "978-0-06-112008-4",
            "pages": 281,
            "language": "English"
        },
        {
            "yearPublished": 2004,
            "publisher": "Time Warner",
            "isbn": "978-0446310789",
            "pages": 374,
            "language": "English"
        },
        {
            "yearPublished": 2015,
            "publisher": " Random House UK Ltd",
            "isbn": "978-1784870799",
            "pages": 485,
            "language": "English"
        }
    ]
}

Here is my database structure

CREATE TABLE BOOK
(
  BOOK_ID     NUMBER(9)  NOT NULL,
  BOOK_NAME   VARCHAR2(500 CHAR) NOT NULL,
  AUTHOR_ID   NUMBER(9) NOT NULL,
  GENRE       VARCHAR2(100) NOT NULL,
  FIRST_PUBLISHED_YEAR NUMBER(4),
  PAGES             NUMBER(4)
);

CREATE TABLE AUTHOR
(
  AUTHOR_ID   NUMBER(9)  NOT NULL,
  FIRST_NAME  VARCHAR2(500 CHAR) NOT NULL,
  LAST_NAME   VARCHAR2(500 CHAR) NOT NULL
);

CREATE TABLE EDITIONS
(
  BOOK_ID   NUMBER(9)  NOT NULL,
  YEAR_PUBLISHED NUMBER(4),
  ISBN   VARCHAR2(13 CHAR) NOT NULL,
  PUBLISHER   VARCHAR2(500 CHAR) NOT NULL,
  PAGES   VARCHAR2(500 CHAR) NOT NULL,
  LANGUAGE   VARCHAR2(50 CHAR) NOT NULL
);

The first json comes from a WS defined as a simple select:

BEGIN
  ORDS.define_service(
    p_module_name    => 'test',
    p_base_path      => 'test/',
    p_pattern        => 'books/getBookById/:book_id',
    p_method         => 'GET',
    p_source_type    => ORDS.source_type_collection_feed,
    p_source         => 'select BOOK_NAME as title, 
    (select FIRST_NAME||'' ''||LAST_NAME from author a where a.author_id = t.author_id) as author,
    FIRST_PUBLISHED_YEAR as yearPublished,
    GENRE as genre,
    PAGES as pages
from BOOK
where BOOK_ID = :book_id;',
    p_items_per_page => 0);

  COMMIT;
END;
/

Is it possibile to get the second json output (I suppose a function/procedure is needed)?

I tried to define the service using a stored procedure returning cursor, but it cannot have the "structure". May be with a procedure that firstly reads the BOOK table and then the EDITIONS one, but how to "merge" the results to achieve the second json?

Thanks, Vjncenzo


Solution

  • With GROUP BY or with LATERAL:

    select json_object(
        'title' : b.book_name,
        'author' : json_object(
            'firstName' : a.first_name,
            'lastName' : a.last_name
        ),
        'yearFirstPublished' : b.first_published_year,
        'genre' : b.genre,
        'editions' : json_arrayagg(
            json_object(
                'yearPublished' : e.year_published,
                'publisher' : e.publisher,
                'isbn' : e.isbn,
                'pages' : e.pages,
                'language' : e.language
            )
        )
         RETURNING CLOB
    ) as "{}book"
    from book b
    join author a using (author_id)
    join editions e using (book_id)
    where book_id = :book_id
    group by book_id, b.book_name, a.first_name, a.last_name, 
        b.first_published_year, b.genre
    ;
    
    select json_object(
        'title' : b.book_name,
        'author' : a.author,
        'yearFirstPublished' : b.first_published_year,
        'genre' : b.genre,
        'editions' : e.editions
         RETURNING CLOB
    ) as "{}book"
    from book b,
    lateral( 
        select json_object(
            'author' : json_object(
                'firstName' : a.first_name,
                'lastName' : a.last_name
            ) 
        ) author
        from author a where a.author_id = b.author_id
    ) a,
    lateral (
        select json_arrayagg(
            json_object(
                'yearPublished' : e.year_published,
                'publisher' : e.publisher,
                'isbn' : e.isbn,
                'pages' : e.pages,
                'language' : e.language
            )
             RETURNING CLOB
        ) editions 
        from editions e where e.book_id = b.book_id
    ) e
    where b.book_id = :book_id
    ;