Archive for February, 2009
Excel string parsing
Parsing strings isn’t the easiest thing to do in Excel 2007 or Excel 2008 but it is an important thing to know how to do. You’ll learn how to parse a set names into first, middle and last names. You can find the data set for these examples at the bottom of the blog post.
Parsing the left substring ↓
This shows you how to parse a left substring from a text cell in Microsoft Excel.
There are two built in functions that let you parse dynamic substrings from the left of a string. They are the LEFT
and FIND
functions. The LEFT
function actually does the parsing but the FIND
function lets you dynamically find a delimiting character, like a space.
Assuming cell A1
holds the value of Joseph F. Smith, you can parse Joseph by using a static value of seven for the first white space in the string. This works because each character maps to a string, and strings start with the number one. The LEFT
function supports three formal parameters, the first is the string you’re parsing, the second is where to stop, and the third is where to start. The position you start at is assumed to be one, which makes the last parameter an optional parameter. You only provide the start with parameter when you want to start someplace other than the left most position.
This is the simplest use of the LEFT
function with its two required parameters.
=LEFT(A1,7) |
It returns the substring Joseph from the string Joseph F. Smith, which is stored in cell A1
.
The simplest solution merely illustrates a concept. You’re not going to do this unless you have a bit more data. Naturally, the list of first names have different lengths in the real world. You must include the FIND
function inside the call to the LEFT
function to make your function dynamic. The FIND
function lets you capture the position of a white space delimiter.
The FIND
function supports three formal parameters, the first is the substring you’re looking for in the string, the second is the string to search, and the third is where to start. Like the LEFT
function, the position you start at is assumed to be one, which makes the last parameter an optional parameter. You only provide the start with parameter when you want to start someplace other than the left most position. While not required, you should consider always providing the start with parameter to the FIND
function.
The following shows you how to use the FIND
function to locate the first occurrence of a white space in a string stored in cell A1
.
=FIND(" ",A1,1) |
You can now make you’re LEFT
function dynamic by putting the FIND
function inside it. As qualified, the FIND
function looks for the first occurrence of a white space, and returns it into the second parameter of the LEFT
function. The sample formula now has a nested function, which makes it a mega formula in Microsoft Excel parlance. The following sample also includes the optional start with parameter for clarity.
=LEFT(A1,FIND(" ",A1,1),1) |
The problem with the foregoing solution occurs when a name in the list doesn’t have a middle initial or name, or last name. The lack of a second name means that there won’t be any white space between to substrings in the base string. This situation causes the logic to fail because the FIND
function returns a #VALUE!
error when it can’t find a white space in the string.
You can prevent the error by wrapping the nested FIND
function and LEFT
function inside two IFERROR
functions. This IFERROR
function returns the positional value of the FIND
function, or an alternative copy of the base string.
=IFERROR(FIND(" ",A1,1),A1) |
This type of logic inside the LEFT
function causes the LEFT
function to fail when the base string is returned to it instead of a position number. You must wrap the LEFT
function inside another IFERROR
function to guarantee that you don’t throw an error. This also lets you return the base string as the valid substring when appropriate.
=IFERROR(LEFT(A1,IFERROR(FIND(" ",A1),A1)-1),A1) |
Assuming you put this formula in cell C1
, you could copy this relative reference formula down in a column. It would require that you had a list of full names that followed a first name, white space, middle initial or name, white space, and last name in the A
column.
Parsing the right substring ↓
This shows you how to parse a right substring from a text cell in Microsoft Excel.
There are three built in functions that let you parse dynamic substrings from the right of strings. They are the RIGHT
and FIND
functions that you may have covered when reading how to parse from the left. The LEN
function is the other function, and it lets you find the length of a string. Together these functions lets you find the length of a substring on the right.
The RIGHT
function actually does the parsing but the FIND
and LEN
functions let you dynamically find where to cut a substring out of a base string. This example continues to use the string Joseph F. Smith.
The RIGHT
function has only two required parameters. The first parameter is the string that you’re parsing. The second parameter is the length of the substring. If you inspect the string, Smith is only five characters long. A static call to the RIGHT
function is shown below.
=RIGHT(A1,5) |
It returns the substring Smith from the string Joseph F. Smith, which is stored in cell A1
.
Like the LEFT
function example, this static approach to parsing merely illustrates a concept. You’re not going to do this with real data because the list of first names have different lengths in the real world. You must include the FIND
function twice inside your call to the RIGHT
function because you’re parsing the string based on the second occurrence of a white space in the string. While the FIND
function lets you dynamically capture the position of the white space delimiter, a nested FIND
function lets you capture the correct start with parameter value. That value is one position after the first occurrence of a white space.
The FIND
function supports three formal parameters, the first is the substring you’re looking for in the string, the second is the string to search, and the third is where to start. Like the LEFT
function, the position you start at is assumed to be one, which makes the last parameter an optional parameter. You only provide the start with parameter when you want to start someplace other than the left most position. While not required, you should consider always providing the start with parameter to the FIND
function. It improves readability.
The following shows how to use a FIND
function to locate the position one beyond where the first occurrence of a white space is found in a string.
=FIND(" ",A1,1)+1 |
If you pass the preceding FIND
function call as the start with parameter to another FIND
function, you can locate the second occurrence of a white space in a string stored in cell A1
. Nesting function calls inside functions creates what are known as mega formulas in Excel. The following demonstrates a mega formula to find the second instance of a white space in a string.
=FIND(" ",A1,FIND(" ",A1,1)+1) |
This returns the value of ten. You now know where to start but not the length of the substring on the right. You first need to find the length of the total string. You use the LEN
function to find that, like the following.
=LEN(A1) |
The LEN
function returns fifteen. You can calculate the length of the substring as five by subtracting the position of the second white space from the length of the string. While you could inspect that value in this one cell, you can’t do that when there are one hundred or one hundred thousand names in a list. The way to dynamically capture the right hand side substring length is shown below.
=LEN(A1)-FIND(" ",A1,FIND(" ",A1,1)+1) |
Now that you know how to capture the length of the substring, you can create a larger mega forumla to parse the substring on the right from the base string. The working example follows below.
=RIGHT(A1,LEN(A1)-FIND(" ",A1,FIND(" ",A1,1)+1)) |
The problem with the foregoing solution is that it fails when one of the names in the list doesn’t have a middle initial or name. The failure occurs because there would only be one white space in the base string, and the logic expects two. The FIND
function looking for the second white space returns a #VALUE!
error.
You can prevent this error by wrapping the nested FIND
function calls and RIGHT
function with calls with an IFERROR
function. An IFERROR
function returns the positional value of the FIND
function, or an alternate value, like a fresh copy of the base string.
=IFERROR(RIGHT(A1,LEN(A1)-IFERROR(FIND(" ",A1,FIND(" ",A1)+1),FIND(" ",A1))),A1) |
The IFERROR
function inside the RIGHT
function can cause the RIGHT
function to fail when a base string is returned instead of a position number. Therefore, you must also wrap the RIGHT
function inside another IFERROR
function to avoid an error. This guarantees the return of the base string as a valid substring.
Assuming you put this formula in cell C1
, you could copy this relative reference formula down in a column. It would require that you had a list of full names that followed a first name, white space, middle initial or name, white space, and last name in the A
column.
Parsing the middle substring ↓
This shows you how to parse a middle substring from a text cell in Microsoft Excel.
There are three built in functions that are required to let you parse dynamic substrings from the middle of base strings. Two of them are the MID
and FIND
functions. You’ve seen how to use the FIND
function in the left and right parsing examples of this blog. The third function is the IFERROR
function, which is required when a middle string doesn’t exist. There are two more functions that let you trap for the possibility of a single base string. They are the IF
and ISNUMBER
functions.
The MID
function takes three required parameters. The first is the text value or cell reference, the second is the start with value, and the third is the length of the substring. Dynamic substrings require you to bracket them, which means you need to find their beginning and ending positions and measure their length.
This basic idea means you parse the middle string from a set of three strings by finding their delimiters. As in the other examples, you’ll work with the string Joseph F. Smith as a base string. First, you find the first character of the middle string. You do this by finding the position of the first delimiting white space with a FIND
function, and then you add one to the returned result value. The example is below.
=FIND(" ",A1,1)+1 |
Next, you find the position of the second delimiter. This requires that you create what is known as a mega formula, which you create by nesting one or more formulas in another. You can use the following formula to do that.
=FIND(" ",A1,FIND(" ",A1)+1) |
You can then calculate the length of the middle string by subracting the first result from the second one, as shown below:
=FIND(" ",A1,FIND(" ",A1)+1)-FIND(" ",A1)-1 |
After you’ve mastered those formulas, you need to create a mega formula with the MID
function. This doesn’t have any error trapping yet, so it is very dependent on data that contains three substrings separated by white spaces.
=MID(A1,FIND(" ",A1)+1,FIND(" ",A1,FIND(" ",A1)+1)-FIND(" ",A1)-1) |
The preceding function returns F.. If the middle string were a middle name, it would return the middle name. It fails when you have a base string that lacks three substrings. You need to wrap the nested FIND
function and MID
function inside two respective IFERROR
functions. An IFERROR
function returns the positional value of the FIND
function, or an alternate value, like a fresh copy of the base string or a numeric equivalent. The following example uses an IFERROR
function call to substitutes a zero value because the absence of a second white space means there isn’t a middle string.
=MID(A5,FIND(" ",A5)+1,IFERROR(FIND(" ",A5,FIND(" ",A5)+1)-FIND(" ",A5)-1,0)) |
A second IFERROR
wrapping the MID
function lets you return a null value for middle name when there is only one name in the base string, like Joseph.
=IFERROR(MID(A5,FIND(" ",A5)+1,IFERROR(FIND(" ",A5,FIND(" ",A5)+1)-FIND(" ",A5)-1,0)),"") |
Assuming you put this formula in cell D1
, you could copy this relative reference formula down in a column. It would require that you had a list of full names that followed a first name, white space, middle initial or name, white space, and last name in the A
column.
Data set ↓
A list of native strings, parsed first, middle, and last names, and concatenated names.
Original Name First Name Middle Name Last Name Resorted Name Joseph Smith Joseph Smith Smith, Joseph Brigham Young Brigham Young Young, Brigham John Taylor John Taylor Taylor, John Wilford Woodruff Wilford Woodruff Woodruff, Wilford Lorenzo Snow Lorenzo Snow Snow, Lorenzo Joseph F. Smith Joseph F Smith Smith, Joseph F Heber J. Grant Heber J Grant Grant, Heber J George Albert Smith George Albert Smith Smith, George Albert David O. Mckay David O Mckay Mckay, David O Joseph Fielding Smith Joseph Fielding Smith Smith, Joseph Fielding Harold B. Lee Harold B Lee Lee, Harold B Spencer W. Kimball Spencer W Kimball Kimball, Spencer W Ezra Taft Benson Ezra Taft Benson Benson, Ezra Taft Howard W. Hunter Howard W Hunter Hunter, Howard W Gordon B. Hinckley Gordon B Hinckley Hinckley, Gordon B Thomas S. Monson Thomas S Monson Monson, Thomas S |
Easier way than NDS
Somebody posted a question about a dynamic NDS example found in the Oracle Database 11g PL/SQL Programming book on page 388. They asked if there was an easier way.
The answer is yes. Here’s a different example implementing the same concept on Native Dynamic SQL (NDS) with an input parameter. I borrowed it from the example I used for an Oracle framework to mimic the MySQL ENUM
data type.
Basically, the following shows how you write a function using a dynamic NDS statement with an input parameter.
CREATE OR REPLACE FUNCTION proper_item_type ( item_type_in VARCHAR2 ) RETURN VARCHAR2 IS -- Define a weakly typed system reference cursor. item_cursor SYS_REFCURSOR; -- Define a target variable for the query result. item_type_out VARCHAR2(30); -- Create NDS statement, with a bind or placeholder variable. stmt VARCHAR2(2000) := 'SELECT type_name ' || 'FROM item_type ' || 'WHERE UPPER(type_name) = UPPER(:type_name_in)'; BEGIN -- Open the cursor and dynamically assign the function actual parameter. OPEN item_cursor FOR stmt USING item_type_in; -- Fetch the first row return and return the value. FETCH item_cursor INTO item_type_out; -- CLose the cursor. CLOSE item_cursor; -- Return the value. RETURN item_type_out; END; / |
This is certainly overkill if you only want to substitute a single parameter into a cursor. A simpler approach would be to write a dynamic cursor, and then open the cursor by passing the actual parameter. Here’s that example.
CREATE OR REPLACE FUNCTION proper_item_type ( item_type_in VARCHAR2 ) RETURN VARCHAR2 IS -- Define a dynamic cursor. CURSOR c (item_type_name VARCHAR2) IS SELECT type_name FROM item_type WHERE UPPER(type_name) = UPPER(item_type_name); BEGIN -- Open the cursor and dynamically assign the function actual parameter. FOR i IN c(item_type_in) LOOP RETURN i.type_name; END LOOP; END; / |
An even more primitive approach relies on implicit assignment, like the following:
CREATE OR REPLACE FUNCTION proper_item_type ( item_type_in VARCHAR2 ) RETURN VARCHAR2 IS BEGIN -- Open the cursor and rely on implicit assignment within the cursor. FOR i IN (SELECT type_name FROM item_type WHERE UPPER(type_name) = UPPER(item_type_in)) LOOP RETURN i.type_name; END LOOP; END; / |
I hope this answers the question. You can click on the Setup Code line to unfold the code. Let me know if you like this approach to posting setup code.
Setup Code ↓
-- Conditionally drop table and sequence before attempting to create them. BEGIN FOR i IN (SELECT TABLE_NAME FROM user_tables WHERE TABLE_NAME IN (UPPER('item_type') ,UPPER('item'))) LOOP EXECUTE IMMEDIATE 'DROP TABLE '||i.table_name||' CASCADE CONSTRAINTS'; END LOOP; FOR i IN (SELECT sequence_name FROM user_sequences WHERE sequence_name IN (UPPER('item_type_s1') ,UPPER('item_s1'))) LOOP EXECUTE IMMEDIATE 'DROP SEQUENCE '||i.sequence_name; END LOOP; END; / -- Create item_type table, index on the natural key, and sequence; then seed data. CREATE TABLE item_type ( item_type_id NUMBER CONSTRAINT pk_item_type PRIMARY KEY , TABLE_NAME VARCHAR2(30) CONSTRAINT nn_item_type_1 NOT NULL , column_name VARCHAR2(30) CONSTRAINT nn_item_type_2 NOT NULL , type_name VARCHAR2(30) CONSTRAINT nn_item_type_3 NOT NULL , code VARCHAR2(5) , meaning VARCHAR2(255) CONSTRAINT nn_item_type_4 NOT NULL); CREATE UNIQUE INDEX item_type_u1 ON item_type(TABLE_NAME,column_name,type_name); CREATE TABLE item ( item_id NUMBER CONSTRAINT pk_item PRIMARY KEY , item_type NUMBER CONSTRAINT nn_item_1 NOT NULL , item_title VARCHAR2(60) CONSTRAINT nn_item_2 NOT NULL , item_rating_id NUMBER CONSTRAINT nn_item_3 NOT NULL); CREATE SEQUENCE item_type_s1 START WITH 1001; CREATE SEQUENCE item_s1 START WITH 1001; INSERT INTO item_type VALUES (item_type_s1.NEXTVAL,'ITEM','ITEM_TYPE','DVD',NULL,'DVD'); INSERT INTO item_type VALUES (item_type_s1.NEXTVAL,'ITEM','ITEM_TYPE','VHS',NULL,'VHS'); INSERT INTO item_type VALUES (item_type_s1.NEXTVAL,'ITEM','ITEM_TYPE','Blu-ray',NULL,'BLRY'); |
Oracle ENUM Framework
I went back and edited that post about MySQL nuances not in Oracle from last week about the MySQL ENUM
data type. You can find a framework suggestion that lets you not have to change existing string conditioning application code during a migration from MySQL to Oracle.
You’ll see this in the middle of the page, just click it to unfold the details. Yes, JQuery has arrived on my blog.
As with everything else on this blog, let me know if you see an opportunity for improvement.
Localhost WordPress on MAMP
There’s so much energy that goes into blogging with big blog pages, I figured it was time to setup WordPress on my MacBook Pro MAMP (Mac, Apache, MySQL, and PHP) installation. That way, the world doesn’t need to know when I mess up a <div> tag.
The biggest benefit of creating a local blog becomes obvious when you start building your own plugins, templates, and themes. You also have the benefit of a built in backup if you post your work locally before moving it to your blog. Likewise, you can work on your blog when not connected to the network. By the way, thanks to Joel for pointing out that I forgot to say why this was important.
Here are the setup instructions for those interested in doing it too.
1. Download the software from WordPress.org by clicking their button or this one. If you need the tar.gz
, click on the web site link.
2. When you download it, you’ll be prompted to open it. Choose to save it.
3. You’ll find it in your downloads folder, where you can double click it to launch the unzip process into a file folder. Mine happens on the desktop because it’s convenient.
4. After you’ve unzipped it into a folder. You drag it into your MAMP htdocs
folder. You can check where the htdocs
folder is by launching the MAMP application, and choosing Preferences.
In the preferences dialog, click the Apache tab, you’ll see where the htdocs
folder location is. Mine is set to the default location.
5. After you drag the wordpress folder into the htdocs
folder, you return to the MAMP application and click the Open start page button.
6. Click the phpMyAdmin link and create a database, like the screen shot.
After a moment, you should see that you’ve created a new database named WordPress.
7. Go to your /Applications/MAMP/htdocs/wordpress
folder, and open up wp-config-sample.php
file in a text editor. You should see the same lines 19, 22, and 25. The editor screen capture is from the TextMate editor.
Edit those lines to reflect your database name, and the root password. Naturally, if you’re concerned that your local host isn’t secure, you’ll need to purchase MAMP Pro to change your root password. You can’t change it in the free version.
// ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ define('DB_NAME', 'WordPress'); /** MySQL database username */ define('DB_USER', 'root'); /** MySQL database password */ define('DB_PASSWORD', 'root'); |
After these changes, save the wp-config-sample.php
file as wp-config.php
in the same folder.
8. Enter the following in URL in your browser, and press the enter key.
http://localhost:8888/wordpress/wp-admin/install.php |
9. On the Welcome web page, enter your blog name and email (mine is omitted for obvious reasons). Click the Install WordPress button to proceed.
10. You’ll then be prompted with the admin
user and a password. You should print it or copy it to your buffer because you’ll need it when you first login. Then, click the Log In button to proceed. You should probably change your password to something that you’ll remember before you do anything else.
11. You now have a complete localhost
WordPress installation. Now, you can go to your real web accessible WordPress install and export your blog from the Tools menu. Then, on your localhost blog, you can import from the export file of your blog. If you’re using plugins (like anybody doesn’t), then copy the contents from the wp-content/plugins
folder from your online blog to your local one; and then, activate them. You can snag your Akismet key from your online site too, but it is unlikely anybody will see your localhost blog.
Violà, c’est fine.
You can now access WordPress on your local machine by typing in the following URL:
http://localhost:8888/wordpress/ |
MySQL nuances not in Oracle
I use Alan Beaulieu’s book in my entry level SQL class because it is simple and short. The problem is that we focus on Oracle products as an Oracle Academic Partner. Three items that come up frequently are the MySQL ENUM
and SET
data types, and how to perform multiple row inserts.
MySQL’s ENUM
data type
The ENUM
data type lets you enter a list of possible string values. It acts like a check constraint in an Oracle database. As such, it restricts what you enter in the column to a value found in the list, or a NULL
value provided you’ve not added a not null column constraint.
You could define a table that contains video store item types, like the following:
CREATE TABLE item_type ( id INT , text ENUM ('VHS','DVD','Blu-ray') ); |
You should note that the case sensitivity for display is set by how you define them in the ENUM
data type when you create the table. They may be entered in mixed, lowercase, or uppercase in an INSERT
statement because they’re actually stored as a number. The numbers correlate to their order in an internal list of values, and that list start with the number one.
An alternative syntax with a VARCHAR
data type is:
CREATE TABLE item_type ( id INT , TYPE VARCHAR(20) CHECK ( TYPE IN ('DVD','VHS','Blu-ray') )); |
In MySQL, this syntax is exactly equivalent in behavior to an ENUM
data type. The same isn’t true in an Oracle database. While the equivalent check constraint statement is simple, it isn’t alike behavior. The comparable statement for Oracle names the constraint. You can’t name constraints in MySQL.
CREATE TABLE item_type ( id NUMBER , TYPE VARCHAR2(20) CONSTRAINT it_type CHECK ( TYPE IN ('DVD','VHS','Blu-ray') )); |
The difference between an ENUM
type and a check constraint in MySQL is that the data may not display in a uniform way. MySQL check constraints don’t impose case sensitive validation on input strings, and they also store the data however it is input. Whereas, Oracle does impose case sensitive check constraints and rejects non-conforming strings.
Both databases support single and multiple row INSERT
statements. The syntax for single row INSERT
statements is very much alike. Multiple row INSERT
statement syntax differs between the implementations, as shown later in the blog post.
Oracle Framework to mimic ENUM
data type ↓
This framework is predicated on two assumptions. You are migrating from a MySQL to Oracle database, and your external code doesn’t enforce case sensitivity on inputs because it once relied on the MySQL ENUM
data type. Oracle check constraints enforce case sensitive inputs, which may be a solution in itself during migration. At least, it is a solution when you handle case sensitive errors gracefully in your external code. Assuming you want an alternative to changing your external application code and you want to avoid throwing errors, here are the steps to do so.
Create testing environment
Create a testing environment that uses a VARCHAR2
column without a database level constraint in conjunction with a database trigger. You can do this because a before insert or update trigger will enforce the equivalent of a database constraint. The trigger lets you define the behavior of the constraint.
Here’s the setup code, which relies on a small item_type
lookup table:
Setup code ↓
-- Conditionally drop table and sequence before attempting to create them. BEGIN FOR i IN (SELECT TABLE_NAME FROM user_tables WHERE TABLE_NAME IN (UPPER('item_type') ,UPPER('item'))) LOOP EXECUTE IMMEDIATE 'DROP TABLE '||i.table_name||' CASCADE CONSTRAINTS'; END LOOP; FOR i IN (SELECT sequence_name FROM user_sequences WHERE sequence_name IN (UPPER('item_type_s1') ,UPPER('item_s1'))) LOOP EXECUTE IMMEDIATE 'DROP SEQUENCE '||i.sequence_name; END LOOP; END; / -- Create item_type table, index on the natural key, and sequence; then seed data. CREATE TABLE item_type ( item_type_id NUMBER CONSTRAINT pk_item_type PRIMARY KEY , TABLE_NAME VARCHAR2(30) CONSTRAINT nn_item_type_1 NOT NULL , column_name VARCHAR2(30) CONSTRAINT nn_item_type_2 NOT NULL , type_name VARCHAR2(30) CONSTRAINT nn_item_type_3 NOT NULL , code VARCHAR2(5) , meaning VARCHAR2(255) CONSTRAINT nn_item_type_4 NOT NULL); CREATE UNIQUE INDEX item_type_u1 ON item_type(TABLE_NAME,column_name,type_name); CREATE TABLE item ( item_id NUMBER CONSTRAINT pk_item PRIMARY KEY , item_type NUMBER CONSTRAINT nn_item_1 NOT NULL , item_title VARCHAR2(60) CONSTRAINT nn_item_2 NOT NULL , item_rating_id NUMBER CONSTRAINT nn_item_3 NOT NULL); CREATE SEQUENCE item_type_s1 START WITH 1001; CREATE SEQUENCE item_s1 START WITH 1001; INSERT INTO item_type VALUES (item_type_s1.nextval,'ITEM','ITEM_TYPE','DVD',NULL,'DVD'); INSERT INTO item_type VALUES (item_type_s1.nextval,'ITEM','ITEM_TYPE','VHS',NULL,'VHS'); INSERT INTO item_type VALUES (item_type_s1.nextval,'ITEM','ITEM_TYPE','Blu-ray',NULL,'BLRY'); |
As you may have noticed, the seeded data contains case sensitive strings. The absence of a database-level constraint on a column of a table requires a database trigger. The trigger acts as an alternative to a check constraint and lets you enforce a filtered insertion rule.
Oracle lets you write logic directly into a database trigger, but it is better to write the logic into a stored function or procedure and call it from the trigger’s execution block.
Create a stored function
The stored function takes a string, looks it up in the item_type
table, and returns the correct case-sensitive string. The logic is implemented in this function:
CREATE OR REPLACE FUNCTION proper_item_type ( item_type VARCHAR2 ) RETURN VARCHAR2 IS CURSOR c (type_name_in VARCHAR2) IS SELECT type_name FROM item_type WHERE UPPER(type_name) = UPPER(type_name_in); BEGIN FOR i IN c (item_type) LOOP RETURN i.type_name; END LOOP; END; / |
Create a database trigger
After you’ve defined the logic for the trigger, you need to define the database trigger. The following creates a before insert or update database trigger on the item
table. The trigger ensures that INSERT
statements to the item
table store case sensitive copies from the list of possible values found in the item_type
lookup table.
CREATE OR REPLACE TRIGGER proper_item_type_t1 BEFORE INSERT OR UPDATE ON item FOR EACH ROW BEGIN :NEW.item_type := proper_item_type(:new_item_type); END; / |
The trigger takes a non-compliant or compliant string and always return a compliant string before anything is put in the item
table.
Test the framework
You can test the framework by inserting an uppercase string BLU-RAY
and evaluating the actual results. The following INSERT
statement tests the outcome of inserting a non-compliant string.
INSERT INTO item VALUES (item_s1.nextval, 'BLU-RAY', 'Star Wars I', 1001); |
You’ll see that a compliant string was actually inserted because the trigger called the function, and the function changed the value from a non-compliant to compliant string. This query let’s you see the result:
SELECT * FROM item; |
It inserted Blu-ray
in the item_type
column.
MySQL’s SET
data type
The SET
data type lets you enter a list of possible string values but differs from the ENUM
data type only in how the values are indexed. Values in the SET
are stored as bit values. You may store up to 64 members in a set.
You create a table the same way as you did with ENUM
data type, except that you use the SET
key word.
CREATE TABLE item_type ( id INT , TYPE SET ('DVD','VHS','Blu-ray')); |
All insert patterns shown for the ENUM
data type work with the SET
data type.
Multiple row INSERT
statements
The most common multiple row insert statement in MySQL is a comma delimited set of parenthetical values. Each parenthetical set of values maps to a row in the INSERT
statement.
INSERT INTO item_type VALUES ( 1, 'DVD' ), ( 2, 'Blu-ray'), ( 3, 'VHS' ); |
You can’t use the foregoing syntax in Oracle. Oralce only supports a multiple row insert with a subquery.
You can write a subquery in MySQL that fabricates an aggregate table by using numeric and string literals, like the following statement.
INSERT INTO item_type SELECT 1, 'DVD' UNION ALL SELECT 2, 'Blu-ray' UNION ALL SELECT 3, 'VHS' ; |
Another way to perform multiple row inserts in MySQL is to select from an existing table or filtered result set, like this:
INSERT INTO item_type SELECT some_int, some_varchar FROM some_table; |
You raise an error when you use parentheses in a multiple INSERT
statement in MySQL. It’s important to note because the Oracle allows you to write the statement either way.
If you’re coming from MySQL to Oracle, you should note that you may use parentheses to enclose a subquery in Oracle. The Oracle SQL parser works with or without them in an INSERT
statement.
The MySQL subquery example is the closest to the Oracle syntax for a multiple row insert. The difference is that Oracle give you the option to enclose a subquery in parentheses when using them inside INSERT
statements. Oracle requires that you use the FROM dual
clause. I actually wish Oracle would adopt the shorter syntax and maintain backward compatibility to the dual
pseudo table.
INSERT INTO item_type ( SELECT 1, 'DVD' FROM dual UNION ALL SELECT 2, 'Blu-ray' FROM dual UNION ALL SELECT 3, 'VHS' FROM dual); |
Another way to perform a multiple row insert is to select from an existing table, like this:
INSERT INTO item_type (SELECT some_int, some_varchar FROM some_table); |
Other resources:
You should check Oracle’s document that qualifies differences between MySQL and Oracle. It is the Oracle® Database SQL Developer Supplementary Information for MySQL Migrations.
Quick XML update
Somebody commented last week that they’d like the source code for an XSL example file found in this older post. I’ve added the requested code there. The code lets you query directly from your XSL against an Oracle database.
I’ve also created a top page for collecting references to XML blog pages and posts. It’s only got a few but it is an area that will be expanded.
Excel 2007 AND() function
Since Excel 2007 and 2008 don’t support a direct exclusive or – =XOR()
– function, you’d think the logical operators could do it. Something like this:
=AND(NOT(AND(A1,B1)),OR(A1,B1)) |
It surprised me when it didn’t work. Curious, I figured out why it wouldn’t work. The
The AND()
function only works correctly when both cell values are not null values. It returns a Boolean true when one value is true and the other value is null, which was quite a surprise.
Fortunately, the IF()
function works correctly. If you check a cell with a null value, the IF()
function finds it false. That means you can wrap the cells in the IF()
function, like:
=AND(NOT(AND(IF(A1,TRUE(),FALSE()),IF(B1,TRUE(),FALSE()))),OR(IF(A1,TRUE(),FALSE()),IF(B1,TRUE(),FALSE()))) |
Naturally, you can substitute the respective string literal for either the TRUE()
or FALSE()
function calls. You could also rewrite the formula to check for a null value by using two double quotes. By the way, it’s fun to note that the TYPE()
function recognizes a null value cell as a number data type.
It’s ashame it doesn’t work the way you’d think it should.
Data normalization notes
I’m trying to eliminate the textbook from my database class, and wrote a data normalization blog page for my students; however, it is only done through 3NF so far. The post tries to remove the discrete math and provide clear examples. The students are checking it out for opportunities to make it clearer, if you’ve thoughts let me know.
Also, I’ve substantially update my initial blog page on SQL joins, and will put one out on set operators probably this Monday. If you’ve time to review it, I would appreciate suggestions for improvement.
The length of these blog pages has compelled me to move to a more friendly editor. I’ve opted for Blogo as my Mac blog editor. It comes from Brainjuice. My son’s experience with their customer service and product convinced me. You can find his blog post discussing customer service here.
Blogo prompts you, but beat the rush. Go to your Site Admin, select Writing under Settings in WordPress, and enable the remote XML-RPC publishing protocol. More or less like this prompt.
No code scrolling on iPhone
I wanted to have scrolling on my iPhone for code segments. It appeared simple. Change the style.css
file by adding .code { overflow-x:scroll; }
. I first tested it in Safari by setting User Agent to Mobile Safari 1.1.3 – iPhone, where it worked. Alas, it doesn’t work on my iPhone. A quick look at the iPhone simulator taught me it doesn’t work there either. It appears that horizontal scrolling is disabled on the iPhone.
That style sheet is found in the /public_html/blog/wp-content/plugins/wptouch/themes/default
directory. I’d love to have missed something. If there’s a trick to do it, let me know.
Notes on SQL Joins
It’s almost funny reflecting on when I first learned how to write SQL in 1985. It was using SQL/DS 1.1 (Structured Query Language/Data System) on a VMS/CMS operating system at the IBM center in the Los Angeles, California. That was in the Crocker Bank Building when I worked for First Interstate Bank. The building is still there and so are databases but both banks are long gone.
After 24 years of SQL a quick primer on joins seems like a good idea. I have a simple example that should help illustrate various joins and SQL semantics. You’ll find it as a blog page here …