SQL Server Delete on read - sql

Is there an easy way in SQLServer touse data as READ_ONCE? What I mean is, can I set it to delete a row after it has selected it?

Off the top of my head, the only way I can think of would be to restrict all logins to prohiibit any Select access, and only allow access through a stored procedure "FetchMyWhateverData" and then delete the rows as second SQL statement inside the stored proc.
CreateProcedure FetchMyWhateverData
#MyEntityId Integer,
As
Set NoCount On
Select * From TableName
Where Id = #MyEntityId
Delete TableName
Where Id = #MyEntityId
Return 0
-- and adding in the other appropriate infrastructure code of course.

If you read it with DELETE ... OUTPUT .... This is how queues work.

You could do this easily if the data is accessed through a stored procedure. You can select the data into a temp table, delete the data and return the temp. All wrapped in a transaction of course.

Related

Can I reference a Sqlite CTE in more than one DML statement?

In a single statement, I want to update one set of rows and delete another set, both based on some CTE's. However, when I execute it, the second DML, the DELETE, complains as if the CTE's are no longer available. Is this not possible in Sqlite?
I looked at the WITH reference for Sqlite but the only thing I found was in the Caveat section and that was about compound statements, which is not what I am doing here.
Here is my SQL with the contents of the CTE's removed because it has some proprietary stuff in there and because I don't think it matters to the question (but let me know if that's not correct):
with cteOldAboutCmds as (
...
)
, cteFrequency as (
...
)
, cteNewAboutCmdMaster as (
...
)
update cmds
set freqrankalltime = freqrankalltime + (select fr from cteFrequency)
where id = (select id from cteNewAboutCmdMaster);
delete
from cmds
where id in (select id from cteOldAboutCmds);
I also tried wrapping the above in begin transaction; and commit transaction; but that didn't help.
The error message is: "no such table: cteOldAboutCmds".
A CTE is part of a single SQL statement, which is pretty much its whole point.
To have named queries available for multiple statements, use views. If you don't wont to affect other connections, use temporary views.
I have already accepted CL's answer but I want to format the code I ended up using, for others' benefit:
begin transaction;
create temp view OldAboutCmds as
select *
from...;
create temp view Frequency as
select...;
create temp view NewAboutCmdMaster as
select...;
update cmds
set ...
where ...;
delete
from cmds
where ...;
commit transaction;
drop view OldAboutCmds;
drop view Frequency;
drop view NewAboutCmdMaster;

How to update and insert in T-SQL in one query

I have a database that needs from time to time an update.
It may also happens that there are new data while the update runs.
In MySQL there is a option
INSERT INTO IGNORE
I can't find something like this in T-SQL.
No Problem to update ID 1-4 but then there is a new record for ID 5.
The UPDATE query don't work here.
And when I try to INSERT all data again I get a DUPLICATE KEY error.
Additional Infos:
I've forgotten to say that my data come from external sources. I call an API to get data from it. From there I have to insert these data into my database.
I have to admit that I don't understand MERGE. So my solution for now is to use TRUNCATE first and then insert all data again.
Not the best solution but MERGE works, so far I understand it, with two tables. But I have only one table. And to create a table temporarly to use MERGE and later drop that table is in my eyes a bit to much for my little table with 200 records in it.
You can use MERGE keyword. Basically, you need to specify the column(s) on which to join the source of data with target table, and depending on whether it is matching (existing record) or not matching (new record), you run an UPDATE or INSERT.
Reference: http://msdn.microsoft.com/en-us/library/bb510625.aspx
Is a stored procedure an option?
CREATE PROCEDURE dbo.Testing (#ID int, #Field1 varchar(20))
AS
BEGIN
UPDATE tblTesting
SET Field1 = #Field1
WHERE ID = #ID
IF ##ROWCOUNT = 0
INSERT INTO tblTesting (ID, Field1) SELECT #ID, #Field1
END

Stored procedure and trigger

I had a task -- to create update trigger, that works on real table data change (not just update with the same values). For that purpose I had created copy table then began to compare updated rows with the old copied ones. When trigger completes, it's neccessary to actualize the copy:
UPDATE CopyTable SET
id = s.id,
-- many, many fields
FROM MainTable s WHERE s.id IN (SELECT [id] FROM INSERTED)
AND CopyTable.id = s.id;
I don't like to have this ugly code in the trigger anymore, so I have extracted it to a stored procedure:
CREATE PROCEDURE UpdateCopy AS
BEGIN
UPDATE CopyTable SET
id = s.id,
-- many, many fields
FROM MainTable s WHERE s.id IN (SELECT [id] FROM INSERTED)
AND CopyTable.id = s.id;
END
The result is -- Invalid object name 'INSERTED'. How can I workaround this?
Regards,
Leave the code in the trigger. INSERTED is a pseudo-table only available in the trigger code. Do not try to pass around this pseudo-table values, it may contain a very large number of entries.
This is T-SQL, a declarative data access language. It is not your run-of-the-mill procedural programming language. Common wisdom like 'code reuse' does not apply in SQL and it will only cause you performance issues. Leave the code in the trigger, where it belongs. For ease of re-factoring, generate triggers through some code generation tool so you can easily refactor the triggers.
The problem is that INSERTED is only available during the trigger
-- Trigger changes to build list of id's
DECLARE #idStack VARCHAR(max)
SET #idStack=','
SELECT #idStack=#idStack+ltrim(str(id))+',' FROM INSERTED
-- Trigger changes to call stored proc
EXEC updateCopy(#idStack)
-- Procedure to take a comma separated list of id's
CREATE PROCEDURE UpdateCopy(#IDLIST VARCHAR(max)) AS
BEGIN
UPDATE CopyTable SET
id = s.id,
-- many, many fields
FROM MainTable s WHERE charindex(','+ltrim(str(s.id))+',',#idList) > 0
AND CopyTable.id = s.id;
END
Performance will not be great, but it should allow you to do what you want.
Just typed in on the fly, but should run OK
The real question is "How to pass array of GUIDs in a stored procedure?" or, more wide, "How to pass an array in a stored procedure?".
Here is the answers:
http://www.sommarskog.se/arrays-in-sql-2005.html
http://www.sommarskog.se/arrays-in-sql-2008.html

Need example of Conditional update stored proc in SQL server

I am just at starting levels in DB usage and have 2 basic questions
I have a generic UPDATE stored proc which updates all columns of a table.
But i need to make it conditional wherein it does not SET when the parameter is NULL.
Usage: I want to use this as a single SP to UPDATE any subset of columns, the caller from C# will fill in corresponding parameter values and leave other parameters NULL.
2
In case of , "UPDATE selected records" do i need to use locking inside stored proc ?
Why ? Isn't the operation in itself locked and transactional ?
I find the same question come up when i need to UPDATE selected(condition) records and then Return updated records.
UPDATE table
SET a = case when #a is null then a else #a end
WHERE id = #id
OR
EXEC 'update table set ' + #update + ' where id = ' + #id
OR
Conditionally update a column at a time
First option to me would usually be preferrable as it is usually efficient enough and you do not need to worry about string escaping
If I have understood the question properly, Why can't you build a query on the fly from sql server SP, and use sp_sqlexecute. So when you build query you can ensure only columns that have value has got updated.
Does this answer your question?

Determine caller within stored proc or trigger

I am working with an insert trigger within a Sybase database. I know I can access the ##nestlevel to determine whether I am being called directly or as a result of another trigger or procedure.
Is there any way to determine, when the nesting level is deeper than 1, who performed the action causing the trigger to fire?
For example, was the table inserted to directly, was it inserted into by another trigger and if so, which one.
As far as I know, this is not possible. Your best bet is to include it as a parameter to your stored procedure(s). As explained here, this will also make your code more portable since any method used would likely rely on some database-specific call. The link there was specific for SQL Server 2005, not Sybase, but I think you're pretty much in the same boat.
I've not tested this myself, but assuming you are using Sybase ASE 15.03 or later, have your monitoring tables monProcessStatement and monSysStatement enabled, and appropriate permissions set to allow them to be accessed from your trigger you could try...
declare #parent_proc_id int
if ##nestlevel > 1
begin
create table #temp_parent_proc (
procId int,
nestLevel int,
contextId int
)
insert into #temp_parent_proc
select mss.ProcedureID,
mss.ProcNestLevel,
mss.ContextID
from monSysStatement mss
join monProcessStatement mps
on mss.KPID = mps.KPID
and mss.BatchID = mps.BatchID
and mss.SPID = mps.SPID
where mps.ProcedureID =##procid
and mps.SPID = ##spid
select #parent_proc_id = (select tpp.procId
from #temp_parent_proc tpp,
#temp_parent_proc2 tpp2
where tpp.nestLevel = tpp2.nestLevel-1
and tpp.contextId < tpp2.contextId
and tpp2.procId = ##procid
and tpp2.nestLevel = ##nestlevel
group by tpp.procId, tpp.contextId
having tpp.contextId = max(tpp.contextId ))
drop table #temp_parent_proc
end
The temp table is required because of the nature of monProcessStatement and monSysStatement.
monProcessStatement is transient and so if you reference it more than once, it may no longer hold the same rows.
monSysStatement is a historic table and is guaranteed to only return an individual rown once to any process accessing it.
if you do not have or want to set permissions to access the monitoring tables, you could put this into a stored procedure you pass ##procid, ##spid, and ##nestlevel to as parameters.
If this also isn't an option, since you cannot pass parameters into triggers, another possible work around would be to use a temporary table.
in each proc that might trigger this...
create table #trigger_parent (proc_id int)
insert into #trigger_parent ##procid
then in your trigger the temp table will be available...
if object_id('#trigger_parent') is not null
set #parent_proc = select l proc_id from #trigger_parent
you will know it was triggered from within another proc.
The trouble with this is it doesn't 'just work'. You have to enforce temp table setup.
You could do further checking to find cases where there is no #trigger_parent but the nesting level > 1 and combine a similar query to the monitoring tables as above to find potential candidates that would need to be updated.