Functio to get Amount in words.

  • My guess would be that it is the SUBSTRING()'s in your ON clauses.

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • Chris Morris (11/26/2008)


    Chris, the link in your signature is broken. You'r missing the right hand bracket on the first instance of the URL IFCode ShortCut.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • rbarryyoung (11/26/2008)


    My guess would be that it is the SUBSTRING()'s in your ON clauses.

    You're spot on. It works fine in 2k, I should have tested it in 2k5 :blush:. The problem is with the first join (hundreds):

    LEFT JOIN @tblNum h ON number < 15 AND SUBSTRING(@numberstr, number-2, 1) BETWEEN h.Num AND h.MaxNum

    In 2k5, the substring is evaluated even when number = 15, which returns the decimal point from the substring expression.

    So here's a 2k5-friendly version:

    [font="Courier New"]ALTER FUNCTION dbo.ToWords(@TheNumber DECIMAL (15,2), @CurrencyName VARCHAR(20), @JoinWord VARCHAR(20), @CentsName VARCHAR(20))

    RETURNS VARCHAR(200)

    -- USAGE: SELECT dbo.ToWords(1.01, 'dollar(s)', 'and', 'cent(s)')

    -- MAX. VALUE: 999999999999.99 (twelve nines before the decimal point)

    BEGIN

    DECLARE @numberstr CHAR (15), @NewString VARCHAR(200)

    SET @numberstr = STR(@TheNumber, 15, 2)

    SET @NewString = ''

    DECLARE @tblNum TABLE(Num BIGint, MaxNum BIGint, NumStr VARCHAR(20))

    INSERT INTO @tblNum

    SELECT 1, 1, 'one' UNION ALL            SELECT 2, 2, 'two' UNION ALL

    SELECT 3, 3, 'three' UNION ALL          SELECT 4, 4, 'four' UNION ALL

    SELECT 5, 5, 'five' UNION ALL           SELECT 6, 6, 'six' UNION ALL

    SELECT 7, 7, 'seven' UNION ALL          SELECT 8, 8, 'eight' UNION ALL

    SELECT 9, 9, 'nine' UNION ALL           SELECT 10, 10, 'ten' UNION ALL

    SELECT 11, 11, 'eleven' UNION ALL       SELECT 12, 12, 'twelve' UNION ALL

    SELECT 13, 13, 'thirteen' UNION ALL     SELECT 14, 14, 'fourteen' UNION ALL

    SELECT 15, 15, 'fifteen' UNION ALL      SELECT 16, 16, 'sixteen' UNION ALL

    SELECT 17, 17, 'seventeen' UNION ALL    SELECT 18, 18, 'eighteen' UNION ALL

    SELECT 19, 19, 'nineteen' UNION ALL     SELECT 20, 29, 'twenty' UNION ALL

    SELECT 30, 39, 'thirty' UNION ALL       SELECT 40, 49, 'forty' UNION ALL

    SELECT 50, 59, 'fifty' UNION ALL        SELECT 60, 69, 'sixty' UNION ALL

    SELECT 70, 79, 'seventy' UNION ALL      SELECT 80, 89, 'eighty' UNION ALL

    SELECT 90, 99, 'ninety' UNION ALL       SELECT 100, 999, 'hundred' UNION ALL

    SELECT 1000, 999999, 'thousand' UNION ALL   SELECT 1000000, 999999999, 'million' UNION ALL

    SELECT 1000000000, 999999999999, 'billion'

    SELECT @NewString = @NewString

       + CASE WHEN h.NumStr IS NULL THEN '' ELSE h.NumStr + ' ' + c.NumStr + ' ' END

       + CASE WHEN number < 15 AND h.NumStr IS NOT NULL AND (t.NumStr IS NOT NULL OR u.NumStr IS NOT NULL) THEN @JoinWord + ' ' ELSE '' END

       + CASE WHEN number < 15 AND t.NumStr IS NOT NULL THEN t.NumStr + ' ' ELSE '' END

       + CASE WHEN number < 15 AND u.NumStr IS NOT NULL THEN u.NumStr + ' ' ELSE '' END

       + CASE WHEN number = 12 THEN @CurrencyName + ' ' ELSE '' END

       + CASE WHEN number = 15 AND (t.NumStr IS NOT NULL OR u.NumStr IS NOT NULL) THEN @JoinWord + ' ' ELSE '' END

       + CASE WHEN number = 15 AND t.NumStr IS NOT NULL THEN t.NumStr + ' ' ELSE '' END

       + CASE WHEN number = 15 AND u.NumStr IS NOT NULL THEN u.NumStr + ' ' ELSE '' END

       + CASE WHEN number = 15 AND (t.NumStr IS NOT NULL OR u.NumStr IS NOT NULL) THEN @CentsName + ' ' ELSE '' END

       + CASE WHEN k.NumStr IS NULL THEN '' ELSE k.NumStr + ', ' END

    FROM (SELECT 3 AS number UNION ALL SELECT 6 UNION ALL SELECT 9 UNION ALL SELECT 12 UNION ALL SELECT 15) n

    LEFT JOIN @tblNum h ON number < 15 AND REPLACE(SUBSTRING(@numberstr, number-2, 1), '.', '') BETWEEN h.Num AND h.MaxNum

    LEFT JOIN @tblNum t ON SUBSTRING(@numberstr, number-1, 2) BETWEEN t.Num AND t.MaxNum AND t.Num > 9  

    LEFT JOIN @tblNum u ON SUBSTRING(@numberstr, number, 1) BETWEEN u.Num AND u.MaxNum AND NOT SUBSTRING(@numberstr, number-1, 2) BETWEEN 10 AND 19

    LEFT JOIN @tblNum c ON number < 15 AND c.Num = 100

    LEFT JOIN @tblNum k ON number < 12 AND k.Num = POWER(10, 12-number) AND k.Num < @TheNumber

    RETURN @NewString

    END

    GO

    --SELECT dbo.ToWords(number, 'dollar(s)', 'and', 'cent(s)')

    --FROM Numbers

    --WHERE number < 30000

    -- about a minute

    SELECT dbo.ToWords(999999999999.99, 'dollar(s)', 'and', 'cent(s)')

    -- nine hundred and ninety nine billion, nine hundred and ninety nine million,

    -- nine hundred and ninety nine thousand, nine hundred and ninety nine dollar(s) and ninety nine cent(s)

    SELECT dbo.ToWords(999999999999.99, 'pounds', 'and', 'pence')

    -- nine hundred and ninety nine billion, nine hundred and ninety nine million,

    -- nine hundred and ninety nine thousand, nine hundred and ninety nine pounds and ninety nine pence

    [/font]

    Cheers

    ChrisM

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Jeff Moden (11/26/2008)


    Chris Morris (11/26/2008)


    Chris, the link in your signature is broken. You'r missing the right hand bracket on the first instance of the URL IFCode ShortCut.

    Thanks Jeff, it's fixed now. Dunno if it's restrictions on the work pc's here but the sig editor has a mind of it's own.

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Chris Morris (11/27/2008)Dunno if it's restrictions on the work pc's here but the sig editor has a mind of it's own.

    I hear that. It took me forever to get my signature right. :crazy:

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • Here's a 2k/2k5-friendly version with a permanent lookup table. It translates 30,000 numbers from a standard tally table, in both SQL Server versions, in about 4 seconds.

    [font="Courier New"]IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[NumbersAsWords]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)

    DROP TABLE [dbo].[NumbersAsWords]

    CREATE TABLE [dbo].[NumbersAsWords] (

       [Num] [bigint] NULL ,

       [MaxNum] [bigint] NULL ,

       [NumStr] [varchar] (20) COLLATE Latin1_General_CI_AS NULL

    ) ON [PRIMARY]

    CREATE  CLUSTERED  INDEX [Num] ON [dbo].[NumbersAsWords]([Num]) ON [PRIMARY]

    CREATE  INDEX [MaxNum] ON [dbo].[NumbersAsWords]([MaxNum]) ON [PRIMARY]

    INSERT INTO dbo.NumbersAsWords

    SELECT 1, 1, 'one' UNION ALL            SELECT 2, 2, 'two' UNION ALL

    SELECT 3, 3, 'three' UNION ALL          SELECT 4, 4, 'four' UNION ALL

    SELECT 5, 5, 'five' UNION ALL           SELECT 6, 6, 'six' UNION ALL

    SELECT 7, 7, 'seven' UNION ALL          SELECT 8, 8, 'eight' UNION ALL

    SELECT 9, 9, 'nine' UNION ALL           SELECT 10, 10, 'ten' UNION ALL

    SELECT 11, 11, 'eleven' UNION ALL       SELECT 12, 12, 'twelve' UNION ALL

    SELECT 13, 13, 'thirteen' UNION ALL     SELECT 14, 14, 'fourteen' UNION ALL

    SELECT 15, 15, 'fifteen' UNION ALL      SELECT 16, 16, 'sixteen' UNION ALL

    SELECT 17, 17, 'seventeen' UNION ALL    SELECT 18, 18, 'eighteen' UNION ALL

    SELECT 19, 19, 'nineteen' UNION ALL     SELECT 20, 29, 'twenty' UNION ALL

    SELECT 30, 39, 'thirty' UNION ALL       SELECT 40, 49, 'forty' UNION ALL

    SELECT 50, 59, 'fifty' UNION ALL        SELECT 60, 69, 'sixty' UNION ALL

    SELECT 70, 79, 'seventy' UNION ALL      SELECT 80, 89, 'eighty' UNION ALL

    SELECT 90, 99, 'ninety' UNION ALL       SELECT 100, 999, 'hundred' UNION ALL

    SELECT 1000, 999999, 'thousand' UNION ALL   SELECT 1000000, 999999999, 'million' UNION ALL

    SELECT 1000000000, 999999999999, 'billion'

    ---------------------------------------------------------------------------------------------------

    GO

    ALTER FUNCTION [dbo].[ToWords](

       @TheNumber DECIMAL (15,2),

       @CurrencyName VARCHAR(20),

       @JoinWord VARCHAR(20),

       @CentsName VARCHAR(20))

    RETURNS VARCHAR(200)

    -- USAGE: SELECT dbo.ToWords(1.01, 'dollar(s)', 'and', 'cent(s)')

    -- MAX. VALUE: 999999999999.99 (twelve nines before the decimal point)

    BEGIN

    DECLARE @numberstr CHAR (15), @NewString VARCHAR(200)

    SET @numberstr = STR(@TheNumber, 15, 2)

    SET @NewString = ''

    SELECT @NewString = @NewString

       + ISNULL(h.NumStr + ' ' + c.NumStr + ' ', '')

       + CASE WHEN (number = 15 OR h.NumStr IS NOT NULL) AND COALESCE(t.NumStr, u.NumStr) IS NOT NULL THEN @JoinWord + ' ' ELSE '' END

       + ISNULL(t.NumStr + ' ', '')

       + ISNULL(u.NumStr + ' ', '')

       + CASE WHEN number = 12 THEN @CurrencyName + ' ' ELSE '' END

       + CASE WHEN number = 15 AND COALESCE(t.NumStr, u.NumStr) IS NOT NULL THEN @CentsName ELSE '' END

       + CASE WHEN COALESCE(h.NumStr, t.NumStr, u.NumStr) IS NOT NULL THEN ISNULL(k.NumStr + ', ', '') ELSE '' END

    FROM (SELECT 3 AS number UNION ALL SELECT 6 UNION ALL SELECT 9 UNION ALL SELECT 12 UNION ALL SELECT 15) n

    LEFT JOIN NumbersAsWords h ON number < 15 AND REPLACE(SUBSTRING(@numberstr, number-2, 1), '.', '') BETWEEN h.Num AND h.MaxNum

    LEFT JOIN NumbersAsWords t ON SUBSTRING(@numberstr, number-1, 2) BETWEEN t.Num AND t.MaxNum AND t.Num > 9  

    LEFT JOIN NumbersAsWords u ON SUBSTRING(@numberstr, number, 1) BETWEEN u.Num AND u.MaxNum AND NOT SUBSTRING(@numberstr, number-1, 2) BETWEEN 10 AND 19

    LEFT JOIN NumbersAsWords c ON number < 15 AND c.Num = 100

    LEFT JOIN NumbersAsWords k ON number < 12 AND k.Num = POWER(10, 12-number) AND k.Num <= @TheNumber

    WHERE number > 15-LEN(@TheNumber)

    RETURN @NewString

    END

    GO

    SELECT dbo.ToWords(number, 'dollar(s)', 'and', 'cent(s)')

    FROM Numbers

    WHERE number < 30000

    -- about four seconds

    SELECT dbo.ToWords(number, 'dollar(s)', 'and', 'cent(s)')

    FROM Numbers

    WHERE number BETWEEN 970001 AND 1000000

    -- about five seconds

    SELECT dbo.ToWords(100.01, 'dollar(s)', 'and', 'cent(s)')

    -- one hundred dollar(s) and one cent(s)

    SELECT dbo.ToWords(999999999999.99, 'dollar(s)', 'and', 'cent(s)')

    -- nine hundred and ninety nine billion, nine hhundred and ninety nine million,

    -- nine hundred and ninety nine thousand, nine hundred and ninety nine dollar(s)

    -- and ninety nine cent(s)

    SELECT dbo.ToWords(999999999999.99, 'pounds', 'and', 'pence')

    -- nine hundred aand ninety nine billion, nine hundred and ninety nine million,

    -- nine hundred and ninety nine thousand, nine hundred and ninety nine pounds

    -- and ninety nine pence

    [/font]

    Many thanks for the help guys, I learn something every time I come here.

    Cheers

    ChrisM

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Man, you write nice code, Chris. I always enjoy reading it.

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • Chris Morris (11/27/2008)


    Here's a 2k/2k5-friendly version with a permanent lookup table. It translates 30,000 numbers from a standard tally table, in both SQL Server versions, in about 4 seconds.

    Ummm... try this and see if that time sticks... πŸ˜‰ it's still 30,000 rows

    SELECT dbo.ToWords(number, 'dollar(s)', 'and', 'cent(s)')

    FROM Numbers

    WHERE number BETWEEN 970001 AND 1000000

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (11/27/2008)


    Chris Morris (11/27/2008)


    Here's a 2k/2k5-friendly version with a permanent lookup table. It translates 30,000 numbers from a standard tally table, in both SQL Server versions, in about 4 seconds.

    Ummm... try this and see if that time sticks... πŸ˜‰ it's still 30,000 rows

    SELECT dbo.ToWords(number, 'dollar(s)', 'and', 'cent(s)')

    FROM Numbers

    WHERE number BETWEEN 970001 AND 1000000

    The timing's very similar Jeff, but many many thanks for this, it did point out some 'areas for improvement'.

    RBarryYoung (11/27/2008)


    Man, you write nice code, Chris. I always enjoy reading it.

    :blush: erm, :blush: so long as it looks nice! Sometimes it works too...

    Cheers

    ChrisM

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Chris Morris (11/27/2008)


    RBarryYoung (11/27/2008)


    Man, you write nice code, Chris. I always enjoy reading it.

    :blush: erm, :blush: so long as it looks nice! Sometimes it works too...

    I mean the logic and the style, not just the formatting.

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • Chris Morris (11/27/2008)


    Jeff Moden (11/27/2008)


    Chris Morris (11/27/2008)


    Here's a 2k/2k5-friendly version with a permanent lookup table. It translates 30,000 numbers from a standard tally table, in both SQL Server versions, in about 4 seconds.

    Ummm... try this and see if that time sticks... πŸ˜‰ it's still 30,000 rows

    SELECT dbo.ToWords(number, 'dollar(s)', 'and', 'cent(s)')

    FROM Numbers

    WHERE number BETWEEN 970001 AND 1000000

    The timing's very similar Jeff, but many many thanks for this, it did point out some 'areas for improvement'.

    I asked only because my machine is a bit old (but I still love it!) and any timing differences are pretty well magnified. The 30k example you posted took 11 seconds on my humble desktop instead of just 4 seconds. The 30k example I asked you to run jumped up to 13 seconds.

    You say "some 'areas for improvement'"... what did you improve?

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Very cool code, Chris... your' guy's efforts made me finish up what I started and it looks nothing like the original code I posted... I couldn't stand the fact that I was using Correlated Subqueries and that is was pretty slow.

    First, like everyone else, we need a reference table with words for things. I took a radically different approach than everyone else did on this one. Heh... it's kind of a "Rosetta Stone" for how to name integers except... note the absence of the number "Zero"...

    --===== Create the table that that is used to form the words

    -- This only need be executed once, ever.

    CREATE TABLE dbo.NumberWord

    (

    Unit INT,

    UnitWord VARCHAR(5),

    TeenWord VARCHAR(9),

    TenWord VARCHAR(7),

    ThousandWord VARCHAR(11),

    CONSTRAINT PK_NumberWord_Unit

    PRIMARY KEY CLUSTERED (UNIT) WITH FILLFACTOR = 100

    )

    INSERT INTO NumberWord

    (Unit,UnitWord,TeenWord ,TenWord ,ThousandWord)

    SELECT 1 ,'One' ,'Eleven' ,'Ten' ,'Thousand' UNION ALL

    SELECT 2 ,'Two' ,'Twelve' ,'Twenty' ,'Million' UNION ALL

    SELECT 3 ,'Three' ,'Thirteen' ,'Thirty' ,'Billion' UNION ALL

    SELECT 4 ,'Four' ,'Fourteen' ,'Fourty' ,'Trillion' UNION ALL

    SELECT 5 ,'Five' ,'Fifteen' ,'Fifty' ,'Quadrillion' UNION ALL

    SELECT 6 ,'Six' ,'Sixteen' ,'Sixty' ,'Quintillion' UNION ALL

    SELECT 7 ,'Seven' ,'Seventeen','Seventy','Sextillion' UNION ALL

    SELECT 8 ,'Eight' ,'Eighteen' ,'Eighty' ,'Septillion' UNION ALL

    SELECT 9 ,'Nine' ,'Nineteen' ,'Ninety' ,'Octillian'

    GO

    As with many other solutions, the Tally table figures prominately as a character splitter. If you don't already have one, now's the time to make this very useful tool. See the following (linked) article for that and more...

    [font="Arial Black"]The "Numbers" or "Tally" Table: What it is and how it replaces a loop.[/font][/url]

    Here's the code I wrote for the function... similar to Chris' but different because of the reference table I used...

    CREATE FUNCTION dbo.NumberToWords

    /**************************************************************************************************

    Purpose:

    Convert a 0 to 2 decimal place number, including negative numbers, to words like you may find

    for the written out amount on a check. "Optional extra" words may be added. See the "Input

    Parameters" section of the code below for more details.

    Notes:

    1. All parameters must be present but may be Blank (empty string) or Null

    Revision History:

    Rev 00 - 27 Nov 2008 - Jeff Moden - Initial creation for the folks at SQLServerCentral.com.

    References: Original request:

    http://www.sqlservercentral.com/Forums/Topic584374-360-1.aspx

    Tally table info:

    http://www.sqlservercentral.com/articles/TSQL/62867/

    **************************************************************************************************/

    (--==== Input parameters

    @SomeNumber DECIMAL (32,2), -- +- 30 digits + up to 2 decimal places

    @IntegerType VARCHAR(32), -- Example: 'Dollar(s)' on checks or NULL or Blank

    @DecimalPlace VARCHAR(32), -- Example: 'And' on checks or NULL or Blank

    @DecimalType VARCHAR(32) -- Example: 'Cent(s)' or '/100 Dollar(s)' on checks.

    -- Can also be NULL or Blank

    )

    RETURNS VARCHAR(8000)

    AS

    BEGIN -------------------------------------------------------------------------------------------

    --===== Declare local variables

    DECLARE @Result VARCHAR(8000), --Holds most of what we want to return

    @NumberString CHAR(33) --The number converted to a right justified string

    --===== Convert the number to a postive number and right justify it so we know where things are at.

    SELECT @NumberString = RIGHT(REPLICATE(' ',33) + CAST(ABS(@SomeNumber) AS VARCHAR(33)),33)

    --===== Find and concatenate all the words we need for the positive integer part of the number

    SELECT @Result = COALESCE(@Result + ',' ,'')

    + ISNULL(' ' + hundred.UnitWord + ' Hundred','')

    + CASE WHEN split.Teen BETWEEN 11 AND 19 THEN '' ELSE ISNULL(' ' + ten.TenWord,'') END

    + CASE WHEN split.Teen BETWEEN 11 AND 19 THEN '' ELSE ISNULL(' ' + one.UnitWord,'') END

    + CASE WHEN split.Teen BETWEEN 11 AND 19 THEN ISNULL(' ' + teen.TeenWord,'') ELSE '' END

    + ISNULL(' ' + thousand.ThousandWord,'')

    FROM (--==== Split the number into component parts based on "triplets" of digits in the number.

    -- This is nothing more than a split using a Tally table in steps of 3 characters

    -- as controlled by Modulo 3.

    SELECT (28-N)/3 AS Thousand,

    NULLIF(SUBSTRING(@NumberString,N,3),0) AS Triplet,

    SUBSTRING(@NumberString,N,1) AS Hundred,

    SUBSTRING(@NumberString,N+1,1) AS Ten,

    SUBSTRING(@NumberString,N+2,1) AS One,

    SUBSTRING(@NumberString,N+1,2) AS Teen

    FROM dbo.Tally

    WHERE N <= 30

    AND N%3 = 1)split

    LEFT JOIN dbo.NumberWord AS hundred ON split.Hundred = hundred.Unit

    LEFT JOIN dbo.NumberWord AS ten ON split.Ten = ten.Unit

    LEFT JOIN dbo.NumberWord AS one ON split.One = one.Unit

    LEFT JOIN dbo.NumberWord AS teen ON RIGHT(split.Teen,1) = teen.Unit

    LEFT JOIN dbo.NumberWord AS thousand ON split.Thousand = thousand.Unit

    WHERE split.Triplet IS NOT NULL

    --===== Add the other markers and parts to display and exit.

    RETURN CASE WHEN @SomeNumber < 0 THEN 'Minus ' ELSE '' END

    + ISNULL(LTRIM(@Result),'Zero')

    + ISNULL(' ' + @IntegerType ,'')

    + ISNULL(' ' + @DecimalPlace ,'')

    + ' ' + RIGHT(@NumberString,2)

    + ISNULL(' ' + @DecimalType ,'')

    END -------------------------------------------------------------------------------------------

    And, here's some tests I ran that also explain some usage possibilities... the "-15000.xx" is just so we can see what happens for negative numbers and when is crosses "Zero"

    SELECT dbo.NumberToWords (t.N-15000,'Dollar(s)','And','Cent(s)')

    FROM dbo.Tally t

    WHERE t.N BETWEEN 1 AND 30000

    SELECT dbo.NumberToWords (t.N-15000.03,NULL,'And','/100 Dollar(s)')

    FROM dbo.Tally t

    WHERE t.N BETWEEN 1 AND 30000

    Chris, would you do me the honor of running my code on your box a couple of times? I just wanna make sure I haven't lost my touch because your code ran damned fast. πŸ™‚

    Oh yeah... I almost forgot the disclaimer to make everyone happy... here goes...

    "This should really be done in the GUI... if you have one." :hehe:

    Heh... speaking of "gew", anyone care to write a CLR for this and comparitively test for performance? I don't care much for CLR's, but this seems like it might be a good place to do this type of nasty conversion.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (11/27/2008)


    I asked only because my machine is a bit old (but I still love it!) and any timing differences are pretty well magnified. The 30k example you posted took 11 seconds on my humble desktop instead of just 4 seconds. The 30k example I asked you to run jumped up to 13 seconds.

    Well, the total string length of your example is about 10-20% larger than Chris's original, so that makes sense, doesn't it?

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • For the most part, Yes. On concatenation of such a small number of parts, not really.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff:

    Your function is called "NumberToWords" but you are calling "MoneyToWords", is this just a typo or am I missing something?

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

Viewing 15 posts - 16 through 30 (of 46 total)

You must be logged in to reply to this topic. Login to reply