Quantcast
Channel: Obsessed with Oracle PL/SQL
Viewing all 312 articles
Browse latest View live

Using JSON_TABLE to move JSON data to a relational table

$
0
0
We are using Zoom to host the webcasts for our AskTOM Office Hours program. We schedule the meetings automatically, using their API. We can then also retrieve the meeting information as JSON documents through that same API.

Blaine Carter, the Developer Advocate who did all the heavy lifting around the Zoom API, suggested we take a daily snapshot of all our meetings, so that in case anything goes wrong, we can check back in time, grab the meeting ID, and still get that session going. Great idea!

He also suggested that I use JSON_TABLE to get the job done. Another great idea!

JSON_TABLE, introduced in 12.2, "enables the creation of an inline relational view of JSON content. The JSON_TABLE operator uses a set of JSON path expressions to map content from a JSON document into columns in the view. Once the contents of the JSON document have been exposed as columns, all of the power of SQL can be brought to bear on the content of JSON document." (quoting product manager Mark Drake from his fantastic LiveSQL tutorial on JSON in Oracle Database).

I relied heavily on examples in the doc and the tutorial linked above to get the job done. Here goes.

First, I need to get the structure of the JSON document returned by Zoom. I used the code Blaine had already constructed for me to get all meetings as a JSON document. I then formatted the JSON and found this to work with (IDs and URL changed to protect the innocent):

Paste your text here.{
"page_count":2,
"page_number":1,
"page_size":30,
"total_records":35,
"meetings":[
{
"uuid":"c7v0ox8sT8+u33386prZjg==",
"id":465763888,
"host_id":"P6vsOBBd333nEC-X58lE7w",
"topic":"PL/SQL Office Hours",
"type":2,
"start_time":"2018-01-19T17:38:11Z",
"duration":30,
"timezone":"America/Chicago",
"created_at":"2018-01-19T17:38:11Z",
"join_url":"https://oracle.zoom.us/j/111222333"
},
...
{
"uuid":"dn7myRyBTd555t1+2GMsQA==",
"id":389814840,
"host_id":"P6vsOBBd555nEC-X58lE7w",
"topic":"Real Application Clusters Office Hours",
"type":2,
"start_time":"2018-03-28T19:00:00Z",
"duration":60,
"timezone":"UTC",
"created_at":"2018-01-24T16:33:46Z",
"join_url":"https://oracle.zoom.us/j/444555666"
}
]
}

I then created the relational table:

CREATE TABLE dg_zoom_meetings
(
account_name VARCHAR2 (100),
uuid VARCHAR2 (100),
id VARCHAR2 (100),
host_id VARCHAR2 (100),
topic VARCHAR2 (100),
TYPE VARCHAR2 (100),
start_time VARCHAR2 (100),
duration VARCHAR2 (100),
timezone VARCHAR2 (100),
created_at VARCHAR2 (100),
join_url VARCHAR2 (100),
created_on DATE
)
/

Then I wrote an INSERT-SELECT, to be executed for each of our two accounts used by Office Hours:

INSERT INTO dg_zoom_meetings (account_name,
uuid,
id,
host_id,
topic,
TYPE,
start_time,
duration,
timezone,
created_at,
join_url,
created_on)
SELECT account_in,
uuid,
id,
host_id,
topic,
TYPE,
start_time,
duration,
timezone,
created_at,
join_url,
SYSDATE
FROM dual,
JSON_TABLE (dg_zoom_mgr.get_meetings(account_in),'$.meetings[*]'
COLUMNS (
uuid VARCHAR2 ( 100 ) PATH '$.uuid',
id VARCHAR2 ( 100 ) PATH '$.id',
host_id VARCHAR2 ( 100 ) PATH '$.host_id',
topic VARCHAR2 ( 100 ) PATH '$.topic',
type VARCHAR2 ( 100 ) PATH '$.type',
start_time VARCHAR2 ( 100 ) PATH '$.start_time',
duration VARCHAR2 ( 100 ) PATH '$.duration',
timezone VARCHAR2 ( 100 ) PATH '$.timezone',
created_at VARCHAR2 ( 100 ) PATH '$.created_at',
join_url VARCHAR2 ( 100 ) PATH '$.join_url'
)
)

The "$.meetings[*]" path says "Start at the top and find the meetings array.

Each of the path clauses inside COLUMNS indicates the name-value pair to be used for that column. My column names match the JSON key names, but they do not have to.

Then I just tack on the timestamp for when the row was added to the table, put the insert inside a procedure, call the procedure in my daily, overnight job, and wonder of wonder, miracle of miracles, it works!

I hope all the rest of my upcoming efforts at learning and putting to use JSON features of Oracle Database 12.1-12.2 go this smoothly and easily.

Don't test PL/SQL features with trivial code

$
0
0
On the one hand, when you test something, you want to keep your test code as simple as possible so that you can focus on the issue you are testing.

On the other hand, if you make your code too simple you might find yourself baffled at the resulting behavior.

Why? Because the PL/SQL compiler is just too darned smart.

Today, I got a DM on Twitter asking me why the package body below was compiling without any errors, even though he specified that the PLW-06009 warning should be treated as a compile error.

The code:

ALTER SESSION SET plsql_warnings = 'Error:6009';

CREATE OR REPLACE PACKAGE pkg_test AS
PROCEDURE test_job (p_test_parameter IN OUT VARCHAR2);
END pkg_test;
/

CREATE OR REPLACE PACKAGE BODY pkg_test AS
PROCEDURE test_job (p_test_parameter IN OUT VARCHAR2) IS
BEGIN
NULL;
EXCEPTION
WHEN OTHERS THEN NULL;
END test_job;
END pkg_test;
/

Certainly seems like that exception handler allows the OTHERS handler to exit test_job without executing a RAISE or RAISE_APPLICATION_ERROR.

Well, PLW-06009, what do you have to say for yourself?

DID HE FIND A BUG?

No. The problem is that his code was just too trivial. The procedure does nothing - literally. It simply executes the NULL; statement.

Well, news flash: the PL/SQL compiler is smart enough to (a) figure that out, (b) conclude that the procedure couldn't possibly raise an exception, (c) ignore the exception entirely and therefore (d) not raise a warning (which would have then been converted into an error).

Good compiler!

Smart compiler!

(yes, that's right, there is a puppy in my household)

It's really easy to see that this is the cse.

Replace the NULL; statement with, say, a call to DBMS_OUTPUT.PUT_LINE, and then the procedure will not compile (it's still fairly trivial, but it does something that the compiler cannot safely ignore). You can see this from the SQLcl session below:

SQL> ALTER SESSION SET plsql_warnings = 'Error:6009';

Session altered.

SQL> CREATE OR REPLACE PACKAGE pkg_test
2 AS
3 PROCEDURE test_job (p_test_parameter IN OUT VARCHAR2);
4 END pkg_test;
5 /

Package PKG_TEST compiled

SQL> SHOW ERRORS
No errors.
SQL> CREATE OR REPLACE PACKAGE BODY pkg_test
2 AS
3 PROCEDURE test_job (p_test_parameter IN OUT VARCHAR2)
4 IS
5 BEGIN
6 DBMS_OUTPUT.put_line ('abc');
7 EXCEPTION
8 WHEN OTHERS
9 THEN
10 NULL;
11 END test_job;
12 END pkg_test;
13 /

Package Body PKG_TEST compiled

Errors: check compiler log

SQL> SHOW ERRORS

Errors for PACKAGE BODY QDB_PROD.PKG_TEST:

LINE/COL ERROR
-------- -----------------------------------------------------------------
8/12 PLS-06009: procedure "TEST_JOB" OTHERS handler does not end in RAISE or
RAISE_APPLICATION_ERROR


So please keep this in mind when you are testing any sort of functionality in PL/SQL, particularly warnings and also performance optimizations. You've got to give the compiler a program that does something if you want to avoid confusion and unintended consequences.

An appreciation of UI developers from a database developer

$
0
0

From what I can tell, JavaScript developers write much more complicated code, to handle much more challenging requirements, than I do, with my SQL and PL/SQL programming in the Oracle Database.

I am quite certain that JavaScript developers feel this sentiment even more strongly. People like me (veterans of multiple decades of database-centric application development) are seen as dinosaurs, using ancient, uncool technologies.

The things we do seem distant, unimportant, even simplistic, compared to the tough stuff they deal with on a daily basis: asynchronous! streams! containers! microservices! promises!....and so forth.

Heck, I haven't even had to change my "framework" for years and years! :-)

But here's something we should all of us keep in mind:
Sometimes relatively simple tasks can also be critical tasks, 
in which case simplicity becomes a significant advantage (a feature, not a bug).
Now, don't get me wrong. I am not saying that I think the task of UI developers is not critical.

In fact, I believe just the opposite. If an application needs to interact with humans, then constructing (and maintaining) a intuitive and attractive user interface is pretty much Job #1, TMC: Task Most Critical.

I suppose that's always been true, but it's even more truthier today. And that's because what it means to be a user has changed so fundamentally.

Different Users, Different UIs

For much of my career in software, applications were sold to (or built within) companies. The employees of those companies constituted almost all software users, and they pretty much had to accept whatever they were given. They didn't get to choose which apps they would run. They also got training on how to use those apps.

Of course, they weren't called "apps" back then. They were called applications (or way meaner names, depending on the quality of the....user interfaces).

But now that the Internet has invaded everyone's lives, most dramatically via the smartphone, the typical (and by far most important) user is the consumer.

And that one change has had an extraordinary, outsized impact on the world of software and the people who hand-craft that software.

Allow me to list the ways....

1. Tiny Footprints

Small screens on mobile devices require completely different, and simpler, interfaces.

2. Users Choose

Users get to choose from a wide variety of apps in their app marketplace (Apple's App Store, Google Play for Android). They no longer having to simply accept whatever is handed to them by an uncaring employer.

3. No Time or Place for Training

The apps have to be usable without training, really without much of any thinking on the part of the user, 'cause who's going to pay for that or even sit still long enough to absorb the training?

4. If It's Not Free, I'm Not Paying For It

Ah, it could have been so different. Maybe it is in a gazillion alternative universes. Maybe in all those other universes, we haven't made a bargain with the devil. We haven't agreed to give away our privacy, our eyeballs, our brains to companies in exchange for "free" services: free email, free shipping, free searches, free, Free, FREE!

But we did and now advertising is the dominant model for revenue generation from both ginormous and tiny companies.

What difference does it make who "pays" and how? When it comes to databases, it makes an enormous difference! (as it does when it comes to the preferences and behavior of young humans who are assaulted daily by hundreds or thousands of ads).

5. Say No to ACID


Reliance on advertising leads to a higher value placed on aggregate data, in addition to (or at least in some developers' minds, as opposed to) transactional data.

For decades, data was collected in relational databases (and still is, of course), and collection was occurred as a series of business-critical transactions. When you saved your changes, baby, they were in there, right then and there, the gold standard of your view of the world. 

But as the volume of data increased, the nature of that data also changed. Suddenly it was OK if the data was eventually consistent, heck it was even OK if some data was eventually found to be lost. Missing out on a tweet is a lot less critical than missing out on a deposit to your bank account.

6. Culture Drives Interfaces

User interfaces are tightly connected to culture. Corporate culture is relatively stable and slow to  change. Consumer culture is a whirlwind, constantly changing, driven both by changes in social mores, by companies seeking to sell more, new stuff, and by changes in what is possible via technological advances.

The faster culture changes, the more rapidly developers must adjust their UIs to keep up. 

7. Bye, Bye Monoliths

Apps are now built on a widely distributed network of services, rather than a monolithic "single source of truth." Apps and their data sources must be able to communicate flexibly, asynchronously, securely and quickly with each other. 

The age of microservices, of bots, of APIs has arrived, bringing with it an enormous amount of confusion and complexity.

That's A Lot of Change

Is it any surprise, given this amount of fundamental change, that no one feels like they have the time to think through a data model, sort out referential integrity, define check constraints?

And why bother?

Two minutes after finishing version 25 of that model, you have to go back and work on version 26!

Well, we on the database side know why it's worth the bother, but you still have to find the time and away....

As we all ever more manically focus on UI design and simplicity, simultaneously
under the covers everything gets fantastically more complex....and the database is increasingly hidden from view.

Let Us Help You

But the database is still there. Well, actually, more likely lots of different databases are still there.

And the data inside those databases is still also absolutely critical, the lifeblood of every company.

So here's the thing: if the code you write to manipulate the data in the database is bad news (if, for example with the Oracle database, you write SQL that processes data on a row-by-row basis and pretty much avoid PL/SQL altogether), you will end up with performance problems, junky data, and security leaks.

And, consequently, really unhappy users. Regardless of the beauty of the UI.

So, really, it's OK if you scoff at the "old style" procedural approach of PL/SQL. And I get it if you dismayed at how hard it can be to adopt a set-oriented mindset and write great SQL.

No offense taken.

But please don't dismiss the importance of the database layer in your stack. Do maintain high standards and expectations for how your database supports your UI.

Make sure that your team includes database experts who know all about how the database can improve application performance and security, and make UI developers more productive:

  • Experts who will take full responsibility for that part of the stack, who understand that the main point of their job is to help you succeed. 


  • Experts who know how to work with JSON in the database, and build REST-based APIs on top of a hard-shell PL/SQL API (#SmartDB) for easy JavaScript (and Python and....) consumption.

Good news: you will likely find that a ratio of 4 UI developers to 1 database developer is sufficient to ensure that all UI developer requests for changes to the data API are satisfied smoothly. Again, the upside of our "simple" backend life: we can churn out PL/SQL-based APIs to SQL/data and wrap them in REST endpoints very, very quickly!

So, thank you, UI developers, for taking the brunt of the pain and frustration of working directly with users to sort out the user interface and functionality they need. And do look to us to help you with the database-centric heavy lifting, ensuring the best possible performance, security and integrity for your data access.

Some Resources

To help you get up to speed on SQL and PL/SQL and generally developing backend code, my team of Developer Advocates offers a whole boatload of videos and blog posts and scripts and quizzes and classes to help you. Check out the resources below.

Videos

Simply Smarter SQL
Magic of SQL
Practically Perfect PL/SQL

Community Sites

Oracle Dev Gym - Quizzes, workouts and classes on database development

Oracle AskTOM - Definitive answers to tough database questions, plus a new Office Hours program offering monthly, free, live Q&A Sessions

LiveSQL - Free SQL and PL/SQL playground on the latest version of Oracle Database + code library + tutorials

Working with JavaScript?

Check out Dan McGhan's JSAO.io site for all things JavaScript and Oracle.



Mining Application Express data dictionary views: find unconditional processes

$
0
0
I ran into a problem yesterday on the Oracle Dev Gym (offering quizzes, workouts and classes on Oracle technologies). A number of rows of data were incorrectly deleted. I was able to use Flashback Query to restore them (thank you, thank you, Flashback Query!). Crisis averted. 

But how did this come about?

I recruited Chris Saxon to help me figure out how this could have happened. In relatively short order, we narrowed down the culprit to a process in the Dev Gym Application Express definition that was unconditionally "removing previews" - but was in fact removing all rows, "previews" or not. Ugh.

So we fixed that.

But it got me wondering and worrying: what other processes in my app are unconditional? And should they be?

While some processes fire unconditionally on a page (for example, to get the data from tables and display them on the screen), many are (or should be!) restricted to a button press, the result of a conditional expression, or an authorization scheme.

And for sure if any of these have the word "remove" in them, I want to review them carefully to make sure I am not causing more and future angst.

So how can I find all the unconditional processes in my application? Sure, I could do some research. Or I could be lazy and ask my friends on Twitter. So I did, and Dimitri Gielis responded almost immediately:


Thanks, Dimitri! 

I don't always like Twitter, but when I do, it's because of how it's become a great way to get questions answered quickly.

Now, as you probably know from personal experience, the first idea/solution someone has doesn't necessarily make it all the way to the end. That was the case here. From my perspective, a process is unconditional if it has no:
  • Server side condition, whether PL/SQL code or client-side JavaScript (as in, "Item = Value')
  • "When Button Pressed" set
  • Authorization scheme
So in the end, my query became:

SELECT page_id, process_name
FROM apex_application_page_proc
WHERE COALESCE (condition_type,
when_button_pressed,
authorization_scheme)
IS NULL
ORDER BY page_id

Hopefully you can find that useful as well. But what I wanted to focus on in this post is: how did I know the names of columns to reference in the query?

You could try to find descriptions in doc, but an even better way to get your answer is from within APEX Application Builder itself.

Click on Workspace Utilities:





Then Application Express Views:

Then use the handy interactive report to find your view:












Then either build and save a report right inside Application Builder or find the columns of interest and roll your own, as I did:






I am certainly a rank amateur when it comes to knowing about and leveraging the APEX views. Making them accessible inside Application Builder itself, and leverage APEX features to make it easier to query those views, is a brilliant move. Just brilliant.

Qualified expressions (aka, constructor functions) for collections and records in 18c

$
0
0
As anyone who has followed me over the years knows, I like the Oracle PL/SQL language. Sure, it's not the newest, coolest kid on the block (it probably never was). But then, either am I. :-) PL/SQL is, on the other hand, a delightfully straightforward, easy to learn and write language that serves its purpose well: implement APIs to data (SQL) and business logic, right inside the database.

To serve that purpose, of course, PL/SQL needs to support lots of "big ticket" functionality: super-smooth and easy native dynamic SQL, canonicalization of static SQL to minimize the need for hard-parsing, invoker rights (AUTHID CURRENT_USER) and so much more.

But I must confess: the features of PL/SQL that I love the best are the relatively "little" things that make it easy for me to be productive as I churn out the packages (and, yes, I still do write lots of PL/SQL code, most lately for the Oracle Dev Gym, an "active learning" website featuring quizzes, workouts and classes).

And that's why my favorite PL/SQL enhancement in Oracle Database 18c is the qualified expression.

That's a fancy way of saying "constructor function". Or as we say in the documentation:

Through Oracle Database 12c release 2, it was possible to supply the value of certain non-scalar datatype with an expression, by using the type constructor for an object type, nested table or varray.

So if I wanted to initialize a nested table of integers with three elements, I can do this:

DECLARE
TYPE numbers_t IS TABLE OF NUMBER;
l_numbers numbers_t := numbers_t (1, 2, 3);
BEGIN
DBMS_OUTPUT.put_line (l_numbers.COUNT);
END;

But if I was using an associative array (aka, index-by table), this was not allowed. Instead, I had to assign elements to the array, one at a time, as in:

DECLARE
TYPE numbers_t IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
l_numbers numbers_t;
BEGIN
l_numbers (1) := 100;
l_numbers (2) := 1000;
l_numbers (3) := 10000;
END;

We have had the same problem with populating values of fields in a record:

DECLARE
TYPE person_rt IS RECORD (last_name VARCHAR2(100), hair_color VARCHAR2(100));
l_person person_rt;
BEGIN
l_person.last_name := 'Feuerstein';
l_person.hair_color := 'Not Applicable';
END;

That's cumbersome, irritating and....as of Oracle Database Release 18c, you don't have bother with that sort of thing anymore.

Now, any PL/SQL value can be provided by a "qualified expression," just like a constructor provides an abstract datatype value.

In PL/SQL, we use the terms "qualified expression" and "aggregate" rather than the SQL term "type constructor", but the functionality is the same. Qualified expressions improve program clarity and developer productivity by providing the ability to declare and define a complex value in a compact form where the value is needed.

A qualified expression combines expression elements to create values of a RECORD type or associative array type (both integer and string indexed). Qualified expressions use an explicit type indication to provide the type of the qualified item. This explicit indication is known as a typemark.

I've put together a couple of LiveSQL scripts to make it easy for you to play around with this great feature:

Qualified Expressons for Records (aka, record constructors)
Qualified Expressions for Associative Arrays (aka, collection constructors)

But, hey, as long as you here, let's go exploring!

Qualified Expressions for Records - Positional Notation

This example uses positional notation to associate values with fields. Notice that I can also use the qualified expression as a default value for my parameter.

DECLARE 
TYPE species_rt IS RECORD (
species_name VARCHAR2 (100),
habitat_type VARCHAR2 (100),
surviving_population INTEGER);

l_elephant species_rt := species_rt ('Elephant', 'Savannah', '10000');

PROCEDURE display_species (
species_in species_rt DEFAULT species_rt ('Not Set', 'Global', 0))
IS
BEGIN
DBMS_OUTPUT.put_line ('Species: ' || species_in.species_name);
DBMS_OUTPUT.put_line ('Habitat: ' || species_in.habitat_type);
DBMS_OUTPUT.put_line ('# Left: ' || species_in.surviving_population);
END;
BEGIN
display_species (l_elephant);

/* Use the default */
display_species ();
END;
/

Species: Elephant
Habitat: Savannah
# Left: 10000
Species: Not Set
Habitat: Global
# Left: 0

Qualified Expressions for Records - Named Notation

This example uses named notation to associate values with fields. Notice that I can also use the qualified expression as a default value for my parameter.

DECLARE 
TYPE species_rt IS RECORD (
species_name VARCHAR2 (100),
habitat_type VARCHAR2 (100),
surviving_population INTEGER);

l_elephant species_rt
:= species_rt (species_name => 'Elephant',
surviving_population => '10000',
habitat_type => 'Savannah');
BEGIN
DBMS_OUTPUT.put_line ('Species: ' || l_elephant.species_name);
END;
/

Species: Elephant

Qualified Expressions for Arrays 

With associative arrays, you always have to specify the index value (integer or string) with each expression your want to stuff into the array, as in:

DECLARE 
TYPE ints_t IS TABLE OF INTEGER
INDEX BY PLS_INTEGER;

l_ints ints_t := ints_t (1 => 55, 2 => 555, 3 => 5555);
BEGIN
FOR indx IN 1 .. l_ints.COUNT
LOOP
DBMS_OUTPUT.put_line (l_ints (indx));
END LOOP;
END;
/

55
555
5555

As should be obvious given the use of named notation, you don't have to specify index values in order - and your array doesn't have to be dense (all index values between lowest and highest defined):

DECLARE
TYPE ints_t IS TABLE OF INTEGER
INDEX BY PLS_INTEGER;

l_ints ints_t := ints_t (600 => 55, -5 => 555, 200000 => 5555);
l_index pls_integer := l_ints.first;
BEGIN
WHILE l_index IS NOT NULL
LOOP
DBMS_OUTPUT.put_line (l_index || ' => ' || l_ints (l_index));
l_index := l_ints.NEXT (l_index);
END LOOP;
END;
/

-5 => 555
600 => 55
200000 => 5555

Works for string-indexed arrays:

DECLARE 
TYPE by_string_t IS TABLE OF INTEGER
INDEX BY VARCHAR2(100);

l_stuff by_string_t := by_string_t ('Steven' => 55, 'Loey' => 555, 'Juna' => 5555);
l_index varchar2(100) := l_stuff.first;
BEGIN
DBMS_OUTPUT.put_line (l_stuff.count);

WHILE l_index IS NOT NULL
LOOP
DBMS_OUTPUT.put_line (l_index || ' => ' || l_stuff (l_index));
l_index := l_stuff.NEXT (l_index);
END LOOP;
END;
/

3
Juna => 5555
Loey => 555
Steven => 55

The index values do not have to be literals. They can be expressions!

DECLARE
TYPE by_string_t IS TABLE OF INTEGER
INDEX BY VARCHAR2 (100);

l_stuff by_string_t :=
by_string_t (UPPER ('Grandpa Steven') => 55,
'Loey'||'Juna' => 555,
SUBSTR ('Happy Family', 7) => 5555);

l_index varchar2(100) := l_stuff.first;
BEGIN
DBMS_OUTPUT.put_line (l_stuff.count);

WHILE l_index IS NOT NULL
LOOP
DBMS_OUTPUT.put_line (l_index || ' => ' || l_stuff (l_index));
l_index := l_stuff.NEXT (l_index);
END LOOP;
END;
/

3
Family => 5555
GRANDPA STEVEN => 55
LoeyJuna => 555

And with arrays of records, you can use qualified expressions with both:

DECLARE
TYPE species_rt IS RECORD (
species_name VARCHAR2 (100),
habitat_type VARCHAR2 (100),
surviving_population INTEGER
);

TYPE species_t IS TABLE OF species_rt INDEX BY PLS_INTEGER;

l_species species_t :=
species_t (
2 => species_rt ('Elephant', 'Savannah', '10000'),
1 => species_rt ('Dodos', 'Mauritius', '0'),
3 => species_rt ('Venus Flytrap', 'North Carolina', '250'));
BEGIN
FOR indx IN 1 .. l_species.COUNT
LOOP
DBMS_OUTPUT.put_line (l_species (indx).species_name);
END LOOP;
END;
/

Dodos
Elephant
Venus Flytrap

More? You Want More?

If you still haven't gotten enough of this great feature, check out Tim Hall's ORACLE-BASE article as well. It's the usual top-notch treatment.

Rankings for 2017 PL/SQL Championship on the Oracle Dev Gym

$
0
0
Thirty-six Oracle Database technologists competed on March 22nd in the 2917 PL/SQL Annual Championship at the Oracle Dev Gym. With five tough quizzes by yours truly, the competition was fierce! Congratulations first and foremost to our top-ranked players:

1st Place: li_bao of Russia
2nd Place: mentzel.iudith of Israel
3rd Place: NielsHecker of Germany

Next, congratulations to everyone who played in the championship. We hope you found it entertaining, challenging and educational.

Finally, our deepest gratitude to our reviewer, Elic, who has once again performed an invaluable service to our community.

In the table below of results for this championship, the number next to the player's name is the number of times that player has participated in a championship. Below that table, you will find another list showing the championship history of each of these players.

RankNameTotal Time% CorrectTotal Score
1li_bao (4)27 m78%5592
2mentzel.iudith (4)44 m78%5520
3NielsHecker (4)43 m76%5374
4Oleksiy Ponomarenko (2)44 m76%5370
5Stelios Vlasopoulos (4)44 m73%5220
6Rimantas Adomauskas (2)34 m71%5112
7Chad Lee (4)41 m71%5082
8Maxim Borunov (4)42 m71%5079
9Karel_Prech (4)43 m71%5076
10Sartograph (1)43 m71%5074
11Henry_A (4)21 m69%5014
12siimkask (4)26 m69%4995
13msonkoly (3)44 m69%4920
14JustinCave (4)27 m67%4839
15Sandra99 (2)37 m67%4801
16Michal P. (2)42 m65%4630
17Ivan Blanarik (4)33 m63%4517
18seanm95 (4)44 m63%4471
19Joaquin_Gonzalez (4)30 m61%4378
20Chase Mei (4)34 m61%4364
21Andrey Zaytsev (4)41 m61%4335
22Jan Šerák (4)43 m61%4326
23PZOL (3)44 m61%4320
24Rytis Budreika (4)14 m59%4294
25JasonC (3)28 m59%4238
26pablomatico (1)22 m57%4111
27Hertha Rettinger (1)24 m55%3950
28Otto Palenicek (2)29 m55%3934
29mcelaya (3)44 m55%3870
30tonyC (4)40 m53%3737
31Rakesh Dadhich (4)11 m51%3705
32swesley_perth (2)23 m51%3657
33Mike Tessier (2)31 m51%3626
35whab@tele2.at (1)24 m47%3353
36patch72 (4)16 m45%3234

Championship Performance History

After each name, the quarter in which he or she played, and the ranking in that championship.

NameHistory
li_bao2014:36th, 2017:1st
mentzel.iudith2014:1st, 2015:2nd, 2016:18th, 2017:2nd
NielsHecker2014:21st, 2015:1st, 2016:15th, 2017:3rd
Oleksiy Ponomarenko2016:10th, 2017:4th
Stelios Vlasopoulos2014:37th, 2015:19th, 2016:24th, 2017:5th
Rimantas Adomauskas2017:6th
Chad Lee2014:13th, 2015:28th, 2016:19th, 2017:7th
Maxim Borunov2015:9th, 2016:17th, 2017:8th
Karel_Prech2014:4th, 2015:6th, 2016:11th, 2017:9th
Sartograph2017:10th
Henry_A2014:32nd, 2016:33rd, 2017:11th
siimkask2014:15th, 2015:14th, 2016:13th, 2017:12th
msonkoly2015:15th, 2017:13th
JustinCave2015:3rd, 2017:14th
Sandra992015:28th, 2017:15th
Michal P.2015:32nd, 2017:16th
Ivan Blanarik2014:16th, 2015:16th, 2017:17th
seanm952014:34th, 2015:4th, 2016:9th, 2017:18th
Joaquin_Gonzalez2014:26th, 2015:36th, 2016:12th, 2017:19th
Chase Mei2014:25th, 2015:26th, 2016:3rd, 2017:20th
Andrey Zaytsev2014:2nd, 2015:5th, 2016:1st, 2017:21st
Jan Šerák2014:24th, 2015:8th, 2016:7th, 2017:22nd
PZOL2015:35th, 2017:23rd
Rytis Budreika2014:18th, 2015:12th, 2016:32nd, 2017:24th
JasonC2015:42nd, 2016:26th, 2017:25th
pablomatico2017:26th
Hertha Rettinger2017:27th
Otto Palenicek2016:29th, 2017:28th
mcelaya2015:38th, 2016:34th, 2017:29th
tonyC2014:31st, 2015:25th, 2016:25th, 2017:30th
Rakesh Dadhich2014:29th, 2015:31st, 2016:35th, 2017:31st
swesley_perth2016:21st, 2017:32nd
Mike Tessier2017:33rd
HotCoder272017:34th
whab@tele2.at2017:35th
patch722014:22nd, 2015:11th, 2017:36th

A new name - and amazing new future - for PL/SQL

$
0
0
[You might think that this was published on April 2nd, but in fact it was published on April 1st.]

PL/SQL, the database programming language from Oracle, introduced in 1991 and used by millions over the years to implement data APIs and business logic in mission critical applications from which billions of humans benefit daily, is undergoing a radical transformation in order to stay relevant for, and meta-cool to, future generations of developers.

After a careful examination of all modern programming languages and the definitive StackOverflow developer surveys, the PL/SQL development team implemented a super-secret plan (yes, that’s correct, even the Distinguished Product Manager for PL/SQL, Bryn Llewellyn, is unaware of what you are about to read. So don’t bother him about it, OK?).

I am, therefore, inordinately pleased and honored to be the first to announce the following changes for PL/SQL in Oracle Database 20c:
  1. PL/SQL will now be a case-insensitive language. Sort of.
  2. Only lower-case letters will be supported.
  3. All keywords will now be encased within squiggly brackets. 
  4. The name of the language will change to {plsql}. 
  5. SQL statements are replaced by “yo, oracle!” commands.
  6. All procedures and functions are implemented as recursive callback functions executed asynchronously across all Oracle Database instances in all parallel universes available through the Oracle Quantum Universe Cloud Service.
Let’s take a look at how {plsql} differs from PL/SQL.

Here’s “Hello World” in PL/SQL 18c:

BEGIN
DBMS_OUTPUT.PUT_LINE (‘Hello World’);
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
/

And now in {plsql}:

{begin}
{'Hello {dbms_output(.)put_line} World’)(call)(home)(et);
{wotn};
{end};
/

And you won’t recognize it, but you sure will be impressed by what’s happened to “select from dual”.

DECLARE
l_dummy sys.dual.dummy%TYPE;
BEGIN
SELECT dummy
INTO l_dummy
FROM sys.dual;
END;
/

And now in {plsql}:

{declare}
l_dummy {yooracle}”What’s the type of dummy in dual?”;
{begin}
{yooracle}”What’s the value of dummy in dual?”:l_dummy;
{end};
/

For really complicated SQL statements, you might want to switch to the even more flexible and powerful MLC (“machine learning cloud”) mode, demonstrated so ably below:

{begin}
{ihaveadream}”What’s the running total for all orders placed
last month by customers located within a kilometer
of their parents?”
=> {oraclevrconsole};
{end};
/

I could go on and on and on and on and on and on and on and on and on and on (Seriously, I could. The Oracle Quantum Universe Cloud Service, while not available in April 2018,  is up and running in 2020. I am using it to spray tachyons backwards and forwards through time, thereby allowing  me to sign up for as many Oracle Public Cloud trials as I want and write this mind-boggling post).

I could even show you an example of a recursive callback function executed asynchronously across all Oracle Database instances in all parallel universes.

But I won’t. I like you too much.

2018 marks the 37th year I am have working with PL/SQL I am proud of the many achievements of Oracle Database developers over those years. And today, on April 1, 2018, I am confident of a “squiggly bright” future for {plsql} over the next 37 years.

Join me for the journey!



Nested blocks, autonomous transactions and "Where do I commit?"

$
0
0
This question rolled into my In Box today:
If I have a procedure that is AUTONOMOUS_TRANSACTION that does an insert and then it calls a procedure with an insert, does the second procedure need a commit, or will the procedure with the AUTONOMOUS_TRANSACTION handle the commit? If you don't know off the top of your head, don't worry, I can build a test.
First of all, if you ever find yourself writing something like "If you don't know off the top of your head, don't worry, I can build a test." then please by all means go right ahead and build yourself a test script.

By doing so, you will better understand the feature in question and remember what you learned. Plus you end up with a script you can share with the community on LiveSQL.

But I don't mind answering such questions. That way I get to better understand the feature in question, remember what I learned, share a script on LiveSQL (link at bottom of post), and also add to my blog. :-)

So here goes: the answer is NO. The "second" procedure - invoked by the first - does not have to include a COMMIT statement.

Would you like proof or more information about the autonomous transaction feature of PL/SQL? Then keep reading.

When you add this statement to the declaration section of a procedure or function...

PRAGMA AUTONOMOUS_TRANSACTION;

the following rule then applies:
Before the subprogram can be closed and control passed back to the calling block, any DML changes made within that subprogram must be committed or rolled back.
If there are any unsaved changes, the PL/SQL engine will raise the ORA-06519 exception, as shown below:

CREATE OR REPLACE FUNCTION nothing RETURN INTEGER
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE employees SET last_name = 'abc';

RETURN 1;
END;
/

BEGIN
DBMS_OUTPUT.put_line (nothing);
END;
/

ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at "STEVEN.NOTHING", line 10
ORA-06512: at line 2

OK, so that's the basic idea. Now let's move on the specific question. What if an autonomous transaction procedure calls another procedure, which does not include the pragma shown above but does execute a DML statement and does not commit? Will we see an ORA-06519 error? The code below shows that we will not.

CREATE TABLE me_and_my_lovelies (name VARCHAR2 (100));

BEGIN
INSERT INTO me_and_my_lovelies VALUES ('Grandpa Steven');
INSERT INTO me_and_my_lovelies VALUES ('Loey');
INSERT INTO me_and_my_lovelies VALUES ('Juna');
COMMIT;
END;
/

CREATE OR REPLACE PROCEDURE not_auton_no_commit
AUTHID DEFINER
IS
BEGIN
UPDATE me_and_my_lovelies
SET name = UPPER (name);
END not_auton_no_commit;
/

CREATE OR REPLACE PROCEDURE auton_commit
AUTHID DEFINER
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
not_auton_no_commit ();

COMMIT;
END auton_commit;
/

BEGIN
auton_commit;
END;
/

SELECT COUNT(*) low_name
FROM me_and_my_lovelies
WHERE name <> UPPER (name)
/

LOW_NAME
--------
0

No error is raised. All rows have been updated. So let's go back to the rule:
Before the subprogram can be closed and control passed back to the calling block, any DML changes made within that subprogram must be committed or rolled back.
You might be thinking: But the UPDATE statement (the "DML change") was not made "within" auton_commit. Yes and no. Yes, the UPDATE statement is not part of the text of auton_commit. But the UPDATE statement was executed within the scope of auton_commit. And that's what counts. Any code executed by auton_commit, either "directly" in its executable section or "indirectly" by invoking another subprogram, is part of the autonomous transaction.

The only point at which the rule is applied is when PL/SQL attempts to close auton_commit and return control to the outer block.

LiveSQL script containing the above code here.

More information about autonomous transactions here.

Tips for a great presentation

$
0
0
There's no shortage of people giving advice on how to improve your presentation skills and impact. I offer a short list of links at the bottom of this post. 

I though I'd take a few moments to share some tips I follow to help me make the most of my time in front of audiences.

Why listen to me? I've been doing talks on the PL/SQL language since 1992 and I am pretty sure that only 3 members of all those audiences ever fell asleep during my talk.

What are the (at most) three key takeaways?

Most attendees will forget most of what you said soon after leaving the session. Certainly almost every single technical detail will be lost. So you need to decide before you start your talk what  are the at most three things you want an attendee to remember.

Then put those in a slide and tell them right at a start.

Remind them during your talk when you are getting to one of those top 3 things.

Use the slide again at the end of your talk to drive the points home.

I also find it helpful to remind attendees that I do not expect them to remember all the details and while they are welcome to ask questions about any of the code, the most important thing to remember is why you would want to use a feature and how it will help you.

That way, you let them off the hook, give them permission to relax and not feel like they have to understand every single thing you say or show.

Who is in your audience?

My team of Developer Advocates includes two members who are tasked with helping open source developers be successful with Oracle Database. Developers from the world of FOSS (free and open source software) often have little knowledge about SQL and even less about technologies like PL/SQL, Oracle Text, Oracle Spatial, flashback, and so on.

Yet these same Developer Advocates also give the same or similar presentations at Oracle User Group events, Oracle Open World, and so on.

It has, therefore, been especially important for Dan and Blaine to ask themselves before they start a talk "Who's in my audience?" Are they mostly Oracle Database-savvy folks? In that case, they can go into a bit more detail on the SQL side of things and they might also position the non-Oracle parts of the stack different.

Conversely, if the audience is mostly composed of "next generation" developers living in the world of JavaScript or Python, the Oracle Database content needs to be positioned and explained differently.

If you are unsure of your audience, it will be quite the challenge to give a fantastic presentation (since "fantastic" is in the minds of the audience, not the presenter).

So ask yourself before you start: "Who's out there?" and if you do not know, make that one of the first things you do in your presentation: Ask the audience. Get a feel for who they are and what they are looking to get out of your talk. Then adjust accordingly.

What's so funny?

About your talk, that is.

If the answer is: "Not much." or "I don't know, never thought about it." then now is precisely the right time to think about it.

Everybody likes to learn new stuff, but what they like even better is to have some fun. So many technical talks are deadly boring precisely because the speaker thinks what they have to say is so seriously important.

That might be true, but that argues even more strongly for making sure you entertain your audience.

Now, you might not have many programmer jokes ready to go (if you want some just search for "programmer jokes"), but that's OK. I don't think you want to tell jokes, per se. Instead you want to inject light-hearted commentary into the presentation itself.

But, wait, don't most jokes end up making fun of somebody or something? Don't we have to be really careful about the "target" of our humor?

Oh my, yes.

Who are you going to make fun of?

Never anyone in the audience. Never anyone with less visibility, influence or reputation points than you.

The safest bet when it comes to telling a joke or making fun during your presentation is to make yourself the target of that joke.

Self-deprecating humor goes over well in a presentation. It comes in especially handy when you are having problems (projector bulb blows, the Internet is unavailable, none of your demos work, etc.).

Don't get flustered or upset. Instead, get people laughing, and they will be on your side and, as often as not, help you resolve the problem.

What words should I avoid using?

When you're presenting, you're the expert (even if you know only a little bit more than those in your audience). This automatically puts you on a pedestal, puts some distance between you and the attendees. This is not an all-good or all-bad thing.

It's a good thing because people came to learn from you and they are open to what you have to sya.

It can be a not-so-good thing if your expertise comes off as a put-down to everyone else. This can happen in very subtle ways, from body language to your choice of words.

I recommend that you avoid words like:

"Obviously" 

If it's so blindingly obvious, you shouldn't have to call it out. You might not need to even bother talking about it. But chances are what is obvious to you the speaker is not so obvious to attendees, and if it is not obvious to them, you just made them feel stupid.

and...."Of course"

A minor variation on "obviously," with all the same drawbacks. See above.

and...."Everyone knows"

Everyone knows that when you say "everyone knows", it is very similar to saying "Obviously" and "Of course."Everyone knows this, except for the ones who don't.

Use words that instead lift up the attendee, make them feel better about themselves, pull them into the talk, rather than pushpin them away. Invite comments and questions regularly from the audience, let them know that you, even you, had trouble getting your head around a given topic.

So: avoid put-down words....

Unless, obviously and of course, you are directing comments with these words jokingly towards yourself, as in:

"Of course, everyone knows that obviously I am an imposter."

Even More Advice

But wait, there's more! You might find these posts interesting as well:

http://jackmalcolm.com/2012/08/how-much-of-your-presentation-will-they-remember/

http://sethgodin.typepad.com/seths_blog/2018/04/how-to-give-a-five-minute-presentation-.html

https://www.linkedin.com/pulse/20140904170930-15291682-5-tips-for-giving-effective-technical-presentations/

Oracle Dev Gym gets a facelift - and more!

$
0
0
Over the weekend of April 21, we upgraded the Oracle Dev Gym site to v3 (code name: ORANGE). Here's the v2 home page:


and now v3:


Now you see the reason for the code name. It's orange!

Here are the key changes you will find on the Dev Gym:
  • Orange theme: all that red was hurting our eyes, but the main reason to switch to orange was to make it visually clear that this site, as with AskTOM, is part of the broader Oracle Developer initiative.
  • Site search: type in a keyword, such as "FORALL" or "listagg" in the search bar on the home page, and we will find all quizzes, workouts and classes that match your criteria. You can further hone your search on the results page.
  • The tournament quizzes are now offered on the home page; no need to click on the Tournaments tab to see them. These quizzes are produced fresh each week, and often focus on the latest features in SQL, PL/SQL and Oracle Database.
  • Your recent activity on the site is available on the home page so that you can more easily continue your workout or class, or check the results of a recently-completed quiz.
  • Improved performance on the Tournament Archive (formerly Library) page, the Quizzes Taken page and more.
  • Redesigned workouts and classes page to make it easier to find and take these exercises at the gym.
Other, smaller changes you might notice:
  • The Trivadis class (which consisted of a single workout) is now a workout. All classes on the Dev Gym henceforth will consist of multiple modules. If you were in the middle of taking that class, visit the Workouts page to continue via the workout.
I hope you like the new, clean UI as much as I do. Many thanks to UI/UX guru and Application Express wunderkind Shakeeb Rahman for guidance. Who did he guide? Eli Feuerstein, the primary APEX developer for the Oracle Dev Gym, who did a fantastic job of rendering Shakeeb's design into CSS and JavaScript, SQL and PL/SQL. Thanks, Eli!

Looking ahead, we will be announcing at least two new classes on the Dev Gym over the next several months (Databases for Developers: Next Level by Chris Saxon and SQL Analytics for Developers by Connor McDonald). And in June we plan to launch a brand-new weekly Java quiz!

How do I get the attribute of my object type in SQL?

$
0
0
This question found its way into my In Box yesterday:

I have a table with an object type column. I want to way to get the value of an attribute of that object type in my query. But Oracle keeps telling me "ORA-00904: invalid identifier". What am I doing wrong?

Almost certainly what you are doing wrong is forgetting to use a table alias. Yeah, it's that simple.
Don't forget the table alias.
Let's take a look.

I create an object type, use that object type as a column in a table, and insert a couple of rows:

CREATE TYPE food_t AS OBJECT (
NAME VARCHAR2 (100)
, food_group VARCHAR2 (100)
, grown_in VARCHAR2 (100)
)
/

CREATE TABLE food_table (id number primary key, my_food food_t)
/

BEGIN
INSERT INTO food_table
VALUES (1, NEW food_t ('Mutter Paneer', 'Curry', 'India'));

INSERT INTO food_table
VALUES (2, NEW food_t ('Cantaloupe', 'Fruit', 'Backyard'));

COMMIT;
END;
/

OK, let's query some data. The following output is what you see in SQLcl:

SQL> SELECT * FROM food_table;

ID
ID MY_FOOD(NAME, FOOD_GROUP, GROWN_IN)
----- ------------------------------------------
1 FOOD_T('Mutter Paneer', 'Curry', 'India')
2 FOOD_T('Cantaloupe', 'Fruit', 'Backyard')

In SQL Developer, I see:


While the output format varies, the basic idea is the same: I have asked for all columns, one of those columns is an object type, so I see the instance of that type.

What I want, though, is to select the individual attributes, such as the same. OK, here goes:

SQL> SELECT name FROM food_table;

Error starting at line : 1 in command -
SELECT name FROM food_table
Error at Command Line : 1 Column : 8
Error report -
SQL Error: ORA-00904: "NAME": invalid identifier

What? No NAME column? Oh, that's right. It's not a column - it's an attribute of a column. OK, let's try that again:

SQL> SELECT my_food.name FROM food_table;
Error starting at line : 1 in command -
SELECT my_food.name FROM food_table
Error at Command Line : 1 Column : 8
Error report -
SQL Error: ORA-00904: "MY_FOOD"."NAME": invalid identifier

Still no good. But, but. OK, so here's the deal: you must provide an alias to the table, prefix the object type column name with the alias, and then you are good to go. You can even reference the attribute in the WHERE clause:

SQL> SELECT ft.my_food.name FROM food_table ft;

MY_FOOD.NAME
-----------------------------------------------
Mutter Paneer
Cantaloupe

SQL> SELECT ft.my_food.name FROM food_table ft
2 WHERE ft.my_food.name LIKE 'C%';

MY_FOOD.NAME
-----------------------------------------------
Cantaloupe

For more information on working with object types in SQL and PL/SQL, check out the Object-Relational Developer's Guide.

Error stack function now (12.2) includes backtrace information!

$
0
0
The DBMS_UTILITY has long (since 10.2) offered three functions that are very handy when either tracing execution or logging errors:
  • FORMAT_CALL_STACK - answering the question "How did I get here?"
  • FORMAT_ERROR_STACK - answering the question "What was the error?" (or a stack of errors, depending on the situation)
  • FORMAT_ERROR_BACKTRACE - answering the question "On what line was my error raised?"
Therefore (and prior to 12.2), if you wanted to get the error information + the line number on which the error was raised, you would need to call both of the "*ERROR*" as in:

CREATE OR REPLACE PROCEDURE p3
AUTHID DEFINER
IS
BEGIN
p2;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_backtrace);
RAISE;
END;

Of course, in the real world, you would not display the text on the screen. You would write them to a log table via an autonomous transaction procedure, as I show in this simple error logging package.

When I run the above procedure on a version of Oracle Database through 12.1, I might see output like this:

ORA-06501: PL/SQL: program error

ORA-06512: at "STEVEN.P1", line 4
ORA-06512: at "STEVEN.PKG", line 6
ORA-06512: at "STEVEN.P2", line 5
ORA-06512: at "STEVEN.P3", line 5

In other words: the error stack shows just the error message, and the backtrace shows just the backtrace.

But as of 12.2, the error stack function has been enhanced to also include backtrace information (more details available in this My Oracle Support note). So the output run on 12.2, 18.1 or higher shows:

ORA-06501: PL/SQL: program error
ORA-06512: at "STEVEN.P1", line 4
ORA-06512: at "STEVEN.PKG", line 6
ORA-06512: at "STEVEN.P2", line 5

ORA-06512: at "STEVEN.P1", line 4
ORA-06512: at "STEVEN.PKG", line 6
ORA-06512: at "STEVEN.P2", line 5
ORA-06512: at "STEVEN.P3", line 5

Nice stuff!

Mutating table errors and multi-row inserts

$
0
0
The Oracle Dev Gym PL/SQL Challenge quiz played 28 Apr - 4 May explored the interactions between row-level triggers and multi-row inserts, particularly when it comes to mutating table errors. If you didn't happen to take the quiz and already learn its lesson, here goes.

[Note: you can also click on the link above and play the quiz right now, before you read this post!]

Here's the main rule to keep in mind:
A BEFORE INSERT trigger will not cause a mutating table error as long as the triggering INSERT statement is a single row insert (INSERT-VALUES).
Let's take a closer look.

I create a table and a trigger on that table:

CREATE TABLE qz_flowers
(
fl_num NUMBER,
fl_name VARCHAR2 (30)
)
/

CREATE OR REPLACE TRIGGER qz_flowers_bir
BEFORE INSERT ON qz_flowers
FOR EACH ROW
DECLARE
l_count INTEGER;
BEGIN
SELECT COUNT (*) INTO l_count FROM qz_flowers;
DBMS_OUTPUT.PUT_LINE ('Count = ' || l_count);
END;
/

The trigger queries from the qz_flowers table, which introduces the possibility of a mutating table error.

But if I insert a single row using the INSERT-VALUES format, I do not get that error:

SQL> BEGIN
2 INSERT INTO qz_flowers VALUES (100, 'Orchid');
3 DBMS_OUTPUT.PUT_LINE ('Inserted');
4 END;
5* /

Count = 0
Inserted

That makes, right? If I am inserting a single row in the table and a BEFORE INSERT trigger fires, that row is not yet in the table, the table is not mutating, and there can be no mutating table error.

I can even insert two rows in the same block and no error:

SQL> BEGIN
2 INSERT INTO qz_flowers VALUES (100, 'Orchid');
3 INSERT INTO qz_flowers VALUES (200, 'Tulip');
4 DBMS_OUTPUT.PUT_LINE ('Inserted');
5 END;
6 /
Count = 0
Count = 1
Inserted

The trigger fires for each individual row inserted. The logic I stated above applies to each insert separately: no mutating table error. Ah, but if my INSERT statement inserts (or, as we shall soon see, might insert) more than one row, KABOOM!

SQL> BEGIN
2 INSERT INTO qz_flowers
3 SELECT 100, 'Orchid' FROM DUAL
4 UNION ALL
5 SELECT 200, 'Tulip' FROM DUAL;
6 DBMS_OUTPUT.PUT_LINE ('Inserted');
7 END;
8 /

Error starting at line : 1 in command -
BEGIN
INSERT INTO qz_flowers
SELECT 100, 'Orchid' FROM DUAL
UNION ALL
SELECT 200, 'Tulip' FROM DUAL;
DBMS_OUTPUT.PUT_LINE ('Inserted');
END;
Error report -
ORA-04091: table QDB_PROD.QZ_FLOWERS is mutating, trigger/function may not see it
ORA-06512: at "QDB_PROD.QZ_FLOWERS_BIR", line 4
ORA-04088: error during execution of trigger 'QDB_PROD.QZ_FLOWERS_BIR'
ORA-06512: at line 2
04091. 00000 - "table %s.%s is mutating, trigger/function may not see it"
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.

Once that first row is inserted, the table is now "mutating" and the SELECT against that table cannot be safely performed.

And even if my INSERT-SELECT only inserts a single row, the ORA-04091 error will be raised.

SQL> BEGIN
2 INSERT INTO qz_flowers
3 SELECT 100, 'Orchid' FROM DUAL;
4 DBMS_OUTPUT.PUT_LINE ('Inserted');
5 END;
6 /

Error report -
ORA-04091: table QDB_PROD.QZ_FLOWERS is mutating, trigger/function may not see it

And what about FORALL? In at least one way, that PL/SQL bulk processing statement acts like an INSERT-SELECT. Specifically, ON INSERT statement-level triggers (firing before or after) fire just once for the entire FORALL statement.

When it comes to the mutating table errors and the BEFORE INSERT trigger, FORALL behaves like a mix of INSERT-VALUES and INSERT-SELECT.

If the FORALL statement binds just a single value from its array, then it behaves like a single row INSERT-VALUES statement:

SQL> DECLARE
2 TYPE flowers_t IS TABLE OF qz_flowers%ROWTYPE;
3 l_flowers flowers_t := flowers_t();
4 BEGIN
5 l_flowers.EXTEND;
6 l_flowers (1).fl_num := 100;
7 l_flowers (1).fl_name := 'Orchid';
8
9 FORALL indx IN 1 .. l_flowers.COUNT
10 INSERT INTO qz_flowers VALUES l_flowers (indx);
11 DBMS_OUTPUT.PUT_LINE ('Inserted');
12 END;
13 /
Count = 0
Inserted

If the FORALL statement binds more than one value from its array, then it behaves like INSERT-SELECT:

SQL> DECLARE
2 TYPE flowers_t IS TABLE OF qz_flowers%ROWTYPE;
3 l_flowers flowers_t := flowers_t();
4 BEGIN
5 l_flowers.EXTEND (2);
6 l_flowers (1).fl_num := 100;
7 l_flowers (1).fl_name := 'Orchid';
8 l_flowers (2).fl_num := 200;
9 l_flowers (2).fl_name := 'Tulip';
10
11 FORALL indx IN 1 .. l_flowers.COUNT
12 INSERT INTO qz_flowers VALUES l_flowers (indx);
13 DBMS_OUTPUT.PUT_LINE ('Inserted');
14 END;
15 /
Count = 0

Error report -
ORA-04091: table QDB_PROD.QZ_FLOWERS is mutating, trigger/function may not see it


Check out these links for more information on mutating table errors and getting around them:

Mutating Table Exceptions by Tim Hall
Get rid of mutating table trigger errors with the compound trigger (my blog)

The SmartDB Resource Center

$
0
0
I put together this blog post for those interested in learning more about the SmartDB (also or formerly known as "ThickDB") architecture and how to apply it in your applications. I will update it as more resources become available.

What is SmartDB?

Bryn Llewellyn, PL/SQL Product Manager, offers this description:

Large software systems must be built from modules. A module hides its implementation behind an interface that exposes its functionality. This is computer science’s most famous principle. For applications that use an Oracle Database, the database is, of course, one of the modules. The implementation details are the tables and the SQL statements that manipulate them. These are hidden behind a PL/SQL interface.

This is the Smart Database paradigm: select, insert, update, delete, merge, commit, and rollback are issued only from database PL/SQL. Developers and end-users of applications built this way are happy with their correctness, maintainability, security, and performance. But when developers follow the NoPlsql paradigm, their applications have problems in each of these areas and end-users suffer.

Experts

The two leading proponents of SmartDB from Oracle are:

Bryn Llewellyn, Product Manager for PL/SQL and Edition-based Redefinition

Bryn Llewellyn has worked in the software field for more than thirty-five years. He joined Oracle UK in 1990 at the European Development Center to work on the Oracle Designer team. He transferred to the Oracle Text team and then into consulting as the text specialist for Europe. He relocated to Redwood Shores in 1996 to join the Oracle Text Technical Marketing Group. He has been the product manager for PL/SQL since 2001. In 2005, he became responsible, additionally, for edition-based redefinition (EBR for short). This is the Oracle Database capability that supports online application upgrade.

It’s hard for Bryn to remember his life before Oracle. He started off doing image analysis and pattern recognition at Oxford University (programming in FORTRAN) and then worked in Oslo, first at the Norwegian Computing Center and then in a startup. In Norway, Bryn programmed in Simula (its inventors were his close colleagues). This language is recognized as the first object-oriented programming language and was the inspiration for Smalltalk and C++. Bryn is an Oak Table member.

Follow Bryn on Twitter and subscribe to his blog.

Toon Koppelaars, Real World Performance Team

Toon has been part of the Oracle eco-system since 1987. He is currently a member of Oracle's Real World Performance team. RWP troubleshoots application performance issues in and around the DBMS. The way applications currently use (or, rather, abuse) the DBMS is often at the root of these performance issues. Prior to joining the RWP team, Toon was mainly involved in database application development. He is the co-author of "Applied Mathematics for Database Professionals" (Apress 2016), a member of the OakTable network (http://www.oaktable.net/) and alumni Oracle ACE-Director. His special interests are: architecting applications for performance and scalability, database design, and business rules / constraints modeling. He is a long-time champion of the Smart Database paradigm, as witnessed by his authorship of the Helsinki Declaration (IT Version) in 2009.

Follow Toon on Twitter.

AskTOM Office Hours on SmartDB

AskTOM, famous for its exhaustive Q&A on Oracle Database, has added free, monthly trainings and Q&A, in the guise of Office Hours.

Bryn and Toon offer a monthly series on SmartDB. Subscribe here for reminders to stay up on the very latest with SmartDB!

Resources

NoPLSql and Thick Database Approaches with Toon Koppelaars
Which one do you think requires a bigger database server?

Toon Koppelaars describes an experiment to measure the work done by Oracle Database to complete a specific task using different approaches. The NoPlsql approach treats the database as no more than a persistence layer, using only naive single-row SQL statements; it implements all business logic outside of it. The Thick Database approach treats the database as a processing engine; it uses a combination of sophisticated set-based SQL statements and PL/SQL to implement all business logic inside it. “No business logic in the database” advocates take note: the Thick Database approach gets the task done with far less database work than the NoPlsql approach. 

Guarding Your Data Behind a Hard Shell PL/SQL API

This session examines in practical detail how to ensure that the hard shell of a database’s PL/SQL API is impenetrable. It advocates strict adherence to the principle of least privilege by using a four-schema model (data, code implementation, API, and connect) and invokers rights units together with code-based access control. Scrupulous care is taken to ensure that the privileges needed for installation and patching are not available at runtime, and the approach is reinforced by secure error-handling.

The Database: Persistence Layer (NoPlsql) or Processing Engine (SmartDB)?

Slide deck from Toon's presentations at ODTUG's Kscope17 conference. Toon goes deep into the question of where business logic should reside, and the benefits you get from putting that logic into the database.

Also: Why SmartDB?

How to install a #SmartDB application back-end

Bryn Llewellyn offers a "sketch" of how developers and DBAs should set up their application in the database to follow a SmartDB architecture.

Why Use PL/SQL?

Bryn Llewellyn's definitive white paper on the key advantages accrued when you use the PL/SQL language, to build secure, maintainable, high performance applications that guarantee data integrity and consistency.

Doing SQL from PL/SQL: Best and Worst Practices

Assuming you buy into the SmartDB paradigm and will enclose your SQL statements inside  PL/SQL "hard shell", this white paper from Bryn Llewellyn will help you do it properly.

Moovit: A View From the Trenches

Millions of people develop applications on top of Oracle Database. The most secure and optimized of those applications take full advantage of SQL and PL/SQL. In this CodeTalk webcast, Steven Feuerstein interviews Oren Nakdimon of Moovit (http://moovitapp.com, lead developer for the backend of this popular transit app, to find out just how he and his small team have made the most of PL/SQL, and how they manage their PL/SQL code base.

How to Prove That Your SmartDB App Is Secure

If you are guarding your data behind a hard shell PL/SQL API as Bryn Llewellyn, Toon Koppelaars and others recommend, then it should be quite easy to prove, that your PL/SQL application is secured against SQL injection attacks. The basic idea is 1) that you do not expose data via tables nor views to Oracle users used in the middle-tier, by end-users and in the GUI; and 2) that you use only static SQL within PL/SQL packages. By following these two rules, you ensure that only SQL statements with bind variables are used in your application, making the injection of unwanted SQL fragments impossible. In this blog post, Philipp Salvisberg shows how to check if an application is complying to these two rules.

How many times does my table function execute?

$
0
0
A left correlation join occurs when you pass as an argument to your table function a column value from a table or view referenced to the left in the table clause. This technique is used with XMLTABLE and JSON_TABLE built-in functions, but also applies to your own table functions.

Here's the thing to remember:
The table function will be called for each row in the table/view that is providing the column to the function. 
Clearly, this could cause some performance issues, so be sure that is what you want and need to do.

The following code demonstrates this behavior, for both pipelined and non-pipelined functions.

CREATE TABLE things
(
thing_id NUMBER,
thing_name VARCHAR2 (100)
)
/

BEGIN
INSERT INTO things VALUES (1, 'Thing 1');
INSERT INTO things VALUES (2, 'Thing 2');
COMMIT;
END;
/

CREATE OR REPLACE TYPE numbers_t IS TABLE OF NUMBER
/

CREATE OR REPLACE FUNCTION more_numbers (id_in IN NUMBER)
RETURN numbers_t
IS
l_numbers numbers_t := numbers_t ();
BEGIN
l_numbers.EXTEND (id_in * 5);

FOR indx IN 1 .. id_in * 5
LOOP
l_numbers (indx) := indx;
END LOOP;

DBMS_OUTPUT.put_line ('more numbers');
RETURN l_numbers;
END;
/

BEGIN
FOR rec IN (SELECT th.thing_name, t.COLUMN_VALUE thing_number
FROM things th, TABLE (more_numbers (th.thing_id)) t)
LOOP
DBMS_OUTPUT.put_line ('more numbers ' || rec.thing_number);
END LOOP;
END;
/

more numbers
more numbers
more numbers 1
more numbers 2
more numbers 3
more numbers 4
more numbers 5
more numbers 1
more numbers 2
more numbers 3
more numbers 4
more numbers 5
more numbers 6
more numbers 7
more numbers 8
more numbers 9
more numbers 10

CREATE OR REPLACE FUNCTION more_numbers (id_in IN NUMBER)
RETURN numbers_t PIPELINED
IS
BEGIN
DBMS_OUTPUT.put_line ('more numbers');
FOR indx IN 1 .. id_in * 5
LOOP
PIPE ROW (indx);
END LOOP;
RETURN;
END;
/

BEGIN
FOR rec IN (SELECT th.thing_name, t.COLUMN_VALUE thing_number
FROM things th, TABLE (more_numbers (th.thing_id)) t)
LOOP
DBMS_OUTPUT.put_line ('more numbers ' || rec.thing_number);
END LOOP;
END;
/

more numbers
more numbers
more numbers 1
more numbers 2
more numbers 3
more numbers 4
more numbers 5
more numbers 1
more numbers 2
more numbers 3
more numbers 4
more numbers 5
more numbers 6
more numbers 7
more numbers 8
more numbers 9
more numbers 10



How to avoid spamming users from your applications

$
0
0
Does your application send out emails? Lots of emails?

Did you ever get that feeling like someone punched you in the stomach when you realize that you mistakenly sent out hundreds or thousands of emails to your users when you didn't mean to?

I have. It's a terrible feeling. And these days, in the age of GDPR, there can be real consequences for invading the privacy of your users. This post explores how to make sure that, at least when you are developing and testing your code, you do not inadvertently spam your users.

The Oracle Dev Gym sends out lots of different kinds of emails to those players who have opted-in for them, such as:

  • Results of the quiz you just completed
  • Confirmation of sign-up in a class
  • Reminder to take our weekly tournament quizzes
  • Hourly reports to site admins with any new errors in our log
  • Weekly activity summaries to quizmasters
The Dev Gym is an Oracle Application Express app, so we are able to happily and easily take advantage of the APEX_MAIL package, and its SEND procedure. Here's a diagram of what our email workflow could  look like:



In other words, wherever and whenever I need to send an email, whether it is from the quiz manager package or the class manager package or the site admin utilities package, I simply call apex_mail.send directly.

Like I say, I could take this approach. But that would be a really bad idea. Why?

With multiple accesses to the "core" send email procedure, it is difficult to put any kind of controls in place regarding email delivery, and also more challenging to debug and enhance email-related code.

Consider the situation at the Dev Gym. We currently have 72,000 users registered at the Dev Gym, and of those 30,000 have opted-in for emails. We have development, test/stage and production environments for the Dev Gym. Here's the rule we need to follow regarding emails:
When running in development or test, our users should never get an email. Specifically, unless the email "send to" is one of our developers, redirect all emails to the admin email address.
In other words, we still need to see the emails to verify the format and other behaviors, but those emails should never "escape" from the development team.

If APEX_MAIL.SEND is called from dozens of locations in my code, then I need to go to each of those locations and apply my rule. Therein lies madness and the inevitable mistake that results in emails spamming users.

So instead, we follow this rule when writing email-related code:
Never call APEX_MAIL.SEND directly. Instead call qdb_communication_mgr.send_email. 
Our own send_email procedure, in turn, calls APEX_MAIL.SEND. With this approach, the flow of email requests look like this:

By following this rule, we ensure that APEX_MAIL.SEND is called in just one place in our entire code base: inside our own wrapper, the qdb_communication_mgr.send_email procedure (the "qdb" prefix translates to "quiz database").

Here's a greatly simplified version of this procedure :

PROCEDURE send_mail (
to_user_id_in IN INTEGER,
subject_in IN VARCHAR2,
html_in IN CLOB,
push_to_queue_in IN BOOLEAN DEFAULT TRUE)
IS
l_email_id INTEGER;
l_text CLOB;

/* Just a placeholder for this demonstration */
FUNCTION is_developer (user_id_in IN INTEGER)
RETURN BOOLEAN
IS
BEGIN
RETURN TRUE;
END;

FUNCTION user_email_address (user_id_in IN INTEGER)
RETURN VARCHAR2
IS
l_email_address qdb_users.email_address%TYPE;
BEGIN
SELECT email_address
INTO l_email_address
FROM qdb_users
WHERE user_id = user_id_in;

RETURN l_email_address;
END;

FUNCTION send_to_email_address (user_id_in IN INTEGER)
RETURN VARCHAR2
IS
c_email_address qdb_users.email_address%TYPE
:= user_email_address (user_id_in);
BEGIN
RETURN CASE
WHEN qdb_utilities.in_production THEN c_email_address
WHEN is_developer (user_id_in) THEN c_email_address
ELSE qdb_config.admin_email_address ()
END;
END;
BEGIN
l_email_id :=
apex_mail.send (p_to => send_to_email_address (to_user_id_in),
p_from => 'noreply@oracle.com',
p_subj => subject_in,
p_body => l_text,
p_body_html => html_in,
p_cc => NULL,
p_bcc => NULL);

IF push_to_queue_in
THEN
apex_mail.push_queue;
END IF;
END send_mail;

Here are the highlights:
  • I create a nested function to return the email address to which the email will be sent. Inside that function, I have all the logic to implement my requirement.
  • The qdb_utilities package contains a function that returns TRUE if I am currently running this code in production.
  • Since the only call to apex_mail.send occurs within my send_mail procedure, I am now 100% protected from accidentally sending out an email to users when it was not intended.
  • I don't always push the email out of the queue. For example, when I doing a batch email, I want to wait till everything is put on the queue, and then push it. If I am notifying a user of a specific event or accomplishment, I might want to push immediately.
So: nothing terribly fancy, no rocket science. Just another demonstration of how encapsulating low-level functionality to control access to that functionality gives you flexibility and control that is otherwise very difficult to achieve.

This particular encapsulation is just another example of the fundamental rule of the SmartDB paradigm: create a "hard shell" PL/SQL API around your data structures (and, in this case, infrastructure or plumbing). More info on SmartDB

Once you've got your encapsulation layer in place, it becomes really easy to add more functionality to your email management features. For example, we recently added our own email queue, so we can re-send an email if there was an error, and also verify that emails were produced and sent as expected.

Since we knew for a fact that APEX_MAIL.SEND is only called in one place, we could easily ensure that all email attempts were place in the queue as follows:

PROCEDURE send_mail (
to_user_id_in IN INTEGER,
subject_in IN VARCHAR2,
html_in IN CLOB,
push_to_queue_in IN BOOLEAN DEFAULT TRUE)
IS
...
BEGIN
write_to_queue (
email_sent => 'N',
to_address => send_to_email_address (to_user_id_in),
from_address => 'noreply@oracle.com',
subject => subject_in,
body => l_text,
body_html => html_in,
cc => NULL,
bcc => NULL,
email_id_out => l_email_queue_id);

l_email_id :=
apex_mail.send (p_to => send_to_email_address (to_user_id_in),
p_from => 'noreply@oracle.com',
p_subj => subject_in,
p_body => l_text,
p_body_html => html_in,
p_cc => NULL,
p_bcc => NULL);

IF push_to_queue_in
THEN
apex_mail.push_queue;
END IF;

mark_email_sent (l_email_queue_id);
END send_mail;

If you have not already protected your users and yourself from the trauma of an accidental spamming,  I hope that you can learn from and use the approach we've taken.

Have you taken care of this problem in another way? Do you have suggestions for improving upon what we've done? Please let me know in the comments section.

    Class on PL/SQL Table Functions at the Oracle Dev Gym

    $
    0
    0
    http://bit.ly/dg-tf

    A table function is a function that can act like a table inside a SELECT statement. The function returns a collection, and the SQL engine converts that collection into rows and columns that can be manipulated with standard SQL operations (joins, unions, where clauses, etc.).

    Far and away the most popular post on this blog is an introduction to a series of articles on table functions:














    Given that level of interest in a very interesting feature of PL/SQL, I thought it would be a good thing to give you even more resources to learn about table functions.

    So I put together a FREE class at the Oracle Dev Gym on PL/SQL table functions. It consists of four modules and gives you a solid grounding in table function fundamentals:



    Each modules consists of a video that covers the basics, followed by a LiveSQL tutorial that dives into more of the details, and gives you an opportunity to run and play with the code. We then finish up the module with quizzes to reinforce and deepen the new knowledge you've just gained.

    To get the most out of this class, you should be comfortable writing SELECT statements and PL/SQL functions. Knowledge of collections is a big plus, but I cover the fundamentals in the first module.

    Dev Gym classes are "mini-MOOCs" - they are fully online and on-demand classes but are relatively "light" in the amount of time you will need to complete them. You can start the class whenever you have time, take breaks as needed, and when you are finished, print out a certificate of completion.

    I hope you will find the class helpful. Just click on this link for more information and to register:

    http://bit.ly/dg-tf

    The PL/Scope Resource Center

    $
    0
    0
    PL/Scope is a compiler-driven tool that collects PL/SQL and SQL identifiers as well as SQL statements usage in PL/SQL source code.  PL/Scope collects PL/SQL identifiers, SQL identifiers, and SQL statements metadata at program-unit compilation time and makes it available in static data dictionary views. The collected data includes information about identifier types, usages (DECLARATION, DEFINITION, REFERENCE, CALL, ASSIGNMENT) and the location of each usage in the source code.

    Starting with Oracle Database 12cRelease 2 (12.2), PL/Scope has been enhanced to report on the occurrences of static SQL, and dynamic SQL call sites in PL/SQL units. The call site of the native dynamic SQL (EXECUTE IMMEDIATE, OPEN CURSOR FOR) and DBMS_SQL calls are collected. Dynamic SQL statements are generated at execution time, so only the call sites can be collected at compilation time. The collected data in the new DBA_STATEMENTS view can be queried along with the other data dictionary views to help answer questions about the scope of changes required for programming projects, and performing code analysis. It is also useful to identify the source of SQL statement not performing well.

    PL/Scope provides insight into dependencies between tables, views and the PL/SQL units. This level of details can be used as a migration assessment tool to determine the extent of changes required.
    PL/Scope can help you answer questions such as :
    • Where and how a column x in table y is used in the PL/SQL code?
    • Where does the same SQL statement appear more than once in my code
    • What are the constants, variables and exceptions in my application that are declared but never used? 
    • Is my code at risk for SQL injection?
    • What are the SQL statements with an optimizer hint coded in the application? 
    • Where is a variable modified in my package? 
    I've collected below a set of resources to help you make the most of PL/Scope.

    Using PL/Scope - The Doc

    The documentation contains key information you'll need to turn on the gathering of identifier data and write your own queries against the PL/Scope data dictionary views.


    LiveSQL Scripts on PL/Scope

    LiveSQL offers 24x7 access to the most recent release of Oracle Database. You can write your own scripts, play around with SQL and PL/SQL....and take advantage of a code library of scripts submitted by Oracle experts and the community. I've uploaded a whole bunch of scripts showcasing the capabilities of PL/Scope.



    plscope-utils – Utilities for PL/Scope by Philipp Salvisberg

    Philipp offers a fantastic set of utilities to help you make the most of PL/Scope. His description:

    PL/Scope was introduced with Oracle Database version 11.1 and covered PL/SQL only. SQL statements such as SELECT, INSERT, UPDATE, DELETE and MERGE were simply ignored. Analysing PL/SQL source code without covering SQL does not provide a lot of value. Hence, PL/Scope was neglected by the Oracle community. But this seems to change with version 12.2. PL/Scope covers SQL statements, finally. This makes fine grained dependency analysis possible. Fine grained means on column level and on package unit level (procedure/function).

    PL/Scope is something like a software development kit (SDK) for source code analysis. It consists basically of the following two components:

    1. The compiler, which collects information when compiling source code (e.g. plscope_settings=’identifiers:all’)
    2. The dictionary views, providing information about collected identifiers (dba_identifiers) and SQL statements (dba_statements).

    The provided views are based on a recursive data structure which is not that easy to understand. Querying them will soon need recursive self joins and joins to other Oracle data dictionary views. Everybody is going to build some tools (scripts, reports, views, etc.) for their analysis. Wouldn’t it make sense to have some open sourced library doing that once for all? – Obviously the answer is yes. This library exists. It is available as a GitHub repository named plscope-utils.

    Other Helpful Stuff on PL/Scope

    The always helpful coverage from Tim Hall's ORACLE-BASE site:

    PL/Scope in Oracle Database 11g Release 1 (11.1)
    PL/Scope Enhancements in Oracle Database 12c Release 2 (12.2)

    Using PL/Scope in SQL Developer from That Jeff Smith

    Articles in Oracle Magazine:

    Zoom In on Your Code
    Powerful Impact Analysis with 12.2 PL/Scope

    The PL/SQL Collection Resource Center

    $
    0
    0
    Collections (Oracle PL/SQL's data structure to implement arrays, lists, stacks, queues, etc.) are not only handy in and of themselves, but are used for many key features of this powerful database programming language, including:
    • High performance querying with BULK COLLECT
    • Super-fast, bulk non-query DML operations with FORALL
    • Table functions (functions that can be treated like a table in a SELECT's FROM clause)
    PL/SQL offers three types of collections - associative arrays, nested tables, and varrays - each with their own characteristics and ideal use cases.

    If you are not already using collections on a regular basis in PL/SQL, you are really missing out.

    Use this article as starting point for accessing a number of useful resources for getting up to speed on collections, and putting them to use in your programs.

    Documentation

    The PL/SQL User Guide offers detailed coverage of collection features here. It starts by reviewing the differences between collections types.


    Articles

    ORACLE-BASE: Collections in PL/SQL - a roundup of collection features by Tim Hall, with lots of coverage of MULTISET operators (set operations in PL/SQL, not SQL)

    Multi-level collections in Oracle - great article by Adrian Billington on this advanced feature of collections (also known as nested collections)

    New to 18c: Qualified expressions for associative array: it's just one new feature for collections, but it's a great one - essentially constructor functions for this type of collection. Plus I wanted to show you that even this "late in the game" as 18c, we are still enhancing collection functionality.

    LiveSQL Scripts

    LiveSQL offers 24x7 access to the latest version of Oracle Database. You can "play around" with SQL and PL/SQL in a scratchpad environment. It also offers a code library. There are literally dozens of scripts in that library that focus on various aspects of collections. Start here and then tune your search accordingly.

    Videos


    An 11-video Youtube playlist on my Practically Perfect PL/SQL channel. Just to give you a sense of the depth of coverage, total time is over 4.5 hours.



    Manish (aka, Rebellion Rider) is one of the most prolific publishers of videos on Oracle PL/SQL. Check out this 17 video playlist for fast, basic videos on various aspects of collections.





    Code You Should Never See in PL/SQL

    $
    0
    0
    If you ever run across any of the following, apply the suggested cleanup, or contact the owner of the code, or run for the hills.

    And I am pretty sure many of my readers will have suggestions for other code that should never appear in your PL/SQL programs. Let me know in the comments and I will add them to the post (giving me all the credit of course - no, just joking! YOUR NAME will be bright lights. :-) ).


    Set default value to NULL

    Whenever you declare a variable it is assigned a default value of NULL. So you should not explicitly provide NULL as a default value. It won't do any harm, but it will tell others who read your code that your understanding of PL/SQL is, shall we say, incomplete.

    Bad Code

    DECLARE
    l_number NUMBER := NULL;
    BEGIN
    ...
    END;

    Cleaned Up

    Just remove the assignment.

    DECLARE
    l_number NUMBER;
    BEGIN
    ...
    END;

    Select from DUAL for....just about anything

    A long, long time ago, before PL/SQL was all grown up, it didn't have native implementations for some SQL functions like SYSDATE. So developers would use a "dummy" SELECT against a pre-defined table like DUAL in order to fall back on the SQL engine to get the job done, as in:

    Bad Code

    DECLARE
    l_now DATE;
    BEGIN
    SELECT SYSDATE INTO l_now FROM dual;
    END;

    Plus, if you wanted to get the next (or current) value of a sequence, you had to call the appropriate function via SQL, like:

    DECLARE
    l_next_pky INTEGER;
    BEGIN
    SELECT my_seq.NEXTVAL INTO l_next_pky FROM dual;
    END;

    Cleaned Up

    DECLARE
    l_now DATE;
    l_next_pky INTEGER;
    BEGIN
    l_now := SYSDATE;
    l_next_pky := my_seq.NEXTVAL;
    END;

    Declaration of FOR loop iterator

    When you use a FOR loop (numeric or cursor), PL/SQL automatically declares a variable for the record referenced in that loop, and then releases memory for that variable when the loop terminates. If you declare a variable with the same name, your code will compile, but you could easily introduce bugs into that code as you see below:

    Bad Code

    In both of the blocks below, the "confirming" call to DBMS_OUTPUT.put_line to confirm that the desired action occurred will never be executed. In the first block, the "indx"referenced in the IF statement resolves to the locally declared variable, which is not the same indx as the loop iterator. It is initialized to NULL and stays that well. Same with the explicitly declared rec variable in the second block.

    DECLARE
    indx INTEGER;
    BEGIN
    FOR indx IN 1 .. 12
    LOOP
    DBMS_OUTPUT.put_line (TO_DATE ('2018-' || indx || '-01', 'YYYY-MM-DD'));
    END LOOP;

    IF indx = 12
    THEN
    DBMS_OUTPUT.put_line ('Displayed all twelve months!');
    END IF;
    END;
    /

    DECLARE
    rec employees%ROWTYPE;
    BEGIN
    FOR rec IN (SELECT * FROM employees)
    LOOP
    DBMS_OUTPUT.put_line (rec.last_name);
    END LOOP;

    IF rec.employee_id IS NOT NULL
    THEN
    DBMS_OUTPUT.put_line ('Displayed all employees!');
    END IF;
    END;
    /

    Cleaned Up

    First step in clean up is to remove the declarations of those unused and unnecessary variables. The next step is for you to decide what logic you need to replace the IF statements after the loops. In the first case, why would I need an IF statement? If I got past the loop without an exception, then I certainly did display all the months.

    In the second block, it's a little bit trickier. When I use a cursor FOR loop, the PL/SQL engine does everything for me: open the cursor, fetch the rows, close the cursor. So once the cursor is closed, I have no visibility into what happened inside the cursor. If I want to know whether at least one row was fetched, for example, I need to set a local variable as you see below.

    BEGIN
    FOR indx IN 1 .. 12
    LOOP
    DBMS_OUTPUT.put_line (TO_DATE ('2018-' || indx || '-01', 'YYYY-MM-DD'));
    END LOOP;

    DBMS_OUTPUT.put_line ('Displayed all twelve months!');
    END;
    /

    DECLARE
    l_displayed BOOLEAN := FALSE;
    BEGIN
    FOR rec IN (SELECT * FROM employees)
    LOOP
    DBMS_OUTPUT.put_line (rec.last_name);
    l_displayed := TRUE;
    END LOOP;

    IF l_displayed
    THEN
    DBMS_OUTPUT.put_line ('Displayed all employees!');
    END IF;
    END;
    /

    WHEN NO_DATA_FOUND for non-query DML

    When you execute an implicit query (SELECT-INTO), PL/SQL raises NO_DATA_FOUND if no rows are returned by the query. As in:

    DECLARE
    l_id employees.employee_id%TYPE;
    BEGIN
    SELECT employee_id
    INTO l_id
    FROM employees
    WHERE 1 = 2;
    EXCEPTION
    WHEN NO_DATA_FOUND
    THEN
    DBMS_OUTPUT.put_line ('Not with that WHERE clause!');
    END;
    /

    Not with that WHERE clause!

    Bad Code

    But when I run this block (changing the SELECT to an UPDATE), no output is displayed.

    BEGIN
    UPDATE employees
    SET last_name = UPPER (last_name)
    WHERE 1 = 2;
    EXCEPTION
    WHEN NO_DATA_FOUND
    THEN
    DBMS_OUTPUT.put_line ('Not with that WHERE clause!');
    END;
    /

    That's because the PL/SQL engine does not raise an error for non-query DML that change no rows.

    Cleaned Up

    If you need to know that a row was modified (or how many rows were modified), use the SQL%ROWCOUNT attribute.

    BEGIN
    UPDATE employees
    SET last_name = UPPER (last_name)
    WHERE 1 = 2;

    IF SQL%ROWCOUNT = 0
    THEN
    DBMS_OUTPUT.put_line ('Not with that WHERE clause!');
    END IF;
    END;
    /

    Not with that WHERE clause!

    Loop containing non-query DML inside

    Last up, a biggie. That is, bad code with a potentially enormous negative impact on performance. Here goes:

    Bad Code

    CREATE TABLE parts
    (
    partnum NUMBER PRIMARY KEY,
    partname VARCHAR2 (15) UNIQUE NOT NULL
    )
    /

    BEGIN
    FOR indx IN 1 .. 10000
    LOOP
    INSERT INTO parts (partnum, partname)
    VALUES (indx, 'Part' || indx);
    END LOOP;
    END;
    /

    When you execute the same DML inside a loop repeatedly, changing only the variables that are bound into the statement, you are unnecessarily (and often dramatically) slowing down your code. The problem here is that I am switching between the PL/SQL and SQL engines 10000 times. Those context switches are relatively expensive.

    You should avoid this kind of row-by-row processing whenever possible. The key anti-pattern to look for is a loop with non-query DML (insert, update, delete, merge) inside it.

    Cleaned Up

    There are two ways to clean up this slow code.

    1. Write it in "pure SQL" if you can. If you don't need PL/SQL algorithms to process the data, and can do it all in SQL, then do it that way. For example in the above case, I could have written:

    BEGIN
    INSERT INTO parts
    SELECT LEVEL, 'Part ' || LEVEL
    FROM DUAL
    CONNECT BY LEVEL <= 1000;
    END;

    If you need PL/SQL or simply cannot figure out how to write it in pure SQL, then use the FORALL statement instead of a FOR loop. This statement results in just one context switch, with PL/SQL sending all the bind variables from the bind array (l_parts) across to SQL with a single INSERT statement (that statement, after all, never changes).

    DECLARE
    TYPE parts_t IS TABLE OF parts%ROWTYPE;

    l_parts parts_t := parts_t ();
    BEGIN
    FOR indx IN 1 .. 10000
    LOOP
    l_parts (indx).partnum := indx;
    l_parts (indx).partname := 'Part' || indx;
    END LOOP;

    FORALL indx IN 1 .. l_parts.COUNT
    INSERT INTO parts (partnum, partname)
    VALUES (l_parts (indx).partnum, l_parts (indx).partname);
    END;
    /

    For a much more detailed demonstration of converting row-by-row processing to bulk processing, check out my LiveSQL script.

    Check the doc for lots more information on PL/SQL bulk processing.

    Whew

    Well, I could probably go on and on, but this should be enough to give you some solid tips for writing better PL/SQL code - and inspire my readers to offer their own additions to the list.


    Viewing all 312 articles
    Browse latest View live