Using date in ref cusor with pl/sql - date

I have a var: acc_date with type date.
It takes its value from a cursor and when I insert its value to logger table as:
insert into logger values(1,acc_date);
the out put when a select it from logger is
1 01-JAN-10
but when i use it to compare with another Date value in another cursor as
OPEN c_get_date_id
for 'SELECT Date_D.DATEKEY from Date_D where Date_D.DATEVALUE='||acc_date;
EXIT WHEN c_get_date_id%NOTFOUND;
FETCH c_get_date_id
INTO date_id;
insert into logger values (1,'Now with date_id'||date_id);
CLOSE c_get_date_id;
an error occurs:
Error report:
ORA-00904: "JAN": invalid identifier
ORA-06512: at "HW.FILLFACT", line 82
ORA-06512: at line 1
00904. 00000 - "%s: invalid identifier"
*Cause:
*Action:
strong text

You need at least add some quotes around the date:
....' where Date_D.DATEVALUE='''||acc_date||'''';
Double apostrophes within a string will be concatenated to a single apostrophe, so that the expression becomes
where Date_D.DATEVALUE='....';
In order to make the thing more foolprof, I'd also add a specific to_date:
.... ' where Date_D.DATEVALUE=to_date(''' || acc_date || ', ''dd-mon-yy'')';

At the moment your dynamic query is being interpreted as:
SELECT Date_D.DATEKEY from Date_D where Date_D.DATEVALUE=01-JAN-10
The error is because string representation of the date isn't being quoted, so it's seeing JAN as an identifier - and nothing matches that name. You could enclose the date value in quotes:
open c_get_date_id
for 'SELECT Date_D.DATEKEY from Date_D where Date_D.DATEVALUE='''||acc_date||'''';
But you're treating the date as a string, and forcing conversion of all your table values to strings to be compared, using your session's NLS_DATE_FORMAT. It would be better to compare it as a date (although this somewhat assumes all your values have the time portion set to midnight):
open c_get_date_id
for select date_d.datekey from date_d where date_d.datevalue = acc_date;
Your exit is in the wrong place though, and you aren't looping, so maybe you want:
open c_get_date_id
for select date_d.datekey from date_d where date_d.datevalue = acc_date;
loop
fetch c_get_date_id into date_id;
exit when c_get_date_id%notfound;
insert into logger values (1, 'Now with date_id'||date_id);
end loop;
close c_get_date_id;
If you only have one value in the first place though, you probably don't want a loop or cursor at all, and could do a simple select ... into instead:
select date_d.datekey into date_id from date_d
where date_d.datevalue = acc_date;
insert into logger values (1, 'Now with date_id'||date_id);
Though of course that would error if you had no matching date in your table, or more than one, and you'd need to deal with that - but then I guess you'd want to anyway.

Related

How to subtract dates in Oracle PL SQL

I'm using Oracle 18c.
I'm trying to determine elapsed time, but I get an error when I subtract two date variables in PL SQL.
The following code works fine:
DECLARE
l_zero_date date;
l_current_date date;
l_elapsed_time date;
BEGIN
Execute Immediate 'ALTER SESSION set nls_timestamp_format = "DD-MM-YYYY HH24:MI:SS"';
l_zero_date := to_date('01-01-1900 00:00:00', 'DD-MM-YYYY HH24:MI:SS');
dbms_output.put_line('The value of l_zero_date is: ' || l_zero_date);
Select ls.duration Into l_current_date From LIT_STATS ls Where ls.prim_key = 1002;
dbms_output.put_line('The value of l_curr_date is: ' || l_current_date);
-- dbms_output.put_line('The elapsed time is: ' || l_current_date - l_zero_date);
END;
This produces the results:
The value of l_zero_date is: 1900-01-01 00:00:00
The value of l_curr_date is: 1900-01-01 00:35:22
However, If I un-comment the last dbms_output line I get the error:
Error report -
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 14
06502. 00000 - "PL/SQL: numeric or value error%s"
*Cause: An arithmetic, numeric, string, conversion, or constraint error
occurred. For example, this error occurs if an attempt is made to
assign the value NULL to a variable declared NOT NULL, or if an
attempt is made to assign an integer larger than 99 to a variable
declared NUMBER(2).
*Action: Change the data, how it is manipulated, or how it is declared so
that values do not violate constraints.
I don't understand why I get the error on subtraction involving two fields declared as DATE. For example, the following code works fine:
declare
a date;
b date;
begin
a := sysdate;
dbms_lock.sleep(10); -- sleep about 10 seconds give or take
b := sysdate;
dbms_output.put_line( b-a || ' of a day has elapsed' );
dbms_output.put_line( (b-a)*24 || ' of an hour has elapsed' );
dbms_output.put_line( (b-a)*24*60 || ' of a minute has elapsed' );
dbms_output.put_line( (b-a)*24*60*60 || ' seconds has elapsed' );
end;
Why does the line dbms_output.put_line('The elapsed time is: ' || l_current_date - l_zero_date); produce an error?
Thanks for looking at this.
As I mentioned in the comments, this is an order of operations issue. Take the following example:
SELECT 'TEST'||SYSDATE-SYSDATE FROM DUAL
When this runs, I get the following error: ORA-00932: inconsistent datatypes: expected CHAR got DATE
But when I wrap the dates in ( and ):
SELECT 'TEST'||(SYSDATE-SYSDATE) FROM DUAL
The result is TEST0.
It is order of operations, the code moves left to right unless there are parentheses informing it to do the date subtraction first.
Here is a DBFiddle showing the queries being run (LINK)

DB2 SQL Error: SQLCODE=-302 while executing prepared statement

I have a SQL query which takes user inputs hence security flaw is present.
The existing query is:
SELECT BUS_NM, STR_ADDR_1, CITY_NM, STATE_CD, POSTAL_CD, COUNTRY_CD,
BUS_PHONE_NB,PEG_ACCOUNT_ID, GDN_ALERT_ID, GBIN, GDN_MON_REF_NB,
ALERT_DT, ALERT_TYPE, ALERT_DESC,ALERT_PRIORITY
FROM ( SELECT A.BUS_NM, AE.STR_ADDR_1, A.CITY_NM, A.STATE_CD, A.POSTAL_CD,
CC.COUNTRY_CD, A.BUS_PHONE_NB, A.PEG_ACCOUNT_ID, 'I' ||
LPAD(INTL_ALERT_DTL_ID, 9,'0') GDN_ALERT_ID,
LPAD(IA.GBIN, 9,'0') GBIN, IA.GDN_MON_REF_NB,
DATE(IAD.ALERT_TS) ALERT_DT,
XMLCAST(XMLQUERY('$A/alertTypeConfig/biqCode/text()' passing
IAC.INTL_ALERT_TYPE_CONFIG as "A") AS CHAR(4)) ALERT_TYPE,
, ROW_NUMBER() OVER () AS "RN"
FROM ACCOUNT A, Other tables
WHERE IA.GDN_MON_REF_NB = '100'
AND A.PEG_ACCOUNT_ID = IAAR.PEG_ACCOUNT_ID
AND CC.COUNTRY_CD = A.COUNTRY_ISO3_CD
ORDER BY IA.INTL_ALERT_ID ASC )
WHERE ALERT_TYPE IN (" +TriggerType+ ");
I changed it to accept TriggerType from setString like:
SELECT BUS_NM, STR_ADDR_1, CITY_NM, STATE_CD, POSTAL_CD, COUNTRY_CD,
BUS_PHONE_NB,PEG_ACCOUNT_ID, GDN_ALERT_ID, GBIN, GDN_MON_REF_NB,
ALERT_DT, ALERT_TYPE, ALERT_DESC,ALERT_PRIORITY
FROM ( SELECT A.BUS_NM, AE.STR_ADDR_1, A.CITY_NM, A.STATE_CD, A.POSTAL_CD,
CC.COUNTRY_CD, A.BUS_PHONE_NB, A.PEG_ACCOUNT_ID,
'I' || LPAD(INTL_ALERT_DTL_ID, 9,'0') GDN_ALERT_ID,
LPAD(IA.GBIN, 9,'0') GBIN, IA.GDN_MON_REF_NB,
DATE(IAD.ALERT_TS) ALERT_DT,
XMLCAST(XMLQUERY('$A/alertTypeConfig/biqCode/text()' passing
IAC.INTL_ALERT_TYPE_CONFIG as "A") AS CHAR(4)) ALERT_TYPE,
ROW_NUMBER() OVER () AS "RN"
FROM ACCOUNT A, other tables
WHERE IA.GDN_MON_REF_NB = '100'
AND A.PEG_ACCOUNT_ID = IAAR.PEG_ACCOUNT_ID
AND CC.COUNTRY_CD = A.COUNTRY_ISO3_CD
ORDER BY IA.INTL_ALERT_ID ASC )
WHERE ALERT_TYPE IN (?);
Setting trigger type as below:
if (StringUtils.isNotBlank(request.getTriggerType())) {
preparedStatement.setString(1, triggerType != null ? triggerType.toString() : "");
}
Getting error as
Caused by: com.ibm.db2.jcc.am.SqlDataException: DB2 SQL Error: SQLCODE=-302, SQLSTATE=22001, SQLERRMC=null, DRIVER=4.19.26
The -302 SQLCODE indicates a conversion error of some sort.
SQLSTATE 22001 narrows that down a bit by telling us that you are trying to force a big string into a small variable. Given the limited information in your question, I am guessing it is the XMLCAST that is the culprit.
DB2 won't jam 30 pounds of crap into a 4 pound bag so to speak, it gives you an error. Maybe giving XML some extra room in the cast might be a help. If you need to make sure it ends up being only 4 characters long, you could explicitly do a LEFT(XMLCAST( ... AS VARCHAR(64)), 4). That way the XMLCAST has the space it needs, but you cut it back to fit your variable on the fetch.
The other thing could be that the variable being passed to the parameter marker is too long. DB2 will guess the type and length based on the length of ALERT_TYPE. Note that you can only pass a single value through a parameter marker. If you pass a comma separated list, it will not behave as expected (unless you expect ALERT_TYPE to also contain a comma separated list). If you are getting the comma separated list from a table, you can use a sub-select instead.
Wrong IN predicate use with a parameter.
Do not expect that IN ('AAAA, M250, ABCD') (as you try to do passing a comma-separated string as a single parameter) works as IN ('AAAA', 'M250', 'ABCD') (as you need). These predicates are not equivalent.
You need some "string tokenizer", if you want to pass such a comma-separated string like below.
select t.*
from
(
select XMLCAST(XMLQUERY('$A/alertTypeConfig/biqCode/text()' passing IAC.INTL_ALERT_TYPE_CONFIG as "A") AS CHAR(4)) ALERT_TYPE
from table(values xmlparse(document '<alertTypeConfig><biqCode>M250, really big code</biqCode></alertTypeConfig>')) IAC(INTL_ALERT_TYPE_CONFIG)
) t
--WHERE ALERT_TYPE IN ('AAAA, M250, ABCD')
join xmltable('for $id in tokenize($s, ",\s?") return <i>{string($id)}</i>'
passing cast('AAA, M250 , ABCD' as varchar(200)) as "s"
columns token varchar(200) path '.') x on x.token=t.ALERT_TYPE
;
Run the statement as is. Then you may uncomment the string with WHERE clause and comment out the rest to see what you try to do.
P.S.:
The error you get is probably because you don't specify the data type of the parameter (you don't use something like IN (cast(? as varchar(xxx))), and db2 compiler assumes that its length is equal to the length of the ALERT_TYPE expression (4 bytes).

Need help understanding quotation marks in a SQL string to be passed to linked server.

I need to build a SQL statement to be submitted on a linked server. The statement makes use of #parameters and case statements which contain quotation marks.
I found this Microsoft article 'How to pass a variable', which seemed ideal, however I am not able to get everything going. It seems that the linked server is not enabled for the final and neatest suggestion of calling Sp_executesql, so I have been trying the first two examples.
To start with, here is a cut down example of my SQL statement on its own:
SELECT *,
CASE WHEN FLDA = 'ABC' THEN 'DEF' ELSE 'ABC' END AS COL1
FROM MYTABLE
WHERE FLDB = #PARM
1, I can get the query to work when excluding the CASE statement:
DECLARE #TSQL NVARCHAR(4000), #PARM NVARCHAR(10)
SET #PARM = 'ABC'
SET #TSQL = 'SELECT * FROM OPENQUERY(MYLINKEDSERVER, ''
SELECT *
FROM MYTABLE
WHERE FLDA = '''''+#PARM+''''''')'
EXEC (#TSQL)
However I don't understand why I require 5 quotes before #PARM and then 7(!) after it? When coding SQL statements in a string in previous languages just 2 quotations together acted as a single. So why 5 and 7?
2, I can't get the SQL to work at all when attempting to add the CASE statement. I have tried all combinations of 2,3,4 quotations but to no avail: Do I again need a certain amount of opening quotes and then a different amount of closing quotes?
DECLARE #TSQL NVARCHAR(4000), #PARM NVARCHAR(10)
SET #PARM = 'ABC'
SET #TSQL = 'SELECT * FROM OPENQUERY(MYLINKEDSERVER, ''
SELECT *,
CASE WHEN FLDA = ''ABC'' THEN ''DEF'' ELSE ''ABC'' END AS COL1
FROM MYTABLE
WHERE FLDA = '''''+#PARM+''''''')'
EXEC (#TSQL)
Any help greatly appreciated!
Cheers
Mark
my first question was why do I need 5 and 7 quotes, so there was no error message there, but I get the point that I could have listed some of the errors seen when I was getting the incorrect number of quotes.
However the tip to use Print was very useful, so thank you all for that!
So it transpires that I do indeed require a pair of
quotes where a single quote is required. However, as I am creating a SQL string within a string, I need to double that again. So I first need to end my string with a single quote and then add 4 quotes to create the double quote required to proceed the variable - hence 5. And likewise, I need 4 quotes to get a pair of quotes following the variable, another pair of quotes for the quote to end the statement and then a final one to wrap around the end bracket of the OPENQUERY command....I hope that sort of reads correct!
So:
WHERE FLDA = '''''+#PARM+''''''')'
Printed as:
WHERE FLDA = ''ABC''')
And for my CASE statement, I required 4 set of quotes, to equate to 2. So:
CASE WHEN FLDA = ''''ABC'''' THEN ''''DEF'''' ELSE ''''ABC'''' END AS COL1
Printed as:
CASE WHEN FLDA = ''ABC'' THEN ''DEF'' ELSE ''ABC'' END AS COL1

How to insert a value to postgres database column which contains double quotes and triple quotes

I want to insert a query string into a Postgres database column in the following format
{"enrolled_time":'''SELECT DISTINCT enrolled_time AT TIME ZONE %s FROM alluser'''}
I try this:
UPDATE reports SET raw_query = {"enrolled_time":'''SELECT DISTINCT enrolled_time AT TIME ZONE %s FROM alluser'''} WHERE id=37;
It gives error like
ERROR: syntax error at or near "{"
LINE 1: UPDATE base_reports SET extra_query = {"enrolled_time":'''SE...
When I try using single quotes it throws error like following:
ERROR: syntax error at or near "SELECT"
LINE 1: ...DATE reports SET raw_query = '{"enrolled_time":'''SELECT DIS...
How can I overcome this situation
Use dollar quoting:
UPDATE reports
SET raw_query = $${"enrolled_time":'''SELECT DISTINCT enrolled_time AT TIME ZONE %s FROM alluser'''}$$
WHERE id = 37;

Convert a string representing a timestamp to an actual timestamp in PostgreSQL?

In PostgreSQL: I convert string to timestamp with to_timestamp():
select * from ms_secondaryhealthcarearea
where to_timestamp((COALESCE(update_datetime, '19900101010101'),'YYYYMMDDHH24MISS')
> to_timestamp('20121128191843','YYYYMMDDHH24MISS')
But I get this error:
ERROR: syntax error at end of input
LINE 1: ...H24MISS') >to_timestamp('20121128191843','YYYYMMDDHH24MISS')
^
********** Error **********
ERROR: syntax error at end of input
SQL state: 42601
Character: 176
Why? How to convert a string to timestamp?
One too many opening brackets. Try this:
select *
from ms_secondaryhealthcarearea
where to_timestamp(COALESCE(update_datetime, '19900101010101'),'YYYYMMDDHH24MISS') >to_timestamp('20121128191843','YYYYMMDDHH24MISS')
You had two opening brackets at to_timestamp:
where to_timestamp((COA.. -- <-- the second one is not needed!
#ppeterka has pointed out the syntax error.
The more pressing question is: Why store timestamp data as string to begin with? If your circumstances allow, consider converting the column to its proper type:
ALTER TABLE ms_secondaryhealthcarearea
ALTER COLUMN update_datetime TYPE timestamp
USING to_timestamp(update_datetime,'YYYYMMDDHH24MISS');
Or use timestamptz - depending on your requirements.
Another way to convert a string to a timestamp type of PostgreSql is the above,
SELECT to_timestamp('23-11-1986 06:30:00', 'DD-MM-YYYY hh24:mi:ss')::timestamp without time zone;
I had the same requirement as how I read the title. How to convert an epoch timestamp as text to a real timestamp. In my case I extracted one from a json object. So I ended up with a timestamp as text with milliseconds
'1528446110978' (GMT: Friday, June 8, 2018 8:21:50.978 AM)
This is what I tried. Just the latter (ts_ok_with_ms) is exactly right.
SELECT
data->>'expiration' AS expiration,
pg_typeof(data->>'expiration'),
-- to_timestamp(data->>'expiration'), < ERROR: function to_timestamp(text) does not exist
to_timestamp(
(data->>'expiration')::int8
) AS ts_wrong,
to_timestamp(
LEFT(
data->>'expiration',
10
)::int8
) AS ts_ok,
to_timestamp(
LEFT(
data->>'expiration',
10
)::int8
) + (
CASE
WHEN LENGTH(data->>'expiration') = 13
THEN RIGHT(data->>'expiration', 3) ELSE '0'
END||' ms')::interval AS ts_ok_with_ms
FROM (
SELECT '{"expiration": 1528446110978}'::json AS data
) dummy
This is the (transposed) record that is returned:
expiration 1528446110978
pg_typeof text
ts_wrong 50404-07-12 12:09:37.999872+00
ts_ok 2018-06-08 08:21:50+00
ts_ok_with_ms 2018-06-08 08:21:50.978+00
I'm sure I overlooked a simpler version of how to get from a timestamp string in a json object to a real timestamp with ms (ts_ok_with_ms), but I hope this helps nonetheless.
Update: Here's a function for your convenience.
CREATE OR REPLACE FUNCTION data.timestamp_from_text(ts text)
RETURNS timestamptz
LANGUAGE SQL AS
$$
SELECT to_timestamp(LEFT(ts, 10)::int8) +
(
CASE
WHEN LENGTH(ts) = 13
THEN RIGHT(ts, 3) ELSE '0'
END||' ms'
)::interval
$$;