oos-utils icon indicating copy to clipboard operation
oos-utils copied to clipboard

SQL Select or refCursor to HTML

Open zhudock opened this issue 9 years ago • 14 comments

Not sure if anyone else sees the benefit, but I'd be interested in a function added to oos_util_web that accepts either a refCursor or a select statement, and returns an HTML table.

I found this example on Tom Kyte's blog, but it's missing the line to close the context

http://tkyte.blogspot.com/2006/01/i-like-online-communities.html

Here's my slightly modified version that closes the context and allows an additional param for full html document or table-only

CREATE OR REPLACE FUNCTION fncRefCursor2HTML (
   rf           SYS_REFCURSOR,
   full_html    BOOLEAN DEFAULT FALSE)
   RETURN CLOB
IS
   lRetVal       CLOB;
   lHTMLOutput   XMLTYPE;

   lXSL          CLOB;
   lXMLData      XMLTYPE;

   lContext      DBMS_XMLGEN.CTXHANDLE;
BEGIN
   -- get a handle on the ref cursor --
   lContext := DBMS_XMLGEN.NEWCONTEXT (rf);
   -- setNullHandling to 1 (or 2) to allow null columns to be displayed --
   DBMS_XMLGEN.setNullHandling (lContext, 2);
   -- create XML from ref cursor --
   lXMLData := DBMS_XMLGEN.GETXMLTYPE (lContext, DBMS_XMLGEN.NONE);

   -- this is a generic XSL for Oracle's default XML row and rowset tags --
   -- " " is a non-breaking space --
   lXSL := lXSL || q'[<?xml version="1.0" encoding="ISO-8859-1"?>]';
   lXSL :=
         lXSL
      || q'[<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">]';
   lXSL := lXSL || q'[ <xsl:output method="html"/>]';
   lXSL := lXSL || q'[ <xsl:template match="/">]';
   IF full_html = TRUE
   THEN
      lXSL := lXSL || q'[ <html>]';
      lXSL := lXSL || q'[  <body>]';
   END IF;
   lXSL := lXSL || q'[   <table border="1">]';
   lXSL := lXSL || q'[     <tr bgcolor="cyan">]';
   lXSL := lXSL || q'[      <xsl:for-each select="/ROWSET/ROW[1]/*">]';
   lXSL := lXSL || q'[       <th><xsl:value-of select="name()"/></th>]';
   lXSL := lXSL || q'[      </xsl:for-each>]';
   lXSL := lXSL || q'[     </tr>]';
   lXSL := lXSL || q'[     <xsl:for-each select="/ROWSET/*">]';
   lXSL := lXSL || q'[      <tr>]';
   lXSL := lXSL || q'[       <xsl:for-each select="./*">]';
   lXSL := lXSL || q'[        <td><xsl:value-of select="text()"/> </td>]';
   lXSL := lXSL || q'[       </xsl:for-each>]';
   lXSL := lXSL || q'[      </tr>]';
   lXSL := lXSL || q'[     </xsl:for-each>]';
   lXSL := lXSL || q'[   </table>]';
   IF full_html = TRUE
   THEN
      lXSL := lXSL || q'[  </body>]';
      lXSL := lXSL || q'[ </html>]';
   END IF;
   lXSL := lXSL || q'[ </xsl:template>]';
   lXSL := lXSL || q'[</xsl:stylesheet>]';

   -- XSL transformation to convert XML to HTML --
   lHTMLOutput := lXMLData.transform (XMLType (lXSL));
   -- convert XMLType to Clob --
   lRetVal := lHTMLOutput.getClobVal ();

   DBMS_XMLGEN.CLOSECONTEXT (lContext);

   RETURN lRetVal;
END fncRefCursor2HTML;

zhudock avatar Mar 23 '16 14:03 zhudock

@zhudock very nice example of translating the XML to HTML. A similar problem came up for me the other day and may use it for a solution.

I've tagged this as a Future release item as I don't think it'll go in the initial version. I want to give it some more thought as to a more global approach. One idea that comes to mind is something similar but to csv.

martindsouza avatar Apr 05 '16 04:04 martindsouza

Glad you were able to use it! Bill and Tom did all the work, I just added a couple tiny changes.

I see a lot of benefit to a to-CSV version, or possibly making the xslt a parameter as well so users can generate a variety of output. My free time is at a minimum right now but I'll see what I can come up with.

Feel free to change the function name too.

zhudock avatar Apr 05 '16 11:04 zhudock

This is a great pattern but I struggle a bit how this could be a "general purpose" ? I have used the similar kind of pattern too (but making the transformation with XQuery).

@zhudock if you have different implementation ideas can you please create a separate repository for this where we can easily try and share different ideas ? (I have some ideas too but they could be the same than yours ;)

janihur avatar Apr 07 '16 05:04 janihur

Please have a look to oos_transform in https://github.com/janihur/oos-utils-sandbox

That's my initial generalization of refcursor to xml conversion. As you can see there is no much code when XSLT/XQuery is excluded.

janihur avatar May 11 '16 13:05 janihur

Very Nice!

zhudock avatar May 11 '16 13:05 zhudock

I added two different draft versions of refcursor to csv to oos_transform https://github.com/janihur/oos-utils-sandbox

janihur avatar May 11 '16 15:05 janihur

I like the CSV methods. I haven't had much time to dig into either of them, but how difficult would it be to include the column names on the first line?

zhudock avatar May 18 '16 20:05 zhudock

how difficult would it be to include the column names on the first line?

Trivial.

I know how to do it in dbms_sql based implementation, but I'm not sure about xquery based implementation (my xquery skills are not that good).

I guess the column names feature is worth of a switch:

  function refcur2csv2(
    p_rc        in out sys_refcursor
   ,p_column_names in boolean default false
   ,p_separator in varchar2 default ','
   ,p_endline   in varchar2 default chr(10)||chr(13)
   ,p_date_fmt  in varchar2 default 'YYYY-MM-DD HH24:MI:SS'
  ) return clob

janihur avatar May 19 '16 16:05 janihur

I agree with making the column names a switch. I knew it was trivial in dbms_sql, as I had a rather quickly and poorly coded solution I used in the past. I'm not terribly familiar with xquery either

zhudock avatar May 19 '16 17:05 zhudock

This should take care of it for dbms_sql. Still researching xquery

if p_column_names
then
    for i in 1 .. v_col_count
    loop
        v_buf := '"' || v_col_desc(i).col_name || '"';
        if i < v_col_count
        then
            v_buf := v_buf || p_separator;
        end if;
        dbms_lob.writeappend(v_lob, length(v_buf), v_buf);
    end loop;
    dbms_lob.writeappend(v_lob, length(p_endline), p_endline);
end if;

I'd also suggest swapping the order of the 2 characters in the default for p_endline to match the CRLF pattern for Windows line endings.

e.g.

function refcur2csv2(
    p_rc        in out sys_refcursor
   ,p_column_names in boolean default false
   ,p_separator in varchar2 default ','
   ,p_endline   in varchar2 default chr(13)||chr(10)
   ,p_date_fmt  in varchar2 default 'YYYY-MM-DD HH24:MI:SS'
)

zhudock avatar May 24 '16 14:05 zhudock

I'm getting close with XQUERY. It looks like 2 distinct calls to your xquery function will be needed. one for the column headers, one for the column values. I've been experimenting with quoting as well, but so far i can only get it to output "&quot;" instead of a real double-quote character. However, the xquery I used is below

v_xquery_headers constant varchar2(32767) := q'[let $nl := codepoints-to-string(10),
$q := codepoints-to-string(34),
$nodes := /ROWSET/ROW 
return concat(string-join(distinct-values($nodes/*/concat($q, name(.), $q)), ','), $nl)
]';

zhudock avatar May 24 '16 20:05 zhudock

Thanks @zhudock - I'll apply your comments soon. Meanwhile please keep working with xquery :)

janihur avatar May 25 '16 05:05 janihur

@janihur - Do you have any objections to me submitting a pull request here that includes your transform package and my additions to it?

zhudock avatar Jun 13 '16 16:06 zhudock

@janihur @zhudock FYI I'm off for a few weeks and will look at all PRs when I'm back / have time.

martindsouza avatar Jun 13 '16 16:06 martindsouza