Using Oracle SQL to Convert Numbers to Words and Back Again

Published in Complex SQL, SQL by gpike Friday May 11, 2007

As a infrequent poster (but constant lurker) on the Oracle forums, I often see questions about converting numbers to their English language equivalent. Recently, the PL/SQL and SQL forum was asked the opposite question: How does one convert words (i.e. “ONE HUNDRED TWENTY THREE”) to a number (i.e. 123). After the talented members of the forum came up with several solutions, I decided the whole topic was deserving of a SingleQuery posting.

Converting Numbers to Words

First the easy part. This is an old-school Oracle trick that’s been around forever:

SELECT TO_CHAR(TO_DATE(123,'J'),'JSP') to_words
FROM   dual;

TO_WORDS
------------------------
ONE HUNDRED TWENTY-THREE

Pretty straight forward. The TO_DATE(123,’J') creates the 123rd Julian Day, which is defined as the 123rd day after Monday, January 1, 4713 BC. Click here to learn more about the Julian Day if you feel obligated. The TO_CHAR with the ‘JSP’ format mask converts the Julian Day into the words “ONE HUNDRED TWENTY-THREE”.

Unfortunately, there are some limitations with this little trick. Oracle cannot compute the Julian Day past 31-DEC-9999 so that means we can only convert numbers to English up to 5,373,484 (the number of days between the date the pyramids were constructed and the date that the Chicago Cubs will win the World Series again). Also, this method only works in English; I have seen no solution to do this in other languages.

Converting Words to Numbers

Now the hard part. As previously mentioned, one of the Oracle forums recently discussed how one can convert from the English language to numbers. There were lots of interesting alternatives (click here to see the forum thread) with many focused upon procedural solutions. But since my specialty is a Single Query solution, I wanted to take a stab at it and came up with the following that works on 9i and 10g:

SELECT LEVEL wordasint
FROM   dual
WHERE  TO_CHAR(TO_DATE(LEVEL,'J'), 'JSP') = 'ONE HUNDRED TWENTY-THREE'
CONNECT BY TO_CHAR(TO_DATE(LEVEL-1,'J'),'JSP') != 'ONE HUNDRED TWENTY-THREE'
AND    LEVEL < 10001;

 WORDASINT
----------
       123

This is a rather strange CONNECT BY construct that you may not have seen before and a good explanation of what this does can be found here. It works, although with the similar maximum limitation of 5,373,484 (”FIVE MILLION THREE HUNDRED SEVENTY-THREE THOUSAND FOUR HUNDRED EIGHTY-FOUR”) because of the Julian Day. Also, the larger the number requested, the slower this query gets since it performs more and more CONNECT BYs as the requested number gets bigger. The last clause “AND LEVEL < 10001" is designed to stop typos (i.e. "ONES HUNDERD") from causing this query to execute until it exceeds the maximum. Simply provide the largest number+1 you will ever expect to receive and this query will return no rows if it is not found (but will not fail)!

Here is another slick solution for Oracle 10g (provided by Sundar M) that uses the MODEL clause, but it unfortunately stops functioning at 86399:

SELECT sp,
       n
FROM   (
        SELECT 'EIGHTY-SIX THOUSAND THREE HUNDRED NINETY-NINE' sp
        FROM dual
       )
   MODEL
   DIMENSION BY (1 dim)
   MEASURES (0 n, sp)
   RULES ITERATE (86400) UNTIL (TO_CHAR(DATE '0001-01-01' +
         (ITERATION_NUMBER/86400),'SSSSSSP')=sp[1])
         ( n[1]=ITERATION_NUMBER );

SP                                                     N
--------------------------------------------- ----------
EIGHTY-SIX THOUSAND THREE HUNDRED NINETY-NINE      86399

At 86400:

SP                                        N
-------------------------------- ----------
EIGHTY-SIX THOUSAND FOUR HUNDRED      86399  <--  Whoops!

Words to Numbers for Oracle 8/8i

The prior queries include functionality not available for Oracle 8/8i databases, but this one should work. I will admit, I don’t have an 8i database laying around, so this is untested:


SELECT theword,
       thenum
FROM
       (
        SELECT 'ZERO' theword,
               0      thenum
               FROM   dual
        UNION  ALL
        SELECT /*+ parallel (rbt 2) */
               TO_CHAR(TO_DATE(ROWNUM,'J'), 'JSP')  theword,
               ROWNUM
        FROM   really_big_table rbt
        WHERE ROWNUM <100001
       )
WHERE  theword  = 'TEN THOUSAND FIVE HUNDRED THIRTY-FOUR';

The limitations for this query are typical; you can’t generate numbers larger than 5,373,484 and you need a really_big_table to use as the seed table for generating rows (as a replacement for the CONNECT BY). Also, the bigger the maximum number (in this case 100,000), the longer this query will take to execute. However, this query can be modified for speed if specific numeric ranges are known in advance.

Dollars and Cents From Words

Oracle 8/8i (or 9i and 10g as well of course)i:


SELECT /*+ ordered all_rows */
       TO_CHAR(temp1.thenum+(temp2.thenum/100),'$9,999,999.99') amount
FROM
       (
        SELECT thenum,
               SUBSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',
                 INSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',' AND')+5,
                 INSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',' CENTS')
                 - INSTR
                 ('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',' AND')
                 -5) thecents
        FROM
               (
                SELECT 'ZERO' theword,
                0      thenum
                FROM   dual
                UNION ALL
                SELECT TO_CHAR(TO_DATE(ROWNUM,'J'), 'JSP')  theword,
                       ROWNUM thenum
                FROM   really_big_table
                WHERE  ROWNUM < 100000
               )
        WHERE  theword=SUBSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',1,
                   INSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',' AND')-1)
       ) temp1,
       (
        SELECT 'ZERO' theword,
               0 thenum
        FROM DUAL
        UNION ALL
        SELECT TO_CHAR(TO_DATE(ROWNUM,'J'), 'JSP'),
               ROWNUM
        FROM   really_big_table
        WHERE  ROWNUM <101
       ) temp2
WHERE  temp1.thecents = temp2.theword;

AMOUNT
--------------
       $123.12

Oracle 9i/10g (formatted to fit in this page):

SELECT /*+ ordered all_rows */
       TO_CHAR(temp1.thenum+(temp2.thenum/100),'$9,999,999.99') amount
FROM
       (
        SELECT LEVEL thenum,
               SUBSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',
                 INSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',' AND')+5,
                 INSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',' CENTS')
                 - INSTR
                 ('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS','
                 AND')-5) thecents
        FROM    dual
        WHERE  TO_CHAR(TO_DATE(LEVEL,'J'), 'JSP') =
                 SUBSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',1,
                 INSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',' AND')-1)
        CONNECT BY TO_CHAR(TO_DATE(LEVEL-1,'J'),'JSP') !=
                     SUBSTR('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS',1,
                     INSTR
                     ('ONE HUNDRED TWENTY-THREE AND TWELVE CENTS','
                     AND')-1)
        AND LEVEL < 100001
       ) temp1,
       (
        SELECT LEVEL thenum,
               TO_CHAR(TO_DATE(LEVEL,'J'), 'JSP') theword
        FROM   dual
        CONNECT BY 1 = 1
        AND LEVEL < 101
       ) temp2
WHERE  temp1.thecents = temp2.theword;

AMOUNT
--------------
       $123.12

Complete Solutions

Probably the most complete solution (but not a Single Query) was provided by a forum frequent poster that goes by the name of Volder. His combination of a query and a supporting function are only viable in Oracle 10g, but allow word descriptions of numbers up to 65 digits to be very quickly converted! For the sake of space, I am not reprinting his solution. Please refer to the forum thread for details.

If you have any other bright ideas on how to turn words into numbers, please don’t hesitate to post a comment. Happy coding!


Greg Pike

Greg Pike
PIOCON Technologies Website

3 Responses to “Using Oracle SQL to Convert Numbers to Words and Back Again”

  1. SnippetyJoe Says:

    For converting Numbers to Words you might want to check out my “SQL Snippets: Transformations - Numbers to Words tutorial” at http://www.sqlsnippets.com/en/topic-12355.html . The solutions there use FF instead of J, thus avoiding the limitations of Julian dates.


    Joe Fuda
    http://www.sqlsnippets.com/

  2. sajpda Says:

    This is great! thank you very much!

  3. Hofan20th Says:

    i am newbie here, i want to know how to convert number to word without using Julian day????
    i mean using conditional or something…. because i want to convert that number to Indonesian language….

    PS: sorry for my crappy english…^___^

Leave a Reply

27 queries. 0.442 seconds.
Powered by Wordpress
theme by cmoanz