Check structure fields for being non-initial? - abap

I need to fetch records from csv file and process it. Initially I need to check if all the values are there or not (around 15 fields), if the value is initial (i.e. blank) I need to throw the error (output has to be written in a file for each record).
Current logic I am following is :
LOOP AT gt_filedata into gs_filedata.
IF gs_filedata-var1 IS INITIAL.
concatenate gv_msg text-001 into gv_msg SEPARATED BY ','.
ENDIF.
IF gs_filedata-var2 IS INITIAL.
concatenate gv_msg text-002 into gv_msg SEPARATED BY ','.
ENDIF.
" And so on...
ENDLOOP.
I need to know if there any function module or any other way to optimize my code and improve its performance.

Assuming, as your code indicates, that you want to produce, not to fetch (as you wrote) a csv line:
field-symbols: <lv_part> type any,
<ls_filedata> like line of gt_filedata.
data: lv_part type string,
lv_msg type string.
loop at gt_filedata assigning <ls_filedata>.
clear lv_msg.
do.
assign component sy-index of structure <ls_filedata> to <lv_part>.
if sy-subrc ne 0.
exit. " exit DO
endif.
if <lv_part> is not initial.
lv_part = <lv_part>. " Converts to type C
if lv_msg is not initial.
concatenate lv_msg ',' into lv_msg.
endif.
concatenate lv_msg lv_part into lv_msg.
endif.
enddo.
append lv_msg to lt_csv. " Or transfer line to output file here
endloop.

If you want not to check each workarea field via IF, you should read the dd03 from the type of the workarea. Then you need some hard coded strings, which partially identify those fields in the workarea. Then you need an inner loop where you loop over the dd03 workarea, with a "contains pattern" instruction previously, which guarantees, that You loop only over the relevant field names of the dd03. Then you need assign-component (inner loop over dd03l-fieldnameof-actual looped field) of structure YourWorkarea to <anyfieldsymbol>, which you created before.
If you use "casting" afterwards you can use rollname to cast the actual looped field to rollname which is a part of dd03l. This quarantees that a fieldsymbol typed by "any" can properly be filled and therefore checked for being initial more precisely.
Do you need some code? Here it is:
Define some variables:
lo_structdesc TYPE REF TO cl_abap_structdescr,
dfies_wa TYPE dfies,
dfies_tab TYPE STANDARD TABLE OF dfies,
lv_ident TYPE fieldname value 'INP_'. " imagine all fields start like this.
FIELD-SYMBOLS: <fsany> TYPE any.
FIELD-SYMBOLS: <fsanyv> TYPE any
Imagine you want to get the datatype of Your workarea ( there are several ways):
" get struct by type
lo_structdesc ?= cl_abap_structdescr=>describe_by_data( my_struct ).
" introspect components
dfies_tab = lo_structdesc->GET_DDIC_FIELD_LIST( ).
loop at dfies_tab into dfies_wa where fieldname cp lv_ident.
assign-component ( dfies_wa-fieldname ) of structure my_struct to <fsany> casting type (dfies_wa-rollname).
if <fsany> is assigned.
if <fsany> is initial.
" ERROR HERE
else.
" continue with loop.
endif.
endif.
endloop.
Untested shrunk copy out of my source for dynamic where statement creation, cut down to the most fitting parts, removed overhead, so untested.

Related

Number to String conversion function in ABAP

I want to show a message of type E for which I have to first create a string. The string has mixed string and integer variables to be joined.
Since only strings can be concatenated, I copy integer variable into string variable, make a whole string and concatenate.
Is there a conversion function such as to_string(integer_variable) that can convert integers to string?
PROGRAM abc.
DATA: im_acc_no TYPE i VALUE 100,
lv_acc_no TYPE string,
lv_msg TYPE string.
START-OF-SELECTION.
lv_acc_no = im_acc_no.
CONCATENATE 'Acnt# ' lv_acc_no ' does not exist' INTO lv_msg.
MESSAGE lv_msg TYPE 'E'.
There is the CONV operator (SAP help) which can do something similar to to_string but it is not allowed in the CONCATENATE, so won't help you in your scenario.
You could use the && operator (SAP help) to create the message in-place in the MESSAGE command like:
MESSAGE |Acnt# | && lv_acc_no && | does not exist| type 'E'.
Side note: do not use this variant of the MESSAGE command, it might be easy to program but it makes it hard to investigate where a message is being generated. For this reason it is better to actually create a message in SE91 and use that. Variable replacements (&) in the message also handle integers just fine.

PSQL: Invalid input syntax for integer on COPY

I have a .txt file with a plain list of words (one word on each line) that I want to copy into a table. The table was created in Rails with one row: t.string "word".
The same file loaded into another database/table worked fine, but in this case I get:
pg_fix_development=# COPY dictionaries FROM '/Users/user/Documents/en.txt' USING DELIMITERS ' ' WITH NULL as '\null';
ERROR: invalid input syntax for integer: "aa"
CONTEXT: COPY dictionaries, line 1, column id: "aa"
I did some googling on this but can't figure out how to fix it. I'm not well versed in SQL. Thanks for your help!
If you created that table in Rails then you almost certainly have two columns, not one. Rails will add an id serial column behind your back unless you tell it not to; this also explains your "input syntax for integer" error: COPY is trying to use the 'aa' string from your text file as a value for the id column.
You can tell COPY which column you're importing so that the default id values will be used:
copy dictionaries(word) from ....

Limitting character input to specific characters

I'm making a fully working add and subtract program as a nice little easy project. One thing I would love to know is if there is a way to restrict input to certain characters (such as 1 and 0 for the binary inputs and A and B for the add or subtract inputs). I could always replace all characters that aren't these with empty strings to get rid of them, but doing something like this is quite tedious.
Here is some simple code to filter out the specified characters from a user's input:
local filter = "10abAB"
local input = io.read()
input = input:gsub("[^" .. filter .. "]", "")
The filter variable is just set to whatever characters you want to be allowed in the user's input. As an example, if you want to allow c, add c: local filter = "10abcABC".
Although I assume that you get input from io.read(), it is possible that you get it from somewhere else, so you can just replace io.read() with whatever you need there.
The third line of code in my example is what actually filters out the text. It uses string:gsub to do this, meaning that it could also be written like this:
input = string.gsub(input, "[^" .. filter .. "]", "").
The benefit of writing it like this is that it's clear that input is meant to be a string.
The gsub pattern is [^10abAB], which means that any characters that aren't part of that pattern will be filtered out, due to the ^ before them and the replacement pattern, which is the empty string that is the last argument in the method call.
Bonus super-short one-liner that you probably shouldn't use:
local input = io.read():gsub("[^10abAB]", "")

Reading a character string of unknown length

I have been tasked with writing a Fortran 95 program that will read character input from a file, and then (to start with) simply spit it back out again.
The tricky part is that these lines of input are of varying length (no maximum length given) and there can be any number of lines within the file.
I've used
do
read( 1, *, iostat = IO ) DNA ! reads to EOF -- GOOD!!
if ( IO < 0 ) exit ! if EOF is reached, exit do
I = I + 1
NumRec = I ! used later for total no. of records
allocate( Seq(I) )
Seq(I) = DNA
print*, I, Seq(I)
X = Len_Trim( Seq(I) ) ! length of individual sequence
print*, 'Sequence size: ', X
print*
end do
However, my initial statements list
character(100), dimension(:), allocatable :: Seq
character(100) DNA
and the appropriate integers etc.
I guess what I'm asking is if there is any way to NOT list the size of the character strings in the first instance. Say I've got a string of DNA that is 200+ characters, and then another that is only 25, is there a way that the program can just read what there is and not need to include all the additional blanks? Can this be done without needing to use len_trim, since it can't be referenced in the declaration statements?
To progressively read a record in Fortran 95, use non-advancing input. For example:
CHARACTER(10) :: buffer
INTEGER :: size
READ (unit, "(A)", ADVANCE='NO', SIZE=size, EOR=10, END=20) buffer
will read up to 10 characters worth (the length of buffer) each time it is called. The file position will only advance to the next record (the next line) once the entire record has been read by a series of one or more non-advancing reads.
Barring an end of file condition, the size variable will be defined with the actual number of characters read into buffer each time the read statement is executed.
The EOR and END and specifiers are used to control execution flow (execution will jump to the appropriately labelled statement) when end of record or end of file conditions occur respectively. You can also use an IOSTAT specifier to detect these conditions, but the particular negative values to use for the two conditions are processor dependent.
You can sum size within a particular record to work out the length of that particular record.
Wrap such a non-advancing read in a loop that appropriately detects for end of file and end of record and you have the incremental reading part.
In Fortran 95, the length specification for a local character variable must be a specification expression - essentially an expression that can be safely evaluated prior to the first executable statement of the scope that contains the variable's declaration. Constants represent the simplest case, but a specification expression in a procedure can involve dummy arguments of that procedure, amongst other things.
Reading the entire record of arbitrary length in is then a multi stage process:
Determine the length of the current record by using a series of incremental reads. These incremental reads for a particular record finish when the end of record condition occurs, at which time the file position will have moved to the next record.
Backspace the file back to the record of interest.
Call a procedure, passing the length of the current record as a dummy argument. Inside that procedure have an character variable whose length is given by the dummy argument.
Inside that called procedure, read the current record into that character variable using normal advancing input.
Carry out further processing on that character variable!
Note that each record ends up being read twice - once to determine its length, the second to actually read the data into the correctly "lengthed" character variable.
Alternative approaches exist that use allocatable (or automatic) character arrays of length one. The overall strategy is the same. Look at the code of the Get procedures in the common ISO_VARYING_STRING implementation for an example.
Fortran 2003 introduces deferred length character variables, which can have their length specified by an arbitrary expression in an allocate statement or, for allocatable variables, by the length of the right hand side in an assignment statement. This (in conjunction with other "allocatable" enhancements) allows the progressive read that determines the record length to also build the character variable that holds the contents of the record. Your supervisor needs to bring his Fortran environment up to date.
Here's a function for Fortran 2003, which sets an allocatable string (InLine) of exactly the length of the input string (optionally trimmed), or returns .false. if end of file
function ReadLine(aunit, InLine, trimmed) result(OK)
integer, intent(IN) :: aunit
character(LEN=:), allocatable, optional :: InLine
logical, intent(in), optional :: trimmed
integer, parameter :: line_buf_len= 1024*4
character(LEN=line_buf_len) :: InS
logical :: OK, set
integer status, size
OK = .false.
set = .true.
do
read (aunit,'(a)',advance='NO',iostat=status, size=size) InS
OK = .not. IS_IOSTAT_END(status)
if (.not. OK) return
if (present(InLine)) then
if (set) then
InLine = InS(1:size)
set=.false.
else
InLine = InLine // InS(1:size)
end if
end if
if (IS_IOSTAT_EOR(status)) exit
end do
if (present(trimmed) .and. present(InLine)) then
if (trimmed) InLine = trim(adjustl(InLine))
end if
end function ReadLine
For example to do something with all lines in a file with unit "aunit" do
character(LEN=:), allocatable :: InLine
do while (ReadLine(aunit, InLine))
[.. something with InLine]
end do
I have used the following. Let me know if it is better or worse than yours.
!::::::::::::::::::::: SUBROUTINE OR FUNCTION :::::::::::::::::::::::::::::::::::::::
!__________________ SUBROUTINE lineread(filno,cargout,ios) __________________________
subroutine lineread(filno,cargout,ios)
Use reallocate,ErrorMsg,SumStr1,ChCount
! this subroutine reads
! 1. following row in a file except a blank line or the line begins with a !#*
! 2. the part of the string until first !#*-sign is found or to end of string
!
! input Arguments:
! filno (integer) input file number
!
! output Arguments:
! cargout (character) output chArActer string, converted so that all unecessay spaces/tabs/control characters removed.
implicit none
integer,intent(in)::filno
character*(*),intent(out)::cargout
integer,intent(out)::ios
integer::nlen=0,i,ip,ich,isp,nsp,size
character*11,parameter::sep='=,;()[]{}*~'
character::ch,temp*100
character,pointer::crad(:)
nullify(crad)
cargout=''; nlen=0; isp=0; nsp=0; ich=-1; ios=0
Do While(ios/=-1) !The eof() isn't standard Fortran.
READ(filno,"(A)",ADVANCE='NO',SIZE=size,iostat=ios,ERR=9,END=9)ch ! start reading file
! read(filno,*,iostat=ios,err=9)ch;
if(size>0.and.ios>=0)then
ich=iachar(ch)
else
READ(filno,"(A)",ADVANCE='no',SIZE=size,iostat=ios,EOR=9); if(nlen>0)exit
end if
if(ich<=32)then ! tab(9) or space(32) character
if(nlen>0)then
if(isp==2)then
isp=0;
else
isp=1;
end if
eend if; cycle;
elseif(ich==33.or.ich==35.or.ich==38)then !if char is comment !# or continue sign &
READ(filno,"(A)",ADVANCE='yes',SIZE=size,iostat=ios,EOR=9)ch; if(nlen>0.and.ich/=38)exit;
else
ip=scan(ch,sep);
if(isp==1.and.ip==0)then; nlen=nlen+1; crad=>reallocate(crad,nlen); nsp=nsp+1; endif
nlen=nlen+1; crad=>reallocate(crad,nlen); crad(nlen)=ch;
isp=0; if(ip==1)isp=2;
end if
end do
9 if(size*ios>0)call ErrorMsg('Met error in reading file in [lineread]',-1)
! ios<0: Indicating an end-of-file or end-of-record condition occurred.
if(nlen==0)return
!write(6,'(a,l)')SumStr1(crad),eof(filno)
!do i=1,nlen-1; write(6,'(a,$)')crad(i:i); end do; if(nlen>0)write(6,'(a)')crad(i:i)
cargout=SumStr1(crad)
nsp=nsp+1; i=ChCount(SumStr1(crad),' ',',')+1;
if(len(cargout)<nlen)then
call ErrorMsg(SumStr1(crad)// " is too long!",-1)
!elseif(i/=nsp.and.nlen>=0)then
! call ErrorMsg(SumStr1(crad)// " has unrecognizable data number!",-1)
end if
end subroutine lineread
I'm using Fortran 90 to do this:
X = Len_Trim( Seq(I) ) ! length of individual sequence
write(*,'(a<X>)') Seq(I)(1:X)
You can simply declare Seq to be a large character string and then trim it as your write it out. I don't know how kosher this solution is but it certainly works for my purpose. I know that some compilers do not support "variable format expressions", but there are various workarounds to do the same thing almost as simply.
GNU Fortran variable expression workaround.

How to write structures?

How can I show the value inside a structure? see below the example:
DATA: BEGIN OF line,
col1 TYPE i,
col2 TYPE i,
END OF line.
DATA: itab LIKE TABLE OF line,
jtab LIKE TABLE OF line.
DO 3 TIMES.
line-col1 = sy-index.
line-col2 = sy-index ** 2.
APPEND line TO itab.
ENDDO.
MOVE itab TO jtab.
line-col1 = 10. line-col2 = 20.
APPEND line TO itab.
IF itab GT jtab.
WRITE / 'ITAB GT JTAB'.
ENDIF.
Write: itab, jtab.
because i want to know why itab is greater than jtab?.
If you want to see the contents of a field purely for debugging purposes you can also just put a break point in your code and look at the contents in debugger.
Just don't leave the break point in productive code!
break-point.
"or use break yourusername <= this use is safer
EDIT:
You can also just use a session break-point, which does not require you to change the code (and will only be applicable to your user for the duration of the session):
In the system where you are running the program:
Open the Program
Select the line that you would like the program to stop on
Click the session Break-point button
The break-point icon will appear next to the line (you can also just click in the place where the icon appeared, to set/delete the break-point).
I assume that this is just a quick example and you don't want to use (parts of) this in a productive environment - so I ignore the other potential issues there are in your code.
Down to your question, you need to loop over your itab to access its values. You can then access a value like so:
DATA: ls_current_line LIKE line.
" ...
LOOP AT itab INTO ls_current_line.
WRITE / ls_current_line-col1.
ENDLOOP.
You could use function module REUSE_ALV_GRID_DISPLAY.
For example:
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
TABLES
t_outtab = itab.
ITAB is greater than JTAB because it contains more lines; ITAB has 4 lines while JTAB has 3 lines.
When it comes to internal tables, the GT operator first takes a look at the number of lines in the tables. More details on the comparison operators (for internal tables) can be found at http://help.sap.com/saphelp_nw04/helpdata/en/fc/eb3841358411d1829f0000e829fbfe/content.htm. [I see that your example is also taken from this help page.]