Llayland’s Food, Oracle, and Haskell

May 31, 2011

An example of circular programming

Filed under: Haskell — llayland @ 6:36 am

Something clicked the other day and I was able to get past the mental block I had with circular programming.

I was trying to optimize counting the transpositions between a sting and a permutation of that string. Earlier code left me at a nice starting point,where the pair of strings was represented as [[(Int,Int)]] with the first Int being the position of a character in the first string and the second being the position of the same character int the permutation. For example:

abcd_abcd = [ [(1,1)]
            , [(2,2)]
            , [(3,3)]
            , [(4,4)]
            ] :: [[(Int,Int)]]

abcd_acbd = [ [(1,1)]
            , [(2,3)]
            , [(3,2)]
            , [(4,4)]
            ] :: [[(Int,Int)]]

dcbaabcd_dbcaacbd = [ [(4,4),(5,5)]
                    , [(3,2),(6,7)]
                    , [(2,3),(7,6)]
                    , [(1,1),(8,8)]
                    ] :: [[(Int,Int)]]

One way to solve this is to concatenate the lists, sort by the first elements of the pairs, and then fold with a function that counts the changes in direction of the second elements of the pairs.

foldl' f (GT,0,0) . map snd . sort . concat
   where f (dir, cnt,n) n' = (dir',  if dir = dir' then cnt else cnt +1, n')
         dir' = compare n' n

This works, but I wanted a one pass solution. I also didn't need a full sort, just the ability to access the next and previous match. I got stuck for quite a while before deciding to start with a two pass solution. First I have a hashmap defined recursively in terms of itself. I did not realize it at the time, but this is actually very similar to the fold above. The base case of the first element corresponds loosely to the initializer and the recursive case corresponds to carrying of "dir" and "n" in the accumulating function

h :: [[(Int,Int)]] -> HM.HashMap Int (Ordering, Ordering, Int)
h ms =  foldl (foldr f) HM.empty ms
   where h' = h ms
         f (key, val) = case key of
                          1 -> HM.insert 1 (GT, GT, val)
                          n -> let (old_dir, old_val) = find (n-1) in HM.insert n (old_dir, compare val old_val, val)
         find n = let (Just (dir, _, val)) = HM.lookup n h' in (dir, val)

Now I just need a second pass for the counting:


trans :: HM.HashMap Int (Ordering, Ordering, Int) -> Int
trans = HM.fold f 0
  where f (old_dir, dir, _) c = if dir == old_dir then c else c+1


This seems really easy in retrospect, but writing it involved a lot of false starts and felt like going down the rabbit hole. Once I got done celebrating, moving on to a one pass solution was easy. I already knew I could have a structure that would have the preceding element available by the time I needed it. I just need one that also has the next element available by the time I need it.


h2 :: [[(Int,Int)]] -> HM.HashMap Int (Ordering, Ordering, Int, Int)
h2 ms = foldl (foldr f ) HM.empty ms
   where h2' = h2 ms
         f (key, val) = case key of
                          1 ->  HM.insert key (GT, GT, val, 0)
                          n -> let (old_dir, old_val) = prev n
                                   new_c = next n
                                   dir = compare val old_val
                                   in HM.insert n (old_dir, dir, val, if old_dir == dir then new_c else new_c+1)
         prev n = let (Just (dir, _, val, _)) = HM.lookup (n-1) h2' in (dir, val)
         next n = case HM.lookup (n+1) h2' of
                       Nothing -> 0
                       Just (_, _, _, c) -> c 

trans2 ms = let (Just (_, _, _, c)) = HM.lookup 2 (h2 ms) in c

The correspondence to my original fold has broken down. The base case is still the same, but now it looks like my recursive case is recursing in both directions at once. So why do I need to go both ways? If I accumulated upwards, I would not know where to look for the answer. Accumulating downwards lets me always look at element 2. Similarly, I can't calculate the direction changes downwards because I wouldn't know where to start.

My takeaways from this exercise are:

  1. Just pretend like you already have the structure you need
  2. don't get caught up in how magical it seems.
  3. don't overthink it. Assume it will work and do it

December 10, 2010

crazy rewrites

Filed under: Oracle — llayland @ 4:48 am

Jonathan Lewis’s posts on index joins inspired me to run a few test cases of my own. I thought it was pretty neat that the optimizer could split a single table query into a 6 way self join. It really blew my mind when I found it could do the reverse. I also found it quite cute that it does the opposite of what I tell it too. That’s just how stuff works for me.

This was run on a fresh install of the developer vm in VirtualBox:

Edit – I really need to learn to preview. Here is the corrected script and output
set echo on
set linesize 200

drop table t1;

create table t1 (n1,n2,n3,n4,n5,n6,filler)
nocache
as
select trunc(rownum/100),
trunc(rownum/100),
trunc(rownum/100),
trunc(rownum/100),
trunc(rownum/100),
trunc(rownum/100),
lpad(rownum, 1000, '0')
from dual connect by level <= 100000;

create bitmap index t1_n1 on t1(n1);
create bitmap index t1_n2 on t1(n2);
create bitmap index t1_n3 on t1(n3);
create bitmap index t1_n4 on t1(n4);
create bitmap index t1_n5 on t1(n5);
create bitmap index t1_n6 on t1(n6);

call dbms_stats.gather_table_stats('SCOTT','T1');

set timing on;

set autotrace traceonly;

set arraysize 5000;

select n1,n2,n3,n4,n5,n6 from t1;

select a.n1, b.n2, c.n3, d.n4, e.n5, f.n6
from t1 a, t1 b, t1 c, t1 d, t1 e, t1 f
where a.rowid = all(b.rowid, c.rowid, d.rowid, e.rowid, f.rowid);



SQL> select n1,n2,n3,n4,n5,n6 from t1;

100000 rows selected.

Elapsed: 00:00:01.65

Execution Plan
———————————————————-
Plan hash value: 362797529

——————————————————————————————————
| Id  | Operation                         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
——————————————————————————————————
|   0 | SELECT STATEMENT                  |                  |   100K|  2343K|  2018   (1)| 00:00:25 |
|   1 |  VIEW                             | index$_join$_001 |   100K|  2343K|  2018   (1)| 00:00:25 |
|*  2 |   HASH JOIN                       |                  |       |       |            |          |
|*  3 |    HASH JOIN                      |                  |       |       |            |          |
|*  4 |     HASH JOIN                     |                  |       |       |            |          |
|*  5 |      HASH JOIN                    |                  |       |       |            |          |
|*  6 |       HASH JOIN                   |                  |       |       |            |          |
|   7 |        BITMAP CONVERSION TO ROWIDS|                  |   100K|  2343K|    11   (0)| 00:00:01 |
|   8 |         BITMAP INDEX FULL SCAN    | T1_N1            |       |       |            |          |
|   9 |        BITMAP CONVERSION TO ROWIDS|                  |   100K|  2343K|    11   (0)| 00:00:01 |
|  10 |         BITMAP INDEX FULL SCAN    | T1_N2            |       |       |            |          |
|  11 |       BITMAP CONVERSION TO ROWIDS |                  |   100K|  2343K|    11   (0)| 00:00:01 |
|  12 |        BITMAP INDEX FULL SCAN     | T1_N3            |       |       |            |          |
|  13 |      BITMAP CONVERSION TO ROWIDS  |                  |   100K|  2343K|    11   (0)| 00:00:01 |
|  14 |       BITMAP INDEX FULL SCAN      | T1_N4            |       |       |            |          |
|  15 |     BITMAP CONVERSION TO ROWIDS   |                  |   100K|  2343K|    11   (0)| 00:00:01 |
|  16 |      BITMAP INDEX FULL SCAN       | T1_N5            |       |       |            |          |
|  17 |    BITMAP CONVERSION TO ROWIDS    |                  |   100K|  2343K|    11   (0)| 00:00:01 |
|  18 |     BITMAP INDEX FULL SCAN        | T1_N6            |       |       |            |          |
——————————————————————————————————

Predicate Information (identified by operation id):
—————————————————

2 – access(ROWID=ROWID)
3 – access(ROWID=ROWID)
4 – access(ROWID=ROWID)
5 – access(ROWID=ROWID)
6 – access(ROWID=ROWID)

Statistics
———————————————————-
1  recursive calls
0  db block gets
86  consistent gets
0  physical reads
0  redo size
526501  bytes sent via SQL*Net to client
628  bytes received via SQL*Net from client
21  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
100000  rows processed

SQL>
SQL> select a.n1, b.n2, c.n3, d.n4, e.n5, f.n6
2  from t1 a, t1 b, t1 c, t1 d, t1 e, t1 f
3  where a.rowid = all(b.rowid, c.rowid, d.rowid, e.rowid, f.rowid);

100000 rows selected.

Elapsed: 00:00:07.78

Execution Plan
———————————————————-
Plan hash value: 3617692013

————————————————————————–
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————–
|   0 | SELECT STATEMENT  |      |   100K|  3515K|  3919   (1)| 00:00:48 |
|   1 |  TABLE ACCESS FULL| T1   |   100K|  3515K|  3919   (1)| 00:00:48 |
————————————————————————–

Statistics
———————————————————-
1  recursive calls
0  db block gets
14307  consistent gets
14286  physical reads
0  redo size
103737103  bytes sent via SQL*Net to client
628  bytes received via SQL*Net from client
21  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
100000  rows processed

February 12, 2009

Thanks Computer Junkyard

Filed under: Oracle — llayland @ 5:46 am

Just a quick thank you to Computer Junkyard I had a great experience there today.

If you are in or around Tampa, please check them out.

January 21, 2009

Understanding Monads I

Filed under: Haskell — llayland @ 5:39 am

It turns out that understanding monads is even harder than finding the time to write about monads. I don’t want to waste this opportunity, so I’m just going to make it up as I go along.

For me to really have a motivation to understand something, I need to see a need for it. So just what good are monads anyways? The first thing I thought of was you can view monads as models to evaluate functions, that are  defined with the monadic operators, under. Admittedly this is stupidly simple example but I’m at the stupidly simple stage of learning:

> let failOn0 n = n >>= \x -> if x == 0 then fail “zero” else return x

This seems tailor made to evaluate under the Maybe monad which models possible failure:

> failOn0 (Just 1)
Just 1
> failOn0 (Just 0)
Nothing

You could also evaluate under the List monad which models non-deterministic choice. Basically, you get back all possible results:

> failOn0 [1,2,3]
[1,2,3]
> failOn0 [0,2,3]
[2,3]
> failOn0 [0,0,0]
[]

Finally you could evaluate it under the Identity monad which models variable substitution.  This one is a little different because it can really fail:

> failOn0 (Id 1)
Id 1

>failOn0 (Id 0)

*** Exception: zero

So I can write one function and evaluate it several different ways.  That’s enough of a reason for me to dig in deeper.  Now I’d suggest reading Monads as containers for good explanation of writing simple monads. Then I would write Identity Monad and the Tree Monad on paper. I would follow that up by writing the reductions for several examples of fmap, join, and >>= on paper.  And yes, I really do mean on paper.

I just finish the paper exercizes today and I think I have a good handle it. I think I can almost tackle part II where I will get into more of the usefulness of monads, conditioning myself to what >>= really means, and the state monad.

September 13, 2008

Bitter Gourd Curry (Indian/Thai Fusion)

Filed under: Food — llayland @ 9:44 pm

Here’s another recipe I dug up that was very popular at work.  This was my second attempt at making bitter gourd. The stuff is difficult to work with since it is so incredibly bitter.  Honestly, this was still a bit too bitter for me, but it was a big hit for the people that were used to other bitter gourd dishes.

Here is my best shot at a recipe for the curry. I almost never measure anything, so you have to use your judgment.

1) Mix in a large zip loc bag to marinate at least overnight:

3 chinese bitter gourd, diced. each about the size of a large cucumber.

1 handful raw bird chilies, rough chopped

1/2 to 1 bulb of garlic sliced thick

canola oil to cover and toasted sesame oil for flavor

salt

cracked black pepper

2) Ingredients

strained oil from marinade

1 handful raw bird chilies, sliced lengthwise

dried red pepper flakes (to taste for extra heat, but do use some as they have a different flavor than the bird chiles)

4-5 kaffir lime leaf (fresh, dried is pointless)

3-4 bay leaf (dried)

1/2 can red curry paste (yes, I cheated a little bit since I was in a hurry)

black mustard seed – No clue how much, probably enough to cover the bottom of the pan about 30-40%

powdered cumin – a lot of this, probably about a heaping palmful.  I kept having to add more to get the flavor right

cinamon – not to much, it is just there to accent the cumin

8-12 baby bok choy washed, whites diced and greens left whole – I had used the whites for another dish, but they would be fine in the curry if diced finely

1/2 to 1 bulb garlic sliced thick

bitter gourd, chile, and garlic from marinade

natural sugar (the brown crystals) – about 3/8 cup, but I might reduce this to 1/4 cup since it was a bit on the sweet side

cream of coconut

rice wine vinegar

asefoetida – enought to lightly cover the pan

3) Method

drain the oil from the marinade into a large skillet.  Avoid getting much of the juice from the bitter melon in the skillet to avoid splattering, but do not discard the juice.

add chiles, red pepper flaeks, mustard, cumin, cinamon, bay leaf,  kaffir lime leaf, and curry paste.

Turn on the burner to a low to medium low heat. You want to let the oil come up to temperature slowly to allow the flavors to infuse and to minimize splattering from any liquids from in the oil.

When the oil get to temperature add the garlic, bitter gourd with juice, whites from the bok choy and salt to taste -then turn the burner up to medium high.

Cook until the gourd is tender (probably 7 to 10 minutes) stirring occasionally

taste and reseason if needed – I had to add more cumin, cinamon, and salt here

Pour off excess oil into a jar to save (do leave some in the skillet)

stir the greens into the mixture until they are wilted (less than a minute)

add sugar and stir until thoroughly dissolved

give it a good sprinkling of the rice wine vinegar

sprinkle asefoetida powder over the entire skillet.  I used a good amount because I felt the dish was lacking pungency.

stir, taste and reseason or add more vinegar if needed

mix in enough cream of coconut to thicken the sauce. the cream of coconut also serves to bring all the flavors together and tone down some of the bitterness.  start with a small amount and add more if needed.

I think that is pretty much how I made it.  Next time I’d like to add some fresh curry leaves.  The excess oil is also very good, especially for roasting potatoes.

Okra Coconut Curry (sort of)

Filed under: Food — llayland @ 9:32 pm
  • 5 cups Okra sliced into rounds
  • handful of mustard seed
  • hing  (Asafoetida) powder
  • 2 tbsp oil
  • 1/2 grated coconut (not the sweetened kind)
  • handful of green chiles
  • 6 garlic cloves, sliced
  • 1 tsp tamarind paste
  • 1 cup water
  • 1 8oz can of coconut cream
  • a two fingered ping of dried methi leaves

What’s the most important question you can ask yourself while cooking? If you are anything like me, it is “How do I fix this?” I can’t seem to get through a dish without making at least one mistake.  Thankfully I’m good at the fix it game and I usually manage to turn the problem into a plus. Here is one example:

I had a nice bag of okra, so I set out to make the Okra Curry I found on Aayi’s Recipes. My first mistake was forgetting to buy curry leaves. I’d been to three stores already and didn’t feel like going out again so I decided to go ahead without them. The second was thinking that I had a large bag of shredded coconut in the freezer. It turned out to be about a half a cup. Thankfully I had a can of coconut cream in the pantry that did the trick. The third was forgetting to add the tamarind paste. I had to dissolve it is a quarter cup of water that I boiled in the microwave to make it easy to blend into the finished dish. Despite all this it turned out really well.

So this is how it should have gone:

  1. grind the chilies and coconut into a paste
  2. fry the mustard seed in the oil until it begins to pop
  3. add the okra and garlic, sprinkle liberally with hing, and saute until about halfway done
  4. add the coconut chili mixture, saute for a minute or two
  5. add the water and tamarind paste, stir until the paste is dissolved
  6. add the coconut cream and cook until okra is almost done
  7. add the methi leaves and simmer for a minute or two

August 14, 2008

A quick gripe

Filed under: Oracle — llayland @ 5:14 am

I really did not want the Oracle section of this blog to be my whining about all the nasty things Oracle does to me. It is just hard to find the time and motivation to write up anything positive when I run into three new (to me at least) bugs in three days. So, I’m going to gripe:

I hit the bug in metalink note 469587.1 where “Error PLS-00167 : keyword BULK is used in a wrong context” is raised with an incorrect interval literal. Of course, I had to come up with a unique twist of my own. Apparently this:

INTERVAL '.1' SECOND

is not valid and should be:

INTERVAL '0.1' SECOND

The other thing I ran into is that binds of objects tend to cause bugs. This applies to both explicit binds in dynamic sql and the implicit binds of pl/sql variables in sql statements. I’ll write up a good post sometime soon, but for now just note that the type information seems to get lost and that using the treat function around the bound variable to get the type information back seems to fix things.

August 8, 2008

Indian style vegetarian “tamales”

Filed under: Food — llayland @ 4:59 am

I haven’t made anything terribly exciting lately, so I thought I go back a few months to a dish I made that was a big hit at the office. I had been reading a couple of food blogs and two items stuck in my mind:

  • Cholkya vayli bhakri – A roti made on banana leaf
  • vaygana bajji – Smoked eggplant

The eggplants paticularly caught my eye because I had promissed some of my vegetarian coworkers that I would try to find something I could make them on my smoker. The roti appealled to me as it finally gave me a use for my apparently sterile banana trees. Unfortunately, I was extremely busy at the time and didn’t have any time to spare in the kitchen. Finally, I decided that I needed to give it a rest a do something I enjoy.  In spite of the fact that I was extremely tired, or maybe because of it, I came up with a great idea, “Why not combine the two recipes?” This saved my the effort of frying all the rotis while allowing much more flavor from the banana leaves and smoke into the dish.

I’ve rambled on enough; here is the recipe:

2 cups (less 4 tbps) all purpose flour (Maida)
4 tbsp corn starch
1 13.5oz can of cream of coconut
——————–
mix into a loose batter

good amount of black mustard seed
good amount of dried red pepper
1 black cardomon pod
1 clove garlic
2 tsp tomato curry
salt
———————————
mash into a paste

2 large eggplants
——————————
smoke low and slow (225 degrees) until softened, then blacken over direct flame
remove skin and some seeds
smoke cleaned eggplant to remove excess moisture
mash lightly with above paste, should be very chunky

cut squares of fresh banana leaf 4-5 inches
scoop batter onto each leaf. just scoop it in the middle and spread lightly- should be about 2/3 covered (round). place a dollop of eggplant mixture in the batter round, about 2/3 of the way across. pick up the near edge of the leaf and fold it over so near end of the round covers the eggplant mixture and overshoots the other other end a little
press down lightly and pull towards you to create a tube of filling.
fold that tube forward over the two edges (should be roughly together)
smoke low and slow for a couple of hours until the batter sets
increase temperature to 400 degrees for about 20 minutes to finish them off
A note on smoking:

If you don’t have a smoker, you can use a  grill  just make sure to keep the coals to one side and the food to the other so you get indirect heat. I’d recommend using larger chunks of wood, but chips will work as well. I believe I used  hickory, pecan, and cherry which gives a nice blended flavor.  That would have been overkill just for the tamales, so I had the other half of the smoker filled with mojo marinated chicken quarters.  Plain old oak and most other woods would have been fine as well.  I’d recommend against mesquite as it could be overpowering
2 cups (less 4 tbps) all purpose flour (Maida)
4 tbsp corn starch
1 13.5oz can of cream of coconut
——————–
mix into a loose batter

good amount of black mustard seed
good amount of dried red pepper
1 black cardomon pod
1 clove garlic
2 tsp tomato curry
salt
———————————
mash into a paste

2 large eggplants
——————————
smoke low and slow (225 degrees) until softened, then blacken over direct flame
remove skin and some seeds
smoke cleaned eggplant to remove excess moisture
mash lightly with above paste, should be very chunky

cut squares of fresh banana leaf 4-5 inches
scoop batter onto each leaf. just scoop it in the middle and spread lightly- should be about 2/3 covered (round). place a dollop of eggplant mixture in the batter round, about 2/3 of the way across. pick up the near edge of the leaf and fold it over so near end of the round covers the eggplant mixture and overshoots the other other end a little
press down lightly and pull towards you to create a tube of filling.
fold that tube forward over the two edges (should be roughly together)
smoke low and slow for a couple of hours until the batter sets
increase temperature to 400 degrees for about 20 minutes to finish them off

August 7, 2008

Default precision fractional for timestamp datatype is 6. Huh?

Filed under: Oracle — llayland @ 6:09 am

I’ve always read that the default precision for timestamps was 6.

From “Oracle® Database SQL Language Reference 11g Release 1 (11.1) Part Number B28286-03

TIMESTAMP Datatype

The TIMESTAMP datatype is an extension of the DATE datatype. It stores the year, month, and day of the DATE datatype, plus hour, minute, and second values. This datatype is useful for storing precise time values. Specify the TIMESTAMP datatype as follows:

TIMESTAMP [(fractional_seconds_precision)]

where fractional_seconds_precision optionally specifies the number of digits Oracle stores in the fractional part of the SECOND datetime field. When you create a column of this datatype, the value can be a number in the range 0 to 9. The default is 6.

I found a fun little case where the default precision is 0. It was the end of a long day when I did something similar to this:

SQL> create or replace type timestamp_tab as table of timestamp;
2 /

Type created.

SQL>
SQL> create or replace function get_ts return timestamp_tab
2 is
3 retval timestamp_tab;
4 begin
5 select
6 to_timestamp('20080102','YYYYMMDD')
7 + interval '0.1' second * (rownum - 2)
8 bulk collect into retval
9 from dual
10 connect by level <= 3;
11
12 return retval;
13 end get_ts;
14 /

Function created.

SQL>
SQL> begin
2 for rec in (select * from table(get_ts)) loop
3 dbms_output.put_line(rec.column_value);
4 dbms_output.put_line(to_char(rec.column_value, 'FF'));
5 end loop;
6 end ;
7 /
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000

PL/SQL procedure successfully completed.

Strange, I’ve lost my fractional seconds. Maybe I need to explicitly set the precision in my type:


SQL> create or replace type timestamp_tab as table of timestamp(4);
2 /Type created.

SQL>
SQL> begin
2 for rec in (select * from table(get_ts)) loop
3 dbms_output.put_line(rec.column_value);
4 dbms_output.put_line(to_char(rec.column_value, 'FF'));
5 end loop;
6 end ;
7 /
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000

PL/SQL procedure successfully completed.

No dice. Let’s go back to basics to make sure I’m not just braindead at this point:


SQL> begin
2 dbms_output.put_line(
3 to_timestamp('20080102','YYYYMMDD') + interval '0.1' second * -1
4 );
5 end;
6 /
2008-01-01 23:59:59.900000000PL/SQL procedure successfully completed.

SQL>
SQL> begin
2 for rec in (
3 select to_timestamp('20080102','YYYYMMDD')
4 + interval '0.1' second * -1 ts
5 from dual
6 ) loop
7 dbms_output.put_line(rec.ts);
8 end loop;
9 end;
10 /
2008-01-01 23:59:59.900000000

PL/SQL procedure successfully completed.

So, I do normally get six digits by default.  Maybe it is a bulk collect issue?


SQL> create or replace function get_ts2 return timestamp_tab
2 is
3 retval timestamp_tab := timestamp_tab();
4 begin
5 retval.extend(3);
6
7 for rec in (
8 select rownum n,
9 to_timestamp('20080102','YYYYMMDD')
10 + interval '0.1' second * (rownum - 2) ts
11 from dual
12 connect by level <= 3
13 ) loop
14 retval(rec.n) := rec.ts;
15 end loop;
16 return retval;
17 end get_ts2;
18 /Function created.

SQL>
SQL> begin
2 for rec in (select * from table(get_ts2)) loop
3 dbms_output.put_line(rec.column_value);
4 dbms_output.put_line(to_char(rec.column_value, 'FF'));
5 end loop;
6 end ;
7 /
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000

PL/SQL procedure successfully completed.

Nope.  Maybe an explicit cursor?


SQL>
SQL> declare
2 cursor ts_cur is
3 select * from table(get_ts);
4 begin
5 for rec in ts_cur loop
6 dbms_output.put_line(rec.column_value);
7 dbms_output.put_line(to_char(rec.column_value, 'FF'));
8 end loop;
9 end ;
10 /
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000PL/SQL procedure successfully completed.

Nope.  Hmm, I vaguely remember that you can declare the return type of a cursor but I’ve never had to do it before.


SQL>
SQL> declare
2 type ts_rec is record(ts timestamp);
3 cursor ts_cur return ts_rec is
4 select column_value from table(get_ts);
5 begin
6 for rec in ts_cur loop
7 dbms_output.put_line(rec.ts);
8 dbms_output.put_line(to_char(rec.ts, 'FF'));
9 end loop;
10 end ;
11 /
2008-01-01 23:59:59.900000
900000000
2008-01-02 00:00:00.000000
000000000
2008-01-02 00:00:00.100000
100000000PL/SQL procedure successfully completed.

Aha, so the default precision for a timestamp when returned from a table function into a cursor is 0. Just for grins:


SQL>
SQL> declare
2 type ts_rec is record(ts timestamp(0));
3 cursor ts_cur return ts_rec is
4 select column_value from table(get_ts);
5 begin
6 for rec in ts_cur loop
7 dbms_output.put_line(rec.ts);
8 dbms_output.put_line(to_char(rec.ts, 'FF'));
9 end loop;
10 end ;
11 /
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000
2008-01-02 00:00:00.
000000000PL/SQL procedure successfully completed.

So, problem solved. Hopefully this will save someone out there some time and head scratching.

Theme: Silver is the New Black. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.