Showing posts with label PL/SQL. Show all posts
Showing posts with label PL/SQL. Show all posts

Friday, September 21, 2018

DBMS_OUTPUT Reports in Oracle SQL Developer

Although I've been working a lot with SQL Server databases lately, I've never entirely left the Oracle world.  For instance, I've used SQL Developer Data Modeler (SDDM) to design my database, and exported my models to a reporting repository in an Oracle XE database.  What kind of reporting?  Well, for one thing, Oracle SQL Developer has a very good report generator built into it, and comes with a number of pre-written reports, including some for SDDM.  Here is the report window:

You can also build your own reports.  Have a look at Jeff Smith's blog (http://www.thatjeffsmith.com/) for lots of great information about SQL Developer and SDDM.  Specifically, look at the bottom of this page for links to Jeff's posts about the reporting capability: https://www.thatjeffsmith.com/sql-developer/.  One good way to build your own report is to copy one of the built-in reports and modify it.

But, what if I want something that none of the built-in reports has, or I want to do formatting that the reporting tools don't do natively?  For instance, what if you want to print a report something like this?
  
This is a data dictionary for the HR sample schema formatted according to my client's wishes for data dictionaries.  There are very good reports from the Oracle data dictionary that are built into SQL Developer, and I like the master child formatting they have where you can click on the table name and see information about the table's columns, but this is more of a report that you can print off if you need to, or read into an Excel spreadsheet.

No worries.  You can write your own report in PL/SQL to produce HTML that you output with the DBMS_OUTPUT built-in package.  Then in SQL Developer you create it as a PL/SQL DBMS_OUTPUT style report:

The query for this one is:
BEGIN
  DATA_DICTIONARY;
END;
So how did I write this?  You COULD just hand write the PL/SQL, but since this is going to produce HTML, I like to write it as a PL/SQL server page.  I've written extensively about the many uses for PL/SQL server pages (PSPs) before, for instance, see this about using a PSP to produce Spreadsheet ML: https://it.toolbox.com/blogs/jjflash/spreadsheetml-in-a-pl-sql-server-page-part-1-of-2-022015.

Basically, a PSP is an HTML page with some PL/SQL embedded in it.  The PL/SQL is used for flow control like LOOPs and to embed data from the database into the page.  You compile it with the LOADPSP utility that comes with all Oracle databases, including Oracle XE.  And the compiled PSP is actually a stored procedure that uses the PL/SQL Web Toolkit to write to the web - this is the same underlying technology as is used by Oracle Application Express - i.e. APEX.  The specific toolkit procedure the compiled PSP uses is HTP.PRN which outputs all of the HTML.   But we aren't going to write to the web, we want to write our HTML to DBMS_OUTPUT.

So what I did was compile my PSP with LOADPSP, then edited the resulting stored procedure, named DATA_DICTIONARY.  I made three changes:

  1. Since the underlying SQL uses the USER_* data dictionary views, I wanted to make sure it shows the tables owned by the user that executes it.  So I added "AUTHID CURRENT_USER" to the procedure definition.  That makes it an Invoker's Rights procedure.
  2. I did a FIND/REPLACE to change all occurrences of HTP.PRN to DBMS_OUTPUT.PUT.
  3. I found out that when you are writing to the DBMS_OUTPUT buffer, nothing gets written until there is a call to DBMS_OUTPUT.PUT_LINE;  So I added "DBMS_OUTPUT.PUT_LINE('');" as the last line of the procedure.
Recompiled, and it all worked. Almost.  It seems that SQL Developer ignores your <head> section, including any <style> section you might have put there.  There are several possible work-arounds, but what I advise is to put any styling that you want into "style" attributes in the HTML elements to be styled.  Yes, I know this is against HTML best practices, but what can you do?

So I hope this inspires you to try the SQL Developer reporting features.  And if the need arises, write your own reports your way.

Thursday, January 25, 2018

From Oracle Expert to SQL Server Newbie - First Impressions

Oops, I've done it again - I left you readers hanging when I published the first part of two on generating code for your lookup/reference/code tables.  I promise to get back to it.

But in the meantime:
I have a new job.  Yeah, it was pretty sudden - finished my old job at the end of the year, and jumped directly into the new one at the beginning of the new year.  And the biggest shock is that this old Oracle DBA and Database Architect is going to be designing a database to be implemented in Microsoft SQL Server.  No, I didn't lie about my credentials, but I've worked with this boss before, and he said, "Oh, you'll do fine.  You're a smart guy.  And SQL is SQL, right? RDBMS is RDBMS, right?"

So, here I am, the newbie, posing as an expert.  I've been learning all I can about SQL Server, and trying things out, watching tutorials (Microsoft has some really good video lessons, designed especially for the Oracle DBA transitioning.)  Here are some first impressions:


  • SQL is SQL.  Yes, there are differences, but the basics of the SELECT, INSERT, UPDATE, DELETE, and most of the DDL commands too, are pretty close.
  • T-SQL is NOT PL/SQL.  Think of Transact-SQL as more like a SQL*Plus script with some flow control statements like IF/ELSE.  Some of it is nice, like no difference between a substitution variable and a language variable.  I think (not sure) that a T-SQL variable does act as a bind variable where PL/SQL variables would, but you can do things that PL/SQL would have to put inside an EXECUTE IMMEDIATE.  Cool.
  • I miss PL/SQL packages.
  • All triggers are statement level triggers.  This means that you write triggers very differently. Hard to get used to.  That doesn't mean you can't do row-level stuff - instead of getting a pseudo-record with the contents of the pre-DML row (the :OLD record) and one with the contents of the post-DML row (the :NEW record) you get pseudo-tables containing the old and new rows.  I don't know for sure, but I think this means that you don't deal with mutating table problems.
  • The real kicker is that most triggers are AFTER statement triggers. If you want to do BEFORE stuff, you write an INSTEAD OF trigger.  In Oracle, INSTEAD OF is just for views.  Just like Oracle, the INSTEAD OF trigger needs to do the DML on the underlying table(s).
  • There are two SQL Developer-like GUI tools - the SQL Server Management Studio, and SQL Server Data Tools for Visual Studio.  These are nice, but I wish they had a good SQL formatting tool.
  • SQL Developer CAN talk to a SQL Server database, which is nice. But it doesn't really understand T-SQL, so it can have problems with it.  I did use its formatting tool on some SELECTs, however, and it worked pretty well.  Also, if you export a table as INSERTs - it writes them as Oracle INSERTs - doesn't work for dates because SQL Server doesn't have TO_DATE.
  • Oracle has nothing like the SQL Server Integration Server - this is nice but TOTALLY new to me.
  • SQL Server Reporting Server and its report building tool are MUCH better than SQL Reports from Oracle.  But then, I have always hated Oracle's reporting tool.

Monday, March 20, 2017

Triggers, Jobs, Events and Queues - Part 1

A question in the OTN SQLand PL/SQL Forum got me to thinking about database triggers and the correct way to implement some requirements that lead people to misuse them.  This relates back to my earlier post on Updatable Views - Code Schema with Updatable Views.  I said:
"I must emphasize that INSTEAD OF triggers operate within the scope of the current transaction.  Any change they make to the underlying data is part of the same transaction, and all changes are either entirely committed, or entirely rolled back.  This can work to your advantage – take the example of the funds transfer that I mentioned before.  This is two INSERTs and either both are committed, or both are rolled back.  But there are things that a trigger could do that are not part of the transaction, such as calls to UTL_FILE to write a file outside of the database.  These happen even if the transaction is rolled back.  Not only that, but there are cases when the code in a trigger might actually be run more than once.  If you want to call one of the built-in packages with a name starting with “UTL”, you should probably queue an event to do it, rather than doing it directly."

Send an E-Mail on an Event

So here is a case where you might be tempted to use a trigger:  In the HR sample schema that comes with all Oracle databases, there is a table, EMPLOYEES.  Each Employee has a foreign key, DEPARTMENT_ID that points to DEPARTMENTS.  Each Department has a foreign key, MANAGER_ID, which points to the EMPLOYEES row for the Department Manager.  What we want to do is send an e-mail to the department manager whenever a new Employee is added to that Department, or an existing Employee’s DEPARTMENT_ID is changed to point to a new Department.
You say, "Fine, I’ve got a SEND_MAIL procedure that calls the UTL_SMTP built-in package to send e-mails.  I’ll just write a trigger to call it when inserting a new EMPLOYEES row with a non-null DEPARTMENT_ID, or updating it with the new DEPARTMENT_ID not equal to the old one."

CREATE OR REPLACE TRIGGER employees_manager_change_email
  AFTER INSERT OR UPDATE OF department_id
  ON employees
  FOR EACH ROW
DECLARE
  manager_first_name employees.first_name%TYPE;
  manager_last_name  employees.last_name%TYPE;
  manager_email      employees.email%TYPE;
BEGIN
  IF :NEW.department_id IS NOT NULL AND
     (INSERTING OR
      (UPDATING AND :NEW.department_id <> NVL(:OLD.department_id,0))) THEN
    SELECT first_name, last_name, email
      INTO manager_first_name, manager_last_name, manager_email
      FROM employees
     WHERE employee_id = (SELECT manager_id
                            FROM departments
                           WHERE department_id = :NEW.department_id);
    SEND_MAIL (p_from    => 'HR',
               P_to      => manager_email,
               P_subject => 'New Employee in your Department',
               P_text    => 'Please welcome '||:NEW.first_name ||
                            ' '||:NEW.last_name || ' to Department '||
                            TO_CHAR(:NEW.department_id)||' as a '||
                            :NEW.job_id ||'.');
  END IF;
END;

Ah, but it won’t work – you’ll get a mutating table exception because you can't select from the EMPLOYEES table at the same time that you are updating it.  Some people would then take the offending code and put it in a procedure and make it AUTONOMOUS transaction.  That way, since the SELECT from EMPLOYEES is in a separate transaction from the insert or update, it works.
I'm certainly in favor of making this a separate procedure, but making it autonomous is a bad idea.  Suppose the change to an Employee's department isn't committed?  The user changed his/her mind, and decided not to save the change.  The manager would get the e-mail anyway, because the e-mail transaction is no longer dependent on the insert or update transaction.  No, we need to make sure that the e-mail only gets sent if the change is committed.  Can we make the change raise some sort of notification that an e-mail should be sent, but as part of the current transaction, so it ONLY happens on COMMIT?

The Procedure

So, first, let's make this a procedure to separate the code for sending the e-mail from the trigger code.  We are still going to use a trigger to call it because we want it to send the e-mail automatically on the event, but this is not going to be an AUTONOMOUS TRANSACTION, so we can't just call it.  We'll need to pass it all the information it needs from the EMPLOYEES row that is being changed by the transaction.  Although I'm going to show this as a stand-alone procedure, my usual practice is to put all my procedures and functions in packages.
CREATE OR REPLACE PROCEDURE employee_change_manager_email (
  p_department_id IN employees.department_id%TYPE,
  p_first_name    IN employees.first_name%TYPE,
  p_last_name     IN employees.last_name%TYPE,
  p_job_id        IN employees.job_id%TYPE
  ) IS
  manager_first_name employees.first_name%TYPE;
  manager_last_name  employees.last_name%TYPE;
  manager_email      employees.email%TYPE;
BEGIN
  SELECT first_name, last_name, email
    INTO manager_first_name, manager_last_name, manager_email
    FROM employees
   WHERE employee_id = (SELECT manager_id
                          FROM departments
                         WHERE department_id = p_department_id);
  SEND_MAIL (p_from    => 'HR',
             p_to      => manager_email,
             p_subject => 'New Employee in your Department',
             p_text    => 'Please welcome '||p_first_name ||
                          ' '||p_last_name || ' to Department '||
                          TO_CHAR(p_department_id)||' as a '||
                          p_job_id ||'.');
END employee_change_manager_email;

But how to call it?  I can't just call it from the trigger, because it would still raise a mutating table error.  If I make it AUTONOMOUS, it gets called whether the transaction is committed or not.  So I need to call it in some way that depends on the transaction, but is still in some way autonomous.  Fortunately, there are two possibilities in Oracle.  We can submit a job with DBMS_JOB, or we can queue it as an event with Oracle Advanced Queuing (AQ).  DBMS_JOB is simpler, so I'll save Oracle AQ for another post.

Method 1: DBMS_JOB

We mostly think of the DBMS_JOB package as a way to schedule jobs and have them execute at particular times.  And in that role, it has mostly been supplanted by the DBMS_SCHEDULER package.  However, it is also possible to use DBMS_JOB to schedule a job to run immediately.  And a job runs in its own transaction context, independent of the transaction that scheduled it.  Best of all, the DBMS_JOB.SUBMIT procedure requires a COMMIT, which means that if you run it from a trigger, the job does not start running until the triggering transaction is committed.  DBMS_SCHEDULER does not need a commit, so it won't do for this task.  If the triggering transaction is rolled back, the job will not run.  This is exactly what we want for this requirement.  So here is our new version of the trigger:
CREATE OR REPLACE TRIGGER employees_manager_change_email
  AFTER INSERT OR UPDATE OF department_id
  ON employees
  FOR EACH ROW
DECLARE
  job_number NUMBER;
BEGIN
  IF :NEW.department_id IS NOT NULL AND
     (INSERTING OR
      (UPDATING AND :NEW.department_id <> NVL(:OLD.department_id,0))) THEN
    DBMS_JOB.SUBMIT (job  => job_number,
                     what => 'employee_change_manager_email (
  p_department_id => '||:new.department_id||',
  p_first_name    => '''||:new.first_name||''',
  p_last_name     => '''||:new.last_name||''',
  p_job_id        => '''||:new.job_id||''');'
                     );
  END IF;
END;

This is a little hard to read because the call to the procedure to send the mail is submitted as a string, and we need to concatenate the parameters into the string.  The trigger runs DBMS_JOB.SUBMIT to create and submit a job, and the missing "next_date" and "interval" parameters default to running the job immediately and only one time.  But the job doesn't actually get submitted until the COMMIT for the INSERT or UPDATE of EMPLOYEES.  When the job runs, the procedure runs in a separate transaction, so there is no mutating table.
In another post, I'll give the Oracle AQ method.