MacLochlainns Weblog

Michael McLaughlin's Technical Blog

Site Admin

Archive for September, 2015

REGEXP_LIKE Behavior

with one comment

Often, the biggest problem with regular expressions is that those who use them sometimes don’t use them correctly. A great example occurs in the Oracle Database with the REGEXP_LIKE function. For example, some developer use the following to validate whether a string is a number but it only validates whether the first character is a number.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DECLARE
  lv_input  VARCHAR2(100);
BEGIN
  /* Assign input value. */
  lv_input := '&input';
 
  /* Check for numeric string. */
  IF REGEXP_LIKE(lv_input,'[[:digit:]]') THEN
    dbms_output.put_line('It''s a number.');
  ELSE
    dbms_output.put_line('It''s a string.');
  END IF;
END;
/

When they test numbers it appears to works, it even appears to work when the test string start with number, but it fails with any string that starts with a character. That’s because the REGEXP_LIKE function on line 8 only checks the first character, but the following checks all the characters in the string.

8
  IF REGEXP_LIKE(lv_inputs(i),'[[:digit:]]{'||LENGTH(lv_inputs(i))||'}') THEN

You can also fix it with the following non-Posix solution:

8
  IF REGEXP_LIKE(lv_input,'[[0-9]]') THEN

You can add a collection to the program and use it to test single-digit, double-digit, and string with a leading integer. Save the program as test.sql and you can test three conditions with one call.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DECLARE
  /* Declare the local collection type. */
  TYPE inputs IS TABLE OF VARCHAR2(100);
 
  /* Declare a local variable of the collection type. */
  lv_inputs  INPUTS;
BEGIN
  /* Assign the inputs to the collection variable. */
  lv_inputs := inputs('&1','&2','&3');
 
  /* Read through the collection and print whether it's an number or string. */
  FOR i IN 1..lv_inputs.COUNT LOOP
    IF REGEXP_LIKE(lv_inputs(i),'[[:digit:]]{'||LENGTH(lv_inputs(i))||',}') THEN
      dbms_output.put_line('It''s a number.');
    ELSE
      dbms_output.put_line('It''s a string.');
    END IF;
  END LOOP;
END;
/

You can run the test.sql program like this:

SQL> @test.sql 1 12 1a

It prints:

It's a number.
It's a number.
It's a string.

As always, I hope this helps those looking for a solution.

Written by maclochlainn

September 30th, 2015 at 7:23 pm

Create MySQL Index

without comments

Indexes are separate data structures that provide alternate pathways to finding data. They can and do generally speed up the processing of queries and other DML commands, like the INSERT, UPDATE, REPLACE INTO, and DELETE statements. Indexes are also called fast access paths.

In the scope of the InnoDB Database Engine, the MySQL database maintains the integrity of indexes after you create them. The upside of indexes is that they can improve SQL statement performance. The downside is that they impose overhead on every INSERT, UPDATE, REPLACE INTO, and DELETE statement, because the database maintains them by inserting, updating, or deleting items for each related change in the tables that the indexes support.

Indexes have two key properties—usability and visibility. Indexes are both usable and visible by default. That means they are visible to the MySQL cost-based optimizer and usable when statements run against the tables they support.

You have the ability to make any index invisible, in which case queries and DML statements won’t use the index because they won’t see it. However, the cost-based optimizer still sees the index and maintains it with any DML statement change. That means making an index invisible isn’t quite like making the index unusable or like dropping it temporarily. An invisible index becomes overhead and thus is typically a short-term solution to run a resource-intensive statement that behaves better without the index while avoiding the cost of rebuilding it after the statement runs.

It is also possible to make an index unusable, in which case it stops collecting information and becomes obsolete and the database drops its index segment. You rebuild the index when you change it back to a usable index.

Indexes work on the principal of a key. A key is typically a set of columns or expressions on which you can build an index, but it’s possible that a key can be a single column. An index based on a set of columns is a composite, or concatenated, index.

Indexes can be unique or non-unique. You create a unique index anytime you constrain a column by assigning a primary key or unique constraint, but they’re indirect indexes. You create a direct unique index on a single column with the following syntax against two non-unique columns:

1
2
CREATE INDEX common_lookup_u1
ON common_lookup (common_lookup_table) USING BTREE;

You could convert this to a non-unique index on two columns by using this syntax:

1
2
CREATE INDEX common_lookup_u1
ON common_lookup (common_lookup_table, common_lookup_column) USING BTREE;

Making the index unique is straightforward;, you only need to add a UNIQUE key wordk to the CREATE INDEX statement, like

1
2
3
4
CREATE UNIQUE INDEX common_lookup_u1
ON common_lookup ( common_lookup_table
                 , common_lookup_column
                 , common_lookup_type) USING BTREE;

Most indexes use a B-tree (balanced tree). A B-tree is composed of three types of blocks—a root branch block for searching next-level blocks, branch blocks for searching other branch blocks, or and leaf blocks that store pointers to row values. B-trees are balanced because all leaf-blocks are at the same level, which means the length of search is the same to any element in the tree. All branch blocks store the minimum key prefix required to make branching decisions through the B-tree.

Written by maclochlainn

September 29th, 2015 at 6:41 pm

SQL*Plus Tricks

with 3 comments

Have you ever wondered how to leverage substitution variables in anonymous block programs? There are several tricks that you can use beyond passing numeric and string values to local variable. The generic default appears to take a number unless you cast it as a string but that’s not really the whole story. The first two are standard examples of how to use numeric and string substitution values.

The following accept a numeric substitution value:

1
2
3
4
5
6
7
8
9
10
DECLARE
  lv_input  NUMBER;
BEGIN
  /* Assign substitution value to local variable. */
  lv_input := &input;
 
  /* Print the local variable. */
  dbms_output.put_line('['||lv_input||']');
END;
/

The following accept a string substitution value, casts the input as a string, assigns the string value to a 4,000 character length local variable, checks whether the 4,000 character length is greater than 10, and assigns the first 10 characters to the lv_parse_input variable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DECLARE
  lv_unparsed_input  VARCHAR2(4000);
  lv_parsed_input    VARCHAR2(10);
BEGIN
  /* Assign substitution value to local variable. */
  lv_unparsed_input := '&input';
 
  /* Check size of input value. */
  IF LENGTH(lv_unparsed_input) > 10 THEN
    lv_parsed_input := SUBSTR(lv_unparsed_input,1,10);
  END IF;
 
  /* Print the local variable. */
  dbms_output.put_line('Print {lv_parsed_input}: ['||lv_parsed_input||']');
END;
/

Next, let’s examine two tricks. The first passes a case insensitive variable name and the second passes a case sensitive variable name as a parameter to an anonymous block program.

This declares an anonymous block program that uses a substitution value as a variable name:

1
2
3
4
5
6
7
DECLARE
  mine  VARCHAR2(10) := 'Default';
BEGIN
  /* Print the local variable's value. */
  dbms_output.put_line('Print {mine} variable value: ['||&input||']');
END;
/

When you run the anonymous block, you’re prompted for an input variable. You provide a case insensitive variable name as the input value:

Enter value for input: MINE
old   5:   dbms_output.put_line('['||&input||']');
new   5:   dbms_output.put_line('['||MINE||']');
Print {mine} variable value: [Default]

The downside of this approach, yields an ORA-06550 and PLS-00201 exception. Neither of these can be caught because Oracle raises the errors during parsing when the variable name isn’t a 100% case insensitive match. The same type of problem occurs in the next example when the input variable isn’t a 100% case sensitive match.

You can rewrite the program to handle case insensitive variables like this:

1
2
3
4
5
6
7
DECLARE
  "Mine"  VARCHAR2(10) := 'Default';
BEGIN
  /* Print the local variable's value. */
  dbms_output.put_line('Print {mine} variable value: ['||"&input"||']');
END;
/

When you run the anonymous block, you’re prompted for an input variable. You provide a case sensitive variable name as the input value:

Enter value for input: Mine
old   5:   dbms_output.put_line('['||&input||']');
new   5:   dbms_output.put_line('['||"Mine"||']');
Print {Mine} variable value: [Default]

Hope this helps those looking for a solution.

Written by maclochlainn

September 24th, 2015 at 1:19 am