Date/Timestamp Handling in Informatica PowerCenter - informatica

I want to compare 2 date/timestamp fields. One of those comes from a table on Oracle, second one comes from a mapping variable for which a value is define in a parameter file. The datatype of the variable is defined as date/timestamp in the mapping. A filter makes the comparison for which the condition is:
DATE_COMPARE(LAST_UPD, $$CDC_STRT_TS) >=0 AND DATE_COMPARE(LAST_UPD, $$CDC_END_TS) < 0
However, on executing the session, I receive:
TE_7002 [<<PM Parse Error>> missing operator
... DATE_COMPARE(LAST_UPD, 02/01/2014>>>> <<<<01:01:01.000000000) >=0 AND DATE_COMPARE(LAST_UPD, 03/01/2014 01:01:01.000000000) < 0]
It appears that Informatica is putting a space when replacing the variable with its value. If I convert it to a string and use date conversion functions then Informatica encloses the variable in single quotes and does not replace it with the value defined in the parameter file (notice the where clause).
ORA-00942: table or view does not exist
Oracle Fatal Error
Database driver error...
Function Name : Execute
SQL Stmt : SELECT CX_NET_TER_HIER.ROW_ID, CX_NET_TER_HIER.CREATED, CX_NET_TER_HIER.CREATED_BY, CX_NET_TER_HIER.LAST_UPD, CX_NET_TER_HIER.LAST_UPD_BY, CX_NET_TER_HIER.MODIFICATION_NUM, CX_NET_TER_HIER.CONFLICT_ID, CX_NET_TER_HIER.PAR_ROW_ID, CX_NET_TER_HIER.DB_LAST_UPD, CX_NET_TER_HIER.AREA_CODE, CX_NET_TER_HIER.AREA_CODE_NAME, CX_NET_TER_HIER.BILL_INTG_ID, CX_NET_TER_HIER.CITY, CX_NET_TER_HIER.DB_LAST_UPD_SRC, CX_NET_TER_HIER.NAME, CX_NET_TER_HIER.PRODUCT_ALIAS, CX_NET_TER_HIER.REFRENCE_ID, CX_NET_TER_HIER.TERRITORY_ID, CX_NET_TER_HIER.TERRITORY_TYPE, '12775'
FROM CX_NET_TER_HIER
WHERE
(((CASE WHEN CX_NET_TER_HIER.LAST_UPD = TO_TIMESTAMP('$$CDC_STRT_TS', 'MM/DD/YYYY HH24:MI:SS') THEN 0 WHEN CX_NET_TER_HIER.LAST_UPD > TO_TIMESTAMP('$$CDC_STRT_TS', 'MM/DD/YYYY HH24:MI:SS') THEN 1 WHEN CX_NET_TER_HIER.LAST_UPD < TO_TIMESTAMP('$$CDC_STRT_TS', 'MM/DD/YYYY HH24:MI:SS') THEN -1 ELSE NULL END) >= 0) AND ((CASE WHEN CX_NET_TER_HIER.LAST_UPD = TO_TIMESTAMP('$$CDC_END_TS', 'MM/DD/YYYY HH24:MI:SS') THEN 0 WHEN CX_NET_TER_HIER.LAST_UPD > TO_TIMESTAMP('$$CDC_END_TS', 'MM/DD/YYYY HH24:MI:SS') THEN 1 WHEN CX_NET_TER_HIER.LAST_UPD < TO_TIMESTAMP('$$CDC_END_TS', 'MM/DD/YYYY HH24:MI:SS') THEN -1 ELSE NULL END) < 0))
Oracle Fatal Error].
The parameter file looks like:
[s_CRM_10_020_LoadToStageTableCXNETTERHIER]
$$CDC_LAST_TS=01/01/2014 01:01:01
$$CDC_STRT_TS=02/01/2014 01:01:01
$$CDC_END_TS=03/01/2014 01:01:01
I have tried using 03/01/2014_01:01:01 in parameter file to avoid the space and set the session DateTime Format String to handle the underscore, but this does not help.
I think if there is some way to avoid the enclosing into single quotes in case of string or avoiding the space error in case of date/timestamp then the session will run fine.
Any help is really appreciated. Thank you!

You probably declared the variable as a Expression Variable (IsExprVar = True). So, it first expands the variable, and then evaluates the expression in filter causing the error due to the space.
The same reason, why the variable is not expanded in SQ.
Try making IsExprVar=False. Or, you can define the variable as string, and convert it to date in the expression.

Related

How to transfer data with multiple conditions with pgsql?

I would like to create a trigger with I can check the uploaded data and I can insert into another table as well. In the initial table (capture) I have 15 columns, but I would like to transfer only 5 columns (ring_number, code, year, date, species, location) to another table (ring).
The ring table is a background table in which I am collecting the combinations of ring_number and code, more specifically one ring_number could be paired only with one code. There is one exception, when the code include "X", than it can be changed later, and in this case this code can be paired with more ring_number, and if originally belongs to the ring_number a code with "X" it can be changed later.
In the capture table, could be possible to upload the same combination of code and ring_number multiple times with a condition of a third column. But still the ring_number can be paired only with one code, with exceptions of codes included "X". The name of the conditional column is recapture. If recapture (boolean column type) is "true", then you can upload the combination of code and ring_number again. If it is "empty" or "no" you can upload only new combinations of code and ring_number. If somebody uploads old combinations then the following error message has to raise: this combination already exists, please check your data and if it is a recapture, then set the recapture column to yes.
Additionally: ring_number is a not null column, but code can be empty. And different ring_number can be paired with empty code than later can be paired with actual value.
I have several problems with my code:
1: I would like to define the exception to X with regex, and the X can be anywhere in the code. But can not manage the regex in a good way. It does just not work.
2: I write conditional checkpoint with recapture column and if I have an old combination this is work on the right way and say please set the recapture column to yes. But! If I set the recapture column to yes I get the same error message.
Could you help to solve these issues?
Here is my code:
Declare
a integer := 0;
b integer := 0;
c integer := 0;
d integer := 0;
Begin
IF new.code <> '' THEN
--Az 'a' means whether the given ring_number already exist in the database with a code, which is not empty
SELECT INTO a COUNT(*) FROM plover_captures PC WHERE PC.ring_number = new.ring_number AND PC.code <> new.code AND PC.code <> '' AND PC.code ~ '[X]{2}[\.]{1}[X]{2}[|]{1}[X]{2}[\.]{1}[X]{2}';
--Az 'b' means the given code already exist in the database with a ring_number
SELECT INTO b COUNT(*) FROM plover_captures PC WHERE PC.ring_number <> new.ring_number AND PC.code = new.code AND PC.code ~ '[X]{2}[\.]{1}[X]{2}[|]{1}[X]{2}[\.]{1}[X]{2}';
--Az 'c' how much times exist the given ring_number with the given code in the database
SELECT INTO c COUNT(*) FROM plover_captures PC WHERE PC.ring_number = new.ring_number AND PC.code = new.code AND PC.code ~ '[X]{2}[\.]{1}[X]{2}[|]{1}[X]{2}[\.]{1}[X]{2}';
--Az 'd' means the given combination already exist in ring table or not
SELECT INTO d COUNT(*) FROM plover_rings PC WHERE PC.ring_number = new.ring_number AND PC.code = new.code;
IF a > 0 THEN
raise exception 'This ring_number is already paired with another code before. %', new.ring_number;
END IF;
IF b > 0 THEN
raise exception 'This code is already paired with another ring_number before. %', new.code;
END IF;
IF c > 0 AND (new.rettrap IS null OR new.rettrap IS false) THEN
raise exception 'This ring_number and code pair is already in the database. So it is a rettrap but the rettrap attribute set to false or null. %, %, %', new.ring_number, new.code, new.rettrap;
END IF;
IF c = 0 AND new.rettrap IS true THEN
raise exception 'The rettrap attribute set to true but this ring_number and code pair is not in this database yet. %, %, %', new.ring_number, new.code, new.rettrap;
END IF;
IF c = 0 AND d = 0 THEN
Insert into plover_rings values(new.ring_number,new.code,new.species,new.location,new.year, new.date);
END IF;
END IF;
Return new;
End

Date format substitution in PL/SQL. Example: from 5y 6m 20d to 050620

I am writing a query where I need to perform a date format transformation to meet the specified requirements.
In the database which I have to search, the date format looks like the one in the example: 5y 6m 10d with spaces in between and with optional digits (10y 30d; 1m 23d; 6m are also valid) and they are always ordered (first years, then month and then days).
The format transformation should be the following:
10y 6m 10d => 100610
1y 10m 1d => 011001
6m 2d => 000602
So that the output is always a 6-digit number.
I tried writing regular expressions within REGEX_SUBSTR to isolate the tokens and then concatenate them together in the type of SELECT REGEXP_SUBSTR(text_source, '(\d+)*y') FROM database and I also tried using the REGEX_REPLACE function. Nevertheless, I am not able to perform the transformation to two digits per token without spaces, nor replace one pattern by another, I can only replace the pattern by another string.
Although I am able to output the token separation without spaces by writing the function above. I am not able to get the whole transformation. Is there any possibility of writing a RegEx and combining it with any of the PL/SQL functions in order to transform the dates stated on the list above ? I am also open to hear any other solutions not involving RegEx, I just thought it was sensible to make a proper use of them here.
Here is a simple solution in SQL.
you get the values for year, month and day e.g. with regexp_substr.
with nvl you set the value to 0 if there it is null.
lpad it with 0
with tab as(
select '10y 6m 10d' as str from dual union all
select '1y 10m 1d ' as str from dual union all
select '6m 2d ' as str from dual
)
select lpad(nvl(y,0), 2,'0') ||lpad(nvl(m,0), 2,'0')|| lpad(nvl(d,0), 2,'0')
from (
select rtrim(regexp_substr(str, '[0-9]{1,2}y', 1),'y') as y
,rtrim(regexp_substr(str, '[0-9]{1,2}m', 1),'m') as m
,rtrim(regexp_substr(str, '[0-9]{1,2}d', 1),'d') as d
from tab
)
;
LPAD(N
------
100610
011001
000602
I hope it works
declare
myDate_ varchar2(50) := REPLACE('1y 10m 81d',' ','');
year_ varchar2(50);
month_ varchar2(50);
day_ varchar2(50);
begin
if instrb(myDate_,'y',1,1)>0 then
year_ := lpad(regexp_substr(substr(myDate_,0,instrb(myDate_,'y',1,1)), '[^y]+',1 , 1),2,0);
end if;
if instrb(myDate_,'m',1,1)>0 then
month_ := lpad(regexp_substr(substr(myDate_,instrb(myDate_,'y',1,1)+1,instrb(myDate_,'m',1,1)), '[^m]+',1 , 1),2,0);
end if;
if instrb(myDate_,'d',1,1)>0 then
day_ := lpad(regexp_substr(substr(myDate_,instrb(myDate_,'m',1,1)+1,instrb(myDate_,'d',1,1)), '[^d]+',1 , 1),2,0);
end if;
dbms_output.put_line(year_||month_||day_);
end;

How to cast expression in select clause with high performance

I have a table and want to convert a column of type decimal to an integer based on a condition. I need to do this as performant as possible.
My currently working query looks like this:
select *, (case when C_CUSTKEY < 20 then 1 else 0 end) as bit
from TPCH.CUSTOMER
However I'm trying to improve this. Is it possible to do the conversion on the fly, e.g. something like this:
select *, cast((C_CUSTKEY < 20) as integer) as bit
from TPCH.CUSTOMER
Or even for a simpler version like:
select *, (C_CUSTKEY < 20) as bit
from TPCH.CUSTOMER
No matter what functionality I use, I always get the following error:
sql syntax error: incorrect syntax near "<"
Update 1
So for better understanding an actual usecase of what I want to do is as following:
select ( (case when col1 < x then 1 else 0 end)
+ (case when col2 > y then 2 else 0 end) ) as bitset
from TPCH.CUSTOMER
In general there can be a large number of 'case when' expressions (>100).
Now the first problem is that the expression is very slow and I need to improve performance so I wanted to know if its possible to to the conversion on the fly smth like 2 * to_int(col2 > y) but I cannot find a way to do this.
Second problem is when I have many 'case when' expressions then I get the following error: SQL internal parse tree depth exceeds its maximum: parse tree depth exceeds its maximum:255
Not quite sure about the use case here.
Generally speaking, you cannot have a different data type for the same column at the same time. That's a fundamental constraint of the relational data model.
If however, the requirement merely is to have an indicator for "value in C_CUSTKEY < 20" then using the CASE statement is a straightforward way to do this.
In order to avoid this evaluation at query time, you could create a calculated column for this.
alter table TPCH.CUSTOMER add ("BIT" integer
generated always as
case
when ("C_CUSTKEY" < 20)
then 1
else 0
end);
Concerning the syntax errors: well, you're just not using the correct syntax. Check the example above for a correctly working example.

insert data into table using the today() function

I have a silly question...
for some reason I just can't get it work...
I want to insert a row into an empty table using the today(function).
This is what I do:
insert into gal_risk_factor (RISK_FACTOR_ID, VALID_FROM_DTTM,
RISK_FACTOR_NM, EFFECTIVE_FROM_DTTM, EFFECTIVE_TO_DTTM)
values ("1",today(),
"GGG",
"01JAN1901:00:00:00"dt, "01JAN2999:00:00:00"dt
)
This is the error I get:
today(),
_____
22
202
ERROR 22-322: Syntax error, expecting one of the following: a quoted string, a numeric constant, a datetime constant,
a missing value, ), +, ',', -, MISSING, NULL, USER.
ERROR 202-322: The option or parameter is not recognized and will be ignored.
What am I missing here...?
Thank you in advance,
Gal.
I guess the VALUES list cannot contain function, only constants.
Try creating a macro var and use it:
%let today=%sysfunc(today());
insert into gal_risk_factor (RISK_FACTOR_ID, VALID_FROM_DTTM,
RISK_FACTOR_NM, EFFECTIVE_FROM_DTTM, EFFECTIVE_TO_DTTM)
values ("1", &today,
"GGG",
"01JAN1901:00:00:00"dt, "01JAN2999:00:00:00"dt
)
Edit:
In case VALID_FROM_DTTM is meant to store datetime values use a constant like this:
%let today_dttm=%sysfunc(dhms(%sysfunc(today()), 0, 0, 0));

PL/SQL optimize searching a date in varchar

I have a table, that contains date field (let it be date s_date) and description field (varchar2(n) desc). What I need is to write a script (or a single query, if possible), that will parse the desc field and if it contains a valid oracle date, then it will cut this date and update the s_date, if it is null.
But there are one more condition - there are must be exactly one occurence of a date in the desc. If there are 0 or >1 - nothing should be updated.
By the time I came up with this pretty ugly solution using regular expressions:
----------------------------------------------
create or replace function to_date_single( p_date_str in varchar2 )
return date
is
l_date date;
pRegEx varchar(150);
pResStr varchar(150);
begin
pRegEx := '((0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)\d\d)((.|\n|\t|\s)*((0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)\d\d))?';
pResStr := regexp_substr(p_date_str, pRegEx);
if not (length(pResStr) = 10)
then return null;
end if;
l_date := to_date(pResStr, 'dd.mm.yyyy');
return l_date;
exception
when others then return null;
end to_date_single;
----------------------------------------------
update myTable t
set t.s_date = to_date_single(t.desc)
where t.s_date is null;
----------------------------------------------
But it's working extremely slow (more than a second for each record and i need to update about 30000 records). Is it possible to optimize the function somehow? Maybe it is the way to do the thing without regexp? Any other ideas?
Any advice is appreciated :)
EDIT:
OK, maybe it'll be useful for someone. The following regular expression performs check for valid date (DD.MM.YYYY) taking into account the number of days in a month, including the check for leap year:
(((0[1-9]|[12]\d|3[01])\.(0[13578]|1[02])\.((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\.(0[13456789]|1[012])\.((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\.02\.((19|[2-9]\d)\d{2}))|(29\.02\.((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))
I used it with the query, suggested by #David (see accepted answer), but I've tried select instead of update (so it's 1 regexp less per row, because we don't do regexp_substr) just for "benchmarking" purpose.
Numbers probably won't tell much here, cause it all depends on hardware, software and specific DB design, but it took about 2 minutes to select 36K records for me. Update will be slower, but I think It'll still be a reasonable time.
I would refactor it along the lines of a single update query.
Use two regexp_instr() calls in the where clause to find rows for which a first occurrence of the match occurs and a second occurrence does not, and regexp_substr() to pull the matching characters for the update.
update my_table
set my_date = to_date(regexp_subtr(desc,...),...)
where regexp_instr(desc,pattern,1,1) > 0 and
regexp_instr(desc,pattern,1,2) = 0
You might get even better performance with:
update my_table
set my_date = to_date(regexp_subtr(desc,...),...)
where case regexp_instr(desc,pattern,1,1)
when 0 then 'N'
else case regexp_instr(desc,pattern,1,2)
when 0 then 'Y'
else 'N'
end
end = 'Y'
... as it only evaluates the second regexp if the first is non-zero. The first query might also do that but the optimiser might choose to evaluate the second predicate first because it is an equality condition, under the assumption that it's more selective.
Or reordering the Case expression might be better -- it's a trade-off that's difficult to judge and probably very dependent on the data.
I think there's no way to improve this task. Actually, in order to achieve what you want it should get even slower.
Your regular expression matches text like 31.02.2013, 31.04.2013 outside the range of the month. If you put year in the game,
it gets even worse. 29.02.2012 is valid, but 29.02.2013 is not.
That's why you have to test if the result is a valid date.
Since there isn't a full regular expression for that, you would have to do it by PLSQL really.
In your to_date_single function you return null when a invalid date is found.
But that doesn't mean there won't be other valid dates forward on the text.
So you have to keep trying until you either find two valid dates or hit the end of the text:
create or replace function fn_to_date(p_date_str in varchar2) return date is
l_date date;
pRegEx varchar(150);
pResStr varchar(150);
vn_findings number;
vn_loop number;
begin
vn_findings := 0;
vn_loop := 1;
pRegEx := '((0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)\d\d)';
loop
pResStr := regexp_substr(p_date_str, pRegEx, 1, vn_loop);
if pResStr is null then exit; end if;
begin
l_date := to_date(pResStr, 'dd.mm.yyyy');
vn_findings := vn_findings + 1;
-- your crazy requirement :)
if vn_findings = 2 then
return null;
end if;
exception when others then
null;
end;
-- you have to keep trying :)
vn_loop := vn_loop + 1;
end loop;
return l_date;
end;
Some tests:
select fn_to_date('xxxx29.02.2012xxxxx') c1 --ok
, fn_to_date('xxxx29.02.2012xxx29.02.2013xxx') c2 --ok, 2nd is invalid
, fn_to_date('xxxx29.02.2012xxx29.02.2016xxx') c2 --null, both are valid
from dual
As you are going to have to do try and error anyway one idea would be to use a simpler regular expression.
Something like \d\d[.]\d\d[.]\d\d\d\d would suffice. That would depend on your data, of course.
Using #David's idea you could filter the ammount of rows to apply your to_date_single function (because it's slow),
but regular expressions alone won't do what you want:
update my_table
set my_date = fn_to_date( )
where regexp_instr(desc,patern,1,1) > 0