Store any transaction in log table SQL Server - sql

I have lots of tables in my SQL Server database. When any action happens, like an Insert, Update, Delete, I want to store the data in a Log table as shown here:
Product table:
| ID | Name | Other |
| --- | ------- | --- |
| 1 | Book | ... |
| 2 | Bicycle | ... |
If any Insert, Update or Delete happens, I want to have something like this:
Log table:
| ID | RowId | TableName | Action |
| --- | ------- | ---------- | ------ |
| 1 | 1 | Product | Insert |
| 2 | 2 | Product | Insert |
| 3 | 15 | Category | Update |
| 4 | 60 | Customer | Insert |
I'm using Microsoft SQL Server 2008 R2 (SP2) - 10.50.4000.0 (X64) Express edition with Advanced Services - some features might not be available for me (reference to documentation).
For a single table a trigger is a good idea and it works fine, but what about all tables? As the example shows, Category and Customer tables are other tables in my database.

If your problem is the number of triggers, you should create; then create a job to create the trigger when a new table has been created. The below code checks the tables that have not that kind of triggers using specific formula of naming. In my example tablename plus '_dml_audit'. You can change it anyway.
Set NOCOUNT ON
select Concat (QUOTENAME(sch.name) , '.' , QUOTENAME(tbl.name)) TableNames
into #Tables
From sys.tables tbl
INNER JOIN sys.schemas sch on sch.schema_id = tbl.schema_id
LEFT JOIN sys.triggers trg on tbl.object_id = trg.parent_id and trg.name like tbl.name + '_dml_audit'
Where trg.object_id is null
GO
exec sp_MSforeachtable 'IF ''?'' IN (Select TableNames FROM #Tables) Begin PRINT ''?'' END'
Drop Table #Tables
For older version that does not support #table use this:
Create Table TempTable (TableNames sysname)
GO
Insert TempTable
SELECT Concat ('[', sch.name , '].[' , tbl.name, ']') TableNames
From sys.tables tbl
INNER JOIN sys.schemas sch on sch.schema_id = tbl.schema_id
LEFT JOIN sys.triggers trg on tbl.object_id = trg.parent_id and trg.name like tbl.name + '_dml_audit'
Where trg.object_id is null
GO
exec sp_MSforeachtable 'IF ''?'' IN (Select TableNames FROM TempTable) Begin PRINT ''?'' END'
GO
Drop Table TempTable
Replace Trigger Definition with the PRINT statement and Define a job for it.

Related

Query to revert db changes to a specific time by checking audit data

I have a module which handles some business rules in my application, there are a few tables where these rules are stored.
Orignal Business Rule table :
br_tbl_1
br_id | col_1 | col_2
------+-------+--------
1 | a | myk
2 | b | abc
Related Tables: br_tbl_2
id | br_id | col_1
---+-------+--------
1 | 1 | something
2 | 1 | something_else
3 | 2 | Another thing
and so on...
Now to track the changes made to the business rules, I have an audit table for each of the above tables, like so..
Business Rule Audit Table: br_tbl_1_audit
id | br_id | col_1 | col_2 | audit_dtme | operation
---+-------+--------+-------+---------------------+-----------------
1 | 1 | a | xyz | 01-01-2001 12:30:10 | INSERT
2 | 1 | a | myk | 02-01-2001 01:00:00 | UPDATE
3 | 2 | b | abc | 02-01-2001 01:10:30 | INSERT
by looking at the data from br_tbl_1_audit table we can see that the value for col_2 for br_id = 1 has changed from "xyz" to "myk"
Similarly we have an audit table for the other business rules tables.
Related Table's Audit Table: br_tbl_2_audit
id | br_id | col_1 | audit_dtme | operation
---+-------+------------------+----------------------+--------------
1 | 1 | something | 01-01-2001 12:30:10 | INSERT
2 | 1 | something_else | 01-01-2001 12:30:10 | INSERT
3 | 2 | Another thing | 02-01-2001 01:10:30 | INSERT
I need a Query which takes in a br_id and an audit_date_time and rolls back all the data for that br_id in all tables to that audit_dtme
I can do this with a Script, however I am not very good with SQL Queries, I appriciate the help.
FYI : I am using Postgres, but any SQL should be enough t push me in the right direction.
In any given table, you can use distinct on:
select distinct on (a.br_id) a.*
from br_tbl_1_audit a
where a.audit_dtime <= $audit_date_time
order by a.br_id, a.audit_dtime desc;
You can also filter for one or more br_id values as well.
You can repeat this for all the tables you care about.
If you need to replace a row, then you can use update:
update br_tbl_1 t
set col_1 = a.col_1,
col_2 = a.col_2
from (select a.*
from br_tbl_1_audit a
where a.audit_dtime <= $audit_date_time and
a.br_id = 1
order by a.audit_dtime desc
limit 1
) a
where t.br_id = 1;
I would probably say that this would be very tough to handle if you have too many tables linked.
Following is the sample code if you have to just delete from one table. Now you can modify this as per your requirement.
declare #id int,
#br_id int,
#br_id_input int = 1,
#col_1 varchar(100),
#col_2 varchar(100),
#audit_dtme datetime,
#operation varchar(100),
#audit_date_time datetime = '2001-01-01 12:30:10.000';
declare cur cursor
for select id, br_id, col_1, col_2, audit_dtme, operation
from br_tbl_1_audit
where br_id = #br_id_input and audit_dtme > #audit_date_time
order by id desc
open cur
fetch next from cur into #id, #br_id, #col_1, #col_2, #audit_dtme, #operation
while ##fetch_status = 0
begin
if (#operation = 'INSERT')
begin
delete from br_tbl_1 where br_id = #br_id;
end
else if (#operation = 'DELETE')
begin
set identity_insert br_tbl_1 on;
insert into br_tbl_1 (br_id, col_1, col_2)
values (#br_id, #col_1, #col_2)
set identity_insert br_tbl_1 off;
end
else
begin
;with cte
as
(
select top 1 * from br_tbl_1_audit
where br_id = #br_id and audit_dtme < #audit_dtme
order by id desc
)
update tb1
set tb1.col_1 = cte.col_1,
tb1.col_2 = cte.col_2
from br_tbl_1 tb1
join cte on cte.br_id = tb1.br_id
end
delete from br_tbl_1_audit where id = #id;
fetch next from cur into #id, #br_id, #col_1, #col_2, #audit_dtme, #operation
end
close cur
deallocate cur
For deleting in the foreign key tables, you will have to add another cursor inside the main cursor which will insert/update/delete in the foreign key tables as per the primary key table rows.
Although cursor may not be the best solution as it may be slow if there are too many tables or data rows to restore.

Get values from 2 Tables in different Databases into another table

I have a Database for say MasterDB which has list of Some Databases Name in a Table tbl_B .Each DataBase Name is identified by an ID.
The structure of the table tbl_B is like the following
tbl_B
ID | DB_Name
-------------
1 | DelhiDB
2 | MumbaiDB
There are DataBases with the same name i.e DelhiDB and MumbaiDB and each of them have a Table with name tbl_C which will have some data for eg.
tbl_C for Delhi
custIDDelhi | custNameDelhi | CustPhoneDelhi |
----------------------------------------------
1 | John | 123456 |
2 | Monika | 789945 |
Please note here that the column names for Both the databases can be Different
Also Please note that DelhiDB and MumbaiDB are separate Database each having a table named tbl_C
I want to create a Table called tblCusotmer_Dictionary in MasterDB
With Data something like this
ColumnName | DataBaseName | DataBaseID | db_ColumnNamme
-----------------------------------------------------------
CustomerID | DelhiDB | 1 | custIDDelhi
CustomerName | DelhiDB | 1 | custNameDelhi
CustomerPhone | DelhiDB | 1 | CustPhoneDElhi
CustomerID | MumbaiDB | 2 | custIDMumbai
CustomerName | MumbaiDB | 2 | custNameMumbai
CustomerPhone | MumbaiDB | 2 | CustPhoneMumbai
Here I dont want any customer data just a list of column name from both the databases along with Database name and ID ,
the column ColumnName in the above table is the Generic Name I am giving to the column db_ColumnNamme
I have taken example for 2 databases and 3 columns for simplicity But there can can be N number for databases each having a table with a same name ( tbl_c here) with fixed no of columns.
Let me know in comments for any clarifications.
if I understood your question correctly then below is the solution which you are looking for. Let me know if it works for you.
DECLARE #tblDatabaseName AS TABLE (Id INT, dbName VARCHAR(100))
--DROP TABLE #tmpREcord
INSERT INTO #tblDatabaseName(id,dbName) VALUES (1,'DelhiDB'),(1,'MumbaiDB')
DECLARE #SQL AS VARCHAR(8000)
DECLARE #Id INT
DECLARE #dbName AS VARCHAR(100)
CREATE TABLE #tmpRecord (
columnName VARCHAR(20),DBID INT, DatabaseName VARCHAR(100))
DECLARE cur_Traverse CURSOR FOR SELECT Id , dbName FROM #tblDatabaseName
OPEN cur_Traverse
FETCH NEXT FROM cur_Traverse INTO #id ,#dbName
WHILE ##FETCH_STATUS =0
BEGIN
SET #SQL = 'INSERT INTO #tmpRecord (ColumnName,DbId,DatabaseName )
SELECT name ,' + CONVERT(VARCHAR(10),#Id) + ' AS DBID, ''' + #dbName + ''' as dbname'
+ ' FROM ' + #dbName + '.sys.all_columns s
WHERE object_Id = (SELECT TOP(1) object_Id FROM ' + #dbName + '.sys.all_objects WHERE name=''tbl_C'')'
PRINT #SQL
EXECUTE (#SQL)
FETCH NEXT FROM cur_Traverse INTO #Id, #dbName
END
CLOSE cur_Traverse
DEALLOCATE cur_Traverse
SELECT * FROM #tmpRecord
You appears to want :
select t.colsname as ColumnName,
b.db_name as DataBaseName,
b.id as DataBaseID,
t.cols as db_ColumnNamme
from tbl_C c
cross apply (values ('custID', 'CustomerID'), ('custName', 'CustomerName'),
('CustPhone', 'CustomerPhone')
) t (cols, colsname)
inner join tbl_B b on b.id = c.custID;

PostgreSQL: Check if substring is in a string and if true then replace the entire string with the substring

I have a column in a table like this:
| biz_name |
---------------
| www.Dog.com |
| Dog LLC. |
| Dog Inc. |
| www.Cat.com |
| Cat Corp |
And for each element in the column, I want to check if it contains the substring 'Dog' or 'Cat' and if it does, then to replace the entire string with either 'Dog' or 'Cat'.
The results should be:
| biz_name |
---------------
| Dog |
| Dog |
| Dog |
| Cat |
| Cat |
Is there a way to do this without using CASE WHEN as such:
CASE
WHEN biz_name ilike '%Dog%' THEN 'Dog'
WHEN biz_name ilike '%Cat%' THEN 'Cat'
ELSE biz_name END as biz_name
The above table is just an example to demonstrate what I'm trying to do, but the actual table I'm working on has many more substrings I'm trying to search for and replace with that using multiple CASE WHEN statements can get tedious. Does anyone have a more efficient way to do it? Thanks.
1) can you do a quick insert into a temp table
--assuming table name is biz
declare #name varchar(max)
declare #TEMP table (Name varchar(max))
-- insert all your values
insert into #TEMP values ('dog')
insert into #TEMP values ('cat')
set #name = select top 1 name from #TEMP
delete from #temp where name = #name -- remove from que /table
WHILE #name IS NOT NULL
BEGIN
update dbo.biz
set biz_name = #name
where biz_name like '%' + #name '%'
set #name = select top 1 name from #TEMP
delete from #TEMP where name = #name
END

Taking a SUM of a text field representing a column name in postgres

Here's a sample of my table
[myTable]
id | random1 | random2 | random3 | random4
1 | 123 | 5357 | 10 | 642
2 | 423 | 34 | 20 | 531
3 | 9487 | 234 | 30 | 975
4 | 34 | 123 | 40 | 864
Here's my current query, but it isn't working like I'd expect it to:
SELECT
cols.*,
(SELECT SUM(cols.column_name) FROM myTable t)
FROM
(SELECT
table_name::text, column_name::text
FROM
information_schema.columns
where
table_name = 'myTable') as cols
I'm getting the error: function sum(text) does not exist - which makes sense. I'm pretty sure that mysql is can be messy enough to allow a reference like that, but I don't know how to do this in postgres.
What I'd really like to have is an end result somewhere along the lines of...
table_name | column_name | sum
myTable | id | 10
myTable | random1 | 10067
myTable | random2 | 5748
myTable | random3 | 100
myTable | random4 | 3012
I want to take this query a lot further, but I'm getting really hung up on being able to reference the column name.
SQL queries are static. They select before-known columns from before-known tables. You cannot make them look up table names and columns from the database dictionary and then magically glue these names into themselves.
What you can do: Write a program (Java, C#, PHP, whatever you like) doing the following:
Send a query to the DBMS to find the column names for the table you are interested in.
Build a SQL query string with the column names got.
Send this query to the DBMS.
declare #tableName varchar(255) = 'myTable' --Change this to the table name you want
/*create table and column name dataSet and insert values*/
if object_id('tempdb.dbo.#objectSet') is not null
drop table #objectSet
create table #objectSet
(table_name varchar(256),
columnID int,
column_name varchar(256),
[sum] int)
insert into #objectSet
(table_name,
columnID,
column_name)
select O.name table_name,
C.column_id columnID,
C.name column_name
from sys.all_objects O
join sys.all_columns C
on O.object_id = C.object_id
join sys.types T
on C.user_type_id = T.user_type_id
where O.object_id = object_id(#tableName)
and T.name in ('int', 'tinyint', 'smallint', 'bigint') --Columns with Aggregatable datatypes only, all other columns will be excluded from the set.
/*Create loop variables for each column*/
declare #SQL as varchar(4000),
#counter int = 1,
#maxCount int
select #maxCount = SQ.maxCount
from ( select count(*) maxCount
from #objectSet OS) SQ
/*Run loop, updating each column as it goes*/
while #counter <= #maxCount
begin
select #SQL = 'update OS set OS.[sum] = SQ.[sum] from #objectSet OS join (select sum(DS.' + OS.column_name + ') [sum] from ' + #tableName + ' DS) SQ on OS.column_name = ''' + OS.column_name + ''''
from #objectSet OS
where OS.columnID = #counter
exec (#SQL)
select #counter += 1
end
/*Display Results*/
select OS.table_name,
OS.column_name,
OS.[sum]
from #objectSet OS
Using system object tables, some dynamic T-SQL, and a loop should do it.

select records from table given as column name. Dynamic sql

I've searched some topics here but there have not been any answers which I need.
I want to make a query where I will join a table basing on the column name in the first table.
I'm using sql server so it would be appreciate if someone know solution for this technology.
Here is a sample what I wanna do:
Tables:
main_table
----------
id | tab | another_col
----------------------
1 | product_x | abcd
2 | product_y | efgh
table_product_x
----------------------
id | yyy
----------------------
1 | simple_yyy_value1
table_product_y
----------------------
id | yyy
----------------------
2 | simple_yyy_value4
Output:
product_x | simple_yyy_value1 | abcd
product_y | simple_yyy_value4 | efgh
Query(sketch)
select tab, yyy, another_col from main_table
join 'table_'+tab xxx on xxx.id = main_table.id
You can build this using union all and some dynamic SQL.
declare #SQL nvarchar(max)
declare #Pattern nvarchar(100)
set #Pattern = 'select ''[TABLE_NAME]'' as TableName, yyy from table_[TABLE_NAME]'
select #SQL = stuff((select ' union all '+replace(#Pattern, '[TABLE_NAME]', tab)
from main_table
for xml path(''), type).value('.', 'nvarchar(max)'), 1, 11, '')
exec (#SQL)
The statement executed will look something like this:
select 'product_x' as TableName, yyy
from table_product_x
union all
select 'product_y' as TableName, yyy
from table_product_y