How to keep in a database the number of calls of each record? - sql

For example, I want to know at any moment most popular records that users searched in the database.
I expect that for each record I need to introduce a new number field. Thus, the record will be like this:
key - value - counter
How I can to increase the value of counter inside a database?
I think it's something like calling a stored procedure while a query, but I'm not sure. Perhaps the question is quite simple, I'm just a beginner and I apologize in that case.

You should use a trigger for this. Triggers are commands that execute on events, everytime an INSERT, UPDATE or DELETE statement is executed, even if their calls do not modify any records. Due tot this, you can't directly create a trigger for updating the count field of a record when you SELECT (read) it.
But, you can try a workaround in which you also have a date field in your table, and update it everytime a record is called. Use your application to send this datetime value to the database, which will trigger an UPDATE.
By making an UPDATE statement, your trigger is called and this way you can add your code to modify the count column.
CREATE TRIGGER tafter AFTER INSERT OR DELETE ON tbl1 FOR EACH ROW UPDATE SET counter = counter + 1 where key = 'keyval';

Firstly, this sounds like an awful performance problem. Every time you select a record you have to update it if you're tracking the selects with a single number, which just stores total selects, otherwise you have to insert timestamped values into another table to be able to analyse when the rows were read.
Anyway, you can do this with a common table expression in which you update a counter in the table and return the results to the main query: http://sqlfiddle.com/#!1/1aa41/6
Code something like:
create table my_table(col1 varchar(30), col2 numeric, select_count numeric);
insert into my_table values ('A',1,0);
insert into my_table values ('B',2,0);
insert into my_table values ('C',3,0);
insert into my_table values ('D',4,0);
insert into my_table values ('E',5,0);
with upd as (
update my_table
set select_count = select_count+1
where col1 = 'A'
returning *)
select *
from upd;
with upd as (
update my_table
set select_count = select_count+1
where col1 = 'B'
returning *)
select *
from upd;
with upd as (
update my_table
set select_count = select_count+1
where col1 = 'A'
returning *)
select *
from upd;
with upd as (
update my_table
set select_count = select_count+1
returning *)
select count(*)
from upd;
with upd as (
update my_table
set select_count = select_count+1
returning *)
select sum(col2)
from upd;
with upd as (
update my_table
set select_count = select_count+1
returning *)
select *
from upd;

Related

Trigger works based on type of insert SQL Server

I am working in SQL Server Management Studio v18, and I have the following trigger:
CREATE TRIGGER [dbo].[update_surface]
ON [dbo].[my_table]
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #surface_m2 REAL
SET #surface_m2 = (SELECT cast(round(CAST(Dimension1*Dimension2 as decimal)/ cast(1000000 as decimal),3,3) as decimal(10,3)) AS surface FROM my_table WHERE Surface_m2 IS NULL)
UPDATE dbo.my_table SET Surface_m2 = #surface_m2
END
I have two columns in my_table, which are Dimesion1 and Dimension2. I want that the trigger multiplies them, and set the result to other column in the same table, which is Surface_m2, whenever this column is null. The trigger does his function, but based on the type of insert I do:
If I insert a row in my_table by the graphic environment the trigger works as I wish. With each new row, Surface_m2 has his own result.
But if I insert by INSERT INTO my_table VALUES ().... (query) the trigger updates Surface_m2 column of all previous rows with the result of each new insert.
Why is the trigger working like that? Is there any other simple way to do what I am trying to do?
Thanks.
Insert trigger gives you actual values that are inserted in a special table called... "inserted".
So what you need to do is join this table against your main table and perform the logic needed, no variables required.
Something like this untested code
create trigger...
begin
UPDATE t
SET Surface_m2 = cast(round(CAST(t.Dimension1*t.Dimension2 as decimal)
/ cast(1000000 as decimal),3,3) as decimal(10,3))
from dbo.my_table t
inner join inserted i
ON i.ID = t.ID
WHERE t.Surface_m2 IS NULL
end
This begs the question though, why can't you just insert the Surface_m2 value yourself. Or even better, change Surface_m2 to be a computed column if it's always depends on Dimension1 and Dimension2

UPDATE and INSERT should fire trigger only once

Is there any way to combine an update and an insert statements in a way that they fires a trigger only once?
I have one particular table that has (and currently needs) a trigger AFTER INSERT, UPDATE, DELETE. Now I want to update one row and insert another row and have the trigger fire only once for that.
Is this at all possible?
I already tried a MERGE-Statement without success: The trigger fires once for the update- and once for the insert-part.
Well, problem solved for me. I did NOT find a way to combine the statements into one fire-event of the trigger. But the trigger behaves in an interesting way, that was good enough for me: Both calls to the trigger do already have access to the fully updated data.
Just execute the following statements and you will see what I mean.
CREATE TABLE Foo (V INT)
GO
CREATE TRIGGER tFoo ON Foo AFTER INSERT, UPDATE, DELETE
AS
SELECT 'inserted' AS Type, * FROM inserted
UNION ALL
SELECT 'deleted', * FROM deleted
UNION ALL
SELECT 'actual', * FROM Foo
GO
DELETE FROM Foo
INSERT Foo VALUES (1)
;MERGE INTO Foo
USING (SELECT 2 AS V) AS Source ON 1 = 0
WHEN NOT MATCHED BY SOURCE THEN DELETE
WHEN NOT MATCHED BY TARGET THEN INSERT (V) VALUES (Source.V);
As a result, the trigger will be called twice for the MERGE. But both times, "SELECT * FROM Foo" delivers the fully updated data already: There will be one row with the value 2. The value 1 is deleted already.
This really surprised me: The insert-trigger is called first and the deleted row is gone from the data before the call to the delete-trigger happens.
Only the values of "inserted" and "deleted" correspond to the delete- or insert-statement.
You could try something like this:
The trigger would check for the existence of #temp table.
If it doesn't exist, it creates it with dummy data. It then checks if the recent values contain the same user (SPID) that is running now and if the last time it was triggered was within 20 seconds.
If these are true then it will PRINT 'Do Nothing' and drop the table, otherwise it will do your trigger statement.
At the end of your trigger statement it inserts into the table the SPID and current datetime.
This temp table should last as long as the SPID connection, if you want it to last longer make it a ##temp or a real table.
IF OBJECT_ID('tempdb..#temp') IS NULL
begin
Create table #temp(SPID int, dt datetime)
insert into #temp values (0, '2000-01-01')
end
If ##SPID = (select top 1 SPID from #temp order by dt desc)
and Convert(datetime,Convert(varchar(19),GETDATE(),121)) between
Convert(datetime,Convert(varchar(19),(Select top 1 dt from #temp order by dt desc),121)) and
Convert(datetime,Convert(varchar(19),DateAdd(second, 20, (select top 1 dt from #temp order by dt desc)),121))
begin
PRINT 'Do Nothing'
Drop table #temp
end
else
begin
--trigger statement
Insert into #temp values (##SPID, GETDATE())
end

SQL Trigger update another table

I am newbie to triggers... can anybody help me with a trigger?
I have Table:
Name | Number
I want to write a trigger when my table receives a query like
update MyTable
set Number = Number + 1
where Name = 'myname'
When this query is running, the trigger should update another table for example:
Update MyTable 2
set Column = 'something'
where Name = 'myname (above name)
Thank you very much !
You will need to write an UPDATE trigger on table 1, to update table 2 accordingly.
Be aware: triggers in SQL Server are not called once per row that gets updated - they're called once per statement, and the internal "pseudo" tables Inserted and Deleted will contain multiple rows, so you need to take that into account when writing your trigger.
In your case, I'd write something like:
-- UPDATE trigger on "dbo.Table1"
CREATE TRIGGER Table1Updated
ON dbo.table1 FOR UPDATE
AS
BEGIN
-- update table2, using the same rows as were updated in table1
UPDATE t2
SET t2.Column = 'something'
FROM dbo.Table2 t2
INNER JOIN Inserted i ON t2.ID = i.ID
END
GO
The trick is to use the Inserted pseudo table (which contains the new values after the UPDATE - it has the exact same structure as your table the trigger is written for - here dbo.Table1) in a set-based fashion - join that to your dbo.Table2 on some column that they have in common (an ID or something).
create a trigger on table 1 for update:
CREATE TRIGGER dbo.update_trigger
ON table1
AFTER UPDATE
AS
BEGIN
DECLARE #Name VARCHAR(50)
SELECT #Name=Name FROM INSERTED
Update MyTable 2
SET Column = 'something'
WHERE Name = #Name
END
GO
try this ;)

After insert not working

all id columns has auto_increment
In my trigger:
ALTER trigger [dbo].[mytrig]
on [dbo].[requests]
after INSERT, UPDATE
as
begin
declare #MyId1 int
set #MyId1 = (select Id from inserted)
declare #MyId2 int
declare #MyId3 int
if (select column1 from inserted) = 1
begin
insert into [dbo].[contracts] select column1,column2,column3 .... from inserted
set #MyId2 = SCOPE_IDENTITY()
insert into [dbo].[History] select column1,column2,column3 .... from inserted
set #MyId3 = SCOPE_IDENTITY()
insert into [dbo].[contracts_depts](Id_Contract ,column5) select #MyId2,column6 from request_depts where Id_request=#MyId1
insert into [dbo].[History_depts] (Id_InHistory,column5) select #MyId3,column6 from request_depts where Id_request=#MyId1
end
end
#MyId1 returns value only after update but not after insert. Do I have to use scope_identity() or something ?
Your main issue is: you're assuming the triggers is called once per row - that is NOT the case!
The trigger is called once per statement, and if your statement affects multiple rows, the Inserted pseudo table will contain multiple rows - so your statement here
set #MyId1 = (select Id from inserted)
really isn't going to work - it will select one arbitrary row (out of however many there are).
You'll need to rewrite your trigger to take this fact into account! Assume that Inserted contains 100 rows - how do you want to deal with that? What are you trying to achieve? Triggers don't return values - they will record into an audit table, or update other rows, or something like that ....

Insert into a temporary table and update another table in one SQL query (Oracle)

Here's what I'm trying to do:
1) Insert into a temp table some values from an original table
INSERT INTO temp_table SELECT id FROM original WHERE status='t'
2) Update the original table
UPDATE original SET valid='t' WHERE status='t'
3) Select based on a join between the two tables
SELECT * FROM original WHERE temp_table.id = original.id
Is there a way to combine steps 1 and 2?
You can combine the steps by doing the update in PL/SQL and using the RETURNING clause to get the updated ids into a PL/SQL table.
EDIT:
If you still need to do the final query, you can still use this method to insert into the temp_table; although depending on what that last query is for, there may be other ways of achieving what you want. To illustrate:
DECLARE
id_table_t IS TABLE OF original.id%TYPE INDEX BY PLS_INTEGER;
id_table id_table_t;
BEGIN
UPDATE original SET valid='t' WHERE status='t'
RETURNING id INTO id_table;
FORALL i IN 1..id_table.COUNT
INSERT INTO temp_table
VALUES (id_table(i));
END;
/
SELECT * FROM original WHERE temp_table.id = original.id;
No, DML statements can not be mixed.
There's a MERGE statement, but it's only for operations on a single table.
Maybe create a TRIGGER wich fires after inserting into a temp_table and updates the original
Create a cursor holding the values from insert and then loop through the cursor updating the table. No need to create temp table in the first place.
You can combine steps 1 and 2 using a MERGE statement and DML error logging. Select twice as many rows, update half of them, and force the other half to fail and then be inserted into an error log that you can use as your temporary table.
The solution below assumes that you have a primary key constraint on ID, but there are other ways you could force a failure.
Although I think this is pretty cool, I would recommend you not use it. It looks very weird, has some strange issues (the inserts into TEMP_TABLE are auto-committed), and is probably very slow.
--Create ORIGINAL table for testing.
--Primary key will be intentionally violated later.
create table original (id number, status varchar2(10), valid varchar2(10)
,primary key (id));
--Create TEMP_TABLE as error log. There will be some extra columns generated.
begin
dbms_errlog.create_error_log(dml_table_name => 'ORIGINAL'
,err_log_table_name => 'TEMP_TABLE');
end;
/
--Test data
insert into original values(1, 't', null);
insert into original values(2, 't', null);
insert into original values(3, 's', null);
commit;
--Update rows in ORIGINAL and also insert those updated rows to TEMP_TABLE.
merge into original original1
using
(
--Duplicate the rows. Only choose rows with the relevant status.
select id, status, valid, rownumber
from original
cross join
(select 1 rownumber from dual union all select 2 rownumber from dual)
where status = 't'
) original2
on (original1.id = original2.id and original2.rownumber = 1)
--Only math half the rows, those with rownumber = 1.
when matched then update set valid = 't'
--The other half will be inserted. Inserting ID causes a PK error and will
--insert the data into the error table, TEMP_TABLE.
when not matched then insert(original1.id, original1.status, original1.valid)
values(original2.id, original2.status, original2.valid)
log errors into temp_table reject limit 999999999;
--Expected: ORIGINAL rows 1 and 2 have VALID = 't'.
--TEMP_TABLE has the two original values for ID 1 and 2.
select * from original;
select * from temp_table;