I am adding a new entry to a sorted internal table inside a loop. As the loop I'm in has a sort order that's different from that of the sorted table, I have to use an INSERT INTO statement instead of an APPEND TO as the latter risks violating the sort order causing a dump.
However, when I add that code, I get a syntax check warning with internal message code "MESSAGE GJK", in EPC it says:
Program: ZCL_CLASS Method METHOD_NAME Row: 301
Syntax check warning.
In the table "LT_TABLE_NAME" a row was to be changed,
deleted or inserted. It is not possible
to determine statically if a LOOP is active over "LT_TABLE_NAME"
Internal message code: MESSAGE GJK
Cannot be hidden using a pragma.
But "Cannot be hidden using a pragma" just doesn't work for me. I understand the reason for the warning but I know at build time with 100% certainty that no loop will be active on the internal table that I'm inserting new records into. Yet I cannot hide this warning. Aside from causing useless warnings while developing, in some environments I wouldn't be able to transport code with syntax check warnings in it!
Is there any way to suppress this insuppresible warning?
Failing that, is there any way to avoid it? I can probably do so by using a temporary unsorted table as an intermediate and then just APPENDing the rows into the sorted table, but I balk at creating a useless (million-row) internal table just to bypass what seems to be a glaring oversight.
This message cannot be supressed, as it has been already stated in your previous question.
However, we can get rid of the initial cause of the problem and it is the only right way of doing here.
This error reports that some operation on internal table was carried out using implicit index specification, as it described in detailed message:
During the program flow, the current LOOP row is used, this means INDEX sy-tabix is used. If no LOOP is active over the table at this time, the runtime error TABLE_ILLEGAL_STATEMENT occurs.
For the current case of such an implicit operation, no encompassing LOOP statement for the table can be statically found (using the syntax check).
For some reason compiler doesn't see your loop and therefore cannot find loop index. What can be done in that case:
Use INSERT wa INTO TABLE instead of short form of INSERT.
Use explicit index for you INSERT statement
INSERT wa INTO itab INDEX loopIdx.
ABAP documentation for INSERT wa INTO itab syntax variant confirms that this syntax requires LOOP:
This variant is only possible within a LOOP across the same table and if the addition USING KEY is not specified in the LOOP. Each row to be inserted can be inserted before the current row in the LOOP.
P.S. Full text of this message could be fetched using DOCU_CALL FM passing message code TRMSG_MESSAGE_GJK there. All message codes are stored in DOKIL table.
The most likely reason for getting this warning is actually a syntax error! It will happen whenever you've got a statement like the following:
INSERT [work area] INTO [internal table].
The actual syntax for insert into an itab requires INTO TABLE:
INSERT [work area] INTO TABLE [internal table].
The warning's description doesn't seem to match what is actually happening here. Presumably it's considering that the table might have a header area (which is not the case). If you run this code, you'll get a TABLE_ILLEGAL_STATEMENT dump with a much more descriptive error message:
An attempt was made to change, delete or add a row in internal table "[internal table]". There is no valid cursor for this table however.
This is actually the second time I've encountered this but it's such a confusing message that I didn't remember the solution. I didn't intend to self-answer when I posted this but I realised my mistake when I got the dump. I'm guessing the main problem is relying on syntax errors to tell me when I'm using incorrect syntax: syntax check apparently doesn't consider this an outright error even if it probably should.
Related
I'm trying to add data from a internal table to a custom one.
DATA: BEGIN OF TMP_CTRYGRP_T OCCURS 1000,
CTYGR TYPE /SAPSLL/CTYGR,
TEXT1 TYPE /SAPSLL/TEXT60,
END OF TMP_CTRYGRP_T.
SELECT ctygr, text1 FROM /SAPSLL/CTYGPT INTO TABLE #DATA(lt_countryGroupsTable)
LOOP AT lt_countryGroupsTable ASSIGNING FIELD-SYMBOL(<ls_countryGroups>).
APPEND <ls_countryGroups> TO TMP_CTRYGRP_T.
ENDLOOP.
Then I want to add the line in a custom Table Type ZZ_T_TAB
So I've tried to create a field-symbol of this table, creating an internal table from it, but none of the solutions I've tried was permitting me to add lines in that Custom table (even if the one in the program had the lines).
The problem I mainly encountered was:
are not mutually convertible in a Unicode program.
So my questions are:
Why does that error happen? Googling it didn't provide me an understandable answer
For the moment I'm using an internal table limited to 1000 rows. But I don't really know by advance the number of lines the search could provide. Is there any way to improve that?
How to add lines from any solution to my ZZ_T_TAB then? And afterwards how could I add other fields in the same table, for the rows already existing?
As some of you maybe understood, I'm quite a rookie in ABAP.
So if there's any useful link to understand all of that I would be happy if you can share it with me.
Why don't you directly select into the table?
Don't use OCCURS as it is declared obsolete and already forbidden in classes.
Declare your own structure as type and mark your custom internal table as TYPE STANDARD TABLE OF struct_type. This way, there will be no upper bounds
TYPES:
BEGIN OF struct_type,
CTYGR TYPE /SAPSLL/CTYGR,
TEXT1 TYPE /SAPSLL/TEXT60,
END OF struct_type.
DATA tmp_ctrygrp_t TYPE STANDARD TABLE OF struct_type WITH EMPTY KEY.
Why does that error happen? Googling it didn't provide me an
understandable answer
You cannot use APPEND with non-identical structures. You have to "convert" it before. Look up for the command MOVE-CORRESPODING in ABAP help (F1 on command in editor).
For the moment I'm using an internal table limited to 1000 rows. But I
don't really know by advance the number of lines the search could
provide. Is there any way to improve that?
Do not use OCCURS extension it is deprecated (as lausek wrote), old syntax.
How then to add lines from any solution to my ZZ_T_TAB ? And
afterwards how could I add other fields in the same table, for the
rows already existing?
You can modify a DB table various ways.:
1, Use UPDATE statement to directly update a field value.
2, Use MODIFY statement to modify field values from a (for example) pre-selected
structure.
Look up the UPDATE and MODIFY command in ABAP help, there are really helpful code examples.
Background
I am creating a database to tracks lab samples. I wish to put a restriction in place that prevents a technician from reporting multiple results for the same sample.
Current strategy
My query called qselReport lists all samples being reported.
SELECT tblResult.strSampleName, tblResult.ysnReport FROM tblResult WHERE (((tblResult.ysnReport)=True));
When a technician wishes to report a result for a given sample, I use a Before Change Event to check for that sample in qselReport (the code block below is my event macro N.B. it is not VBA).
If Updated("ysnReport") And Old.[ysnReport]=False Then
Look Up A Record In qselReport
Where Condition = [strSampleName]=[tblResult].[strSampleName]
Alias
RaiseError
Error Number 1
Error Description This sample is already being reported.
End If
That all works fine and dandy. The error message pops up if a second result is selected to report for a sample.
The problem
I like to keep things as sleek as possible, so I don't want qselReport unless it's absolutely necessary. So I made a slight adjustment to the LookupRecord block so that it operates on a SQL statement rather than on the query. Here's what that looks like (again N.B. not VBA, just a macro):
If Updated("ysnReport") And Old.[ysnReport]=False Then
Look Up A Record In SELECT tblResult.strSampleName, tblResult.ysnReport FROM tblResult WHERE [tblResult].[ysnReport]=True;
Where Condition = [strSampleName]=[tblResult].[strSampleName]
Alias
RaiseError
Error Number 1
Error Description This sample is already being reported.
End If
Now I get the error message every time that a result is reported, even if it's the first instance for that sample. From what I can tell, the issue is that the SQL statement WHERE clause does not filter the records to only those where ysnReport=True.
Can anyone explain why I can do LookupRecord in a query but not LookupRecord in an identical SQL statement? Thanks for the input.
If you want things as sleek as possible, at least performance-wise, a stored query should, in principle, outperform dynamic SQL.
Syntax-wise, I'm not familiar with the macro constructs, but I'd consider enclosing the select statement in parentheses if it accepts them and also adding an explicit alias. I suspect that alias would in turn need to be referenced in your WHERE condition:
Where Condition = MySelect.[strSampleName]=[tblResult].[strSampleName]
Alias MySelect
I found the solution to my problem. The SQL statement where clause needed to be moved to the LookupRecord data block Where condition. This works:
If Updated("ysnReport") And Old.[ysnReport]=False Then
Look Up A Record In SELECT tblResult.strSampleName, tblResult.ysnReport FROM tblResult;
Where Condition = [ysnReport]=True And [strSampleName]=[tblResult].[strSampleName]
Alias
RaiseError
Error Number 1
Error Description This sample is already being reported.
End If
I have an ABAP class method, say, select_something. select_something has an exporting parameter, say, et_result. et_result is of type standard table because the type of et_result cannot be determined until runtime.
The method sometimes gives a short dump saying With ABAP/4 Open SQL array select, the output table is too small at "select * into table et_result from (lv_tablename) where..."
Error analysis:
......in this particular case, the database table is 3806 bytes wide, but the internal table is only 70 bytes wide.
I tried "any table" too and the error is the same.
You could return a data reference. Your query will no longer fail, and you can assign the data to a correctly typed field symbol afterwards.
" Definition
class-methods select_all
importing
!tabname type string
returning
value(results) type ref to data.
...
...
" Implementation
method select_all.
data dref type ref to data.
create data dref type standard table of (tabname).
field-symbols <tab> type any table.
assign dref->* to <tab>.
select * from (tabname) into table <tab>.
get reference of <tab> into results.
endmethod.
Also, I agree with #vwegert that dynamic queries (and programming for that matter) should be avoided when possible.
What you're trying to do looks horribly wrong on many levels. NEVER use SELECT FROM (whatever) unless someone points a gun at your head AND the door is locked tight. You'll loose every kind of static error checking the system might be able to provide you with. For example, the compiler will no longer be able to tell you "Hey, that table you're reading from is 3806 bytes wide." It simply can't tell, even if you use constants. You'll find that out the hard way, producing short dumps, especially when switching between unicode and NUC systems, quite likely some in production systems. No fun.
(Actually there are a few - very very VERY few - good uses for dynamic table names in the SELECT statement. I need them about once every two to three years, and I code quite a lot weird stuff. Just avoid them wherever you can, even at the cost of writing more code. It's just not worth the trouble fixing broken stuff later.)
Then, changing the generic formal parameter type does not do anything to the type of the actual parameter. If you pass a STANRDARD TABLE OF mandt WITH DEFAULT KEY to your method, that table will have lines of 3 characters. It will be a STANDARD TABLE, and as such, it will also be an ANY TABLE, and that's about it. You can twist the generic types anywhere you like, there's no way to enforce correctness using generic types the way you use them. It's up to the caller to make sure that all the right types are used. That's a bad way to fly.
First off, I agree with vwegert's response, try to avoid dynamic sql selections if you can
That said, check the short dump. If the error is an exception class, you can wrap the SELECT statement in a try/catch block and at least stop it from dumping.
You can also try "INTO CORRESPONDING FIELDS OF TABLE et_result". If ET_RESULT is dynamic, you might have to cast it into the proper structure using RTTS. This might give you some ideas...
Couldn't agree more to vwegert, but if there is absolutely no other way (and there usually is) of performing your task than using dynamic select statements and dynamically typed parameters, do some checks on the type of the table and the parameter at runtime.
Use CL_ABAP_TYPEDESCR and its subclasses to do so.
This way, you can handle errors at runtime without your program dumping,
But as vwegert said, this dynamic stuff is pure evil and will most certainly break at some point during runtime. Adding the necessary error handling will most likely be a lot more work and a lot harder than redesigning your code to none dynamic SQL and typed parameters
I have an SQLCLR trigger. It contains a large and messy SELECT inside, with parts like:
(CASE WHEN EXISTS(SELECT * FROM INSERTED I WHERE I.ID = R.ID)
THEN '1' ELSE '0' END) AS IsUpdated -- Is selected row just added?
as well as JOINs etc. I like to have the result as a single table with all included.
Question 1. Can I move this SELECT to SQL Server side? If yes, how to do this?
Saying "move", I mean to create a stored procedure or something else that can be executed before reading dataset in while cycle.
The 2 following questions make sense only if answer is "yes".
Why do I want to move SELECT? First off, I don't like mixing SQL with C# code. At second, I suppose that server-side queries run faster, since the server have more chances to cache them.
Question 2. Am I right? Is it some sort of optimizing?
Also, the SELECT contains constant strings, but they are localizable. For instance,
WHERE R.Status = "Enabled"
"Enabled" should be changed for French, German etc. So, I want to write 2 static methods -- OnCreate and OnDestroy -- then mark them as stored procedures. When registering/unregistering my assembly on server side, just call them respectively. In OnCreate format the SELECT string, replacing {0}, {1}... with required values from the assembly resources. Then I can localize resources only, not every script.
Question 3. Is it good idea? Is there an existing attribute to mark methods to be executed by SQL Server automatically after (un)registartion an assembly?
Regards,
Well, the SQL-CLR trigger will also execute on the server, inside the server process - so that's server-side as well, no benefit there.
But I agree - triggers ought to be written in T-SQL whenever possible - no real big benefit in having triggers in C#.... can you show the the whole trigger code?? Unless it contains really odd balls stuff, it should be pretty easy to convert to T-SQL.
I don't see how you could "move" the SELECT to the SQL side and keep the rest of the code in C# - either your trigger is in T-SQL (my preference), or then it is in C#/SQL-CLR - I don't think there's any way to "mix and match".
To start with, you probably do not need to do that type of subquery inside of whatever query you are doing. The INSERTED table only has rows that have been updated (or inserted but we can assume this is an UPDATE Trigger based on the comment in your code). So you can either INNER JOIN and you will only match rows in the Table with the alias of "R" or you can LEFT JOIN and you can tell which rows in R have been updated as the ones showing NULL for all columns were not updated.
Question 1) As marc_s said below, the Trigger executes in the context of the database. But it goes beyond that. ALL database related code, including SQLCLR executes in the database. There is no client-side here. This is the issue that most people have with SQLCLR: it runs inside of the SQL Server context. And regarding wanting to call a Stored Proc from the Trigger: it can be done BUT the INSERTED and DELETED tables only exist within the context of the Trigger itself.
Question 2) It appears that this question should have started with the words "Also, the SELECT". There are two things to consider here. First, when testing for "Status" values (or any Lookup values) since this is not displayed to the user you should be using numeric values. A "status" of "Enabled" should be something like "1" so that the language is not relevant. A side benefit is that not only will storing Status values as numbers take up a lot less space, but they also compare much faster. Second is that any text that is to be displayed to the user that needs to be sensitive to language differences should be in a table so that you can pass in a LanguageId or LocaleId to get the appropriate French, German, etc. strings to display. You can set the LocaleId of the user or system in general in another table.
Question 3) If by "registration" you mean that the Assembly is either CREATED or DROPPED, then you can trap those events via DDL Triggers. You can look here for some basics:
http://msdn.microsoft.com/en-us/library/ms175941(v=SQL.90).aspx
But CREATE ASSEMBLY and DROP ASSEMBLY are events that are trappable.
If you are speaking of when Assemblies are loaded and unloaded from memory, then I do not know of a way to trap that.
Question 1.
http://www.sqlteam.com/article/stored-procedures-returning-data
Question 3.
It looks like there are no appropriate attributes, at least in Microsoft.SqlServer.Server Namespace.
When I tried to commit a sql query I ended up with the following message:
cannot insert duplicate key row in object [] with unique index '[]$[]$[]$'
What does that mean? and what "'[]$[]$[]$'" is composed of?
You insert something with the same primary key maybe?
It would be good if you show us the code that executes it ...
'[]$[]$[]$' --> I think it's generated by your code.
Try this
select * from yourtable where your_primary_key = '[]$[]$[]$' ;
I'm just guessing since you didn't provide source code.
A duplicate key error means that you have tried to insert a row with the same key value as some other row already indexed by the named index.
The weird message formatting suggests that although you got an error, it was not being handled well by the DBMS - or the code in the client responsible for formatting the error message has mishandled it. In the former case, it might mean that you are running into some sort of bug in the DBMS (which one are you using?).