Using BETWEEN operator in a WHERE clause with dates from an internal table - abap

I have an internal table populated with start and end dates for each type of period. I want to use this internal table in a WHERE clause of an SQL query to select items whose start and end dates are within the open period of their respective type.
TYPES: BEGIN OF s_openprd,
TETXT TYPE TETXT,
fromdate TYPE d,
todate TYPE d,
END OF s_openprd.
DATA: it_openprd TYPE TABLE OF s_openprd WITH KEY TETXT.
SELECT * FROM FPLT
INNER JOIN #it_openprd AS OP ON FPLT~TETXT = OP~TETXT
WHERE FPLT~FKDAT BETWEEN OP~fromdate AND OP~todate
AND FPLT~NFDAT BETWEEN OP~fromdate AND OP~todate
However I get the error saying that OP~fromdate should be of a compatible type to be used as an operator with BETWEEN. The types listed include the date type d.
I've tried replacing BETWEEN with regular >= and <= operators:
SELECT * FROM FPLT
INNER JOIN #it_openprd AS OP ON FPLT~TETXT = OP~TETXT
WHERE FPLT~FKDAT >= OP~fromdate AND FPLT~FKDAT <= OP~todate
AND FPLT~NFDAT >= OP~fromdate AND FPLT~NFDAT <= OP~todate
But the query returns incorrect results.
I assume the ABAP type d is incompatible with SQL type d ?
How can I use an internal table to restrict the selection in this way ?

Nothing prevents you from using FOR ALL ENTRIES instead of joining with internal table, if you ABAP version does not support it.
Regarding "incorrect results" I agree with Sandra, BETWEEN and LT/GT have totally identical sense, so it is more a matter of what you expect than correctness. I'd rather utilize standard logic for dealing with the issue that bothers you:
The problem with FPLT-NFDAT and FPLT-FKDAT is that their order is not consistent. In one entry, the value of NFDAT may be anterior to FKDAT and in another entry it's the opposite.
Following the same approach for you SQL query, you can write something like this:
TYPES: BEGIN OF ty_fplt,
fplnr TYPE fplnr,
fkdat TYPE fkdat,
nfdat TYPE nfdat,
END OF ty_fplt,
tt_fplt TYPE STANDARD TABLE OF ty_fplt WITH NON-UNIQUE KEY fkdat nfdat.
DATA(lt_fplt_base) = VALUE tt_fplt( ).
SELECT fplnr, CASE WHEN nfdat < fkdat THEN nfdat ELSE fkdat END AS fkdat,
CASE WHEN nfdat < fkdat THEN fkdat ELSE nfdat END AS nfdat
FROM fplt
INTO TABLE #lt_fplt_base.
SELECT *
FROM fplt AS f
INTO TABLE #DATA(result)
FOR ALL ENTRIES IN #lt_fplt_base
WHERE f~fplnr = #lt_fplt_base-fplnr
AND f~fkdat >= #lt_fplt_base-fkdat
AND f~nfdat <= #lt_fplt_base-nfdat.
Don't take it as a rule of thumb, it is just a quick suggestion.
P.S. Joining by text field INNER JOIN #it_openprd AS OP ON FPLT~TETXT = OP~TETXT does not make sense in any context. Text/string fields are often ambiguous, they often contain control characters, whitespaces, etc., which make them useless for primary key.

Related

How to write an Open SQL statement with substring in the JOIN ON condition? [duplicate]

I have the following select statement in ABAP:
SELECT munic~mandt VREFER BIS AB ZZELECDATE ZZCERTDATE CONSYEAR ZDIMO ZZONE_M ZZONE_T USAGE_M USAGE_T M2MC M2MT M2RET EXEMPTMCMT EXEMPRET CHARGEMCMT
INTO corresponding fields of table GT_INSTMUNIC_F
FROM ZCI00_INSTMUNIC AS MUNIC
INNER JOIN EVER AS EV on
MUNIC~POD = EV~VREFER(9).
"where EV~BSTATUS = '14' or EV~BSTATUS = '32'.
My problem with the above statement is that does not recognize the substring/offset operation on the 'ON' clause. If i remove the '(9) then
it recognizes the field, otherwise it gives error:
Field ev~refer is unknown. It is neither in one of the specified tables
nor defined by a "DATA" statement. I have also tried doing something similar in the 'Where' clause, receiving a similar error:
LOOP AT gt_instmunic.
clear wa_gt_instmunic_f.
wa_gt_instmunic_f-mandt = gt_instmunic-mandt.
wa_gt_instmunic_f-bis = gt_instmunic-bis.
wa_gt_instmunic_f-ab = gt_instmunic-ab.
wa_gt_instmunic_f-zzelecdate = gt_instmunic-zzelecdate.
wa_gt_instmunic_f-ZZCERTDATE = gt_instmunic-ZZCERTDATE.
wa_gt_instmunic_f-CONSYEAR = gt_instmunic-CONSYEAR.
wa_gt_instmunic_f-ZDIMO = gt_instmunic-ZDIMO.
wa_gt_instmunic_f-ZZONE_M = gt_instmunic-ZZONE_M.
wa_gt_instmunic_f-ZZONE_T = gt_instmunic-ZZONE_T.
wa_gt_instmunic_f-USAGE_M = gt_instmunic-USAGE_M.
wa_gt_instmunic_f-USAGE_T = gt_instmunic-USAGE_T.
temp_pod = gt_instmunic-pod.
SELECT vrefer
FROM ever
INTO wa_gt_instmunic_f-vrefer
WHERE ( vrefer(9) LIKE temp_pod ). " PROBLEM WITH SUBSTRING
"AND ( BSTATUS = '14' OR BSTATUS = '32' ).
ENDSELECT.
WRITE: / sy-dbcnt.
WRITE: / 'wa is: ', wa_gt_instmunic_f.
WRITE: / 'wa-ever is: ', wa_gt_instmunic_f-vrefer.
APPEND wa_gt_instmunic_f TO gt_instmunic_f.
WRITE: / wa_gt_instmunic_f-vrefer.
ENDLOOP.
itab_size = lines( gt_instmunic_f ).
WRITE: / 'Internal table populated with', itab_size, ' lines'.
The basic task i want to implement is to modify a specific field on one table,
pulling values from another. They have a common field ( pod = vrefer(9) ). Thanks in advance for your time.
If you are on a late enough NetWeaver version, it works on 7.51, you can use the OpenSQL function LEFT or SUBSTRING. Your query would look something like:
SELECT munic~mandt VREFER BIS AB ZZELECDATE ZZCERTDATE CONSYEAR ZDIMO ZZONE_M ZZONE_T USAGE_M USAGE_T M2MC M2MT M2RET EXEMPTMCMT EXEMPRET CHARGEMCMT
FROM ZCI00_INSTMUNIC AS MUNIC
INNER JOIN ever AS ev
ON MUNIC~POD EQ LEFT( EV~VREFER, 9 )
INTO corresponding fields of table GT_INSTMUNIC_F.
Note that the INTO clause needs to move to the end of the command as well.
field(9) is a subset operation that is processed by the ABAP environment and can not be translated into a database-level SQL statement (at least not at the moment, but I'd be surprised if it ever will be). Your best bet is either to select the datasets separately and merge them manually (if both are approximately equally large) or pre-select one and use a FAE/IN clause.
They have a common field ( pod = vrefer(9) )
This is a wrong assumption, because they both are not fields, but a field an other thing.
If you really need to do that task through SQL, I'll suggest you to check native SQL sentences like SUBSTRING and check if you can manage to use them within an EXEC_SQL or (better) the CL_SQL* classes.

SQL regex and field

I want to change the query to return multiply values in extra_fields, how can I change the regex? Also I don't understand what extra_fields is - is it a field? If so why it is not called with the table prefix like i.extra_fields?
SELECT i.*,
CASE WHEN i.modified = 0 THEN i.created ELSE i.modified END AS lastChanged,
c.name AS categoryname,
c.id AS categoryid,
c.alias AS categoryalias,
c.params AS categoryparams
FROM #__k2_items AS i
LEFT JOIN #__k2_categories AS c ON c.id = i.catid
WHERE i.published = 1
AND i.access IN(1,1)
AND i.trash = 0
AND c.published = 1
AND c.access IN(1,1)
AND c.trash = 0
AND (i.publish_up = '0000-00-00 00:00:00'
OR i.publish_up <= '2013-06-12 22:45:19'
)
AND (i.publish_down = '0000-00-00 00:00:00'
OR i.publish_down >= '2013-06-12 22:45:19'
)
AND extra_fields REGEXP BINARY '(.*{"id":"2","value":\["[^\"]*1[^\"]*","[^\"]*2[^\"]*","[^\"]*3[^\"]*"\]}.*)'
ORDER BY i.id DESC
The extra_fields is a column of the #__k2_items table. The table qualifier can be omitted, because it is not ambiguous in this query. The column is JSON encoded. That is a serialization format used to store information which is not searchable by design. Applying a RegExp may work one day, but fail another day, since there is no guarantee for id preceeding value (as in your example).
The right way
The right way to filter this is to ignore the extra_fields condition in the SQL query an evaluate in the resultset instead. Example:
$rows = $db->loadObjectList('id');
foreach ($rows as $id => $row) {
$extra_fields = json_decode($row->extra_fields);
if ($extra_fields->id != 2) {
unset($rows[$id]);
}
}
The short way
If you can't change the database layout (which is true for extensions you want to keep updateable), you must split the condition into two, because there is no guarantee for a certain order of the subfields. For some reason, one day value may occur before id. So change your query to
...
AND extra_fields LIKE '%"id":"2"%'
AND extra_fields REGEXP BINARY '"value":\[("[^\"]*[123][^\"]*",?)+\]'
Prepare an intermediate table to hold the contents of extra_fields. Each extra_fields field will be converted into a series of records. Then do a join.
Create a trigger and cronjob to keep the temp table in sync.
Another way is to write UDF in Perl that will decode the field, but AFAIK it is not indexable in mysql.
Using an external search engine is out of scope.
Ok, i didnt want to change the db strucure, i gost some help and changed the regex intoAND extra_fields REGEXP BINARY '(.*{"id":"2","value":\[("[^\"]*[123][^\"]*",?)+\]}.*)'
and i got the right resaults
Thanks

Getting table(records) to update properply using the MERGE Statement

Good morning everyone!
Below is a piece of code I stitched together: I used a CTE to grab the records(data) from a link table and than convert strings to dates, than use the merge statement to get the data into a local table:
I am having a problem with the column(field) LAST_RACE_DATE this field is set to NULL and is not required but it does not update with my current set up. What I am trying to accomplished is for this field to populate when data is entered but also update, meaning it should also update with NULL.
So if the field has a specific date, and a new date is entered in the remote database, this field should update as well, even if the data is deleted in the back end, it should also remove the local table data for this field.
WITH CTE AS(
SELECT MEMBER_ID
,[MEMBER_DATE] = MAX(CONVERT(DATE, MEMBER_DATE))
,RACE_DATE = MAX(CONVERT(DATE, RACE_DATE))
,LAST_RACE_DATE = MAX(CONVERT(DATE, LAST_RACE_DATE))
FROM [EXAMPLE].[dbo].[LINKED_MEMBER_DATA]
WHERE (MEMBER_DATE IS NOT NULL) AND (ISDATE(MEMBER_DATE)<> 0) AND (RACE_DATE IS NOT NULL) AND (ISDATE(RACE_DATE)<> 0)
AND (LAST_RACE_DATE IS NULL) OR (ISDATE(LAST_RACE_DATE)<> 0)
GROUP BY MEMBER_ID)
MERGE dbo.LINKED_MEMBER_DATA AS Target
USING (SELECT
MEMBER_ID, MEMBER_DATE, RACE_DATE, LAST_RACE_DATE
FROM CTE
GROUP BY MEMBER_ID, RACE_DATE, LAST_RACE_DATE)AS SOURCE ON (Target.MEMBER_ID = SOURCE.MEMBER_ID)
WHEN MATCHED AND
(Target.MEMBER_DATE) <> (SOURCE.MEMBER_DATE)
OR (Target.RACE_DATE) <> (SOURCE.RACE_DATE)
OR ISNULL(TARGET.LAST_RACE_DATE , Target.LAST_RACE_DATE) <> ISNULL(SOURCE.LAST_RACE_DATE, SOURCE.LAST_RACE_DATE)
THEN UPDATE SET
Target.MEMBER_DATE = SOURCE.MEMBER_DATE
,Target.RACE_DATE = SOURCE.RACE_DATE
,Target.LAST_RACE_DATE = SOURCE.LAST_RACE_DATE
WHEN NOT MATCHED BY TARGET THEN
INSERT(
MEMBER_ID, MEMBER_DATE, RACE_DATE, LAST_RACE_DATE)
VALUES (Source.MEMBER_ID, Source.MEMBER_DATE, Source.RACE_DATE, Source.LAST_RACE_DATE);
I also tried this:
ISNULL(Target.LAST_RACE_DATE,'N/A') <> ISNULL(SOURCE.LAST_RACE_DATE,'N/A')
But it generates the below error for dates conversion:
Conversion failed when converting date and/or time from character string.
Thanks a Million!!
Your current statement is failing because the ISNULLs that you have don't do anything (if one of the values is NULL the expression will evaluate to NULL), and NULL values don't compare. Your second attempt doesn't work because ISNULL requires the data types of the two values to be the same, so you could try eg ISNULL(Target.LAST_RACE_DATE, '1970-01-01') <> ISNULL(Source.LAST_RACE_DATE, '1970-01-01').
Another option would be to simply enumerate the different cases (eg, (((Source.LAST_RACE_DATE IS NULL AND Target.LAST_RACE_DATE IS NOT NULL) OR (Source.LAST_RACE_DATE IS NOT NULL AND Target.LAST_RACE_DATE IS NULL) OR (Source.LAST_RACE_DATE <> Target.LAST_RACE_DATE))). Enumerating the different situations makes the code a bit more verbose, but it can result in better performance (whether it is measurably better really depends on how much data you are processing).

Comparing Date Values in Access - Data Type Mismatch in Criteria Expression

i'm having an issue comparing a date in an access database. basically i'm parsing out a date from a text field, then trying to compare that date to another to only pull newer/older records.
so far i have everything working, but when i try to add the expression to the where clause, it's acting like it's not a date value.
here's the full SQL:
SELECT
Switch(Isdate(TRIM(LEFT(bc_testingtickets.notes, Instr(bc_testingtickets.notes, ' ')))) = false, 'NOT ASSIGNED!!!') AS [Assigned Status],
TRIM(LEFT(bc_testingtickets.notes, Instr(bc_testingtickets.notes, ' '))) AS [Last Updated Date],
bc_testingtickets.notes AS [Work Diary],
bc_testingtickets.ticket_id,
clients.client_code,
bc_profilemain.SYSTEM,
list_picklists.TEXT,
list_picklists_1.TEXT,
list_picklists_2.TEXT,
list_picklists_3.TEXT,
bc_testingtickets.createdate,
bc_testingtickets.completedate,
Datevalue(TRIM(LEFT([bc_TestingTickets].[notes], Instr([bc_TestingTickets].[notes], ' ')))) AS datetest
FROM list_picklists AS list_picklists_3
RIGHT JOIN (list_picklists AS list_picklists_2
RIGHT JOIN (list_picklists AS list_picklists_1
RIGHT JOIN (bc_profilemain
RIGHT JOIN (((bc_testingtickets
LEFT JOIN clients
ON
bc_testingtickets.broker = clients.client_id)
LEFT JOIN list_picklists
ON
bc_testingtickets.status = list_picklists.id)
LEFT JOIN bc_profile2ticketmapping
ON bc_testingtickets.ticket_id =
bc_profile2ticketmapping.ticket_id)
ON bc_profilemain.id =
bc_profile2ticketmapping.profile_id)
ON list_picklists_1.id = bc_testingtickets.purpose)
ON list_picklists_2.id = bc_profilemain.destination)
ON list_picklists_3.id = bc_profilemain.security_type
WHERE ( ( ( list_picklists.TEXT ) <> 'Passed'
AND ( list_picklists.TEXT ) <> 'Failed'
AND ( list_picklists.TEXT ) <> 'Rejected' )
AND ( ( bc_testingtickets.ticket_id ) <> 4386 ) )
GROUP BY bc_testingtickets.notes,
bc_testingtickets.ticket_id,
clients.client_code,
bc_profilemain.SYSTEM,
list_picklists.TEXT,
list_picklists_1.TEXT,
list_picklists_2.TEXT,
list_picklists_3.TEXT,
bc_testingtickets.createdate,
bc_testingtickets.completedate,
DateValue(TRIM(LEFT([bc_TestingTickets].[notes], Instr([bc_TestingTickets].[notes], ' '))))
ORDER BY Datevalue(TRIM(LEFT([bc_TestingTickets].[notes], Instr([bc_TestingTickets].[notes], ' '))));
the value i'm trying to compare against a various date is this:
DateValue(Trim(Left([bc_TestingTickets].[notes],InStr([bc_TestingTickets].[notes],' '))))
if i add a section to the where clause like below, i get the Data Type Mismatch error:
WHERE DateValue(Trim(Left([bc_TestingTickets].[notes],InStr([bc_TestingTickets].[notes],' ')))) > #4/1/2012#
i've even tried using the DateValue function around the manual date i'm testing with but i still get the mismatch error:
WHERE DateValue(Trim(Left([bc_TestingTickets].[notes],InStr([bc_TestingTickets].[notes],' ')))) > DateValue("4/1/2012")
any tips on how i can compare a date in this method? i can't change any fields in the database, ect, that's why i'm parsing the date in SQL and trying to manipulate it so i can run reports against it.
i've tried googling but nothing specifically talks about parsing a date from text and converting it to a date object. i think it may be a bug or the way the date is being returned from the left/trim functions. you can see i've added a column to the end of the SELECT statement called DateTest and it's obvious access is treating it like a date (when the query is run, it asks to sort by oldest to newest/newest to oldest instead of A-Z or Z-A), unlike the second column in the select.
thanks in advance for any tips/clues on how i can query based on the date.
edit:
i just tried the following statements in my where clause and still getting a mismatch:
CDate(Trim(Left([bc_TestingTickets].[notes],InStr([bc_TestingTickets].[notes],' ')))) > #4/1/2012#
CDate(Trim(Left([bc_TestingTickets].[notes],InStr([bc_TestingTickets].[notes],' ')))) >
CDate("4/1/2012") CDate(DateValue(Trim(Left([bc_TestingTickets].[notes],InStr([bc_TestingTickets].[‌​notes],' '))))) > #4/1/2012#
i tried with all the various combinations i could think of regarding putting CDate inside of DateValue, outside, ect. the CDate function does look like what i should be using though. not sure why it's still throwing the error.
here's a link to a screenshot showing the results of the query http://ramonecung.com/access.jpg. there's two screenshots in one image.
You reported you get Data Type Mismatch error with this WHERE clause.
WHERE DateValue(Trim(Left([bc_TestingTickets].[notes],
InStr([bc_TestingTickets].[notes],' ')))) > #4/1/2012#
That makes me wonder whether [bc_TestingTickets].[notes] can ever be Null, either because the table design allows Null for that field, or Nulls are prohibited by the design but are present in the query's set of candidate rows as the result of a LEFT or RIGHT JOIN.
If Nulls are present, your situation may be similar to this simple query which also triggers the data type mismatch error:
SELECT DateValue(Trim(Left(Null,InStr(Null,' '))));
If that proves to be the cause of your problem, you will have to design around it somehow. I can't offer a suggestion about how you should do that. Trying to analyze your query scared me away. :-(
It seems like you are having a problem with the type conversion. In this case, I believe that you are looking for the CDate function.
A problem might be the order of the date parts. A test in the Immediate window shows this
?cdate(#4/1/2012#)
01.04.2012
?cdate(#2012/1/4#)
04.01.2012
Write the dates backwards in the format yyyy/MM/dd and thus avoiding inadverted swapping of days and months!
DateValue("2012/1/4")
and
CDate(#2012/1/4#)

Issue with union query - select list not compatible

Been working on this query for some time now... Keep getting the error "Corrosponding select-list expressions are not compatible. I am selecting the same # of columns in each select statement.
create volatile table dt as (
SELECT
gcv.I_SYS_IDV,
gcv.i_pln,
gcv.c_typ_cov,
gcv.d_eff,
gcv.d_eff_pln,
gcv.c_sta,
gcv.d_sta,
gcv.c_mde_bft_fst,
gcv.a_bft_fst,
gcv.c_mde_bft_sec,
gcv.a_bft_sec,
gcv.c_mde_bft_trd,
gcv.a_bft_trd,
gcv.p_cre_hom,
gcv.c_cl_rsk,
gpv.c_val,
gpv.i_val,
gcv.c_pol,
gpv.i_prv
FROM Pearl_P.tltc906_gcv gcv,
pearl_p.tltc912_gpv gpv
WHERE gcv.i_pln > 0
AND gcv.i_pln = gpv.i_pln
and gpv.i_prv = '36'
and gcv.c_pol between 'lac100001' and 'lac100004'
UNION
SELECT
gcv.I_SYS_IDV,
gcv.i_pln,
gcv.c_typ_cov,
gcv.d_eff,
gcv.d_eff_pln,
gcv.c_sta,
gcv.d_sta,
gcv.c_mde_bft_fst,
gcv.a_bft_fst,
gcv.c_mde_bft_sec,
gcv.a_bft_sec,
gcv.c_mde_bft_trd,
gcv.a_bft_trd,
gcv.p_cre_hom,
gcv.c_cl_rsk,
gcv.c_pol,
gpv.i_val,
gpv.i_pln,
gpv.i_prv
FROM Pearl_P.tltc906_gcv gcv,
pearl_p.tltc912_gpv gpv
where NOT EXISTS(
SELECT 1
FROM pearl_p.tltc906_gcv gcv,
pearl_p.tltc912_gpv gpv
WHERE gcv.i_pln > 0
AND gcv.i_pln = gpv.i_pln
and gpv.i_prv = '36'
)
) with data
PRIMARY INDEX (i_sys_idv)
on commit preserve rows;
You should check the data types of each column. The data types must be compatible between each SELECT statement.
The last 4 values of your second select statement don't match the ones in your first statement. Try naming(using aliases) those columns the same thing(pairing them). To union you need to have the same columns between the sets.
Check out: http://msdn.microsoft.com/en-us/library/ms180026.aspx
The following are basic rules for combining the result sets of two
queries by using UNION:
The number and the order of the columns must be the same in all
queries.
The data types must be compatible.
Are the data types of each column the same in both portions of the query?
If the first character of the column name indicates the data type (i = integer, c = character, etc.) I'm guessing that the problem is with the second to last column in the select list. The first query is selecting gcv.c_pol, the second query is selecting gpv.i_pln.
Start commenting-out lines until it works. I.e., comment out all of the fields in each select list, and then one-by-one, un-comment the first one out, then the second, etc. You'll find it eventually.