I'm facing to a problem today: how to update the MARA table with custom and non-custom fields?
I found some solutions, but I would like to know what is the best solution.
I came from HCM module. On this module we have changelogs. So I would like to update the MARA table with log changes if possible.
Context:
Select one MARA entry from the table (OK)
Edit fields (OK)
Check for each fields, if the new value is already on the available values
Update the table
Logic:
DATA:
lt_mara TYPE TABLE OF mara,
ls_mara TYPE mara.
lv_matnr = '000000000024856';
* Seelct data
"" matnr from CONVERSION_EXIT_MATN1_INPUT
SELECT SINGLE * INTO ls_mara FROM mara WHERE matnr = lv_matnr.
* Modification
ls_mara-vlumn = '999.9'.
"ls_mara-z* = '...'.
* Checks : volumn is numeric, ...
" [...]
* Update
" [...]
I have only information about the MARA, no data about related tables.
Solution 1 - MATERIAL_MAINTAIN_DARK
Use function module MATERIAL_MAINTAIN_DARK.
CALL FUNCTION 'MATERIAL_MAINTAIN_DARK'
EXPORTING
kz_activ_cad = blank
flag_muss_pruefen = fest_x
sperrmodus = fest_e
max_errors = 0
p_kz_no_warn = fest_x " 'N' ?
kz_prf = blank " 's' ?
kz_verw = fest_x
kz_aend = fest_x
kz_dispo = fest_x
kz_test = blank
kz_mdip = blank
kz_mprp = blank
kz_ale = blank
kz_actv = blank
TABLES
AMARA_UEB = TMARA_UEB
AMERRDAT = lt_amerrdat
EXCEPTIONS
OTHERS = 7.
" Loop lt_amerrdat.
" CALL FUNCTION 'RPY_MESSAGE_COMPOSE' [...]
" WRITE:/ lv_errmsg.
" ROLLBACK WORK.
" or
" CALL FUNCTION 'DB_COMMIT'.
(I used this logic of code https://archive.sap.com/discussions/thread/169786)
Problem: I successfully executed the code, but now I caught some functional errors. If I correctly understand the functionality of this FM, the modification will be executed through a tcode (i.e.: MM01/02/03).
But, I don't know what was the initial tcode for each row and I have functional issues (i.e: Article category not correct, ...) depending of the tcode used.
Do you know how I can skip these checks? Or known the initial tcode?
Solution 2 - BAPI_MATERIAL_SAVEDATA
Use function module BAPI_MATERIAL_SAVEDATA.
This FM allow to update MARA table with standard fields + custom (via EXTENSION(X))
For information, my BAPI_TE_MARA & BAPI_TE_MARAX looks like:
MATERIAL (MATNR, char, 18)
.APPEND (ZBAPI_TE_MARAX)
NOCHANGE (BAPIUPDATE, char, 1)
I guess I have to add each Z* fields on it, before use this FM ?
Moreover, I didn't find a solution to update the fields of the table. If I'm checking the FM, I have some objects but the columns names are not the same.
How I can find the mapping between the fields on this FM and the fields on the MARA table ?
Solution 3
Has I make my checks on integrity on my code, I guess I can use a simple INSERT/UPDATE (MODIFY) ?
This should be the simplest solution.
CONCATENATE sy-mandt lv_matnr INTO lv_mara_key.
" Lock object
CALL FUNCTION 'ENQUEUE_E_TABLE'
EXPORTING
MODE_RSTABLE = 'E'
tabname = 'MARA'
varkey = lv_mara_key
EXCEPTIONS
foreign_lock = 1 system_failure = 2 OTHERS = 3.
ls_mara-ernam = sy-uname.
" ...
" std & custo
MODIFY mara FROM ls_mara.
" Unlock object
CALL FUNCTION 'DEQUEUE_E_TABLE'
EXPORTING
MODE_RSTABLE = 'E'
tabname = 'MARA'
varkey = lv_mara_key
EXCEPTIONS
foreign_lock = 1 system_failure = 2 OTHERS = 3.
I'm interested in all recommendations, tutorials or advices :)
Solution 2. Period. For details see the great comments from #chrisian and #dirk-trilsbeek.
In regards to your follow up question about the field mapping. The more user friendly names are great for people using the BAPI's from outside of SAP but they do make it hard to map to the fields we know inside SAP. Luckily SAP used the same data elements most of the time in these cases so that is one way to match them. Otherwise the different BAPIs often have conversion function modules to translate from the BAPI fields to the database fields. For the material BAPI you mention you can check the subroutines <TABLE>_UEBERGEBEN (this translates to <TABLE>_TRANSFER) where <TABLE> is MARA, MARC etc. Note that for instance the MARA_UEBERGEBEN routine calls the FM MAP2I_BAPI_MARA_TO_MARA_UEB and this FM has the translation from the BAPI structure to MARA, e.g. NET_WEIGHT maps to NTGEW.
Use Solution 1, BUT these custom fields should be assign to group beforehand! This should be done in customization by path:
Logistic General-> Material Master-> Field Selection-> Assign fields to field selection group
You should create new group if there are no groups yet. This group determines when the fields will be enabled/disabled/active/inactive etc.
The code should look like this:
CALL FUNCTION 'MATERIAL_MAINTAIN_DARK'
EXPORTING
sperrmodus = ' '
kz_prf = 'W'
max_errors = ' '
p_kz_no_warn = 'X'
kz_verw = 'X'
kz_aend = 'X'
kz_dispo = 'X'
kz_test = ' '
flag_muss_pruefen = ' '
call_mode = 'ACT'
IMPORTING
matnr_last = w_matnr_last
number_errors_transaction = w_nb_errors
TABLES
amara_ueb = t_amara_ueb
amarm_ueb = t_amarm_ueb
EXCEPTIONS
kstatus_empty = 1
tkstatus_empty = 2
t130m_error = 3
internal_error = 4
too_many_errors = 5
update_error = 6
OTHERS = 7.
IF sy-subrc <> 0.
MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO.
ELSE.
COMMIT WORK.
ENDIF.
Finally this is the solution choosen. We used BAPI_MATERIAL_SAVEDATA with the extension.
I commented the code to help you if you want to use it :)
FORM update_mara
USING
us_result TYPE mara. "Input entry of mara with masterdata to update in the system
DATA : ls_mara TYPE bapi_te_mara,
ls_head TYPE bapimathead,
lt_extensionin TYPE TABLE OF bapiparex,
ls_extensionin TYPE bapiparex,
lt_extensioninx TYPE TABLE OF bapiparexx,
ls_extensioninx TYPE bapiparexx,
ls_return TYPE bapiret2,
lv_x TYPE char41,
lv_mara TYPE string. "Container for all data of ls_mara
" MASTERDATA PART
MOVE-CORRESPONDING us_result TO ls_mara.
ls_mara-material = ls_head-material = us_result-matnr.
"At least the mendatory key field: 'material'
" DATA PART
" Use extension BAPI_TE_MARA
ls_extensionin-structure = 'BAPI_TE_MARA'.
lv_mara = ls_mara.
" Fill data into the extension
ls_extensionin+30(960) = lv_mara.
APPEND ls_extensionin TO lt_extensionin.
" FLAG PART
CLEAR lv_x.
DO 41 TIMES. "Don't forget to change 'lv_x TYPE char41' if needed
CONCATENATE lv_x 'X' INTO lv_x.
"Add an 'X' where you want to activate the update
"Here, it's for all fields
ENDDO. " End of the do loop for all fields of the mara
" Use extension BAPI_TE_MARAX
ls_extensioninx-structure = 'BAPI_TE_MARAX'.
ls_extensioninx-valuepart1(18) = us_result-matnr.
ls_extensioninx-valuepart1+19 = lv_x.
" The first part (18) of the extensioninx is the matnr and the next ones some 'X' flags to activate the update or not.
APPEND ls_extensioninx TO lt_extensioninx.
" PROCESS PART
CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
headdata = ls_head
IMPORTING
return = ls_return
TABLES
extensionin = lt_extensionin
extensioninx = lt_extensioninx
.
IF ls_return-type = 'E'.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
ENDIF. " End if of check of BAPI result
ENDFORM. " UPDATE_MARA
Solution #3 is simply NOT a VALID option in SAP systems.
Both of the solutions 1 and 2 are good. You could directly fill custo-fields, as you did it in the solution 3, after you changed standard fields with one of the FMs.
Related
I have an SQL view, MyView, containing the column ID of type nvarchar.
I wish to create an Excel query which:
takes the values from a column in an Excel sheet,
composes a string corresponding to a WHERE clause for the ID column,
calls the SQL query using this parameter string.
As far as I can tell, I am trying to achieve something very analogous to
Excel cell Value as SQL query where statement
My solution is working fine on my own computer. However, when I try to share my solution with a colleague who has the same reading permissions for the database, I end up with a 'query referencing another query' problem which throws a formula.firewall error, and no data is loaded.
My solution is as follows:
I have created a data connection, Parameters, from the column named ID in Excel which contains my ID values:
let
Source = Excel.CurrentWorkbook(){[Name="Parameters"]}[Content],
#"Changed type" = Table.TransformColumnTypes(Source,{{"ID", type text}})
in
#"Changed type"
Next, I create my parameter string as a new data source, fnGetParameters:
let
Source = Excel.CurrentWorkbook(){[Name="Parameters"]}[Content],
AddString = Table.AddColumn(Source, "Custom", each "ID = " & "'" & [ID] & "' OR "),
RemoveID = Table.SelectColumns(AddString,{"Custom"}),
Custom = Text.Combine(RemoveID[Custom], ""),
Parameter = Text.Start(Custom,Text.Length(Custom)-4)
in
Parameter
which creates a nice string looking like e.g. 'ID = '1' OR ID = '2' OR ID = '3'' depending on the values in the column ID in Parameters.
Finally, I try to create my query:
let
Source = Sql.Database("MyServer", "MyDatabase",[Query="
SELECT *
FROM [MyDatabase].[dbo].[MyView]
WHERE " & fnGetParameters])
in
#"Source"
Incidentally, when removing the WHERE clause from the code above, it works fine on any computer:
let
Source = Sql.Database("MyServer", "MyDatabase",[Query="
SELECT *
FROM [MyDatabase].[dbo].[MyView]"])
in
#"Source"
so it is somehow the addition of the parameter string which causes an error.
I currently have several functions that do the same thing. Each function add a value to a different table. I have a subject table, a format table, a studio table, a language table etc.. Each has its own function to add to it. These tables are then used to pull their values into separate dropdowns (GUI).
I would like to create a single function that insert values into each of these tables. I use lambda to pass the name of the column. With this I add an 's' to name the table (each table have the same name as the column but plural). Then I use the variable in different place in the function.
If I take the "subject" function. The database table is called "subjects," the column is called "subject" and the input box on the GUI is called "subject_add." Therefore to pull the value from the input box I use "subject_add.get()" Same for all other dropdowns but with different wording.
What I don't know is how to make the "subject_add.get()" work if I use a variable for the word "subject." I have here the function I am currently working on.
def add_to_dropdown(column):
table_name = column + "s"
element = column + "_add.get()"
if element:
conn = sqlite3.connect(conndb.data_source)
c = conn.cursor()
c.execute(f'INSERT INTO {table_name}({column}) VALUES (?)', (element,))
messagebox.showinfo("Interest", f"A new {column} has been added to the database")
conn.commit()
c.close()
conn.close()
else:
messagebox.showwarning("Warning", f"{column} field is empty!")
column_add.delete(0, tk.END)
The table_name variable looks fine. The element variable looks fine, but the "if element:" won't work, as well as the "element" in the SQL INSERT command.
Here is the button with it's lambda command that passes the word "subject" to the function.
subject_add_btn = ttk.Button(frame_add_items, text="add ", style='a.TButton', image=arrow, compound=tk.RIGHT, command=lambda: add_to_dropdown("subject"))
subject_add_btn.place(x=480, y=50, height=35, width=90)
Hopefully, this makes sense to you. It's not very easy to explain in words. It's easier to show the code.
To resolve the issue is used the eval() functions. Here are the changes I made for it to work well:
def add_to_dropdown(column):
# This function handle all dropdown
table_name = column + "s"
element = column + "_add.get()"
if eval(element):
conn = sqlite3.connect(conndb.data_source)
c = conn.cursor()
c.execute(f'INSERT INTO {table_name}({column}) VALUES (?)', (eval(element),))
Is there a specified behavior for updating the same column 2+ times in the same UPDATE query, as follows?
UPDATE tbl SET a = 5, b = 'something', a = 6 WHERE c = 'whatever';
Is there a standardized behavior for this, or might it vary between flavors of SQL (e.g. it is "undefined behavior")? A cursory test with sqlite seems to indicate they are executed left-to-right, so the last column value will be the resulting one, but that doesn't imply that will always be the case.
Edit: The reason I'm trying to do this is I'm testing some SQL injection for a class project. One of the fields in an UPDATE is unsafely injected, and I'm trying to use it to overwrite previously SET fields from the same query.
This isn't exactly the answer you're looking for but assuming that the text "something" is a field you are passing in and it isn't parameterized or escaped you may be able to do this. This all depends on how the query is being built and what database it is being run against.
UPDATE tbl SET a = 5, b = 'something'; UPDATE tbl set a = 6;--' WHERE c = 'whatever';
by entering the following in the user input
something'; UPDATE tbl set a = 6;--
This assumes that the query is built dynamically something like this
var query = "UPDATE tbl set a = 5, b = '" + userInput + "' WHERE c = 'whatever'";
Here is a relevant question: How does the SQL injection from the "Bobby Tables" XKCD comic work?
Is there any wrong in this SQL statement in VBA:
myConnection.Execute "update item_gallery set quantity = 'qtToTotal' where item = 'Desc' and gallery ='Gallery_Name'"
where
qtToTotal = qtTo + qtFrom
While Not myRecordSetItem.EOF
If !item_ID = myRecordSetItem.Fields("item_ID").Value Then
Desc = myRecordSetItem.Fields("Desc").Value
End If
myRecordSetItem.MoveNext
Wend
While Not myRecordSetGallery.EOF
If !To = myRecordSetGallery.Fields("Gallery_ID").Value Then
Gallery_Name = myRecordSetGallery.Fields("Gallery_Name").Value
End If
myRecordSetGallery.MoveNext
Wend
it always give me the message :
"data type missmatch in criteria expression"
This:
myConnection.Execute "update item_gallery set quantity = 'qtToTotal' where item = 'Desc' and gallery ='Gallery_Name'"
where
qtToTotal = qtTo + qtFrom
Looks like it should read more like this (but I'm not 100% as I have some questions to ask):
myConnection.Execute "UPDATE item_gallery" _
& " SET quantity = qtToTotal" _
& " WHERE item = Desc" _
& " AND gallery = Gallery_Name" _
& " AND qtToTotal = (qtTo + qtFrom)"
My guess is you're getting this error because you've surrounded what look like field names in single quotes ('), which is what you do when you're telling SQL that you're wanting a piece of text rather than one of the fields in your database.
Examples of when to use single quotes:
...when you want to refer to just a piece of text in a field formatted as text:
"SELECT * FROM tblNames WHERE FirstName = 'John'".
...when you want to refer to text that's currently in a field on an open form that is formatted as text:
"SELECT * FROM tblNames WHERE FirstName = '" & Me.txtFirstName & "'".
But in your case it looks like you want a field from the underlying tables, so you don't need any single quotes around the field name.
Your quantity field sounds like it could well be a numeric field and you've essentially asked it to try and put the piece of text "qtToTotal" in the quantity field, which it won't be able to do if that field is only setup to accept numbers.
A few questions though as I suspect there might be more problems here (and I'll update this based on your answers):
Are all these fields...
quantity
qtToTotal
item
Desc
gallery
qtToTotal
qtTo
qtFrom
...located in the table item_gallery?
If they're not then you need to create JOIN(s) in your SQL statement between the item_gallery table and whatever table(s) the other fields come from using a field that matches/relates the records from both tables.
What is the data type for each of these fields?
Where you've used an = between 2 fields, are both those fields the same data type? If not it will cause a mismatch.
Also: I'd advise against using "Desc" as a field name as DESC is a word reserved for "descending order" in an ORDER BY clause in SQL.
We have a fairly large Oracle database that we are able to connect via Microsoft Access and ODBC with read-only access. We work with a front-end system that does not match the structure behind the scenes and often I need to query the system via Microsoft Access. The problem is that we are not provided any documentation as to structure and the structure needs serious attention. Searching for the field that I need is very time consuming.
With our front end, I'm able to view the values that I want to query, and I know the key fields, but I need to find the field that contains the known value.
If I have a record where I know the value of field "A", and have the value of field "X", is it possible to query field "X"?
Front end shows
Student ID: 12345678
Payments: 23456
Back end
TechID: 12345678
???: 23456
Can I query "???"
You can do this by iterating over the collection of tables, and for each table, the collection of fields.
Open Database
Get all Tables
For Each Table
Get all Fields
For Each Field
If Field type is text ... and
If Field size is not TOO Long ...
Search for string
If found, write to a results bucket
Next
Next
Here is an example of code for cataloging tables (source here)
Public Function GenerateDataDictionary(aDataDictionaryTable As String)
'*** Usage: GenerateDataDictionary("MyDataDictionaryTable")
'*** Extracts the information about the tables for the data dictionary
'*** and inserts it to a table named aDataDictionaryTable
Dim tdf As TableDef, fldCur As Field, colTdf As TableDefs
Dim rstDatadict As Recordset
Dim i As Integer, j As Integer, k As Integer
Set rstDatadict = CurrentDb.OpenRecordset(aDataDictionaryTable)
Set colTdf = CurrentDb.TableDefs
'Go through the database and get a tablename
For Each tdf In CurrentDb.TableDefs
'Do what you want with the table names here.
rstDatadict.AddNew
rstDatadict.Update
rstDatadict.AddNew
rstDatadict![Table] = tdf.NAME
rstDatadict![Field] = "----------------------------"
rstDatadict![Display] = "----------------------------"
rstDatadict![Type] = ""
rstDatadict.Update
rstDatadict.AddNew
rstDatadict![Table] = "Table Description:"
For j = 0 To tdf.Properties.Count - 1
If tdf.Properties(j).NAME = "Description" Then
rstDatadict![Field] = tdf.Properties(j).Value
End If
Next j
rstDatadict.Update
rstDatadict.AddNew
rstDatadict.Update
For i = 0 To tdf.Fields.Count - 1
Set fldCur = tdf.Fields(i)
rstDatadict.AddNew
rstDatadict![Table] = tdf.NAME
rstDatadict![Field] = fldCur.NAME
rstDatadict![Size] = fldCur.Size
Select Case fldCur.Type
Case 1
FieldDataType = "Yes/No"
Case 4
FieldDataType = "Number"
Case 8
FieldDataType = "Date"
Case 10
FieldDataType = "String"
Case 11
FieldDataType = "OLE Object"
Case 12
FieldDataType = "Memo"
Case Else ' Other values.
FieldDataType = fldCur.Type
End Select
rstDatadict![Type] = FieldDataType
For j = 0 To tdf.Fields(i).Properties.Count - 1
If fldCur.Properties(j).NAME = "Description" Then
rstDatadict![DESCRIPTION] = fldCur.Properties(j).Value
End If
If fldCur.Properties(j).NAME = "Caption" Then
rstDatadict![Display] = fldCur.Properties(j).Value
End If
If fldCur.Properties(j).NAME = "Rowsource" Then
rstDatadict![LookupSQL] = fldCur.Properties(j).Value
End If
Next j
rstDatadict.Update
Next i
Debug.Print " " & tdf.NAME
Next tdf
End Function
You can catalog your findings in Access by making a table of field-names which joins to a table of table-names. Then your searches are based on the catalog instead of raw collections.
I reverse-engineered the schema for MAS 90 (with JobOps add-in) this way. There's no map, but I had a read-only ODBC connection which I used in precisely the way you propose. The purchasing accountant would give me a distinctive Product Number and I'd run it through this comprehensive engine. Over time I succeeded in distilling 700 tables comprising 18k fields down to 20 tables and a few hundred fields. That allowed us to export our data.
The answer to your question is simple. No, you cannot do that.
There are two solutions that I can think of. The first is to manually concatenate all the values together and then look for the row that contains the value. This is imperfect, but might work:
select *
from (select t.*, ('|'""col1||'|'||col2+'|' . . .||'|') as allcols
from t
) t
where instr('|23456|', allcols) > 0
This would find any row that has that value in a column. Probably close enough for what you want.
The second is to use UNPIVOT to do essentially the same thing.
I would strongly suggest that you invest a little bit of time to find the mapping between the fields, and then create a view in Oracle that has the field names as seen in the application. It sounds like this would save you a lot of effort in the medium term.