I have a PL/SQL function to parse my XML block and return it into a VARCHAR variable.
My XML block is not too complex. Have only 14 tags and don't have any repeating structure.
Inside my function I made this XMLType operation:
SELECT px_Header
|| 'R5'
|| RPAD (NVL (A1, ' '), 2)
|| RPAD (NVL (A2, ' '), 10)
|| RPAD (NVL (A3, ' '), 3)
|| RPAD (NVL (A4, ' '), 1)
|| RPAD (NVL (A5, ' '), 1)
|| RPAD (NVL (A6, ' '), 10)
|| RPAD (NVL (A7, ' '), 1)
|| RPAD (NVL (A8, ' '), 8)
|| RPAD (NVL (A9, ' '), 1)
|| RPAD (NVL (A10, ' '), 16)
|| RPAD (NVL (A11, ' '), 2)
|| RPAD (NVL (A12, ' '), 8)
|| RPAD (NVL (A13, ' '), 1)
|| RPAD (NVL (A14, ' '), 1)
|| CHR (13)
|| CHR (10)
INTO lv_return
FROM XMLTABLE (
'/IN_DATA'
PASSING px_Block
COLUMNS A1 VARCHAR2 (2) PATH 'A1',
A2 VARCHAR2 (10)
PATH 'A2',
A3 VARCHAR2 (3) PATH 'A3',
A4 VARCHAR2 (1) PATH 'A4',
A5 VARCHAR2 (1)
PATH 'A5',
A6 VARCHAR2 (10)
PATH 'A6',
A7 VARCHAR2 (1)
PATH 'A7',
A8 VARCHAR2 (8) PATH 'A8',
A9 VARCHAR2 (1)
PATH 'A9',
A10 VARCHAR2 (16)
PATH 'A10',
A11 VARCHAR2 (2)
PATH 'A11',
A12 VARCHAR2 (8)
PATH 'A12',
A13 VARCHAR2 (1)
PATH 'A13',
A14 VARCHAR2 (1)
PATH 'A14');
This function takes 0.0945 seconds to perform. Not to so much, I think. But my process need to iterate 100000 times, so I call this function 100000 time.
Is there any faster way to implement this to improve, even if only a few thousandths of a second?
Combine XML and process one large document instead of many little documents. This at least reduces the amount of context switches between SQL and PL/SQL, and
probably allows Oracle to take advantage of other efficiencies as well.
Instead of processing one-row-at-a-time:
<IN_DATA><A1>1</A1><A2>2</A2></IN_DATA>
Process multiple rows:
<ALL_DATA>
<IN_DATA><A1>1</A1><A2>2</A2></IN_DATA>
<IN_DATA><A1>3</A1><A2>4</A2></IN_DATA>
</ALL_DATA>
With code like this:
--Process XML.
declare
v_strings sys.odcivarchar2list;
px_Block xmltype := xmltype('
<ALL_DATA>
<IN_DATA><A1>1</A1><A2>2</A2></IN_DATA>
<IN_DATA><A1>3</A1><A2>4</A2></IN_DATA>
</ALL_DATA>');
begin
--Get strings.
select A1||' '||A2
bulk collect into v_strings
from
xmltable
(
'/ALL_DATA/IN_DATA' passing px_Block columns
a1 varchar2 (2) path 'A1',
a2 varchar2 (10) path 'A2'
);
--Display strings for testing.
for i in 1 .. v_strings.count loop
dbms_output.put_line(v_strings(i));
end loop;
end;
/
Results:
1 2
3 4
EDIT
If the documents cannot be combined, and if the input string is relatively simple and can be trusted, replacing the XML processing with string functions may significantly improve performance.
The example below uses a smaller input string, but changing from XML to string processing shrinks the time from 30 seconds to almost none.
--Process XML.
declare
v_result varchar2(100);
v_xml xmltype := xmltype('<IN_DATA><A1>1</A1><A2>2</A2></IN_DATA>');
v_string varchar2(100) := '<IN_DATA><A1>1</A1><A2>2</A2></IN_DATA>';
function get_string_from_xml(p_xml xmltype) return varchar2 is
v_string varchar2(100);
begin
--Get strings.
select A1||' '||A2
into v_string
from
xmltable
(
'/IN_DATA' passing v_xml columns
a1 varchar2 (2) path 'A1',
a2 varchar2 (10) path 'A2'
);
return v_string;
end get_string_from_xml;
function get_string_from_string(p_string varchar2) return varchar2 is
v_string varchar2(100);
begin
return
substr(p_string, instr(p_string, '<A1>') + 4, instr(p_string, '</A1>') - instr(p_string, '<A1>') - 4)
||' '||
substr(p_string, instr(p_string, '<A2>') + 4, instr(p_string, '</A2>') - instr(p_string, '<A2>') - 4);
end get_string_from_string;
begin
--27 seconds
/*
for i in 1 .. 100000 loop
v_result := get_string_from_xml(v_xml);
end loop;
*/
--0 seconds
for i in 1 .. 100000 loop
v_result := get_string_from_string(v_string);
end loop;
end;
/
Another way of looking at this is that your original function takes 2.5 hours to process 100,000 XML documents with a single thread. That sounds quite reasonable. Some simple parallelism should be able to shrink the time from hours to tens of minutes.
Related
My PL/SQL Procedure provides the values I want, in a CSV file. BUT I want to do some mathematical division using some of the values. I get no output in the relevant column though when I run the code and i'd like to know why.
I've tried putting the variable desctiption in the declaritive section of the procedure, and after 'BEGIN' and also using brackets in various places. The code compiles, and it runs. It just won't give any output in the column that I'm trying to get output for.
create or replace procedure THANOS is
--variables
l_dblink varchar2(100) := 'DB1';
TOTAL_ROW_COUNT varchar2(3000);
TOT_OBJECT_SIZE_MB varchar2(100);
EST_ONE_ROW varchar2(100);
file_handle UTL_FILE.file_type;
v_ts_name varchar2(30);
v_link_name varchar2(10);
v_csv_name varchar2(100);
--
begin
SELECT tablename into v_csv_name
FROM table_tracker
WHERE
CREATED_AT = (select MAX(CREATED_AT) from table_tracker);
EST_ONE_ROW := (TOTAL_ROW_COUNT / TOT_OBJECT_SIZE_MB);
select link_name into v_link_name from link_and_mail where mdate = (select max(mdate) from link_and_mail);
select distinct targetschema into v_ts_name from table;
file_handle := utl_file.fopen('ESTIMATES_CSV', v_csv_name||'_EST_PROC.csv', 'w', 32767);
UTL_FILE.PUT_LINE(file_handle, 'The below report shows total row counts in PROD');
UTL_FILE.PUT_LINE(file_handle, ' for all tables in the request:');
UTL_FILE.PUT_LINE(file_handle, ' ');
utl_file.put_line(file_handle, 'OWNER,TABLE_NAME,TOT_OBJECT_SIZE_MB,TOTAL_ROW_COUNT,EST_ONE_ROW');
for rws in (
select /*+parallel */ a.owner,a.table_name, sum(b.sum_bytes) TOT_OBJECT_SIZE_MB, EST_ONE_ROW
from dba_tables#DB1 a, V_SEG_DATA b
where a.table_name = b.segment_name
and a.table_name in
(select table_name from table)
and a.owner in (select distinct schema from table c)
group by a.owner,a.table_name
order by a.table_name
)
loop
execute immediate' select count(*) from ' ||rws.owner||'.'||rws.table_name || '#' || l_dblink into TOTAL_ROW_COUNT;
utl_file.put_line(file_handle,
rws.OWNER || ',' ||
rws.TABLE_NAME || ',' ||
rws.TOT_OBJECT_SIZE_MB || ',' ||
TOTAL_ROW_COUNT || ',' ||
EST_ONE_ROW
);
end loop;
utl_file.fclose(file_handle);
end THANOS;
The result of this code is to provide a .csv file with the following columns:
OWNER TABLE_NAME TOT_OBJECT_SIZE_MB TOTAL_ROW_COUNT EST_ONE_ROW
However, the EST_ONE_ROW column is always empty.
I want it to have the value for the number of rows divided by the total object size as per what is written:
EST_ONE_ROW := (TOTAL_ROW_COUNT / TOT_OBJECT_SIZE_MB);
Disclaimer -- People may say that this isn't a good way of finding what I'm trying to find, etc etc, but, it'd be great if no-one judged that, and just lead me in the right direction when it comes to what's wrong with the logic of the code itself, what I'm doing wrong with the 'division' logic
Thank you stackies!! :-)
They are all VARCHAR2 and are all empty. So there wouldn't be any result. This would be like this:
set serveroutput on
declare
TOTAL_ROW_COUNT varchar2(3000);
TOT_OBJECT_SIZE_MB varchar2(100);
EST_ONE_ROW VARCHAR2(100);
begin
EST_ONE_ROW := (TOTAL_ROW_COUNT / TOT_OBJECT_SIZE_MB);
dbms_output.put_line('EST_ONE_ROW:'||EST_ONE_ROW);
end;
Output:
EST_ONE_ROW:
PL/SQL procedure successfully completed.
But it look like EST_ONE_ROW is part of V_SEG_DATA so you might need to change the statement:
utl_file.put_line(file_handle,
rws.OWNER || ',' ||
rws.TABLE_NAME || ',' ||
rws.TOT_OBJECT_SIZE_MB || ',' ||
TOTAL_ROW_COUNT || ',' ||
rws.EST_ONE_ROW -- <<<<<<<<<<<<<<< change here
);
Another way if this is purely calculated:
utl_file.put_line(file_handle,
rws.OWNER || ',' ||
rws.TABLE_NAME || ',' ||
rws.TOT_OBJECT_SIZE_MB || ',' ||
TOTAL_ROW_COUNT || ',' ||
(TOTAL_ROW_COUNT / rws.TOT_OBJECT_SIZE_MB)
);
The above fails if TOT_OBJECT_SIZE_MB is zero. As you will get a division by zero failure. You might wnat to handle with an if statement.
So this might work:
create or replace procedure THANOS is
--variables
l_dblink varchar2(100) := 'DB1';
TOTAL_ROW_COUNT varchar2(3000);
TOT_OBJECT_SIZE_MB varchar2(100);
EST_ONE_ROW varchar2(100);
file_handle UTL_FILE.file_type;
v_ts_name varchar2(30);
v_link_name varchar2(10);
v_csv_name varchar2(100);
--
begin
SELECT tablename into v_csv_name
FROM table_tracker
WHERE
CREATED_AT = (select MAX(CREATED_AT) from table_tracker);
select link_name into v_link_name from link_and_mail where mdate = (select max(mdate) from link_and_mail);
select distinct targetschema into v_ts_name from table;
file_handle := utl_file.fopen('ESTIMATES_CSV', v_csv_name||'_EST_PROC.csv', 'w', 32767);
UTL_FILE.PUT_LINE(file_handle, 'The below report shows total row counts in PROD');
UTL_FILE.PUT_LINE(file_handle, ' for all tables in the request:');
UTL_FILE.PUT_LINE(file_handle, ' ');
utl_file.put_line(file_handle, 'OWNER,TABLE_NAME,TOT_OBJECT_SIZE_MB,TOTAL_ROW_COUNT,EST_ONE_ROW');
for rws in (
select /*+parallel */ a.owner,a.table_name, sum(b.sum_bytes) TOT_OBJECT_SIZE_MB, EST_ONE_ROW
from dba_tables#DB1 a, V_SEG_DATA b
where a.table_name = b.segment_name
and a.table_name in
(select table_name from table)
and a.owner in (select distinct schema from table c)
group by a.owner,a.table_name
order by a.table_name
)
loop
execute immediate' select count(*) from ' ||rws.owner||'.'||rws.table_name || '#' || l_dblink into TOTAL_ROW_COUNT;
if rws.TOT_OBJECT_SIZE_MB then
EST_ONE_ROW := TOTAL_ROW_COUNT / rws.TOT_OBJECT_SIZE_MB;
else
EST_ONE_ROW := null;
end if;
utl_file.put_line(file_handle,
rws.OWNER || ',' ||
rws.TABLE_NAME || ',' ||
rws.TOT_OBJECT_SIZE_MB || ',' ||
TOTAL_ROW_COUNT || ',' ||
EST_ONE_ROW
);
end loop;
utl_file.fclose(file_handle);
end THANOS;
Well, the way the procedure looks now (i.e. code you posted), there's no way that EST_ONE_ROW is anything but NULL. Its value is calculated at the beginning of the procedure, when both TOTAL_ROW_COUNT and TOT_OBJECT_SIZE_MB are NULL as well.
See if it helps if you put
EST_ONE_ROW := (TOTAL_ROW_COUNT / TOT_OBJECT_SIZE_MB);
into the loop, right before the UTL_FILE.put_line call as - at that moment - variables used to calculate its value probably aren't NULL any more.
this is the error
i am getting while running the code although. it does not show error in code execution
I want to create a function in which I have to display the total balance in words.
CREATE OR REPLACE FUNCTION spell_number (p_number IN NUMBER)
RETURN VARCHAR2
AS
TYPE my Array IS TABLE OF VARCHAR2 (255);
l_str myArray
:= myArray ('',
' thousand ',
' million ',
' billion ',
' trillion ',
' quadrillion ',
' quintillion ',
' sextillion ',
' septillion ',
' octillion ',
' nonillion ',
' decillion ',
' undecillion ',
' duodecillion ');
l_num VARCHAR2 (50) DEFAULT TRUNC (p_number);
l_return VARCHAR2 (4000);
BEGIN
FOR i IN 1 .. l_str.COUNT
LOOP
EXIT WHEN l_num IS NULL;
IF (SUBSTR (l_num, LENGTH (l_num) - 2, 3) <> 0)
THEN
l_return :=
TO_CHAR (TO_DATE (SUBSTR (l_num, LENGTH (l_num) - 2, 3), 'J'),
'Jsp')
|| l_str (i)
|| l_return;
END IF;
l_num := SUBSTR (l_num, 1, LENGTH (l_num) - 3);
END LOOP;
RETURN l_return;
END;
Ah, that's Reports Builder.
Remove CREATE OR REPLACE and it should be just fine.
how to get output of numeric/numbers in character/alphabet form?
like i give input:- 1234
then output should be like:- one two three four AND (in other condition) one
thousand two hundred thirty four.
Try this
select to_char(to_date(1234, 'j'), 'jsp') from dual
Output will be
one thousand two hundred thirty-four
Execute below function.
CREATE OR REPLACE FUNCTION NUMTOWORDS (P_IN IN NUMBER)
RETURN VARCHAR2
IS
V_WORDS VARCHAR2 (20) := '';
V_CHAR VARCHAR2 (20) := '';
V_LENGTH NUMBER;
TYPE ARRAY_TYPE IS VARRAY (10) OF VARCHAR2 (10);
V_ARRAY ARRAY_TYPE := ARRAY_TYPE ('ZERO', 'ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE');
BEGIN
SELECT LENGTH (TO_CHAR (P_IN)) INTO V_LENGTH FROM DUAL;
FOR I IN 1 .. V_LENGTH
LOOP
V_CHAR := V_ARRAY ( (TO_NUMBER (SUBSTR (TO_CHAR (P_IN), I, 1)) + 1));
V_WORDS := CONCAT (V_WORDS, CONCAT (V_CHAR, ' '));
END LOOP;
RETURN TRIM (V_WORDS);
END;
"" SELECT NUMTOWORDS(674) FROM DUAL;
I wrote a procedure that will automatically get the data from the given query and put the reuslt data set into a file in directory with delimiters. It works good for smaller queries like select * from table_1 but not for big queries like :
SELECT
i.row_id,
translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),
null
FROM communication i,
contact c
WHERE i.last_upd >= (SELECT to_char(last_updated_dt, 'DD-MON-YYYY')
FROM extract_status
WHERE extract_nm = 'INTN300')
AND i.last_upd < sysdate
AND i.x_interaction_type_cd NOT IN ('XRAC','FMS','ATV','IRL')
AND i.pr_con_id = c.row_id
AND c.x_Prospect_Ind = 'Y';
while calling procedure that accepts query procdure : - tab_to_flat('with_the_above_query') it is showing errors like
1) PLS-00103: Encountered the symbol "|" .
2) Encountered the symbol "),
null
FROM communication i,
And many more ..Can anybody help how to pass these queries as a input????
/* Formatted on 06/06/2013 1:42:56 PM (QP5 v5.163.1008.3004) */
CREATE OR REPLACE FUNCTION tab_to_flat (input_query IN CLOB,
dir_name IN VARCHAR2,
file_name IN VARCHAR2,
seperator IN VARCHAR2)
RETURN NUMBER
IS
c_seperator VARCHAR2 (3) := ' ';
incoming_seperator VARCHAR2 (3) := seperator;
no_of_rows NUMBER;
rec_tab DBMS_SQL.DESC_TAB;
col_cnt INTEGER;
src_id INTEGER DEFAULT DBMS_SQL.open_cursor;
val_varchar VARCHAR2 (32767);
val_num NUMBER;
val_date DATE;
file_input UTL_FILE.file_type;
l_start NUMBER;
row_cnt NUMBER := 0;
BEGIN
l_start := DBMS_UTILITY.get_time;
file_input :=
UTL_FILE.fopen (dir_name,
file_name,
'w',
32767);
DBMS_SQL.parse (src_id, input_query, 1);
DBMS_SQL.describe_columns (src_id, col_cnt, rec_tab);
FOR i IN 1 .. col_cnt
LOOP
CASE (rec_tab (i).col_type)
WHEN 1
THEN
DBMS_SQL.define_column (src_id,
i,
val_varchar,
32767);
WHEN 2
THEN
DBMS_SQL.define_column (src_id, i, val_num);
--when 8 then dbms_sql.define_column_long(src_id,i);
WHEN 12
THEN
DBMS_SQL.define_column (src_id, i, val_date);
ELSE
DBMS_SQL.define_column (src_id,
i,
val_varchar,
32767);
END CASE;
END LOOP;
no_of_rows := DBMS_SQL.execute (src_id);
LOOP
EXIT WHEN (DBMS_SQL.FETCH_ROWS (src_id) <= 0);
c_seperator := ' ';
FOR j IN 1 .. col_cnt
LOOP
CASE (rec_tab (j).col_type)
WHEN 1
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_varchar);
UTL_FILE.put (file_input, c_seperator || val_varchar);
WHEN 2
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_num);
UTL_FILE.put (file_input, c_seperator || val_num);
-- when 8 then dbms_sql.column_value_long(src_id,j,4000,1);
WHEN 12
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_date);
UTL_FILE.put (
file_input,
c_seperator || TO_CHAR (val_date, 'MM/DD/YYYY HH24:MI:SS'));
ELSE
DBMS_SQL.COLUMN_VALUE (src_id, j, val_varchar);
UTL_FILE.put (file_input, c_seperator || val_varchar);
END CASE;
c_seperator := incoming_seperator;
END LOOP;
UTL_FILE.new_line (file_input);
row_cnt := row_cnt + 1;
END LOOP;
DBMS_SQL.close_cursor (src_id);
DBMS_OUTPUT.put_line (
'The execution time is : ' || (DBMS_UTILITY.get_time - l_start));
RETURN row_cnt;
EXCEPTION
WHEN OTHERS
THEN
IF (SQLCODE = -942)
THEN
DBMS_OUTPUT.put_line ('Please check the table_name');
ELSE
RAISE;
END IF;
END;
It looks like you just aren't escaping the quotes that are included in the query string, so you're calling it as
tab_to_flat('SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),...')
The single quote around the | you're concatenating after the chr(13) is the immediate problem, but there are others. You could either go through and carefully double-up every quote inside the string:
tab_to_flat('SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||''|'' , '' ''),...')
... or more readibly use the quoted value syntax:
tab_to_flat(q'[SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),...]')
... where the q'[ ... ]' enclose your original string and allow you to use single quote marks without having to escape them. You just need to be sure that the actual query doesn't contain [ or ], or pick different delimiters if it does.
This says nothing about whether what you're doing is a good approach and if you can find a better way to approach your problem, and doesn't address SQL injection etc.; this is just to fix the problem with what you're currently calling and how you're doing it.
What you can do is to use am oracle cursor like this :
FUNCTION SELECT_FROM_MY_TABLE(v_QUERY_TO_BE_EXECUTED VARCHAR2)
RETURN SYS_REFCURSOR
IS
c_my_cursor SYS_REFCURSOR;
BEGIN
OPEN c_my_cursor FOR
v_QUERY_TO_BE_EXECUTED -- When working with Ref Cursors, open-for can be used directly, instead of execute immediate.
RETURN c_my_cursor;
END SELECT_FROM_MY_TABLE;
So basically you have a function, that returns a cursor which contains the information from your query. When you use the cursor, you do it like this :
PROCEDURE procedure_use_cursor
IS
c_my_cursor SYS_REFCURSOR;
r_my_table_row my_table%ROWTYPE;
BEGIN
c_my_cursor := SELECT_FROM_MY_TABLE;
LOOP
FETCH c_my_cursor INTO r_my_table_row;
EXIT WHEN c_my_cursor%NOTFOUND ;
-- do what you want with r_my_table_row
END LOOP;
END procedure_use_cursor;
I have a requirement in db.
1).Table ABC, column: check_amount number number(18,4). This basically contains check amount for eg. 3000.50 to be paid to an employee.
Now a cheque is issued and that check contains this check_amount in number as well as in text form.for eg.check will have:
pay to <emplyee_name> ****$3000.50**** ****THREE THOUSAND DOLLARS AND FIFTY CENTS****
I have to generate this text using DB column value and display that on check.
Can anybody help me out, how can i achieve this in oracle 11g ?
Hint:I have heard of Julien format, but that is not working. Any suggestions is greatly appreciated.
From
Nalin
Since Julian format works only for whole numbers, you can separate the decimal parts and then apply the Julian format trick to the separated numbers. Here's a simple demo.
DECLARE
x NUMBER (8, 2) := 1253.5;
y NUMBER;
z NUMBER;
BEGIN
y := FLOOR (x);
z := 100 * (x - y);
DBMS_OUTPUT.put_line (TO_CHAR (TO_DATE (y, 'j'), 'jsp'));
IF (z > 0)
THEN
DBMS_OUTPUT.put_line (TO_CHAR (TO_DATE (z, 'j'), 'jsp'));
END IF;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('err:' || SQLERRM);
END;
There is a limitation while using Julian dates ,It ranges from 1 to 5373484. That’s why if you put the values after 5373484, it will throw you an error as shown below:
ORA-01854: julian date must be between 1 and 5373484
To cater the above problem ,create a function ,and with little trick with j->jsp ,you can fetch the desired result.
CREATE OR REPLACE FUNCTION spell_number (p_number IN NUMBER)
RETURN VARCHAR2
AS
TYPE myArray IS TABLE OF VARCHAR2 (255);
v_decimal PLS_INTEGER;
l_str myArray
:= myArray ('',
' thousand ',
' million ',
' billion ',
' trillion ',
' quadrillion ',
' quintillion ',
' sextillion ',
' septillion ',
' octillion ',
' nonillion ',
' decillion ',
' undecillion ',
' duodecillion ');
l_num VARCHAR2 (50) DEFAULT TRUNC (p_number);
l_return VARCHAR2 (4000);
BEGIN
FOR i IN 1 .. l_str.COUNT
LOOP
EXIT WHEN l_num IS NULL;
IF (SUBSTR (l_num, LENGTH (l_num) - 2, 3) <> 0)
THEN
l_return :=
TO_CHAR (TO_DATE (SUBSTR (l_num, LENGTH (l_num) - 2, 3), 'J'),
'Jsp')
|| l_str (i)
|| l_return;
END IF;
l_num := SUBSTR (l_num, 1, LENGTH (l_num) - 3);
END LOOP;
v_decimal := 100* (p_number -TRUNC(p_number)) ;
IF v_decimal>0 THEN
RETURN l_return ||' Dollars AND '||TO_CHAR (TO_DATE (v_decimal, 'j'), 'jsp')|| ' Cents';
ELSE
RETURN l_return ||' Dollars' ;
END IF;
END;
/
select spell_number(122344343444444.23) from dual;
Output:
One Hundred Twenty-Two trillion Three Hundred Forty-Four billion Three Hundred Forty-Three million Four Hundred Forty-Four thousand Four Hundred Forty-Four Dollars AND twenty-three Cents Blog Link