I need to track all changes in database: update, insert, delete. I want know WHO and WHAT change.
Need log table with columns:
Date.
Table name.
Column.
Old value.
New value.
IP.
What is the best way to implement this?
Are there ready-made solutions?
I tried use triggers on update / insert / delete. This is a good solution? Or maybe I do not know something about the correct logging in mysql?
My trigger:
CREATE TRIGGER `user_update_trigger`
AFTER UPDATE ON `users`
FOR EACH ROW
BEGIN
DECLARE done int default false;
DECLARE col_name CHAR(255);
DECLARE counter INTEGER(11);
DECLARE column_cursor cursor for SELECT `column_name`
FROM `INFORMATION_SCHEMA`.`COLUMNS`
WHERE `TABLE_SCHEMA`='test'
AND `TABLE_NAME`='users';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
open column_cursor;
myloop: loop
fetch column_cursor into col_name;
if done then
leave myloop;
end if;
/*SET #old_val = OLD.{{col_name}}; <------ HOW GET VALUE? */
/*SET #new_val = NEW.{{col_name}};<------ HOW GET VALUE? */
if #old_val <> #new_val then
/*INSERT INTO `log` ....*/
end if;
end loop;
close column_cursor;
END;
enable mysql audit log filtering.
You can do something like
{
"filter": {
"class": [
{
"name": "connection",
"event": [
{ "name": "connect" },
{ "name": "disconnect" }
]
},
{ "name": "general" },
{
"name": "table_access",
"event": [
{ "name": "insert" },
{ "name": "delete" },
{ "name": "update" }
]
}
]
}
}
or
"event": [
{ "name": "select", "log": false },
{ "name": "insert", "log": true },
{ "name": "delete", "log": true },
{ "name": "update", "log": true }
]
Related
Is it possible to use JSON_MODIFY to update a dictionary value of <int,string>?
If my JSON is this:
{
"items": {
"1": "xxx",
"2": "xxx",
"3": "xxx"
}
}
I'm unable to add/update using the following:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.', #Id), #Value)
I get the error: JSON path is not properly formatted. Unexpected character '1' is found at position 10. obviously because property names can't be numeric. I can workaround the error using a <string,string> dictionary as follows:
{
"items": {
"i1": "xxx",
"i2": "xxx",
"i3": "xxx"
}
}
Then the following works fine:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.i', #Id), #Value)
However, now I'm tasked with extra work after deserializing the JSON to get the numeric ID I actually want. It's just a minor inconvenience, but I was hoping for a possible solution to keep my <int,string> dictionary. Any ideas?
I suppose I could use a dictionary like this <string,object> and simply ignore the keys when deserialized:
{
"items": {
"i1": { "id": 1, "name": "xxx" },
"i2": { "id": 1, "name": "xxx" },
"i3": { "id": 1, "name": "xxx" }
}
}
And then use
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.i', #Id), JSON_QUERY(CONCAT('{"id":', #Id, ',"name":"', #Value, '"}')))
I'm also not opposed to using an array as such:
{
"items": [
{ "id": 1, "name": "xxx" },
{ "id": 2, "name": "xxx" },
{ "id": 3, "name": "xxx" }
]
}
Unfortunately, I don't know a way to update/add when in this format, and the add/update has to be done in a single UPDATE statement.
I'm hoping for some magic formula to continue using my <int,string> dictionary.
Thanks to #Charlieface, the solution is quite simple. You can navigate to numeric dictionary keys via double quotes:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable
SET MyJsonColumn = JSON_MODIFY(ISNULL(MyJsonColumn, '{"items":{}}'), CONCAT('$.items."', #Id, '"'), #Value)
Currently I am generating some jsons with data with oracle for backend purposes and I'm struggling with complex and repetetive structions that I have to process manually.
For example I have this array of objects:
{
"infoColumnsWidgets": [
{
"widgetNamespace": "mot",
"widgetName": "info_column",
"orderNumber": 1,
"navigateToPage": null,
"widgetData": {
"title": "Fact",
"textPattern": "$v0",
"values": [
{
"id": "v0",
"type": "int",
"value": "200000"
}
]
}
},
{
"widgetNamespace": "mot",
"widgetName": "info_column",
"orderNumber": 2,
"navigateToPage": null,
"widgetData": {
"title": "Plan",
"textPattern": "$v0",
"values": [
{
"id": "v0",
"type": "int",
"value": "200000"
}
]
}
},
{
"widgetNamespace": "mot",
"widgetName": "info_column",
"orderNumber": 3,
"navigateToPage": null,
"widgetData": {
"title": "Prognosis",
"textPattern": "$v0",
"values": [
{
"id": "v0",
"type": "int",
"value": "100"
}
]
}
}
]
}
Certainly I generate it in a loop but this structure occurs often and I'd prefer to put it into some function to do the following:
function f_getTest return clob as
v_res clob;
begin
apex_json.initialize_clob_output;
apex_json.open_object;
apex_json.open_object('infoColumnsWidgets');
for rec in (select * from some_table_data)
loop
apex_json.write_raw(f_getWidgetJson(rec.param));
end loop;
apex_json.close_object;
apex_json.close_all;
v_res := apex_json.get_clob_output;
apex_json.free_output;
return v_res;
end;
But as far as I know there is no option to put one json into another using apex_json. I can try with some weird workarounds with putting some placeholders and replacing them in final clob but no, I don't want, please, don't make me do that.
Any ideas are super welcome
Does this help ? I took the example from oracle-base and moved the body code into a separate procedure. In the example below it is an inline procedure but nothing stops you from putting into a standalone procedure or a package.
DECLARE
PROCEDURE dept_object
IS
l_cursor SYS_REFCURSOR;
BEGIN
OPEN l_cursor FOR
SELECT d.dname AS "department_name",
d.deptno AS "department_number",
CURSOR(SELECT e.empno AS "employee_number",
e.ename AS "employee_name"
FROM emp e
WHERE e.deptno = d.deptno
ORDER BY e.empno) AS "employees"
FROM dept d
ORDER BY d.dname;
APEX_JSON.open_object;
APEX_JSON.write('departments', l_cursor);
APEX_JSON.close_object;
END;
BEGIN
APEX_JSON.initialize_clob_output;
dept_object;
DBMS_OUTPUT.put_line(APEX_JSON.get_clob_output);
APEX_JSON.free_output;
END;
/
I want to access over an array object in JSON by index with a variable. Consider the following code:
declare #pjson nvarchar(max)='{
"store":{
"storeId": 100,
"name": "TEST",
"lastUpdatedBy": "MULE",
"location": {
declare #pjson nvarchar(max)='{
"store":{
"storeId": 100,
"name": "TEST",
"lastUpdatedBy": "MULE",
"location": {
"addresses": [
{
"addressType": "MAIN",
"name": "Name1",
"name2": "Name2",
"address": "Address1",
"address2": "Address2",
"city": "City",
"lastUpdateBy": "MULE"
},
{
"addressType": "SECONDARY",
"name": "Name1",
"name2": "Name2",
"address": "Address1",
"address2": "Address2",
"city": "City",
"lastUpdateBy": "MULE"
},
{
"addressType": "BILLING",
"name": "Name1",
"name2": "Name2",
"address": "Address1",
"address2": "Address2",
"city": "City",
"lastUpdateBy": "MULE"
}
]
}
}
}'
Declare #counter1 INT = 0;
Print JSON_VALUE(#pjson,N'lax $.store.location.addresses[#counter1].addressType')
I get an error:
JSON path is not properly formatted. Unexpected character '#' is found
at position 31.
If I try directly by passing number as
Declare #counter1 INT = 0;
Print JSON_VALUE(#pjson,N'lax $.store.location.addresses[0].addressType')
I get the expected result
MAIN
Is there something that I am missing while passing the variable?
I don't think that you can use a T-SQL variable directly as part of the path parameter in the JSON_VALUE() call, but you may try one of the following approaches:
Concatenate the #counter variable in the path parameter (SQL Server 2017 is needed).
Parse the JSON with OPENJSON() and the appropriate WHERE clause.
JSON:
DECLARE #counter INT = 0;
DECLARE #pjson nvarchar(max) = N'{
"store":{
"storeId":100,
"name":"TEST",
"lastUpdatedBy":"MULE",
"location":{
"addresses":[
{
"addressType":"MAIN",
"name":"Name1",
"name2":"Name2",
"address":"Address1",
"address2":"Address2",
"city":"City",
"lastUpdateBy":"MULE"
},
{
"addressType":"SECONDARY",
"name":"Name1",
"name2":"Name2",
"address":"Address1",
"address2":"Address2",
"city":"City",
"lastUpdateBy":"MULE"
},
{
"addressType":"BILLING",
"name":"Name1",
"name2":"Name2",
"address":"Address1",
"address2":"Address2",
"city":"City",
"lastUpdateBy":"MULE"
}
]
}
}
}'
Statement with variable concatenation:
SELECT JSON_VALUE(
#pjson,
CONCAT(N'lax $.store.location.addresses[', #counter, N'].addressType')
)
Statement with OPENJSON():
SELECT JSON_VALUE([value], '$.addressType')
FROM OPENJSON(#pjson, 'lax $.store.location.addresses')
WHERE CONVERT(int, [key]) = #counter
Result:
(No column name)
----------------
MAIN
Try following:
DECLARE #pJson NVARCHAR(4000)
-- ...
DECLARE #Counter INT = 0
DECLARE #PathString NVARCHAR(1000)
SET #PathString = N'lax $.store.location.addresses[' + CAST(#Counter AS NVARCHAR(50)) + N'].addressType'
PRINT JSON_VALUE(#Pjson,#PathString)
I have a JSON string passed into stored procedure as follows,
{"config": {
"site": "Internal",
"library": "test-library",
"folderHierarchy": {
"name": "folder1",
"children": {
"name": "folder2",
"children": {
"name": "folder3"
}
}
},
"meta_data": [{
"meta_text": "date-created",
"meta_value": "2020-04-17"
}, {
"meta_text": "date-modified",
"meta_value": "2020-04-17"
}]
}}
I need to get folder names inside folderHierarchy node , it can have many number or folders inside as children.
First I tried to get folderHierarchy as a JSON string and iterate through that JSON string.
DECLARE #CONFIG VARCHAR(MAX) = '{"config": {
"site": "Internal",
"library": "test-library",
"folderHierarchy": {
"name": "folder1",
"children": {
"name": "folder2",
"children": {
"name": "folder3"
}
}
},
"meta_data": [{
"meta_text": "date-created",
"meta_value": "2020-04-17"
}, {
"meta_text": "date-modified",
"meta_value": "2020-04-17"
}]
}}'
DECLARE #folderHierarchy VARCHAR(MAX);
SET #folderHierarchy = JSON_QUERY(#CONFIG, '$.config.folderHierarchy');
SELECT #folderHierarchy;
SELECT *
FROM openjson(#folderHierarchy)
WITH(
name VARCHAR(max) '$.name',
childern NVARCHAR(MAX) '$.children' AS JSON
) AS P
OUTER APPLY OPENJSON(P.childern)
WITH (name NVARCHAR(MAX) '$.name');
But this give me the first and second folder names.
Can anyone help me to get all the folder names, the main thing is I haven't control about number of child item in a folder.
I mean folder1 can have folder called folder2, and folder 2 can have folder called folder 3, and folder3 can have folder called folder4 etc.. etc..
You could use a Recursive CTE for this. The default settings on SQL Server will allow you to recurse up to 100 levels deep (see MAXRECUSRION)...
declare #Config nvarchar(max) =
N'{"config": {
"site": "Internal",
"library": "test-library",
"folderHierarchy": {
"name": "folder1",
"children": {
"name": "folder2",
"children": {
"name": "folder3"
}
}
},
"meta_data": [{
"meta_text": "date-created",
"meta_value": "2020-04-17"
}, {
"meta_text": "date-modified",
"meta_value": "2020-04-17"
}]
}}';
with JsonFolders as (
select
1 as level,
name,
children
from openjson(#Config, N'$.config.folderHierarchy') with (
name nvarchar(max) N'$.name',
children nvarchar(max) N'$.children' as json
)
union all
select
1 + level,
Child.name,
Child.children
from JsonFolders JF
cross apply openjson(JF.children) with (
name nvarchar(max) N'$.name',
children nvarchar(max) N'$.children' as json
) Child
)
select * from jsonFolders;
Structure of an item in database is as shown below:
{
"cars": {
"x": [
{
"time": 1485700907669,
"value": 23
}
]
},
"date": 1483214400000,
"id":"1"
}
I have to add a new item "z" of type list to cars like
{
"cars": {
"x": [
{
"time": 1485700907669,
"value": 23
}
],
"z": [
{
"time": 1485700907669,
"value": 23
}
]
},
"date": 1483214400000,
"id": "1"
}
What would the update expression in Node.js look like if I want to achieve somethings like this?
So far this is what I came up with:
set #car.#model= list_append(if_not_exists(#car.#model, :empty_list), :value)
However, if the item does not exist at the time of creation it throws error. Any idea how to do this?
This is the updated parameter I am using, still doesn't work
var params = {
TableName:table,
Key:{
"id": id,
"date": time.getTime()
},
ReturnValues: 'ALL_NEW',
UpdateExpression: 'SET #car.#model = if_not_exists(#car.#model,
:empty_list)',
ExpressionAttributeNames: {
'#car': 'cars',
'#model':"z"
},
ExpressionAttributeValues: {
':empty_list': [],
}
};
The solution is to update operation in two steps, first create a empty map for the parent since it does not exist in the first place.
So, in my case
SET #car= :empty_map
where :empty_map = {}
after doing this run the other update expression
SET #car.#model = list_append(if_not_exists(#car.#model, :empty_list), :value)
where :empty_list=[] and :value= {
"time": 1485700907669,
"value": 23
}
Break your update expression apart into two separate expressions:
SET #car.#model = if_not_exists(#car.#model, :empty_list) SET #car.#model = list_append(#car.#model, :value)