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

Object Types and Object-Oriented Development with PL/SQL: the Series

$
0
0
Find below the list of my blog posts in a series on working with object types (a.k.a, classes) in Oracle Database and, specifically, PL/SQL.

This series is not intended to offer an in-depth training on object-oriented development; instead, it introduces basic O-O concepts and shows how they are implemented in Oracle Database with PL/SQL.

Introduction to Object Types, Part 1
Object Types and Inheritance, Part 2
Object Types Methods, Part 3

Each post also contains a link to a LiveSQL script, so you can try out all the code yourself.

Still to come:

Comparison Methods, Part 4
Dynamic Polymorphism, Part 5

Always Free Autonomous Oracle Database: Let's get going!

$
0
0
On September 16, Larry Ellison announced a new Always Free tier for Oracle Cloud, which (for me) most importantly includes an Always Free Autonomous Oracle Database.

For nothing but the "cost" of providing your credit card information (which is only used for identification purposes and is henceforth ignored until you decide to upgrade to a paid service), everyone in the world now can use the most powerful, most advanced database in the world FOR FREE.

Of course, there are limits. But oh my what generous limits they are!

Get lots more details here.

And it's not just the amazing Autonomous Database (in both transaction processing and data warehouse flavors).

You get Oracle Application Express (APEX) so you can build websites writing very little code, and taking full advantage of your SQL and PL/SQL skills.

You get SQL Developer to write what code you need and manage your database.

You get Oracle Rest Data Services to build REST endpoints against your database.

And if you happen to write in Python or Javascript (Node.js) or Java or....the drivers are also there for you. Wow!

Now, I must confess, I've never had trouble getting access too a free Oracle Database. I've been working for Oracle or Oracle partners since 1987. But running my own little database on my laptop for my own experiments is so different from having access to a self-managed database on the Internet that I can use to build my own websites.

Oh my - the ideas I have bubbling around in my head!

As some of you may know, I have gotten very concerned about climate change and human-caused extinctions. That's why I founded fabe - for all a beautiful earth - with Vincent Morneau last year. That's why we are building the fabe app to make it easy to reduce consumption and rescue species.

But I am also - more locally - somewhat obsessed with rescuing native trees and other living things from the human-driven scourge of invasive species. So I am starting up a Carrboro Tree Rescuers group here in town.
And now I can also easily build a website for this initiative.

And then even more personal, family-oriented websites to help us all stay connected.

And...and...

Well, what are you waiting for? Visit https://www.oracle.com/cloud/free/ and sign up now.

Using Always Free Autonomous Database to help me heal our planet

$
0
0
I signed up for my Always Free Autonomous Database (AFAD) and a boatload of other cloud services. OK, what shall I do with them?

Hmmm....well....as some of you may know, I've gotten very concerned about climate change and human-caused extinctions. I started a project with Vincent Morneau called fabe - for all a beautiful earth - to help all of us reduce consumption, rescue species and reconnect to nature. Check it out at https://fab.earth. Yes, it is an APEX application and it is already running on an Oracle Database cloud service, so....what else?

You know the saying "think globally, act locally"? Well, if fabe is global, then my work on invasive species is local, very local.

When I lived in Chicago, I went out to the nearest "wild" spaces I could find and cut back buckthorn trees that didn't belong in Chicago, out-competed native trees for sunlight, and killed off those native species. I rescued trees - and the literally millions of living creatures that call those trees home.

It was just about the most fulfilling and meaningful action I've ever taken in my life.

Now that I live in North Carolina (Chapel Hill area), I have continued that tradition, but now focusing on wisteria, kudzu, English ivy, stilt grass and more. As I drive around, I identify areas that are neglected, areas overrun with invasives and whenever possible I head on over and start cutting back those plants which are wonderful - back where they evolved to grow. Sometimes I do this with permission, sometimes, well, it is an act of of non-violent (to humans, anyway) civil disobedience.

But just think: in two hours of cutting, I can rescue trees that are 50, 75 100 ft tall and being killed by wisteria. Trees that have been on this planet for decades and now will be here for decades more. All with the smallest sacrifice on my part. Joyful moments for me, for sure.

To give you a sense of the impact of my work, here is a section I just started to work on. The areas inside red show the dense grown of wisteria covering and weighing down the trees:
Once I cut the vines at the base (and some of them are 6 inches in diameter!) and they die and dry out, the "bones" of the tree are revealed:
You can see the dead wisteria vines on the left. On the right, all that sky that you can see through the trees was completely hidden by a dense, killing growth of wisteria just a few weeks ago!

Besides my individual work, I am also started up a volunteer group: Carrboro Tree Rescuers.

As I said, I have been identifying many different locations that need my loving attention (love expressed with saw, lopping shear, and pruning shear). So I've decided to build an app on my AFAD to help keep track of those locations.

I'll document the steps I took in building this application, so you can see how easy it is to leverage amazing products like Oracle Application Express, Oracle REST Data Services, SQL, PL/SQL and Oracle Database to help heal our planet and maybe even save some species from extinction.

I plan as much as possible to rely on the default features and behavior of APEX, to maximize my productivity on this side project and also showcase all that APEX does for you these days.

[And I must say that with APEX 19.2 Early Adaptor just out, I wish I could use that!]

I got started today by creating schemas and workspaces for my use in APEX. Then I decided to take advantage of the remarkable QuickSQL (brainchild of my boss, Mike Hichwa, who seriously you should be following) to ahem quickly generate the SQL I need to create the tables that will get me started.

Here's the QuickSQL script I threw together. I want to keep track of the various invasive species that plague this area, the various worksites, the species present at those sites, and a history of my activity at the sites.
invasive_species /auditcols
popular_name vc /nn /unique
species_name vc /nn /unique
impact_description vc /nn
location_list vc
picture blob
mime_type vc75

worksites /auditcols
name v /nn /unique
site_type v /check ADDRESS,INTERSECTION,DESCRIPTION,MILEMARKER /nn
location_status v /check NEW,CLEARED,INPROGRESS /nn
address
city
state
country
postal_code
intersection_street1
intersection_street2
description
milemarker n
before_picture blob
before_mime_type vc75
after_picture blob
after_mime_type vc75

worksite_species /auditcols
species_id /fk invasive_species /nn
worksite_id /fk worksites /nn

rescue_teams /auditcols
name vc /nn /unique
description vc
city
state
country
postal_code

worksite_teams /auditcols
rescue_team_id /fk rescue_teams /nn
worksite_id /fk worksites /nn

worksite_history/auditcols
worksite_id /fk worksites /nn
rescue_team_id /fk rescue_teams
activity_date d /nn
description /nn
picture blob
mime_type vc75
I am sure this will change a bit before I start building the application, but it's a solid start.

Oh and did I mention? If anyone would like to help me with this  (or other similar projects), let me know!

Using Object Types in Relational Tables, Part 4

$
0
0

So far in my series on object-oriented development in Oracle Database, all manipulation of object type instances have taken place in PL/SQL.

But as you may have guessed from the fact that you "CREATE OR REPLACE" object types, those types are also available for us in SQL. You can create relational tables of object types, called object tables. You can also define columns of relational tables whose datatypes are object types.

In this post, I will explore both of these approaches. All the code you see below may be found in this LiveSQL script, so you can get to know these features by playing around with them yourself.

Object Tables

It's easy to create object tables and work with the instances in those tables (both selecting and changing rows of data). Here's a simple example:
CREATE TYPE food_ot AS OBJECT (
name VARCHAR2 (100),
food_group VARCHAR2 (50),
grown_in VARCHAR2 (100)
) NOT FINAL
/

CREATE TABLE food_table OF food_ot
(CONSTRAINT food_table_pk PRIMARY KEY (name))
/

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

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

COMMIT;
END;
/

SELECT *
FROM food_table
/

NAME FOOD_GROUP GROWN_IN
---------------- ------------ --------------
Mutter Paneer Curry India
Cantaloupe Fruit Backyard

BEGIN
UPDATE food_table
SET grown_in = 'Florida'
WHERE name = 'Cantaloupe';
END;
/

SELECT *
FROM food_table
/

NAME FOOD_GROUP GROWN_IN
---------------- ------------ --------------
Mutter Paneer Curry India
Cantaloupe Fruit Florida

SELECT ft.name
FROM food_table ft
/

NAME
----------------
Mutter Paneer
Cantaloupe
Notice:
  • You use the TABLE OF syntax instead of specifying columns.
  • Each attribute in the object type is immediately and directly accessible as columns in each row.

Relational Tables with Object Type Columns

You can also define a relational table that has one or more columns whose datatype is an object type. We like to eat food at meals so let's go with that for our table design.

I will create a type hierarchy of food, desserts and cakes, and then use two of those in the table defintion.
CREATE OR REPLACE TYPE dessert_ot
UNDER food_ot
(
contains_chocolate CHAR (1),
year_created NUMBER (4)
)
NOT FINAL;
/

CREATE OR REPLACE TYPE cake_ot
UNDER dessert_ot
(
diameter NUMBER,
inscription VARCHAR2 (200)
);
/

CREATE TABLE meals (
served_on DATE,
appetizer food_ot,
main_course food_ot,
dessert dessert_ot
);
/
Now I will insert three rows into the table.
BEGIN
-- Populate the meal table
INSERT INTO meals (served_on, appetizer, main_course, dessert)
VALUES (SYSDATE,
food_ot ('Shrimp cocktail', 'PROTEIN', 'Ocean'),
food_ot ('Eggs benedict', 'PROTEIN', 'Farm'),
dessert_ot ('Strawberries and cream',
'FRUIT',
'Backyard',
'N',
2001));

INSERT INTO meals (served_on, appetizer, main_course, dessert)
VALUES (SYSDATE + 1,
food_ot ('House Salad', 'VEGETABLE', 'Farm'),
food_ot ('Stir fry tofu', 'PROTEIN', 'Vat'),
cake_ot ('Apple Pie',
'FRUIT',
'Baker''s Square',
'N',
2001,
8,
NULL));

INSERT INTO meals (served_on, appetizer, main_course, dessert)
VALUES (SYSDATE + 1,
food_ot ('Fried Calamari', 'PROTEIN', 'Ocean'),
dessert_ot ('Butter cookie',
'CARBOHYDRATE',
'Oven',
'N',
2001),
cake_ot ('French Silk Pie',
'CARBOHYDRATE',
'Baker''s Square',
'Y',
2001,
6,
'To My Favorite Frenchman'));

INSERT INTO meals (served_on, appetizer, main_course, dessert)
VALUES (SYSDATE + 1,
NULL,
cake_ot ('French Silk Pie',
'CARBOHYDRATE',
'Baker''s Square',
'Y',
2001,
6,
'To My Favorite Frenchman'),
dessert_ot ('Butter cookie',
'CARBOHYDRATE',
'Oven',
'N',
2001));
END;
/
Notice that even though the main_course is defined as type food_ot, I can have dessert or cake for the main course! And even though the dessert column is defined with type dessert_ot, my dessert can be a cake.

That works because of type substitutability, which can be summarized as follows: every cake is a dessert, but not every dessert is a cake. So wherever I use a food_ot type, dessert_ot and cake_ot also are accepted.

But if I use a subtype, then an instance of a supertype will not work, as you can see with the following attempt to add a row:
BEGIN
INSERT INTO meals (served_on, appetizer, main_course, dessert)
VALUES (SYSDATE,
food_ot ('Shrimp cocktail', 'PROTEIN', 'Ocean'),
dessert_ot ('Strawberries and cream',
'FRUIT',
'Backyard',
'N',
2001),
food_ot ('Lollipop', 'SUGAR', 'Factory'));
END;
/

PL/SQL: ORA-00932: inconsistent datatypes:
expected ORACLEFREE.FOOD_OT got ORACLEFREE.DESSERT_OT
Now let's query data from the meals table. I can, of course, select non-type column values in the usual way. But if I want to get the value of an attribute from one of the type columns, I must use a table alias.
SELECT served_on FROM meals
/

SERVED_ON
---------
03-OCT-19
04-OCT-19
04-OCT-19
04-OCT-19

/* Try to access an attribue of an object type instance... */

SELECT served_on, NVL (appetizer.name, 'Not that hungry') appetizer
FROM meals
/

ORA-00904: "APPETIZER"."NAME": invalid identifier

/* MUST use a table alias! */

SELECT served_on, NVL (m.appetizer.name, 'Not that hungry') appetizer
FROM meals m
/

SERVED_ON APPETIZER.NAME
--------- ------------------------
03-OCT-19 Shrimp cocktail
04-OCT-19 House Salad
04-OCT-19 Fried Calamari
04-OCT-19 Not that hungry
So now let's see which of my main courses contain chocolate.
SELECT m.contains_chocolate
FROM meals m
/

ORA-00904: "M"."CONTAINS_CHOCOLATE": invalid identifier
Huh? Why didn't that work? Because not every instance in the main_course column is of type dessert or cake. Many are of type food_ot, and that does not have a contains_chocolate attribute.

We need to be able to identify which columns contain an instance of the dessert type. For that we will use the TREAT function. With TREAT, you can change the declared type of the expression. I use it below to find all the meals whose main course is actually a dessert.
SELECT m.served_on,
m.main_course.name
FROM meals m
WHERE TREAT (main_course AS dessert_ot) IS NOT NULL
/

SERVED_ON MAIN_COURSE.NAME
--------- ----------------
04-OCT-19 Butter cookie
04-OCT-19 French Silk Pie
OK, so now let's show which of those silly dessert-as-main-courses contains chocolate.
SELECT main_course.contains_chocolate chocolatey
FROM meals
WHERE TREAT (main_course AS dessert_ot) IS NOT NULL
/

ORA-00904: "MAIN_COURSE"."CONTAINS_CHOCOLATE": invalid identifier
Argh! Still not cooperating. Well, that's because I also need to use TREAT in my SELECT clause. This works:
SELECT TREAT (main_course AS dessert_ot).contains_chocolate chocolatey,
TREAT (main_course AS dessert_ot).year_created
FROM meals
WHERE TREAT (main_course AS dessert_ot) IS NOT NULL
/

CHOCOLATEY YEAR_CREATED
---------- ------------
N 2001
Y 2001
OK, not exactly concise, but it gets the job done.

Here's another use of TREAT: suppose I don't want any desserts in my table that are not really and truly cakes. TREAT(dessert AS cake_ot)will return NULL if the instance in the dessert column is not a cake, so this update will set the columns to NULL for non-cakes and the existing cake instance if a cake.
SELECT m.dessert.name
FROM meals m
WHERE TREAT (main_course AS dessert_ot) IS NOT NULL
/

DESSERT.NAME
----------------
French Silk Pie
Butter cookie

UPDATE meals
SET dessert = TREAT (dessert AS cake_ot)
/

4 rows updated.

SELECT m.dessert.name
FROM meals m
WHERE TREAT (main_course AS dessert_ot) IS NOT NULL
/

DESSERT.NAME
----------------
French Silk Pie
All righty. That should be enough to get you started on your exploration of using object types inside relational tables. But wait....one more thing. What if I want to use an ORDER BY clause with an object type column? What if I need to answers questions like "Is this instance greater or less than another instance?"

Let's try it.
SELECT m.main_course.name
FROM meals m
ORDER BY main_course
/

ORA-22950: cannot ORDER objects without MAP or ORDER method
Oh my. An ORDER method? A MAP method? What are those?

I will answer those questions, dear reader, in my very next post in this series. In the meantime, have a look at the doc.

Happy coding!

Check Out the Series

Visit this post that gives you quick access to all the articles in the series.

P.S. Don't forget that if you'd like to try out all this code for yourself, all you have to do is run this LiveSQL script.

Groundbreaker Appreciation: Those Who Give of Themselves

$
0
0

I've been working - and personally benefiting from - Oracle Database technology since 1987. I joined Oracle as a pre-sales consultant, which meant back then I was a techie sidekick for one or more Oracle salespeople. I moved on to various other roles and in 1992 left to become a consultant. Two years later, wrote a book on PL/SQL and have been obsessed with that language ever since. In 1999, I released the first version of utPLSQL - unit test for PLSQL, my version of JUnit. Worked for Quest for many years (bringing Quest Code Tester for Oracle to the market, among other things), and in 2014 rejoined Oracle, where I now lead a team of developer advocates (Blaine Carter, Chris Saxon, Connor McDonald, Dan McGhan).

It's been a great life - and I expect it to keep on being such for a while to come. Part of the point of my little historical review, though, is that I was always paid to provide resources to the community (some of them free, like utPLSQL and my many PL/SQL Scripts, some of them requiring a credit card, like my books and trainings).

But there are many people in our community who give of themselves, freely, who commit a remarkable amount of their "free time" to help others in the community. This help can take many forms: answering questions (with respect and politeness) on forums, offering outstanding technical resources on their websites, creating free plug-ins for tools like SQL Developer and Oracle Application Express.

So I thought I'd use Oracle Groundbreaker Appreciation Day to give a shout-out to some of these generous contributors. There are, of course, many more than you will read about in this post. Please do not take offense if you are not here. Please honor others you know about in the comments on this post. Please feel free to contact me directly (steven.feuerstein@oracle.com or via Twitter) to tell me about your projects. I'd love to keep adding to this blog post over time.

Tim Hall and ORACLE-BASE


ORACLE-BASE and Tim Hall likely need no introduction to anyone reading my blog. Tim's website is the de facto go-to resource on most things Oracle Database. He provides easy-to-consume articles on hundreds of topics, which not only explain things in a clear manner, but include scripts designed with the working professional in mind - easy to copy-paste, easy to run, easy to learn from.

Beyond Oracle Base, Tim has been a voice for the community to Oracle for years, and has maintained a level of clarity and integrity on a variety of challenging scenarios that have made us all better.

Thanks, Tim!

Philipp Salvisberg and His Many SQL Developer Plug-Ins

Philipp is a remarkably prolific contributor to the community. With his development and promotion of PinkDB (a pragmatic approach to secure, efficient leveraging of databases in application development), a blog that never ceases to educate deeply, and his long list of plug-ins for SQL Developer (see below), I wonder if his family even remembers what he looks like. :-)

Thanks, Philipp, for all you do!


The utPLSQL v3 Team


As I noted above, I released the first version of utPLSQL in 1999. I believe it was the first (semi)automated testing framework for PL/SQL, but many others have been added over the years. A few years ago, a group of "next generation" developers (younger than me, wiser than me, adept at more than PL/SQL) decided it was time to freshen up that aging framework.

The result if utPLSQL v3, a complete rewrite of the previous two versions, built around an object-oriented approach that is more consistent with other *Unit frameworks for other languages (such as JUnit for Java). They've been rapidly adding all sorts of powerful features, even came up with a logo, t-shirts and an absolutely irresistible enthusiasm for all things testing when it comes to PL/SQL and Oracle Database.

Many developers contribute to utPLSQL, but I would like to give a shout out and thanks to key players, including Jacek Gebal, Pavel Kaplya and Samuel Nitsche, aka, Sith Lord Tester Magnifique.

Juergen Schuster and apex.world

Juergen is a legend in the world of Oracle Application Express, not just because of his outsized personality and viewpoints. :-) Juergen is deeply committed to helping APEX developers and is the main brain behind apex.world, the #1 community resource for APEX developers (though Juergen will be the first to point out that many others contribute to building and maintaining the site itself). This site offers an active Slack channel, the best site on the planet to find APEX plug-ins, access to other community resources and more.

Thanks, Juergen, for your devotion to fellow-APEX users!


Daniel Hochleitner and APEX Plug-ins

If you are an active user of APEX and looking for ways to enhance your application with some really spiffy functionality, then you might go to apex.world and click on the Plug-ins tab. There you will find over 200 plug-ins, a fact that all by itself says a lot about the amazing user community that's grown up around Application Express.

Look closer and you will find that the #1 most exuberant and busy contributor of such plug-ins is a fellow named Daniel Hochleitner. Daniel's plug-ins, support in enhancing apex.world, and also his work on APEX Theme Styles make him a standout contributor in the APEX community (though I must confess, there are so many others!).

Many thanks, Daniel!

BluShadow on the SQL-PL/SQL Forum

Long before there was StackOverflow, there was the SQL-PLSQL Forum at the Oracle Technology Network (which has since gone through several re-brandings). In addition to AskTOM, this forum was one of the top places to go to get help from Oracle Database experts. It is still fulfilling that function today.

There are literally dozens of expert users (not Oracle employees) who devote an enormous amount of their time on this forum, and I salute them all!

I give a specific shout-out to BluShadow (who has "only" the 5th highest number of points on this forum!), because I have been so impressed not only by the endless patience and helpfulness of his responses to questions, but also by the documents he contributes to the forum's library, covering a wide variety of topics of interest to many database developers.

Thanks, BluShadow, for sticking by our users for so many years!

Morten Braten and the Alexandria Library

Morten has been around a while, and boy has he kept busy. He is perhaps best known for the Alexandria PL/SQL Utility Library, now on Github, which offers a wide variety of utilities such as integrating PL/SQL with Amazon, PayPal, Google, Twitter and other external services.

He also has built plug-ins for APEX, offers the Thoth Gateway, and perhaps nearest and dearest to my heart, published an excellent PL/SQL resource: PL/SQL, the Good Parts.

A big thanks to Morten, for all you've done to make PL/SQL a successful part of modern application development!

OK But Now I Feel Bad

By highlighting the above over-the-top contributors, I am immediately reminded of all the people I am not recognizing for all their amazing work with the community. My apologies in advance! But please don't feel offended. You are all appreciated and recognized and thanked on a regular basis (at least I think you are).

Plus, I will be happy to add to this post over the coming months and years, so don't hesitate to contact me with a name, a site, a write-up of what makes that person so special to you, and they will be added to this list.

Clearly, Oracle Database wouldn't be the product it is today, the software that has helped billions of people live better lives, without the amazing team led by Andy Mendelsohn at Oracle HQ. 

But hey that's their (our) job. Even more amazing, more wonderful, more critical, more inspiring are the thousands of people around the world who give of themselves - their time, their hard-won expertise, their code - to help others be more successful.

So let it be known far and wide:

Comparison Methods for Object Types, Part 5

$
0
0
There are special member methods - map or order methods - that we use to tell Oracle Database how to compare two objects of the same datatype. This capability is critical when we want to perform an equality test in PL/SQL or when sorting objects in SQL.

There is no default way to do this. In other words, if I create a simple object type, add it as a column to a table, and try to compare or sort, all I get are errors. Let's take a look. First I will create a table that has an object type as a column and add a couple of rows.
CREATE TYPE food_ot AS OBJECT
(
name VARCHAR2 (100),
food_group VARCHAR2 (50),
grown_in VARCHAR2 (100)
)
NOT FINAL
/

CREATE TABLE meals
(
served_on DATE,
main_course food_ot
);
/

BEGIN
INSERT INTO meals (served_on, main_course)
VALUES (SYSDATE, food_ot ('Shrimp cocktail', 'PROTEIN', 'Ocean'));

INSERT INTO meals (served_on, main_course)
VALUES (SYSDATE + 1, food_ot ('House Salad', 'VEGETABLE', 'Farm'));

COMMIT;
END;
/
Next I will query the contents of this table. Notice that I can order by an attribute within the type column's value. I can also perform an equality comparison between instances of the object type.
  SELECT m.main_course.name name
FROM meals m
ORDER BY m.main_course.name
/

NAME
---------------
House salad
Shrimp Cocktail

SELECT m.main_course.name name
FROM meals m, meals m2
WHERE m.main_course = m2.main_course
ORDER BY m.main_course.name
/

NAME
---------------
House salad
Shrimp Cocktail
That default equality comparison does an attribute-by-attribute comparison - and that only works if you do not have LOB or user-defined type columns. In those cases, you will see an error:
CREATE TYPE food_with_clob_ot AS OBJECT
(
name VARCHAR2 (100),
grown_in CLOB
)
NOT FINAL
/

CREATE TABLE meals_with_clobs
(
served_on DATE,
main_course food_with_clob_ot
);
/

SELECT m.main_course.name name
FROM meals_with_clobs m, meals_with_clobs m2
WHERE m.main_course = m2.main_course
ORDER BY m.main_course.name
/

ORA-22901: cannot compare VARRAY or LOB attributes of an object type
Now let's try to (a) sort the rows with ORDER BY and (b) do a non-equality comparison. It's a "no go." The default behavior is no longer available to satisfy these queries.
  SELECT m.main_course.name name
FROM meals m
ORDER BY m.main_course
/

ORA-22950: cannot ORDER objects without MAP or ORDER method

SELECT m.main_course.name name
FROM meals m, meals m2
WHERE m.main_course > m2.main_course
/

ORA-22950: cannot ORDER objects without MAP or ORDER method
And as far as equality comparisons go, that only works by default in SQL, not in PL/SQL.
DECLARE
m1 food_ot := food_ot ('Shrimp cocktail', 'PROTEIN', 'Ocean');
m2 food_ot := food_ot ('House Salad', 'VEGETABLE', 'Farm');
BEGIN
IF m1 = m1
THEN
DBMS_OUTPUT.put_line ('Equal');
END IF;

IF m1 <> m2
THEN
DBMS_OUTPUT.put_line ('Unequal');
END IF;
END;
/

PLS-00526: A MAP or ORDER function is required for comparing objects in PL/SQL.
So what's a developer to do? Read those error messages and get to work!

If you "cannot ORDER objects without MAP or ORDER method," perhaps you should create a MAP or ORDER method. :-)

A MAP method is a member method (attached to an instance of the type) that returns a "mapping" of the object value onto a datatype that Oracle Database already knows how to compare, such as a number or string.

An ORDER member method compares two different instances of a type and returns a flag value that indicates their relative ordering.

You can only have one MAP or ORDER method in an object type definition. They cannot co-exist.

The MAP Method

A MAP method performs calculations on the attributes of the object to produce a return value of ny Oracle built-in data types (except LOBs and BFILEs) and ANSI SQL types such as CHARACTER or REAL. 

This method is called automatically by Oracle Database to evaluate such comparisons as obj_1 > obj_2 and comparisons that are implied by the DISTINCT, GROUP BY, UNION, and ORDER BY clauses - since these all require sorting by rows in the table.

"Automatic" means: 

1. You never invoke a map method directly in your code.

2. Assuming the type has a map method called "mapme", then when you write a comparison like this

obj_1 > obj_2

it is automatically translated (invisibly to you) to:

obj_1.mapme() > obj_2.mapme()

Let's add a map method to the food type. I'll keep it simple and silly. Proteins rate higher than liquids, which rate higher than carbs, which rate higher than vegetables. Take that number and add to the length of the food name. Then return that number for mapping. Finally, add some rows.
CREATE TYPE food_t AS OBJECT
(name VARCHAR2 (100)
, food_group VARCHAR2 (100)
, grown_in VARCHAR2 (100)
, MAP MEMBER FUNCTION food_mapping
RETURN NUMBER
)
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY food_t
IS
MAP MEMBER FUNCTION food_mapping
RETURN NUMBER
IS
BEGIN
RETURN (CASE self.food_group
WHEN 'PROTEIN' THEN 30000
WHEN 'LIQUID' THEN 20000
WHEN 'CARBOHYDRATE' THEN 15000
WHEN 'VEGETABLE' THEN 10000
END
+ LENGTH (self.name));
END;
END;
/

BEGIN
-- Populate the meal table
INSERT INTO meals
VALUES (SYSDATE, food_ot ('Shrimp cocktail', 'PROTEIN', 'Ocean'));

INSERT INTO meals
VALUES (SYSDATE + 1, food_ot ('Stir fry tofu', 'PROTEIN', 'Wok'));

INSERT INTO meals
VALUES (SYSDATE + 1,
food_ot ('Peanut Butter Sandwich',
'CARBOHYDRATE',
'Kitchen'));

INSERT INTO meals
VALUES (SYSDATE + 1,
food_ot ('Brussels Sprouts', 'VEGETABLE', 'Backyard'));

COMMIT;
END;
/
Now I perform an ORDER BY on the object type column, check for inequality among rows, and also perform comparisons inside PL/SQL.
  SELECT m.main_course.name name
FROM meals m
ORDER BY main_course
/

NAME
----------------------
Brussels Sprouts
Peanut Butter Sandwich
Stir fry tofu
Shrimp cocktail

SELECT m1.main_course.name name
FROM (SELECT *
FROM meals m
WHERE m.main_course.name LIKE 'S%') m1,
(SELECT *
FROM meals m
WHERE m.main_course.name NOT LIKE 'S%') m2
WHERE m1 > m2
ORDER BY m1.main_course
/

NAME
---------------
Stir fry tofu
Stir fry tofu
Shrimp cocktail
Shrimp cocktail

DECLARE
ot1 food_ot := food_ot ('Eggs benedict', 'PROTEIN', 'Farm');
ot2 food_ot := food_ot ('Brussels Sprouts', 'VEGETABLE', 'Backyard');
ot3 food_ot := food_ot ('Brussels Sprouts', 'VEGETABLE', 'Backyard');
BEGIN
IF ot1 = ot2
THEN
DBMS_OUTPUT.put_line ('equal - incorrect');
ELSE
DBMS_OUTPUT.put_line ('not equal - correct');
END IF;

IF ot2 <> ot3
THEN
DBMS_OUTPUT.put_line ('not equal - incorrect');
ELSE
DBMS_OUTPUT.put_line ('equal - correct');
END IF;
END;
/

not equal - correct
equal - correct
Notice that in the query that joins m1 and m2, "Stir fry tofu" comes before "Shrimp cocktail" because it has fewer characters and thus a small number returned by the map function.

The ORDER Method

Unlike map methods, order methods cannot determine the order of a number of objects. They simply tell you that the current object is less than, equal to, or greater than the object that it is being compared to, based on the criterion used.

An order method is a function for an object (SELF), with one declared parameter that is an object of the same type. The method must return either a negative number, zero, or a positive number. This value signifies that the object (the implicit undeclared SELF parameter) is less than, equal to, or greater than the declared parameter object.

As with map methods, an order method, if one is defined, is called automatically whenever two objects of that type need to be compared.

Order methods are useful where comparison semantics may be too complex to use a map method.

Let's build an order method for the food type. Let's start with the specification for the food type, and also create the type hierarchy:
CREATE TYPE food_ot AS OBJECT
(
name VARCHAR2 (100),
food_group VARCHAR2 (100),
ORDER MEMBER FUNCTION food_ordering (other_food_in IN food_ot)
RETURN INTEGER
)
NOT FINAL;
/

CREATE TYPE dessert_ot UNDER food_ot (
contains_chocolate CHAR (1)
, year_created NUMBER (4)
)
NOT FINAL;
/

CREATE TYPE cake_ot UNDER dessert_ot (
diameter NUMBER
, inscription VARCHAR2 (200)
);
/

Notice that I use the ORDER keyword, pass in an instance of the type, against which SELF will be compared. I return an integer: either -1, 0 or 1. And now the implementation. Here are some notes, given its complexity:
  • Since an instance could be of food, dessert or cake, the first rule is that a supertype is always greater than a subtype. I use self IS OF (ONLY my_type) syntax to determine the type of the instance.
  • I use a string-indexed collection, l_order_by_food_group to establish the hierarchy of ordering by food group. (I really like string-indexed collections!)
  • If after checking for supertype/subtype ordering, I know that other_food_in is of the same type as SELF, then I set return value according to food group.
CREATE OR REPLACE TYPE BODY food_ot
IS
ORDER MEMBER FUNCTION food_ordering (other_food_in IN food_ot)
RETURN INTEGER
/*
Subtypes are always less. Food > Dessert > Cake

If of the same type, same rule AS for MAP:
Vegetable < Carbohydrate < Liquid < Protein
*/
IS
TYPE order_by_food_group_t IS TABLE OF PLS_INTEGER
INDEX BY VARCHAR2 (100);

l_order_by_food_group order_by_food_group_t;
c_self_eq_of CONSTANT PLS_INTEGER := 0;
c_self_gt_of CONSTANT PLS_INTEGER := 1;
c_of_gt_self CONSTANT PLS_INTEGER := -1;
l_ordering PLS_INTEGER := c_self_eq_of;

PROCEDURE initialize
IS
BEGIN
l_order_by_food_group ('PROTEIN') := 1000;
l_order_by_food_group ('LIQUID') := 100;
l_order_by_food_group ('CARBOHYDRATE') := 10;
l_order_by_food_group ('VEGETABLE') := 1;
END initialize;
BEGIN
initialize;

IF self IS OF (ONLY food_ot)
THEN
l_ordering :=
CASE
WHEN other_food_in IS OF (ONLY food_ot) THEN c_self_eq_of
ELSE c_self_gt_of
END;
ELSIF self IS OF (ONLY dessert_t)
THEN
l_ordering :=
CASE
WHEN other_food_in IS OF (ONLY dessert_t) THEN c_self_eq_of
WHEN other_food_in IS OF (ONLY food_ot) THEN c_of_gt_self
ELSE c_self_gt_of
END;
ELSE
/* It is cake. */
l_ordering :=
CASE
WHEN other_food_in IS OF (ONLY cake_t) THEN c_self_eq_of
ELSE c_of_gt_self
END;
END IF;

IF l_ordering = c_self_eq_of
THEN
/*
Further analysis is needed.
*/
l_ordering :=
CASE
WHEN l_order_by_food_group (self.food_group) =
l_order_by_food_group (other_food_in.food_group)
THEN
c_self_eq_of
WHEN l_order_by_food_group (self.food_group) >
l_order_by_food_group (other_food_in.food_group)
THEN
c_self_gt_of
WHEN l_order_by_food_group (self.food_group) <
l_order_by_food_group (other_food_in.food_group)
THEN
c_of_gt_self
END;
END IF;

RETURN l_ordering;
END;
END;
/
Now I will add rows of various types.
BEGIN
-- Populate the meal table
INSERT INTO meals
VALUES (SYSDATE, food_ot ('Shrimp cocktail', 'PROTEIN'));

INSERT INTO meals
VALUES (SYSDATE + 1, food_ot ('Stir fry tofu', 'PROTEIN'));

INSERT INTO meals
VALUES (SYSDATE + 1,
dessert_ot ('Peanut Butter Sandwich',
'CARBOHYDRATE',
'N',
1700));

INSERT INTO meals
VALUES (SYSDATE + 1, food_ot ('Brussels Sprouts', 'VEGETABLE'));

INSERT INTO meals
VALUES (SYSDATE + 1,
cake_ot ('Carrot Cake',
'VEGETABLE',
'N',
1550,
12,
'Happy Birthday!'));

COMMIT;
END;
/
All right, then, let's have some fun! Ordering rows works. SQL comparisons work.
  SELECT m.main_course.name name
FROM meals m
ORDER BY main_course
/

NAME
----------------------
Carrot Cake
Peanut Butter Sandwich
Brussels Sprouts
Shrimp cocktail
Stir fry tofu

SELECT m1.main_course.name name
FROM (SELECT *
FROM meals m
WHERE m.main_course.name LIKE 'S%') m1,
(SELECT *
FROM meals m
WHERE m.main_course.name NOT LIKE 'S%') m2
WHERE m1.main_course > m2.main_course
ORDER BY m1.main_course
/

NAME
---------------
Shrimp cocktail
Shrimp cocktail
Shrimp cocktail
Stir fry tofu
Stir fry tofu
Stir fry tofu
And how about in PL/SQL?
DECLARE
ot1 food_ot := food_ot ('Eggs benedict', 'PROTEIN');
ot2 food_ot := food_ot ('Brussels Sprouts', 'VEGETABLE');
ot3 food_ot := dessert_ot ('Brownie', 'SUGAR', 'Y', 1943);
ot4 food_ot := cake_ot (
'Carrot Cake', 'VEGETABLE', 'N', 1550, 12, 'Happy Birthday!');
BEGIN
IF ot1 = ot1
THEN
DBMS_OUTPUT.put_line ('equal - correct');
ELSE
DBMS_OUTPUT.put_line ('not equal - incorrect');
END IF;

IF ot1 = ot2
THEN
DBMS_OUTPUT.put_line ('equal - incorrect');
ELSE
DBMS_OUTPUT.put_line ('not equal - correct');
END IF;

IF ot2 <> ot3
THEN
DBMS_OUTPUT.put_line ('not equal - correct');
ELSE
DBMS_OUTPUT.put_line ('equal - incorrect');
END IF;

IF ot2 > ot3
THEN
DBMS_OUTPUT.put_line ('food > dessert - correct');
ELSE
DBMS_OUTPUT.put_line ('food < dessert - incorrect');
END IF;

IF ot3 > ot4
THEN
DBMS_OUTPUT.put_line ('dessert > cake - correct');
ELSE
DBMS_OUTPUT.put_line ('dessert < cake - incorrect');
END IF;

IF ot3 < ot4
THEN
DBMS_OUTPUT.put_line ('dessert < cake - incorrect');
ELSE
DBMS_OUTPUT.put_line ('dessert > cake - correct');
END IF;
END;
/

equal - correct
not equal - correct
not equal - correct
food > dessert - correct
dessert > cake - correct
dessert > cake - correct
All good!

Check Out the Entire Series

Visit this post that gives you quick access to all the articles in the series.

P.S. Don't forget that if you'd like to try out all this code for yourself, all you have to do is run this LiveSQL script.

Real World Testing of PL/SQL Code - An Office Hours Session

$
0
0
Since February 2019, I've been running, with Chris Saxon, on my team of Developer Advocates, monthly PL/SQL Office Hours sessions. They generally consist of short presentations on a PL/SQL-related topic, followed by lots of interesting discussion - between Chris and I, with attendees on the session, and with other speakers.

On November 5 at 9 AM Eastern, I am very pleased to have a session focused on testing PL/SQL code, featuring developers who are doing it out there in the "real world."

No application will ever have zero bugs, but you sure want to keep them to a minimum. The best way to do this is to implement automated regression tests of your code, but "best" as usual does not equate to "easiest." Building and managing tests can be a big challenge, so in this Office Hours session, we will hear from developers who are doing just that. Learn from your peers about the obstacles they faced and how they overcame them. Bring your own stories and your questions, and let's all work together on improving our code quality!

Our main presenter will be Jasmin Fluri.
Ms. Fluri is an independent consultant at Schaltstelle GmbH and lectures on software engineering and code review at the University of Applied Sciences North-western Switzerland. Her focus as a database developer and DevOps engineer lies on continuous integration and delivery pipelines, automation of recurring tasks, PL/SQL development, data engineering, and data warehousing.

We will also be joined by Samuel Nitsche, one of the core maintainers of utPLSQL:
Samuel Nitsche is a curiosity-driven software-developer with nearly 20 years of development experience, working at Smart Enterprise Solutions GmbH, a small software company in southern Germany. In his free time he writes regularly about database development and testing topics, presents at meetups and conferences (gladly in sith-robe) and works on making the framework "even more awesome."

If you have been doing any kind of automated regression testing of your Oracle Database code, we'd love to hear from you, whether you are using SQL Developer integrated unit testing, utPLSQL, Quest Code Tester, another open source framework...or even (especially!) your own "homegrown" approach.

All our welcome. Please do get in touch if you'd like to present your experiences.

Subscribe for reminders at the PL/SQL Office Hours home page so that you can get email reminders about this and other sessions!

Why isn't my exception section catching my error?

$
0
0
I got an interesting email today from a reader of one of my PL/SQL 101" articles for Oracle Magazine, Building with Blocks.

Q. had taken the code from the article, made some changes, tried to run them, and got very confused. He wrote:

When I run this code, I see "Hello World".
DECLARE
l_message VARCHAR2(100) := 'Hello World!';
BEGIN
DBMS_OUTPUT.put_line (l_message);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error='||SQLERRM);
END;
/

Hello World!
When I change the block to make the l_message variable too small for its string, I see the VALUE_ERROR error message.
DECLARE
l_message VARCHAR2(10);
BEGIN
l_message := 'Hello World!';
DBMS_OUTPUT.put_line (l_message);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error='||SQLERRM);
END;
/

Error=ORA-06502: PL/SQL: numeric or value error: character string buffer too small
But when I change the name of the variables inside the call to DBMS_OUTPUT.PUT_LINE to "l_message1", the exception handler is not displaying the error message, instead I see the following:
When I change the block to make the l_message variable too small for its string, I see the VALUE_ERROR error message.
DECLARE
l_message VARCHAR2(10) := 'Hello World!';
BEGIN
DBMS_OUTPUT.put_line (l_message1);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error='||SQLERRM);
END;
/

ORA-06550: line 5, column 25:
PLS-00201: identifier 'L_MESSAGE1' must be declared 

Why am I seeing this inconsistent behavior?  

I was confused about the source of his confusion, but after a couple of back-and-forth emails (what you read above is the cleaned-up version of that back-and-forth), the light bulb lit in my brain. Which enabled me to clear up his confusion, and inspire me to write a blog post, in case the same confusion was confusing anyone else.

Here's the most important thing to remember:
Exception sections handle exceptions raised when the block is executed. But first that block must be compiled!
When you are writing code within stored program units, like procedures and functions, this is clearly a two stage process:
  1. Compile
  2. Execute
But when you are working with an anonymous block, all you do is execute the block. So you can certainly be forgiven for thinking that anything that goes wrong will be caught in the exception handler.

The reality is different however. When you run an anonymous block, the PL/SQL engine will first of all parse and compile the block. If all goes well, then the PL/SQL runtime engine will execute the compiled code.

If, however, your code fails to compile, well....you will get a compile error, rather than an runtime exception.

How do you know you've gotten a compile error? Check the prefix. If it is "PLS", something went kablooey at compile time. Your code cannot be executed. If the prefix is "PLW", that's a compile-time warning, which means that the code compiled, but the PL/SQL engine has suggestions for improving it. And if the prefix is "ORA", ah, then you've got a runtime error - an exception.

Only in that last case with the exception handler, if present, be able to trap the exception.

I hope that makes thing nice and clear. Any comments or questions? :-)



PL/SQL Puzzle: Add one statement to stop exceptions

$
0
0
OK, folks, here's a PL/SQL puzzle. It was originally posted on Twitter. I give you the link to that thread at the end of this post. But first....don't you want to try to solve the puzzle yourself? :-)

Please note that the solutions to the puzzle have absolutely NOTHING to do with writing good code. They are simply exercising various features of the PL/SQL language.

The puzzle

Can you come up with just ONE STATEMENT to add to plsqlpuzzle_proc so that it can execute without terminating with an unhandled exception? Use this LiveSQL script as a starting point for your attempted solutions.
CREATE TABLE plsqlpuzzle (n NUMBER) 
/

CREATE OR REPLACE PROCEDURE plsqlpuzzle_proc
IS
r plsqlpuzzle%ROWTYPE;

TYPE r_t IS TABLE OF plsqlpuzzle%ROWTYPE
INDEX BY PLS_INTEGER;

t r_t;
BEGIN
SELECT * INTO r FROM plsqlpuzzle;

DBMS_OUTPUT.put_line (r.n);

DBMS_OUTPUT.put_line (t (1).n);
END;
/

BEGIN
plsqlpuzzle_proc;
END;
/

ORA-01403: no data found
I offer below the solutions that I and others came up with. But first a nice big chunk of white space so you do not see those solutions immediately.


So. You looked at the code and you came up with some ideas? I hope so!

If by chance you have a solution that we do not show below, please add it via comment. I will move it to the post, give you credit and you will be FAMOUS. :-)

Why not just insert a row?

As you likely know, a SELECT-INTO (implicit single-row query) raises the NO_DATA_FOUND exception if no rows are identified. Since the plsqlpuzzle table is empty, an immediate thought is to use that one statement to add a row to the table. Let's try that.
CREATE TABLE plsqlpuzzle (n NUMBER) 
/

CREATE OR REPLACE PROCEDURE plsqlpuzzle_proc
IS
r plsqlpuzzle%ROWTYPE;

TYPE r_t IS TABLE OF plsqlpuzzle%ROWTYPE
INDEX BY PLS_INTEGER;

t r_t;
BEGIN
INSERT INTO plsqlpuzzle VALUES (100);

SELECT * INTO r FROM plsqlpuzzle;

DBMS_OUTPUT.put_line (r.n);

DBMS_OUTPUT.put_line (t (1).n);
END;
/

BEGIN
plsqlpuzzle_proc;
END;
/

ORA-01403: no data found
Huh? Still getting a NO_DATA_FOUND exception? But...but...the table has a row. Yes, it does, but the NO_DATA_FOUND exception is also raised when I try to "read" a "row" in a collection (look at an element in a collection at a specific index value). Well, a collection is kind of like a table, right?

So while the one row in the table fixes the problem with NO_DATA_FOUND from SELECT-INTO, it does not stop the exception from being raised from this line:
DBMS_OUTPUT.put_line (t (1).n); 

RETURNING to the scene of the crime

Sorry, there's no crime. Just needed a catchy title for the header. :-)

So a couple of clever developers (see the Twitter thread link at bottom of post) came up with a modification (or two) to the INSERT statement to get around this problem. Namely: RETURNING.
CREATE OR REPLACE PROCEDURE plsqlpuzzle_proc 
IS
r plsqlpuzzle%ROWTYPE;

TYPE r_t IS TABLE OF plsqlpuzzle%ROWTYPE
INDEX BY PLS_INTEGER;

t r_t;
BEGIN
INSERT INTO plsqlpuzzle VALUES (100)
RETURNING n BULK COLLECT INTO t;

SELECT * INTO r FROM plsqlpuzzle;

DBMS_OUTPUT.put_line (r.n);

DBMS_OUTPUT.put_line (t (1).n);
END;
/

BEGIN
plsqlpuzzle_proc;
END;
/

100
100

DELETE FROM plsqlpuzzle
/

CREATE OR REPLACE PROCEDURE plsqlpuzzle_proc
IS
r plsqlpuzzle%ROWTYPE;

TYPE r_t IS TABLE OF plsqlpuzzle%ROWTYPE
INDEX BY PLS_INTEGER;

t r_t;
BEGIN
INSERT INTO plsqlpuzzle VALUES (100)
RETURNING n INTO t (1).n;

SELECT * INTO r FROM plsqlpuzzle;

DBMS_OUTPUT.put_line (r.n);

DBMS_OUTPUT.put_line (t (1).n);
END;
/

BEGIN
plsqlpuzzle_proc;
END;
/

100
100
In the first version, RETURNING BULK COLLECT INTO populates the collection with all the rows inserted (just happens to be one). In the second iteration, the non-bulk RETURNING-INTO populates a specific element in the collection at index value 1.

Either way, the code executed to display the value of t(1).n no longer throws a NO_DATA_FOUND exception and so they are both excellent solutions.

The RETURNING clause is a really lovely reminder of the tight integration between SQL and PL/SQL. Read lots more about it here.

Another Sort of Return

That use of RETURNING was very clever - I hadn't thought of it when I published the puzzle!

There is another way to achieve the desired effect (add just one statement and no unhandled exception): insert a RETURN statement.

Now, you might be saying: a RETURN statement? But this is a procedure, not a function! You would be absolutely right to say so. But did you know that you can also execute a RETURN inside a procedure? You just don't return anything but control to the outer block or host environment!
CREATE OR REPLACE PROCEDURE plsqlpuzzle_proc 
IS
r plsqlpuzzle%ROWTYPE;

TYPE r_t IS TABLE OF plsqlpuzzle%ROWTYPE
INDEX BY PLS_INTEGER;

t r_t;
BEGIN
DBMS_OUTPUT.put_line ('Running plsqlpuzzle_proc');

RETURN;

SELECT * INTO r FROM plsqlpuzzle;

DBMS_OUTPUT.put_line (r.n);

DBMS_OUTPUT.put_line (t (1).n);
END;
/

BEGIN
plsqlpuzzle_proc;
END;
/

Running plsqlpuzzle_proc

Two Other "Solutions"

I put that word in quotes because sell they are maybe valid solutions, maybe not, but worth mentioning.

One person proposed adding a one line exception section as you see below.
CREATE OR REPLACE PROCEDURE plsqlpuzzle_proc 
IS
r plsqlpuzzle%ROWTYPE;

TYPE r_t IS TABLE OF plsqlpuzzle%ROWTYPE
INDEX BY PLS_INTEGER;

t r_t;
BEGIN
DBMS_OUTPUT.put_line ('Running plsqlpuzzle_proc');

RETURN;

SELECT * INTO r FROM plsqlpuzzle;

DBMS_OUTPUT.put_line (r.n);

DBMS_OUTPUT.put_line (t (1).n);
EXCEPTION WHEN OTHERS THEN NULL;
END;
/

BEGIN
plsqlpuzzle_proc;
END;
/

Running plsqlpuzzle_proc
That avoids there procedure terminating with an unhandled exception. But as you will see on Twitter, I had to reject the solution. That's because while this is a single statement:

EXCEPTION WHEN OTHERS THEN is not a statement in PL/SQL. They are reserved words or key words that define the exception section and the WHEN clause.

Otherwise, a fine idea. Except of course this exception section should never be used in production code.

Finally, Philipp Salvisberg surprised absolutely no one by coming up with something else entirely - and testing the boundary edges of the quiz. Here's his code:
CREATE OR REPLACE PROCEDURE plsqlpuzzle_proc 
IS
r plsqlpuzzle%ROWTYPE;

TYPE r_t IS TABLE OF plsqlpuzzle%ROWTYPE
INDEX BY PLS_INTEGER;

t r_t;
BEGIN
<<burn_cpu>> goto burn_cpu;

SELECT * INTO r FROM plsqlpuzzle;

DBMS_OUTPUT.put_line (r.n);

DBMS_OUTPUT.put_line (t (1).n);
END;
/
Notice the use of GOTO. He is essentially setting up a very tight infinite loop.

When you run this on LiveSQL, it will eventually terminate with the following error:
ORA-00040: active time limit exceeded - call aborted
When you run it in your own database, it will likely run for a much longer time. You will eventually  see that error or (more likely) you will terminate the session. :-)

Does this satisfy the conditions of the quiz? Weeelllll.....it doesn't terminate with an unhandled exception (a PL/SQL exception). It will eventually fail with an Oracle Database error. I will leave it to you to decide.

But at least now you know that there is a GOTO in PL/SQL and as with every other language out there, you should avoid it whenever possible.

You can run all of the above code in LiveSQL by running this script. Feel free, of course, to download and run it in your own database as well.

The Twitter Thread

 This puzzle was originally posted on Twitter. Here is the thread, discussion and solutions as they appeared.

PL/SQL Puzzle: What code can be removed?

$
0
0
I published a PL/SQL puzzle on Twitter on November 6 2019. I asked the following question:
Which lines of code can be removed (either entirely or in part) from the block below and not affect the output of the program in any way?
I neglected to mention in my original tweet a few important assumptions:
  1. You are running this code on Oracle Database 10g or higher.
  2. Server output is turned on.
  3. Whitespace (spaces, tabs, new-lines) don't count.
Here's the code. I will publish it as an image, just as I did on Twitter, so that you can give it a go yourself, before taking a look at the answers from me and others below that.
Check out the Twitter conversation for all the answers that were submitted. It's a fun read!

Here are the full lines that I believe can be removed:

2 - There is not need to declare the iterator used in a FOR loop, numeric or cursor versions.

7 - There is no need to declare an "empty" collection to be used to initialize l_objects.

10 - Collections are empty after declaring, always. So no reason to delete.

12 - 15 - Invoking the LAST method on an empty collection always returns NULL, so that call too DBMS_OUTPUT.PUT_LINE will never happen.

17 - This line has no impact on the behavior of the program because SELECT-BULK COLLECT-INTO always wipes out whatever was in the target collection before filling it.

28 - You don't need - and shouldn't use - an EXIT statement inside a FOR loop. It will automatically terminate when all the index values in the collection have been touched.

And here are pieces of code that can be removed from remaining lines:

5 - We do not have to declare this as an associative array. We can remove "INDEX BY PLS_INTEGER", which makes it a nested table. A SELECT-BULK COLLECT-INTO always initializes and extends nested tables and varrays

8 - ":= l_empty" There is no need to initialize a collection with an empty one. It is automatically set to that state.

In which case, the end result is nothing more than this:
DECLARE
TYPE objects_t IS TABLE OF all_objects.object_name%TYPE;
l_objects objects_t;
BEGIN
SELECT object_name
BULK COLLECT INTO l_objects
FROM all_objects
WHERE object_name LIKE '%TABLE%'
ORDER BY object_name;

FOR indx IN 1 .. l_objects.COUNT
LOOP
DBMS_OUTPUT.put_line (l_objects (indx));
END LOOP;
END;
You can run (and play around with) both versions on LiveSQL with this script.

Did I miss anything? Do you disagree with any of my removals?




PL/SQL Puzzle: No extra code please!

$
0
0
I published yet another PL/SQL puzzle on Twitter yesterday. Generated lots of interest and interesting replies. I don't think any single person caught everything, but as usual the community came through.

I will repeat the puzzle here. If you haven't already seen it on Twitter, please try to solve it yourself before looking at my answer.
What text can be removed from lines 3 though 12 in the code below so that after the anonymous block is executed, "121212" is still displayed on the screen?


White space


so you do not immediately



see my answers. 



:-)


OK, let's dive in.

Notice, first of all, that I asked about text that can be removed, not lines. So you can remove entire lines or portions of lines. I refuse to accept that whitespace is text, so blank lines don't count. :-)

Here are the opportunities for removal that I found:

4 - Remove the IN keyword. That's the default for parameters (though I generally always include it in my code).

5 - Remove AUTHID DEFINER. Again, that is the default (definer's rights) and it means that this procedure will compile with the directly-granted privileges of the schema (roles need not apply).

7 - Remove entirely. The variable is not used. If it was, you could remove just ":= NULL" because by default variables are assigned the NULL not-value.

9 - The value will be displayed through line 19, so it is not needed here.

11 - A RETURN in a procedure? Huh? Yes, you can issue a RETURN in a procedure (and pipelined table function) that returns nothing but control. And it will do that. But as the last line in your procedure? No. That's what a procedure will do as a matter of course. Definitely not needed and not recommended.

That leaves just:
CREATE PROCEDURE my_proc (
arg1 NUMBER, arg2 OUT NUMBER)
IS
BEGIN
arg2 := arg1;
END;

Now, if I had been a bit more relaxed in my puzzle rules and said "What text can be removed between lines 3 and 20?" you would probably would have had more fun - as you can see in the Twitter feed.

Because then you could remove lines 10 and 20, just keep line 19 to display the value, and even remove the TO_CHAR explicit conversion, because that number will be implicitly converted to a string when it is passed too DBMS_OUTPUT.PUT_LINE (which does not have an overloading for numbers). You could then also remove the OUT parameter modifier, since the second parameter is no longer being assigned a value in the procedure!

Did I miss anything? Let me know on the Twitter feed or in comments below. Have an idea for a puzzle of your own? I encourage you to throw it up on Twitter or you can also pass it along to me and I will publish it (giving you credit of course).

PL/SQL Office Hours December 3: Tips on Writing PL/SQL in APEX Apps

$
0
0
I am very pleased that for our December 3 9 AM Eastern AskTOM Office Hours session, three deeply experienced and highly respected APEX pros will join me to share their wisdom on how best to write and manage PL/SQL code for Oracle Application Express projects.

Please mark this event in your calendar - or visit our home page to have it added to your calendar through the magic of software. You won't have to sign in to join our session on the 3rd, but I encourage you to subscribe to the PL/SQL Office Hours series so that you can reminders of this and future events.

Our experts are:

Karen Cannell

Karen promotes APEX topics and best practices through local, regional and national user group presentations and papers. A devoted user group volunteer since 2007, she is especially active in ODTUG, where she serves as the editor of the ODTUG Technical Journal. She is former Associate Editor of IOUG SELECT Magazine. She is co-author of Agile Oracle Application Express, Expert Oracle Application Express and Beginning Oracle Application Express 4. Her most recent presentation at Kscope15 focused on APEX Interactive Reports with a Deep Dive: APEX 5 New Features and Upgrade Cheat Sheet.

Karen has delivered Application Express solutions since its Web DB and HTMLDB beginnings, and continues to leverage the Oracle suite of tools to build quality web applications for clients in government, medical, and engineering industries.  She is known for thinking outside the box to implement maintainable APEX solutions – most are still in production today (upgraded, of course!). . A mechanical engineer by degree (one of them), she has analyzed, designed, developed, converted, upgraded, enhanced, and otherwise improved legacy and commercial database applications for over 25 years, concentrating on Oracle technologies since 1994 and APEX since it was born. Karen is President of TH Technology, a small consulting firm providing Oracle technology services, lately focused on APEX, where she continues to deliver quality solutions for her clients.

Scott Spendolini

Scott is Vice President of the APEX+ Practice of Viscosity, NA, where he manages a team of highly-skilled Oracle APEX developers. He is also responsible for developing and delivering Oracle APEX-related curriculum, as well as managing the development of several APEX-based products.

 Throughout his professional career, he has assisted various clients with their Oracle APEX development and training needs. Spendolini is a long-time and regular presenter at many Oracle-related conferences, including Oracle OpenWorld, Kscope, and RMOUG. He is a recipient of the Oracle Ace Director designation, author of Expert Oracle Application Express Security and co-author of Pro Oracle Application Express. Spendolini is also an Oracle Certified Oracle Application Express developer.

Prior to reigniting and running Sumner Technologies from 2015 to 2018, Spendolini was an APEX Practice director at Accenture and Enkitec from June 2012 through April 2015. Before joining Enkitec as part of an acquisition, he co-founded and ran Sumneva and Sumner Technologies from 2005 through 2012, which focused on Oracle APEX services, education & solutions. Spendolini started his professional career at Oracle Corporation, where he worked with Oracle eBusiness Suite for almost seven years and was a Senior Product Manager for Oracle APEX for just over three years.

Scott Wesley


Scott clearly has a passion for Oracle APEX. In the time since he's left Forms behind, he's recorded a video series; written a book; trained developers around Australia; answered forum questions from around the world; and written a variety of posts at http://grassroots-oracle.com.

Scott has been consulting with clients around Perth since 2000, enjoying his time with the Sage Computing crew for just over a decade. He presents regularly at Australian events, and was recognised as an Oracle ACE in 2014. and often weaves a side interest in science & astronomy into his work.

I hope that you will join us - I expect to learn a lot and I am sure that you will, too!

PL/SQL Puzzle: what assumptions am I making?

$
0
0
Almost certainly, whenever you write a procedure or function, you make certain assumptions. Some of them are quite reasonable, such as "I assume my database is up and running." Some of them are scary, such as "I assume my users will never change their minds."

But many simply go unnoticed. You don't even realize you are making an assumption until it smacks you in face, most likely in production, when an unexpected error exposes the assumption.

So in this PL/SQL puzzle, as I state on Twitter:
The procedure shown below compiles without error. What assumptions am I making so that when it executes, it does not terminate with an exception?


White space


so you do not immediately



see my answers. 



:-)


OK, let's dive in. I provide below all of the assumptions I was aware, and also some others that were provided in Twitter on the very active discussion that followed. As usual, I learned something new from the community!

Line 3: by hardcoding the datatype to VARCHAR2(100) we assume that the last names in the employees table never exceed that length. Better to use: TABLE OF employees.last_name%TYPE

Lines 7 - 10: An unlimited BULK COLLECT assumes there will always be enough PGA (session memory) for the collection, no matter how big the table gets. A nasty assumption, bound to fail long after you left the project. Use FETCH with LIMIT instead as demonstrated in this LiveSQL script.

Also, Jacek Gebal suggests another, related assumption:  The number of rows in the collection doesn't exceed the limit for nested tables. Since that limit is 2**31 (2 raised to the 31st power), I am pretty sure you will run out of PGA memory first. But he's right: it's still an assumption. :-)

Line 12: Assumes that there is at least 1 element in the collection. Otherwise, FIRST and LAST return NULL and PL/SQL raises

ORA-06502: PL/SQL: numeric or value error 

 What should you do instead? Much better:

FOR indx IN 1 .. l_employees.COUNT 

Which assumes a sequentially filled collection from 1. So what is better about that? BULK COLLECT always fills a collection from index 1 and sequentially from there.

Line 14: Assumes that do_more_stuff accepts VARCHAR2 or CLOB values and also does not raise exception.

Line 16 - There is no exception handler! So one really big fat assumption I make is that none of the code in the procedure will cause an exception to be raised. In particular, as pointed out by Abul Samed, I assume that Exception handling is done in procedure do_more_stuff.

Or....I have decided that I do not care if an exception is raised in either of the procedures, because my standards dictate that I only handle exceptions at the outermost block and this procedure is called by others. I don't generally consider that a good idea. I like to handle exceptions locally, log any application state specific to the block (such as values of local variables).

What else did I assume?

Did I miss anything? Do you have any stories to share about assumptions you've made in your code or seen in other developers' code that resulted in some less-than-optimal results?


Wait, that's NOT a reserved word?

$
0
0
When it comes to PL/SQL puzzles via Twitter, I decided to change things up this week. I presented it as multiple choice this time.

Here's the puzzle:
After executing the code shown in the image what will be displayed on the screen (serveroutput is on!)?
a. "No dummy"
b. Unhandled VALUE_ERROR exception
c. Unhandled NO_DATA_FOUND exception
d. Compilation error
e. Your guess is as good as mine.

Before I unveil the amazing, mind-boggling answer....I will give you a moment to try to solve it yourself.
OK. So the first inclination you might have as regards the output from this block is, quite logically, "No dummy!".

After all, there are no rows in the dual table (or any other table for that matter) for which 1 is equal to 2.

So that SELECT-INTO is going to raise a NO_DATA_FOUND exception. No doubt about that at all.

And there's an exception handler for NO_DATA_FOUND (well, no_data_found, wait they are the same thing in PL/SQL! :-) ). So "No dummy" it is, right?

Wrong.

Now, a number of people in Twitterland also chose (d) Compilation error.

They reasoned that "no_data_found" and "pls_integer" are reserved words in PL/SQL. So if you try to use them for your own identifiers, the PL/SQL compiler slaps you down.

That's very solid reasoning....and it would be true if those were actually reserved words.

Now, IF is definitely a reserved word, so if I try to declare a variable with that name, nuh-uh:

NO_DATA_FOUND and PLS_INTEGER are not reserved words, which you can confirm with a query in your database:
That's right - I got a NO_DATA_FOUND exception searching for "NO_DATA_FOUND" as a reserved word. :-)

OK but to be completely honest with you, that view contains SQL keywords. Check Appendix D of the PL/SQL Language Reference for PL/SQL Reserved Words and Keywords.

So "no_data_found" and "pls_integer" are not reserved words, which means I can use them myself in my code. Which then means that any usage of those names in my block will be resolved to my variables or types or whatever they are, and not the pre-defined element in the PL/SQL language.

Specifically the "when no_data_found" handler will trap only the no_data_found exception that I declared in my code, which is not the same exception as that raised by the PL/SQL runtime engine.

In fact, the only way that handler could trap anything is if "RAISE no_data_found" was executed in this block.

I hope that makes sense. It is of course very confusing and even if "no_data_found" is not a reserved word, you should certainly avoid using it for your own identifiers in your code.

But if "no_data_found" is not a reserved word, what is it? Is it a keyword? Not even that. It doesn't appear in the D-2 table, but you should think of it as a keyword since the word on keywords is "You can use keywords as ordinary user-defined identifiers, but it is not recommended."

In fact, "no_data_found" is an exception declared in a special package called STANDARD. That package is owned by SYS and, along with DBMS_STANDARD, define many elements of the PL/SQL language, including exceptions like DUP_VAL_ON_INDEX and VALUE_ERROR and types like PLS_INTEGER.

You can view the package source as follows:
  SELECT line, text
FROM all_source
WHERE TYPE = 'PACKAGE' AND name = 'STANDARD' AND owner = 'SYS'
ORDER BY line
You can also view the package body, but it's not very helpful, except to reveal to you that the PL/SQL dev team has access to functionality that we do not. Which should not come as a surprise to anybody.

In that package specification, you will find lines of code like this:

At which point, I am sure you are now saying to yourself: "What? What is that number for NO_DATA_FOUND? 100? I thought the error code for this exception was -1403." Yeah, it's a rather strange outlier of an exception. Bottom line is that the ANSI standard error code for "no data found" is 100, so we go with that, but when the exception is raised, SQLCODE returns -1403.

Get over it.

And there you have it. Now you know that you, too, can write code that is so hard to read, so confusing, so downright strange that everyone will conclude that you must be a genius.

Since, however, neither you nor I are a genius, I suggest you never write code like this.

Dynamic Polymorphism - Why, What, How

$
0
0
Dynamic means "run-time."

Polymorphism means "multiple shapes."

Synonyms for dynamic polymorphism include "runtime polymorphism" and "dynamic method dispatch."

If you are a "traditional" relational database developer, these terms might sound unfamiliar. But how about overloading? Are you familiar with that?

Overloading occurs when...

Well, guess what? Another name for overloading is "static polymorphism."

Static means "compile-time."

Polymorphism means "multiple shapes."

Why, you might be wondering, does the Oracle Database need to wait till runtime to determine which method in which type in the hierarchy should be called?

After all, it doesn't have any troubling sorting that out with overloading of subprograms in packages!

The answer to that question lies in one word: substitutability.

It's a topic I've touched on both directly and indirectly in my previous posts in this series. The best way to think about substitutability is that if I have the following type hierarchy....
CREATE TYPE food_ot AS OBJECT (
name VARCHAR2(100),
food_group VARCHAR2 (50),
grown_in VARCHAR2 (100)
)
NOT FINAL
;

CREATE TYPE dessert_t UNDER food_ot (
contains_chocolate VARCHAR2(1),
year_created NUMBER(4)
)
NOT FINAL
;
....then this follows:
Every dessert is a food, but not every item of food is a dessert.
And now with the S word:
Where I have an instance of food, I can substitute it with an instance of dessert.
This ability to substitute is precisely what drives the need for dynamic polymorphism in object-oriented languages. Let's find out why.

Since polymorphism has to do with choosing the right method, let's enhance my food-related hierarchy to include a member method in each type.
CREATE TYPE food_ot AS OBJECT (
name VARCHAR2 (100),
food_group VARCHAR2 (100),
grown_in VARCHAR2 (100),
MEMBER FUNCTION price
RETURN NUMBER
)
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY food_ot
IS
MEMBER FUNCTION price
RETURN NUMBER
IS
BEGIN
RETURN (CASE self.food_group
WHEN 'PROTEIN' THEN 3
WHEN 'FRUIT' THEN 2
WHEN 'VEGETABLE' THEN 1
END);
END;
END;
/

CREATE TYPE dessert_ot
UNDER food_ot (
contains_chocolate VARCHAR2 (1),
year_created NUMBER (4),
OVERRIDING MEMBER FUNCTION price
RETURN NUMBER
)
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY dessert_ot
IS
OVERRIDING MEMBER FUNCTION price
RETURN NUMBER
IS
multiplier NUMBER := 1;
BEGIN
RETURN 100;
END;
END;
/
I'm keeping the price formula really simple for desserts. :-)

And now consider the following block:
DECLARE
TYPE foodstuffs_nt IS TABLE OF food_ot;

fridge_contents foodstuffs_nt
:= foodstuffs_nt (food_ot ('Brussels Sprouts', 'VEGETABLE', 'Farm'),
dessert_ot ('Strawberries',
'FRUIT',
'Backyard',
'N',
2001));
BEGIN
FOR indx IN fridge_contents.FIRST .. fridge_contents.LAST
LOOP
DBMS_OUTPUT.put (
CASE
WHEN fridge_contents (indx) IS OF (ONLY food_ot)
THEN
'Food'
WHEN fridge_contents (indx) IS OF (ONLY dessert_ot)
THEN
'Dessert'
END
|| ' price:');

DBMS_OUTPUT.put_line (fridge_contents (indx).price ());
END LOOP;
END;
/
We can see from this code why overloading or static polymorphism is not sufficient when it comes to executing the right method.

When the block is compiled, the PL/SQL engine knows that the fridge_contents nested table is filled with instances of type food_t. It could even, I suppose, notice that the nested table contains instances of food_t and dessert_t.

But it sure is hard to see how at compile time. the PL/SQL engine would know which price method should be used in the call to DBMS_OUTPUT.PUT_LINE. After all, fridge_contents (indx) at compile time is of type food_t (and, of course, because of substitutability, it could also be any subtype of food_t, but what compiler could sort that out?).

It is only when the block is executed that PL/SQL can check to see the actual type of the instance in that element of the collection and invoke the appropriate method.

And as you can see from my use of the IS OF syntax, it is possible for both us and the PL/SQL engine to get that type.

You can run this code for yourself on LiveSQL.

Check Out the Entire Series

Visit this post that gives you quick access to all the articles in the series.



PL/SQL Puzzle: when implicit conversions come calling

$
0
0
I posted the following puzzle on Twitter:
A quick little #PLSQL puzzle: I set serveroutput on. After running the code you see in the block below, what will be displayed on the screen?
Try it yourself before reading the rest of the post!


White space


so you do not immediately



see my answers. 



:-)


The output is:

10
1
9

I expect most of you got the first two right and maybe the third one wrong. Note also that the block does not fail with any kind of exception, such as VALUE_ERROR. The reason for all this can be summed up in one phrase: implicit conversion.

As noted by several people, this is a collection indexed by strings, not integers. Only associative arrays (INDEX BY) types support this. And that makes all the difference in this puzzle.

The value being used in the assignment of 100 to elements in the array is an integer (indx).

Since the index type is a string, the PL/SQL engine implicitly converts integers 1 through 10 yto strings "1", "2" ... "9", and finally "10". These are the actual index values used.

So certainly ten elements are added to the collection and so the count returned is 10.

But when it comes to FIRST and LAST, you have to understand how PL/SQL will determine the "lowest" defined index value and the "highest" defined index value.

For integer-indexed collections, it is clear: whichever number is lowest or highest.

But for a string-indexed collection, "lowest" and "highest" are determined by the character set ordering. And in this case "1" comes before "2", "2" before "3" ... "8" before "9".... but "10"? That is not greater than "9". It is greater than "1" and less than "2" as you can see by running this block:

DECLARE  
TYPE t IS TABLE OF INTEGER
INDEX BY VARCHAR2 (3);

tt t;

l_index VARCHAR2 (3);
BEGIN
FOR indx IN 1 .. 10
LOOP
tt (indx) := 100;
END LOOP;

l_index := tt.FIRST;

WHILE l_index IS NOT NULL
LOOP
DBMS_OUTPUT.put_line (l_index);
l_index := tt.NEXT (l_index);
END LOOP;
END;

1
10
2
3
4
5
6
7
8
9

Sometimes PL/SQL makes it too, too easy to work with compatible datatypes. :-)

Check out my LiveSQL script demonstrating all of this.

PL/SQL Office Hours: DB Setup and Teardown for Automated Testing

$
0
0
On January 14, 2020 at 9 AM Eastern, I am very pleased to hold a PL/SQL Office Hours session on one of the biggest challenges faced by developers setting up automated tests for database code: setup and teardown.

No application will ever have zero bugs, but you sure want to keep them to a minimum. The best way to do this is to implement automated regression tests of your code, but "best" as usual does not equate to "easiest." Building and managing tests can be a big challenge, so in this Office Hours session, we will hear from developers who are doing just that. Learn from your peers about the obstacles they faced and how they overcame them. Bring your own stories and your questions, and let's all work together on improving our code quality!

For this session, we have two presenters: Deepti Bandari and Jasmin Fluri.

Deepti Bandari is a senior software engineer at Fidelity Investments since 2013. Her focus areas include database design and development, test automation and promoting engineering excellence. She will share her experience with data setup and teardown for testing pl/sql, including:

* Testing complex code in a monolithic database. * How to setup data so tests can run fast, in isolation, and are repeatable
* Working with hierarchical data with a lot of dependent tables
* When to rely on existing data in environments


Jasmin Fluri is an independent consultant at Schaltstelle GmbH and lectures on software engineering and code review at the University of Applied Sciences North-western Switzerland. Her focus as a database developer and DevOps engineer lies on continuous integration and delivery pipelines, automation of recurring tasks, PL/SQL development, data engineering, and data warehousing.

Jasmin will talk about stages of testing and management of test data. She will show examples on how to prepare and arrange unit test with test data in development environments. Important points on what to consider when starting with unit testing and test data are explained.

We hope to see you at the session. And I wouldn't be at all surprised if you have some experiences in this area, as well. We hope you will share them with all attendees. Please do get in touch in advance if you'd like to present those experiences as more than a comment in the chat. I'll add you to our agenda.

Finally, subscribe for reminders at the PL/SQL Office Hours home page so that you can get email reminders about this and other sessions!

Oracle Dev Gym PL/SQL Championship for 2019 Players

$
0
0
Throughout 2019, over 1200 Oracle Database developers participated in the Oracle Dev Gym PL/SQL Challenge weekly tournament. The top 50 ranked players shown below will compete for top honors in a championship on February 18, 2020.

The number in parentheses after their names are the number of championships in which they have already participated. As you can see, there are some very dedicated players here!

Congratulations to all listed below on their accomplishment and best of luck in the upcoming competition!

NameRank
Stelios Vlasopoulos (16)1
mentzel.iudith (19)2
NielsHecker (20)3
Chad Lee (16)4
Peterman (6)5
siimkask (19)6
_tiki_4_ (12)7
Chase Mei (5)8
Ludovic Szewczyk (4)9
li_bao (7)10
MarkusId (0)11
Andrey Zaytsev (8)12
Michal P. (3)13
Arjun Barath (0)14
Ivan Blanarik (13)15
Maxim Borunov (6)16
lmikhailov (0)17
mcelaya (4)18
tonyC (5)19
patch72 (6)20
Karel_Prech (9)21
Sandra99 (3)22
Oleksiy Ponomarenko (4)23
Mike Tessier (3)24
Vyacheslav Stepanov (18)25
Jan Šerák (5)26
seanm95 (6)27
msonkoly (4)28
Rakesh Dadhich (11)29
pjas (2)30
PZOL (5)31
korolkov_d_a (0)32
Henry_A (6)33
Talebian (6)34
Sartograph (2)35
Rytis Budreika (7)36
syukhno (2)37
NickL (4)38
swesley_perth (5)39
Nikolay Loginov (0)40
Sachi (3)41
craig.mcfarlane (0)42
RalfK (1)43
JeroenR (12)44
umir (0)45
JasonC (4)46
Otto Palenicek (3)47
Aleksei Davletiarov (1)48
ted (2)49
StasKa (0)50

PL/SQL Puzzle: Getting the "right" error message to appear

$
0
0
I posted the following puzzle on Twitter:
What change(s) can you make to this code so that "ORA-00001: unique constraint" appears on the screen after execution?
Try it yourself before reading the rest of the post!


White space


so you do not immediately



see my answer. 



:-)


Here are the answers from the TwitterSphere:

Change line 5's assignment to dbms_sql.number_table(1=>1,2=>1)

In other words, try to insert the same value twice. Since there is a unique index on the column, that will cause ORA-00001 to be raised.

So that will do it, right?

Wrong. Hans and Dirk both point out why that is not enough, and offer the second part of the solution:

The value deposited in the error_code field of the SQL%BULK_EXCEPTIONS array is unsigned. In other words, 1 rather than -1 is stored. Unfortunately, the SQLERRM function assumes that the error code you pass it will be signed (negatively). So you must multiply the value in the pseudo-collection by -1. Then SQLERRM will return the right string.

Or as Dirk puts it:

(sqlerrm (sql%bulk_exceptions (indx).error_code));

must be

(sqlerrm (0 - sql%bulk_exceptions (indx).error_code));

You don't really need the 0, though. You can write simply:

(sqlerrm (-sql%bulk_exceptions (indx).error_code));

You can see all these variations at work in my LiveSQL script.

Actually, I was surprised that I did not receive any "silly" answers. After all, there are lots of ways to get "ORA-00001: unique constraint" to appear on the screen, such as:

DECLARE 
two_ts DBMS_SQL.number_table := DBMS_SQL.number_table (1=>1,2=>2);
BEGIN
DBMS_OUTPUT.PUT_LINE ('ORA-00001: unique constraint');
RETURN;

FORALL indx IN 1 .. 2
SAVE EXCEPTIONS
INSERT INTO t VALUES (two_ts(indx));
DBMS_OUTPUT.put_line (SQL%ROWCOUNT || ' inserted');
ROLLBACK;
EXCEPTION
WHEN others
THEN
FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.put_line
(SQLERRM (SQL%BULK_EXCEPTIONS (indx).ERROR_CODE));
END LOOP;
END;

Yes, that's right, simply display it on the screen and then shortcut everything else with RETURN; (or not, let the rest of the code execute unchanged).

I guess it was a serious week for the Oracle Database developer community. :-)

PL/SQL Office Hours: Virtual Private Database in the Wild

$
0
0
Virtual Private Database (VPD), also referred to as row-level security or RLS, is a feature built into the Oracle Database that allows you to set up security policies on tables that restrict which rows a user can see or change based on the policy logic.

One of the nicest things about VPD is that this logic (and the fact that a filter is being applied) is completely invisible to the user. They just see the data relevant to them and none the wiser about all that other data in the data.

Here's a simple example to drive the point home: suppose I am building a health care application and it contains a patients table. The security policy is straightforward:

A patient can only see their own information.
A doctor can see only the information about their own patients.
A clinic administers can see information about the patients in their clinic.

In all three cases, the user would sign on to the application and execute the same query:

SELECT * FROM patients

and only their rows would appear.

Of course, there are lots of different and very interesting aspects to setting up your policies.

The documentation for VPD provides many details for a successful implementation, but there's not substitute for real world experience. That's what you'll hear about in our March 3, 2020 10 AM Eastern PL/SQL Office Hours session from AskTOM.

Our Presenter: Praveen Kumar of Wipro

Parveen Kumar is a Java and Oracle Developer with Wipro, based in the UK. He has over eight years experience focused mainly on developing and designing applications using Oracle Database as a primary database. He aims to keep the business logic inside the database and expose data through PL/SQL APIs. He has in various projects taken advantage of native support for XML, JSON and Object datatypes, SOAP/REST APIs for Web Programming and security features like VPD/RLS, Oracle Wallet for Web APIs and other features which provide true fine grained access to different types of users.

In the March Office Hours session, Praveen will show how he has used Virtual Private Database across an entire application to control what end users can see based on their role/access level. The business driver for this use of VPD was to ensure appropriate access to the sensitive/financial data in the application.

Follow this link to subscribe to my monthly PL/SQL Office Hours program, so that you will receive email reminders. You can also view recordings of the dozens of past sessions, including:


Viewing all 312 articles
Browse latest View live