Why does my code break with no changes and no invalid objects?


Recently I encountered an interesting situation where a job has been running successfully for months and then one day it suddenly started failing every time with an ORA-06502: PL/SQL: numeric or value error.  The same error can also manifest itself in bulk operations with ORA-06502: PL/SQL: numeric or value error: Bulk Bind: Truncated Bind.

The more confusing part is that no changes were made to the code and there were no invalid objects.  Digging into the problem some more, the job called a procedure which read from a view, selecting from remote tables via a database link.

So, next step, obviously, I check on the remote side: but again, I find no invalid objects there and looking at the LAST_DDL_TIME in DBA_OBJECTS I see none of the tables had been modified in a couple months either.

Those old remote changes seemed innocuous; but it had been even longer since I had changed anything in the view or procedure within the local db.  I recompiled the view in the local db and the job started working again.  So, what changed and why did it only fail now and not a couple months ago when the remote tables changed?

For this I had to go to SYS and query DBA_TAB_COLS with flashback query to check the columns of my view and sure enough, one of the VARCHAR2 columns had grown.  The reason nothing failed was no new data came in right away using the new, bigger limit.

Once I saw the problem, replicating it was fairly easy.  Another interesting quirk between SQL and PL/SQL is the query within the procedure would run without error when executed as SQL because it didn’t have precompiled size limitations.  So it would adapt on its own.

The example objects and data to replicate the problem are fairly simple but it can be a little tricky following along since the session alternates between the local and remote database.  I’ve left the connect statements and altered the SQL> prompt to include the database name to help highlight where the action is taking place.

sds@REMOTE_DB> connect sds/pa55w0rd@remote_db
Connected.
sds@REMOTE_DB> create table sds.testtable(test_id integer, testtext varchar2(10));

Table created.

sds@REMOTE_DB> insert into sds.testtable(test_id,testtext) values (1,'abcd');

1 row created.

sds@REMOTE_DB> insert into sds.testtable(test_id,testtext) values (2,'efgh');

1 row created.

sds@REMOTE_DB> commit;

Commit complete.

sds@REMOTE_DB> connect sds/pa55w0rd@local_db
Connected.
sds@LOCAL_DB> CREATE DATABASE LINK remote_db_link CONNECT TO sds IDENTIFIED BY "pa55w0rd" USING 'remote_db';

Database link created.

sds@LOCAL_DB> CREATE OR REPLACE VIEW test_view
AS
SELECT * FROM sds.testtable@remote_db_link; 2 3

View created.

sds@LOCAL_DB> SELECT * FROM test_view;

TEST_ID TESTTEXT
---------- ----------
1 abcd
2 efgh

sds@LOCAL_DB> CREATE OR REPLACE PROCEDURE show_remote_data
IS
BEGIN
FOR x IN ( SELECT test_id, testtext
FROM test_view
ORDER BY test_id)
LOOP
DBMS_OUTPUT.put_line(x.test_id || ' ' || x.testtext);
END LOOP;
END; 2 3 4 5 6 7 8 9 10
11 /

Procedure created.

sds@LOCAL_DB> set serveroutput on
sds@LOCAL_DB> exec show_remote_data;
1 abcd
2 efgh

PL/SQL procedure successfully completed.

Up to here everything is working normally, now we’ll make the text column larger.
The procedure will still work correctly even though the local and remote sizes don’t match. The error doesn’t occur until new data shows up that exceeds the prior limit.

sds@LOCAL_DB> connect sds/pa55w0rd@remote_db
Connected.
sds@REMOTE_DB> alter table sds.testtable modify (testtext varchar2(30));

Table altered.

sds@REMOTE_DB> connect sds/pa55w0rd@local_db
Connected.
sds@LOCAL_DB> exec show_remote_data;

PL/SQL procedure successfully completed.

sds@LOCAL_DB> set serveroutput on
sds@LOCAL_DB> exec show_remote_data;
1 abcd
2 efgh

PL/SQL procedure successfully completed.

sds@LOCAL_DB> connect sds/pa55w0rd@remote_db
Connected.
sds@REMOTE_DB> insert into sds.testtable(test_id,testtext) values (3,'abcdefghijklmnopqrstuvwxyz');

1 row created.

sds@REMOTE_DB> commit;

Commit complete.

sds@REMOTE_DB> connect sds/pa55w0rd@local_db
Connected.
sds@LOCAL_DB> set serveroutput on
sds@LOCAL_DB> exec show_remote_data;
1 abcd
2 efgh
BEGIN show_remote_data; END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SDS.SHOW_REMOTE_DATA", line 4
ORA-06512: at line 1

sds@LOCAL_DB>

Missing peaks in ASH results


The ASH charts in OEM are great utilities for getting a quick summary of your system’s activity. However, these results can be misleading because of how the data is represented on screen. First, ASH is data is collected by sampling so it’s not a complete picture of everything that runs. Another thing to consider is that the charting in OEM doesn’t plot every ASH data point. Instead, it will average them across time slices. Within Top Activity and the ASH Analytics summary charts these points are then connected by curves or straight lines which then further dilutes the results.

Some example snapshots will help illustrate these issues.

The OEM Top Activity screen may produce a chart like this…
Top Activity

First, note the large spike around 1:30am on the 16th. This spike was largely comprised of RMAN backups and is a significant increase in overall activity on the server with approximately 9 active sessions at its peak and a sustained activity level of 8 for most of that period.

Next, let’s look at that same database using ASH Analytics and note how that spike is drawn as a pyramid of activity. While the slope of the sides is fairly steep, it’s still significantly more gradual than that illustrated by the Top Activity chart. The peak activity is still approximately 9 active sessions at its highest but it’s harder to determine when and where it tapers off because the charting simply draws straight lines between time slices.

ASH Analytics

But, ASH Analytics offers a zoom window feature and using that we can highlight the 1am-2am hour and we get a different picture that more closely reflects the story told in the Top Activity chart. Note the sharp increase at 1:30 as see in the Top Activity. Also, note the higher peaks approaching and exceeding 12 active sessions whereas each of the previous charts indicated a peak of 9. The last curiosity is when the activity declines it is more gradual than the Top Activity but steeper than the Analytics overall chart.

ASH Analytics wall

The charts above demonstrate some ambiguities in using any one visualization. In those examples though, the data was mostly consistent in magnitude, but differing on rate of change due to resolution of the time slices.

Another potential problem with the averaging is losing accuracy by dropping information. For instance, in the first chart above, note the brief IO spike around 9:30am with a peak of 6 active sessions. If you look on the ASH Analytics summary chart it has averaged the curve down to approximately 2 active sessions. If we now go to the ASH Analytics page and zoom in to only the 9am-10am hour, we see that spike was in fact much larger at 24! This is 4 to 12 times our previous values and more importantly, running at twice the number of available processors. It was a brief surge and the system recovered fine but if you were looking for potential trouble areas of resource contention, the first two charts could be misleading.

ASH Analytics peak

I definitely don’t want to discourage readers from using OEM’s ASH tools; but I also don’t want to suggest you need to zoom in on every single time range in order to get the most accurate picture. Instead I want readers to be aware of the limitations inherent in data averaging and if you do have reason to inspect activity at a narrow time range, then by all means zoom in with ASH Analytics to get the best picture. If you need larger scale summary views, consider querying the ASH data yourself to find extreme values that may have been hidden by the averaging.

The Curse of “Expertise”


Like everyone else, I make mistakes. While the results can sometimes be unfortunate, it’s also a truth that shouldn’t be ignored. A recurring problem though is that as a designated “expert” sometimes people don’t bother to test what I’ve given them. They just roll with it and then are surprised when their production installation goes awry.

I just ran into this situation again a few days ago. I was asked to help with a query that didn’t ever finish. I worked on it for a little while and came up with something that finished in a few seconds. Since the original didn’t finish, I didn’t have a predetermined set of results to test against. I manually walked through some sample data and my results seemed to tie out… so, it seemed like I was on the right track. I showed the client what I had and they were elated with the speed improvement.

I gave a brief description of what I had attempted to do and why it ran quickly. Then I asked them to test and contact me again if there were any questions.

The next day I got a message that they were very happy with the speed and were using it. I was glad to hear that but I also had been thinking that my query was extremely complicated, so even though it has apparently passed inspection I spent a few more minutes on it and came up with a simpler approach. This new method was almost as fast the other one but more significantly it returned more rows than my previous version. Clearly, at least one of them was incorrect.

With the simplified logic of the new version, it was much easier to verify that this second attempt was correct and the older more complicated version was wrong. I reached out to my client again and notified them of the change in query and problem I found. Then suggested they rerun more extensive tests anyway because I still could be wrong.

Fortunately, this second attempt did appear to be truly correct and the performance was still more than adequate.

Finding the name of an Oracle database


Oracle offers several methods for finding the name of a database.

More significantly, 12c introduces new functionality which may change the expected value from some of the old methods due to the multi-tenant feature.

Here are 11 methods for finding the name of a database.

SELECT ‘ora_database_name’ method, ora_database_name VALUE FROM DUAL
UNION ALL
SELECT ‘SYS_CONTEXT(userenv,db_name)’, SYS_CONTEXT(‘userenv’, ‘db_name’) FROM DUAL
UNION ALL
SELECT ‘SYS_CONTEXT(userenv,db_unique_name)’, SYS_CONTEXT(‘userenv’, ‘db_unique_name’) FROM DUAL
UNION ALL
SELECT ‘SYS_CONTEXT(userenv,con_name)’, SYS_CONTEXT(‘userenv’, ‘con_name’) FROM DUAL
UNION ALL
SELECT ‘SYS_CONTEXT(userenv,cdb_name)’, SYS_CONTEXT(‘userenv’, ‘cdb_name’) FROM DUAL
UNION ALL
SELECT ‘V$DATABASE name’, name FROM v$database
UNION ALL
SELECT ‘V$PARAMETER db_name’, VALUE
FROM v$parameter
WHERE name = ‘db_name’
UNION ALL
SELECT ‘V$PARAMETER db_unique_name’, VALUE
FROM v$parameter
WHERE name = ‘db_unique_name’
UNION ALL
SELECT ‘GLOBAL_NAME global_name’, global_name FROM global_name
UNION ALL
SELECT ‘DATABASE_PROPERTIES GLOBAL_DB_NAME’, property_value
FROM database_properties
WHERE property_name = ‘GLOBAL_DB_NAME’
UNION ALL
SELECT ‘DBMS_STANDARD.database_name’, DBMS_STANDARD.database_name FROM DUAL;

The results of these will vary by version, whether the db is a container or not,  and if its is a container, whether the query runs within a pluggable database or the container root database.
Note, the con_name and cdb_name options for the SYS_CONTEXT function do not exist in 11g or lower. So those queries in the union must be removed to execute in an 11g database. Within a pluggable database some of the methods recognize the PDB as the database, while others recognize the container as the database.

So, if you are using any of these methods in an 11g database and you upgrade to a 12c pluggable db, you may expect the PDB name to be returned, but instead you’ll get the CDB name instead.
Also note, some of the methods always return the name in capital letters, others will return the exact value used to create the database.

METHOD 12c

Non-Container

12c

CDB$Root

12c

PDB

11g
GLOBAL_NAME global_name SDS12CR1 SDSCDB1 SDSPDB1 SDS11GR2
DATABASE_PROPERTIES GLOBAL_DB_NAME SDS12CR1 SDSCDB1 SDSPDB1 SDS11GR2
DBMS_STANDARD.database_name SDS12CR1 SDSCDB1 SDSPDB1 SDS11GR2
ora_database_name SDS12CR1 SDSCDB1 SDSPDB1 SDS11GR2
V$DATABASE name SDS12CR1 SDSCDB1 SDSCDB1 SDS11GR2
SYS_CONTEXT(userenv,db_name) sds12cr1 sdscdb1 sdscdb1 sds11gr2
SYS_CONTEXT(userenv,db_unique_name) sds12cr1 sdscdb1 sdscdb1 sds11gr2
SYS_CONTEXT(userenv,con_name) sds12cr1 CDB$ROOT SDSPDB1 n/a
SYS_CONTEXT(userenv,cdb_name) sdscdb1 sdscdb1 n/a
V$PARAMETER db_name sds12cr1 sdscdb1 sdscdb1 sds11gr2
V$PARAMETER db_unique_name sds12cr1 sdscdb1 sdscdb1 sds11gr2

On a related note, only the container of a multi-tenant database has instances. So, while PDBs can declare their own name for the database level with some methods above; there is no corresponding PDB-instance name functionality.

How Oracle Stores Passwords


Several years ago I wrote a small summary of the Oracle password hashing and storage for versions up to 11g.

Today I’ve completed my update of that article up to 12.1.0.2, including code to mimic generation of passwords given the appropriate salts.
The initial publication is in PDF format, I may convert and reformat it to other forms for better distribution.

The pdf file can be downloaded from my dropbox here.

It was interesting and enjoyable digging into the details of the hashes and how they change between versions and interact with the case-sensitivity settings.

I hope you enjoy it as much as I did writing it.

Wow! I’m a finalist in the Oracle Developer Choice Awards!!!


Yesterday I was happily surprised by an email from Steven Feuerstein letting me know I hadn’t responded to my finalist announcement. Well, the reason for that was I never saw it! I’m glad he let me know though, what an honor.

I am a finalist in both the SQL and PL/SQL categories. If you’ve enjoyed any of my articles or I’ve answered a question for you or otherwise helped, I would appreciate and up-vote in the polls.

My profiles can be found here PL/SQL and here SQL.

Thank you very much for your support!

Using SQL to query USGS Data to monitor recent earthquakes


I live in Ohio which is fairly stable as far as earthquakes go; but I have coworkers and friends that live in other, more quake-prone areas of the country.  So, sometimes I like to look up what’s going on in those areas.  Naturally, I decided to see how I could use Oracle features to assist in my monitoring.

First, I needed a reliable source of information.  The U.S. Geological Survey web site is my data source and they have a variety of free data feeds available.  If you’re going to do a lot of data analysis I recommend pulling the data into a local table and saving it rather than hammering their server again and again for each query.

So, while these example show how to access and parse the data, your production use should be to local storage.  You’ll get better performance on repeated access rather than pulling across the internet anyway.

The USGS has a several different formats available.  I’ll be using the Quakeml formatted feed.  Quakeml is simply xml with a predefined schema.  Since I’m not a seismologist I’m only interested in the basic information of when?/where?/how big? and not necessarily all of the minute details like sampling method, uncertainty, etc.

In the examples below I’ll use a rectangular box to define the area I want to examine and I’ll request all of the data for the current and previous 2 days.  For more complete information on constructing other query urls, see the reference here: http://earthquake.usgs.gov/fdsnws/event/1

The service returns a single xml document consisting of multiple event tags.  Each event represents one earthquake.  We’ll extract the text within the following nodes to get our result set.

/q:quakeml/eventParameters/event/description/text
/q:quakeml/eventParameters/event/origin/time/value
/q:quakeml/eventParameters/event/origin/longitude/value
/q:quakeml/eventParameters/event/origin/latitude/value
/q:quakeml/eventParameters/event/origin/depth/value
/q:quakeml/eventParameters/event/magnitude/mag/value

Note text is an xml tag,  not the xquery function text().

The url construction requires some cumbersome syntax but isn’t really all that complicated.  It basically boils down to a base url followed by the parameters for the start and end times (in GMT),  a boundary box of latitude/longitude coordinates, a minimum magnitude and an ordering criteria.

In this case I’m looking for a bounding box that includes the state of Oklahoma, thus yielding a url construction of:

'http://earthquake.usgs.gov/fdsnws/event/1/query.quakeml?starttime='
|| TO_CHAR(FROM_TZ(CAST(TRUNC(SYSDATE - 2) AS TIMESTAMP), 'US/Eastern') AT TIME ZONE 'GMT', 'yyyy-mm-dd')
|| '&endtime='
|| TO_CHAR(SYSTIMESTAMP AT TIME ZONE 'GMT', 'yyyy-mm-dd"%20"hh24:mi:ss')
|| '&maxlatitude=37&minlatitude=33&maxlongitude=-94&minlongitude=-103'
|| '&minmagnitude=0&orderby=time'

 

Using the HTTPURITYPE function we can read the USGS site for that url and extract the xml.  Using the XMLTABLE we can then extract each of the event nodes into individual rows and parse out the values we’re interested in.

SELECT FROM_TZ(TO_TIMESTAMP(time, 'yyyy-mm-dd"T"hh24:mi:ssxff"Z"'), 'GMT') 
            AT TIME ZONE 'US/Central' quake_time,
       latitude,
       longitude,
       magnitude,
       depth_in_meters,
       location_name,
       XMLSERIALIZE(DOCUMENT eventxml AS CLOB INDENT SIZE = 3)
  FROM XMLTABLE(
           xmlnamespaces(DEFAULT 'http://quakeml.org/xmlns/bed/1.2',
                         'http://anss.org/xmlns/catalog/0.1' AS "catalog",
                         'http://quakeml.org/xmlns/quakeml/1.2' AS "q"),
           '/q:quakeml/eventParameters/event'
           PASSING httpuritype(
                          'http://earthquake.usgs.gov/fdsnws/event/1/query.quakeml?starttime='
                       || TO_CHAR(FROM_TZ(CAST(TRUNC(SYSDATE - 2) AS TIMESTAMP), 'US/Eastern')
                                AT TIME ZONE 'GMT','yyyy-mm-dd"')
                       || '&endtime='
                       || TO_CHAR(SYSTIMESTAMP AT TIME ZONE 'GMT', 'yyyy-mm-dd"%20"hh24:mi:ss')
                       || '&maxlatitude=37&minlatitude=33&maxlongitude=-94&minlongitude=-103'
                       || '&minmagnitude=0&orderby=time').getxml()
           COLUMNS 
              eventxml XMLTYPE PATH '.',
              time VARCHAR2(100) PATH './origin/time/value',
              longitude NUMBER PATH './origin/longitude/value',
              latitude NUMBER PATH './origin/latitude/value',
              magnitude NUMBER PATH './magnitude/mag/value',
              depth_in_meters NUMBER PATH './origin/depth/value',
              location_name VARCHAR2(100) PATH './description/text')

 

Since Oklahoma is in US/Central timezone I’ll extract the data in time local to the events.

Running the query above yields a result set like the following…

QUAKE_TIME LATITUDE LONGITUDE MAGNITUDE DEPTH_IN_METERS LOCATION_NAME
2015-07-27 19:24:03.770000000 -05:00
35.9938
-97.5664
3.2
3290
5km NNE of Crescent, Oklahoma
2015-07-27 16:07:46.590000000 -05:00
36.0019
-97.5183
2.5
4490
8km NE of Crescent, Oklahoma
2015-07-27 13:12:15.330000000 -05:00
36.006
-97.5761
4.5
3180
6km NNE of Crescent, Oklahoma
2015-07-27 12:49:28.000000000 -05:00
36.0018
-97.5667
4
5590
6km NNE of Crescent, Oklahoma
2015-07-26 08:26:50.480000000 -05:00
35.9983
-97.5693
3.2
4270
5km NNE of Crescent, Oklahoma
2015-07-26 06:30:48.720000000 -05:00
36.5294
-98.8821
3
5000
30km ENE of Mooreland, Oklahoma
2015-07-26 04:54:33.520000000 -05:00
36.0082
-97.5709
3.9
5860
6km NNE of Crescent, Oklahoma
2015-07-26 04:15:06.100000000 -05:00
35.9656
-96.8124
2.8
4160
4km WSW of Cushing, Oklahoma
2015-07-26 01:59:44.610000000 -05:00
36.0016
-97.5775
2.9
7130
5km NNE of Crescent, Oklahoma
2015-07-25 06:14:44.200000000 -05:00
36.0034
-97.568
3.9
5570
6km NNE of Crescent, Oklahoma
2015-07-25 03:47:53.420000000 -05:00
36.3478
-96.8192
2.9
1100
1km NW of Pawnee, Oklahoma
2015-07-25 01:48:21.880000000 -05:00
36.136
-97.0685
1.8
4280
2km NNW of Stillwater, Oklahoma
2015-07-24 23:49:02.820000000 -05:00
36.1675
-96.967
3
5000
10km NE of Stillwater, Oklahoma
2015-07-24 23:10:33.690000000 -05:00
36.5804
-97.6208
3.1
5000
27km SSE of Medford, Oklahoma
%d bloggers like this: