Tag Archives: SQL

Using a collection as parameter for a stored procedure

Sometimes you want to select rows where a value is in a specific collection. Here’s an example that show how you can select all the rows in the TEST table with an id of 1, 2 or 3. First we create an SQL type to contain a list of numbers:

CREATE TYPE LIST_NUMBER AS TABLE OF NUMBER(10);
/

Next thing to do is add a custom type and function header to the package specification:

PACKAGE TIMVW.TESTPACKAGE AS

TYPE CRSR_REF IS REF CURSOR;
TYPE ARR_IDS IS TABLE OF TEST.TEST_ID%Type INDEX BY BINARY_INTEGER;

PROCEDURE GET_TESTS
(
 P_IDS        IN        ARR_IDS,
 P_CURSOR  OUT     CRSR_REF
);
END;

And offcourse we have to implement the function in the body:

PROCEDURE GET_TESTS
(
 P_IDS       IN        ARR_IDS,
 P_CURSOR OUT     CRSR_REF
)

AS

V_IDS LIST_NUMBER := LIST_NUMBER();

BEGIN

V_IDS.EXTEND(P_IDS.COUNT);
FOR i IN P_IDS.FIRST .. P_IDS.LAST LOOP
  V_IDS(i) := P_IDS(i);
END LOOP;

OPEN       P_CURSOR FOR
SELECT
              TEST.TEST_ID,
              TEST.NAME,
              TEST.TYPE_CODE
FROM
              TEST
WHERE
              TEST_ID IN (SELECT * FROM TABLE(V_IDS))
ORDER BY
              TEST.TEST_ID ASC;
END;

Now that we have done all this we can consume the function from our client code:

using (OracleConnection conn = new OracleConnection("User Id=u;password=p;Data Source=ORCL"))
{
 conn.Open();

 OracleCommand command = conn.CreateCommand();
 command.CommandType = CommandType.StoredProcedure;

 command.CommandText = "TIMVW.TESTPACKAGE.GET_TESTS";
 command.Parameters.Add("P_IDS", OracleDbType.Int32, new int[] { 1, 2, 3 }, ParameterDirection.Input);
 command.Parameters["P_IDS"].CollectionType = OracleCollectionType.PLSQLAssociativeArray;
 command.Parameters.Add("P_CURSOR", OracleDbType.RefCursor, ParameterDirection.Output);

 OracleDataReader reader = command.ExecuteReader();
 while (reader.Read())
 {
  Console.WriteLine("test id: " + reader.GetDecimal(0));
 }

 Console.Write("{0}Press any key to continue...", Environment.NewLine);
 Console.ReadKey();
}

Searching made easy

Very often i have to write queries that return all the rows where one or more columns match a specific value. If i add for every column the condition ‘P_COLUMN IS NULL OR COLUMN = P_COLUMN’ to the WHERE clause i only have to write one query. Here’s an example of such a query:

PROCEDURE FIND_TESTS
(
 P_ID              IN        TEST.ID%Type,
 P_TITLE         IN        TEST.TITLE%Type,
 P_TYPE_CODE IN        TEST.TYPE_CODE%Type,
 P_CURSOR      OUT     CURSOR REF
)

AS

BEGIN

OPEN
              P_CURSOR FOR
SELECT
              ID,
              TITLE,
              TYPE_CODE
FROM
              TEST
WHERE
              (P_ID IS NULL OR ID = P_ID)
              AND (P_TITLE IS NULL OR TITLE LIKE P_TITLE)
              AND (P_TYPE_CODE IS NULL OR TYPE_CODE LIKE P_TYPE_CODE)
ORDER BY
              ID ASC;
END;

A couple of examples how you can use this query:

using (OracleConnection conn = new OracleConnection("User Id=u;password=p;Data Source=ORCL"))
{
 conn.Open();

 OracleCommand command = conn.CreateCommand();
 command.CommandText = "TIMVW.MYPACKAGE.FIND_TESTS";
 command.CommandType = CommandType.StoredProcedure;

 // select all tests of type_code "book" that have a title that starts with "Myst"
 command.Parameters.Add("P_ID", OracleDbType.Int32, 10, DBNull.Value, ParameterDirection.Input);
 command.Parameters.Add("P_TITLE", OracleDbType.Varchar2, 20, "Myst%", ParameterDirection.Input);
 command.Parameters.Add("P_TYPE_CODE", OracleDbType.Varchar2, 20, "book", ParameterDirection.Input);
 command.Parameters.Add("P_CURSOR", OracleDbType.RefCursor, ParameterDirection.Output);

 // select the test with id 1
 //command.Parameters.Add("P_ID", OracleDbType.Int32, 10, 1, ParameterDirection.Input);
 //command.Parameters.Add("P_TITLE", OracleDbType.Varchar2, 20, DBNull.Value, ParameterDirection.Input);
 //command.Parameters.Add("P_TYPE_CODE", OracleDbType.Varchar2, 20, DBNull.Value, ParameterDirection.Input);
 //command.Parameters.Add("P_CURSOR", OracleDbType.RefCursor, ParameterDirection.Output);

 OracleDataReader reader = command.ExecuteReader();
 while (reader.Read())
 {
  Console.WriteLine("test id: " + reader.GetDecimal(0));
 }

 Console.Write("{0}Press any key to continue...", Environment.NewLine);
 Console.ReadKey();
}

tweaking Oracle SQL Developer

A couple of days ago i discovered Oracle SQL Developer, a new and free graphical tool for database development. At first i was impressed by all it’s features but when i tried to modify a couple of existing stored procedures the application freezed. EricH directed me to the FAQ: Can I suppress Code Insight (and why would I want to)?. Now that i have added the ‘AddVMOption -J-Dsdev.insight=false’ to my sqldeveloper.conf the application runs smooth

screenshot of oracle sqldeveloper

Experimenting with Oracle and PL/SQL

As i already wrote, last couple of days i’ve been experimenting with PL/SQL. At work we use Toad for Oracle but since TOADSoft only offers a limited freeware version i decided to write my code with GVim and use SQL*Plus at home. Here are a couple of lines i added to my login.sql file:

DEFINE _EDITOR='gvim -c "set filetype=sql"'
SET SERVEROUTPUT ON
SET LINESIZE 120
SET AUTOCOMMIT OFF
ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD';

In a stored procedure i created and filled an instance of NUMBER_TABLE (CREATE TYPE NUMBER_TABLE AS TABLE OF NUMBER) and my stored procedure tried to select all the rows in that table (SELECT * FROM V_NUMBER_TABLE). Apparently the engine didn’t know this type @runtime despite the fact that i declared it in my stored procedure (V_NUMBER TABLE NUMBER_TABLE := NUMBER_TABLE();) and the engine compiled the package without errors. I got round that problem as following:

SELECT * FROM (CAST(V_NUMBER_TABLE AS NUMBER_TABLE));

Select best 3 laptimes for each player

Imagine that you have a schema where you store all the times a player needed to complete a parcours. A possible schema could be (postgresql):

CREATE TABLE laptimes (
 lap_id SERIAL NOT NULL,
 player_id INT NOT NULL,
 laptime INT NOT NULL,
 PRIMARY KEY (lap_id)
);

INSERT INTO laptimes (player_id, laptime) VALUES (1, 250);
INSERT INTO laptimes (player_id, laptime) VALUES (1, 450);
INSERT INTO laptimes (player_id, laptime) VALUES (1, 350);
INSERT INTO laptimes (player_id, laptime) VALUES (1, 300);
INSERT INTO laptimes (player_id, laptime) VALUES (1, 327);

INSERT INTO laptimes (player_id, laptime) VALUES (2, 327);
INSERT INTO laptimes (player_id, laptime) VALUES (2, 249);
INSERT INTO laptimes (player_id, laptime) VALUES (2, 123);
INSERT INTO laptimes (player_id, laptime) VALUES (2, 489);
INSERT INTO laptimes (player_id, laptime) VALUES (2, 158);

INSERT INTO laptimes (player_id, laptime) VALUES (3, 158);
INSERT INTO laptimes (player_id, laptime) VALUES (3, 120);
INSERT INTO laptimes (player_id, laptime) VALUES (3, 190);

INSERT INTO laptimes (player_id, laptime) VALUES (4, 600);

Now imagine that you want to display the best 3 results for each player. Here’s how:

SELECT *
FROM laptimes AS l1
WHERE lap_id IN (
 SELECT lap_id
 FROM laptimes AS l2
 WHERE l1.player_id = l2.player_id
 ORDER BY laptime ASC
 LIMIT 3
)
ORDER BY player_id ASC, laptime ASC;

Multiple joins explained

I’ll try to explain how a join on more than one table works. I’ve noticed people get confused by it. Assume we have the following tables:

  • newsitems(news_id,post_id)
  • postitems(post_id,user_id,content)
  • users(user_id,name,password)

We want to display for each newsitem the content and the author.

Our base table would be the newsitems, and then we join using the item_id. Thus the query would be:

SELECT *
FROM newsitems
INNER JOIN ON postitems USING (post_id)

This returns a “virtual table” that has looks like this result(news_id,post_id,user_id,content).

Now we still need to get the username, so we use our result table and perform a join on the users table. Thus the query would be:

SELECT *
FROM result
INNER JOIN users USING (user_id)

If we combine our first two queries, we end up with this:

SELECT *
FROM newsitems
INNER JOIN postitems USING (post_id)
INNER JOIN users USING (user_id)

Conclusion: Look at A*B*C as (A*B)*C to easily understand multiple joins

Select the first 50 words of an article

I’m cleaning up my code snippets and i found the following little trick in one of them that i’ve removed. Assuming that different words are separated by spaces we can use SUBSTRING_INDEX as following:

SELECT SUBSTRING_INDEX(body,' ',50) AS dn FROM mytable

Custom ordering with MySQL

As a follow up to Custom Ordering I discovered the nice Field function in MySQL. after it was mentionned on my favorite PHP Forum by Weirdan. It allows one to order a column on a custom order relation.

SELECT *
FROM foo
ORDER BY FIELD(column, 'Z', 'B', 'C')

Custom ordering

Assume we have a fruits table. And we want to select all the fruits that have a red or orange or blue color. But we want the resultset to have first all the fruits with color=red, then the fruits with color=blue and then the fruits with color=orange. It’s obvious we can’t use an alphabetical order. Thus we have to introduce our own order relation.

select *
from fruits
where color = 'red' or color = 'blue' or color = 'orange'
order by case
when color = 'red' then 0
when color = 'blue' then 1
when color = 'orange' then 2
end