Being an MS Access bod, our IT group doesn't let me touch the SQL server much so I don't know how to do looping of recordsets in it for a scheduled job I need. I have pseudo-coded what I want to do but would be very grateful if someone could point out how this is done...
for every record in qry_deliveryqueue
loop
if not fail then
send docID field to SP_sendfile
set delivereddate=getdate()
else
insert into tbl_errors (errdate,docid) values (getdate(), docid)
endif
next record
The syntax you're after is a CURSOR. The documentation has good examples that should point you in the right direction. You probably want to write a stored procedure around the logic and call that from the scheduler.
Cursors are generally inefficient, but I am assuming that your sp_sendfile has some more complex logic that precludes treating all the records as a set
You can Sql while syntax or Sql Cursor Syntax
Try this ion SQL server(This code is sample of while loop.So you have to tweak it as per your requirements)
--In sql server
Declare #idmin int,#idmax int
select #idmin=MIN(id) from qry_deliveryqueue --This is first record in qry_deliveryqueue
select #idmax=MAX(id) from qry_deliveryqueue --This is last record in qry_deliveryqueue
--You can
While(#idmin <= #idmax)
begin
IF #flag<>'Fail'
begin
-- send docID field to SP_sendfile
-- set delivereddate=getdate()
end
else
begin
insert into tbl_errors (errdate,docid) values (getdate(), docid)
end
SET #idmin=#idmin+1
end
Related
I have a SQL trigger on a table, which will fire after insert, update and delete.
I insert all the affected records in a separate physical table with codes defining the state of update. Following code snippet is the trigger defined.
CREATE TRIGGER [dbo].[DATA_CACHE]
ON [dbo].[DATA_USAGE]
for Insert,Update,Delete
AS
BEGIN
if(select COUNT(*) from inserted)>0
begin
if (select COUNT(*) from deleted)>0
BEGIN
--update
INSERT INTO CACHE_UPDATE_TABLE (CODE, ID, DATE, COUNT)
SELECT 2, ins.ID, ins.DATE, ins.COUNT
from inserted ins
END
else
begin
-- insert
INSERT INTO CACHE_UPDATE_TABLE (CODE, ID, DATE, COUNT)
SELECT 1, ins.ID, ins.DATE, ins.COUNT
from inserted ins
end
END
else
BEGIN
-- delete
INSERT INTO CACHE_UPDATE_TABLE (CODE, ID, DATE, COUNT)
SELECT 3, del.ID, del.DATE, del.COUNT
from deleted del
end
END
SELECT * FROM CACHE_UPDATE_TABLE
As you can see in the above trigger i had added an additional statement after the trigger by MISTAKE, selecting all values from the target table. This statement was after the defined trigger, however when i tried to alter the trigger, by right clicking on trigger and selecting modify, it also showed me the select statement after the end block of trigger.
Does this mean, every time the trigger is fired this select statement executes ? this is my first question (Question A) - May be a silly one, but i am a little confused about this.
My second question is (Question B) I encounter locking issue on the CACHE_UPDATE_TABLE, could this be the reason for locking? Also there is a SQL job which runs every one minute to check the CACHE_UPDATE_TABLE table, and then i perform some operation(linked server related) and delete these records from CACHE_UPDATE_TABLE after i am done. Locking Issue could be because of this?? and if so, how do i counter it?
My third question is (Question C) Is this the best way to do this operation using triggers or can i do it some other way? Is the trigger defined proper?
-Any help will be appreciated... Thanks.
You've got a lot of different questions in there which is probably why you've not received any answers, but I'll cover what I can.
A) That's quite an interesting question actually. I would have assumed that it would do nothing - It'd be executed when you create the trigger but then wouldn't be part of the trigger - however I've noticed odd behaviour with this before so I tested with a simple stored procedure:
CREATE PROCEDURE dbo.test ( #i INT ) AS
BEGIN
SELECT #i
END;
SELECT 'hi'
GO
Executing the stored procedure causes the SELECT 'hi' to fire as well as the SELECT #i. I still don't have an answer for your question, but I would definitely make sure not to have any stray SQL outside the trigger when you create it for this reason alone.
I've just investigated this a little more and apparently the end of the stored procedure is wherever the first GO is after the procedure (which SQL Server automatically adds to the end if you don't use one). So you could define your whole procedure after the END - you can still use the parameters too.
This seems to be because the BEGIN and END aren't a required part of the stored procedure definition - they're not actually indicating the begin and end of the stored procedure, they're just an unrelated BEGIN...END block like you might put after and IF statement. You can have as many BEGIN...END blocks as you like in the procedure definition, or none at all.
C) I would definitely change your trigger. You've massively complicated it by combining the 3 triggers without reusing any code. The only reason to combine INSERT,UPDATE and DELETE triggers is so that you don't have to duplicate code. You should either:
Have 3 separate triggers, each containing only the relevant INSERT - that way you remove all of the conditional logic.
Keep them together but work out only the CODE using some conditional logic and have only 1 INSERT statement.
I'd be tempted to go with the 3 separate triggers, or at least an separate out the delete trigger, and then use CASE del.ID IS NULL THEN 1 ELSE 2 END for the CODE on the INSERT/UPDATE trigger. But you could combine them with (untested):
INSERT INTO CACHE_UPDATE_TABLE (CODE, ID, DATE, COUNT)
SELECT CASE WHEN del.ID IS NULL THEN 1
WHEN ins.ID IS NULL THEN 3
ELSE 2 END
,ISNULL(ins.ID, del.ID)
,ISNULL(ins.DATE, del.DATE)
,ISNULL(ins.COUNT, del.COUNT)
FROM deleted del
FULL OUTER JOIN inserted ins ON del.ID = ins.ID
Just remove that
SELECT * FROM CACHE_UPDATE_TABLE
I am having an issue similar to the one described here:
How do I force a refresh of a fulltext index within a transaction in mssql?
However, the recommended solution posted there does not work. I tried posting a follow up to the same thread, but it was deleted by a moderator. So I am starting a new question.
Similar to the original query I am also attempting to implement a unit test within a transaction. I would like to insert data into a full text indexed column, query the data to check its validity, and then roll back the insert afterward.
The problem is that the index does not seem to update until after I have committed the transaction. I have tried "WAITFOR DELAY" approach, but no matter how long I wait the index does not update until after the transaction is committed.
Here's a sample of what I'm trying to do:
BEGIN TRAN
INSERT INTO AMMS.Content
(
ContentTypeId,
Name,
ImportDate,
IsDeleted,
LastModifiedBy,
LastModifiedAt,
DisplayInPortal,
StatusId
)
VALUES
(
4,
'my unit test content',
GETUTCDATE(),
0,
1,
GETUTCDATE(),
1,
2
)
declare #count int
set #count = 0
while #count < 10
begin
SELECT FULLTEXTCATALOGPROPERTY('PRIMARY', 'PopulateStatus') AS Status
select * from amms.Content where contains(Name, 'unit')
waitfor delay '00:00:01'
set #count = #count + 1
end
The populate status stays at 9 and the select returns no rows as long as the transaction is pending. Once I commit the populate status returns 0 and the select returns a single row as expected.
Am I missing something? Is there another way to accomplish this? Is this behavior different under different versions of SQL Server? (I'm currently testing using 2008)
I think you're out of luck. Trying to start a manual refresh inside a transaction...
begin tran
alter fulltext index on dbo.FTS_Table start full population
...gives me this message:
Msg 574, Level 16, State 0, Line 1 ALTER FULLTEXT INDEX statement
cannot be used inside a user transaction.
What alternatives you have probably depend on how you manage your unit tests, perhaps you can just DELETE the data again, or DROP the table completely if it's only used for this test.
Essentially SQL Server won't let you carry out certain actions in transaction (or in snapshot).
The Indexing service in particular quite deliberately runs externally to user connections due to the risks of contention and the huge overhead text indexing imposes.
If you step back and consider the implications for any other database connections and for the current one of a transaction re-writing the full text indexes I'm sure you'll appreciate that in this case Microsoft have a point.
On our SQL Server (Version 10.0.1600), I have a stored procedure that I wrote.
It is not throwing any errors, and it is returning the correct values after making the insert in the database.
However, the last command spSendEventNotificationEmail (which sends out email notifications) is not being run.
I can run the spSendEventNotificationEmail script manually using the same data, and the notifications show up, so I know it works.
Is there something wrong with how I call it in my stored procedure?
[dbo].[spUpdateRequest](#packetID int, #statusID int output, #empID int, #mtf nVarChar(50)) AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #id int
SET #id=-1
-- Insert statements for procedure here
SELECT A.ID, PacketID, StatusID
INTO #act FROM Action A JOIN Request R ON (R.ID=A.RequestID)
WHERE (PacketID=#packetID) AND (StatusID=#statusID)
IF ((SELECT COUNT(ID) FROM #act)=0) BEGIN -- this statusID has not been entered. Continue
SELECT ID, MTF
INTO #req FROM Request
WHERE PacketID=#packetID
WHILE (0 < (SELECT COUNT(ID) FROM #req)) BEGIN
SELECT TOP 1 #id=ID FROM #req
INSERT INTO Action (RequestID, StatusID, EmpID, DateStamp)
VALUES (#id, #statusID, #empID, GETDATE())
IF ((#mtf IS NOT NULL) AND (0 < LEN(RTRIM(#mtf)))) BEGIN
UPDATE Request SET MTF=#mtf WHERE ID=#id
END
DELETE #req WHERE ID=#id
END
DROP TABLE #req
SELECT #id=##IDENTITY, #statusID=StatusID FROM Action
SELECT TOP 1 #statusID=ID FROM Status
WHERE (#statusID<ID) AND (-1 < Sequence)
EXEC spSendEventNotificationEmail #packetID, #statusID, 'http:\\cpweb:8100\NextStep.aspx'
END ELSE BEGIN
SET #statusID = -1
END
DROP TABLE #act
END
Idea of how the data tables are connected:
From your comments I get you do mainly C# development. A basic test is to make sure the sproc is called with the exact same arguments you expect
PRINT '#packetID: ' + #packetID
PRINT '#statusID: ' + #statusID
EXEC spSendEventNotificationEmail #packetID, #statusID, 'http:\\cpweb:8100\NextStep.aspx'
This way you 1. know that the exec statement is reached 2. the exact values
If this all works than I very good candidate is that you have permission to run the sproc and your (C#?) code that calls it doesn't. I would expect that an error is thrown tough.
A quick test to see if the EXEC is executed fine is to do an insert in a dummy table after it.
Update 1
I suggested to add PRINT statements but indeed as you say you cannot (easily) catch them from C#. What you could do is insert the 2 variables in a log table that you newly create. This way you know the exact values that flow from the C# execution.
As to the why it now works if you add permissions I can't give you a ready answer. SQL security is not transparent to me either. But its good to research yourself a but further. Do you have to add both guest and public?
It would also help to see what's going inside spSendEventNotificationEmail. Chances are good that sproc is using a resource where it didn't have permission before. This could be an object like a table or maybe another sproc. Security is heavily dependent on context/settings and not an easy problem to tackle with a Q/A site like SO.
When I execute a sql statement like "Select ...", I can only see "...100%" completed...
I want to log the number of rows affected.
How can we do that?
run your SELECT from within a stored procedure, where you can log the rowcount into a table, or do anything else to record it...
CREATE PROCEDURE SSIS_TaskA
AS
DECLARE #Rows int
SELECT ... --your select goes here
SELECT #Rows=##ROWCOUNT
INSERT INTO YourLogTable
(RunDate,Message)
VALUES
(GETDATE(),'Selected '+CONVERT(varchar(10),ISNULL(#Rows,0))+' rows in SSIS_TaskA')
GO
When you use a SQL Task for a select most of the time you give as destination a DataSet Object, you can count the number of ligne from the DataSet
I believe you could leverage a t-sql output clause on your update or insert statement and capture that as an ssis variable....or just drop it into a sql table.
here is an example...its crappy, but it is an example
UPDATE TOP (10) HumanResources.Employee
SET VacationHours = VacationHours * 1.25
OUTPUT INSERTED.EmployeeID,
DELETED.VacationHours,
INSERTED.VacationHours,
INSERTED.ModifiedDate
INTO #MyTableVar;
You could output ##ROWCOUNT anyplace you need it to be.
Here is output syntax
http://technet.microsoft.com/en-us/library/ms177564.aspx
This one will take some explaining. What I've done is create a specific custom message queue in SQL Server 2005. I have a table with messages that contain timestamps for both acknowledgment and completion. The stored procedure that callers execute to obtain the next message in their queue also acknowledges the message. So far so good. Well, if the system is experiencing a massive amount of transactions (thousands per minute), isn't it possible for a message to be acknowledged by another execution of the stored procedure while another is prepared to so itself? Let me help by showing my SQL code in the stored proc:
--Grab the next message id
declare #MessageId uniqueidentifier
set #MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = #MessageId
--Select the entire message
...
...
In the above code, couldn't another stored procedure running at the same time obtain the same id and attempt to acknowledge it at the same time? Could I (or should I) implement some sort of locking to prevent another stored proc from acknowledging messages that another stored proc is querying?
Wow, did any of this even make sense? It's a bit difficult to put to words...
Something like this
--Grab the next message id
begin tran
declare #MessageId uniqueidentifier
select top 1 #MessageId = ActionMessageId from UnacknowledgedDemands with(holdlock, updlock);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = #MessageId
-- some error checking
commit tran
--Select the entire message
...
...
This seems like the kind of situation where OUTPUT can be useful:
-- Acknowledge and grab the next message
declare #message table (
-- ...your `ActionMessages` columns here...
)
update ActionMessages
set AcknowledgedTime = getdate()
output INSERTED.* into #message
where ActionMessageId in (select top(1) ActionMessageId from UnacknowledgedDemands)
and AcknowledgedTime is null
-- Use the data in #message, which will have zero or one rows assuming
-- `ActionMessageId` uniquely identifies a row (strongly implied in your question)
...
...
There, we update and grab the row in the same operation, which tells the query optimizer exactly what we're doing, allowing it to choose the most granular lock it can and maintain it for the briefest possible time. (Although the column prefix is INSERTED, OUTPUT is like triggers, expressed in terms of the UPDATE being like deleting the row and inserting the new one.)
I'd need more information about your ActionMessages and UnacknowledgedDemands tables (views/TVFs/whatever), not to mention a greater knowledge of SQL Server's automatic locking, to say whether that and AcknowledgedTime is null clause is necessary. It's there to defend against a race condition between the sub-select and the update. I'm certain it wouldn't be necessary if we were selecting from ActionMessages itself (e.g., where AcknowledgedTime is null with a top on the update, instead of the sub-select on UnacknowledgedDemands). I expect even if it's unnecessary, it's harmless.
Note that OUTPUT is in SQL Server 2005 and above. That's what you said you were using, but if compatibility with geriatric SQL Server 2000 installs were required, you'd want to go another way.
#Kilhoffer:
The whole SQL batch is parsed before execution, so SQL knows that you're going to do an update to the table as well as select from it.
Edit: Also, SQL will not necessarily lock the whole table - it could just lock the necessary rows. See here for an overview of locking in SQL server.
Instead of explicit locking, which is often escalated by SQL Server to higher granularity than desired, why not just try this approach:
declare #MessageId uniqueidentifier
select top 1 #MessageId = ActionMessageId from UnacknowledgedDemands
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = #MessageId and AcknowledgedTime is null
if ##rowcount > 0
/* acknoweldge succeeded */
else
/* concurrent query acknowledged message before us,
go back and try another one */
The less you lock - the higher concurrency you have.
Should you really be processing things one-by-one? Shouldn't you just have SQL Server acknowledge all unacknowledged messages with todays date and return them? (all also in a transaction of course)
Read more about SQL Server Select Locking here and here. SQL Server has the ability to invoke a table lock on a select. Nothing will happen to the table during the transaction. When the transaction completes, any inserts or updates will then resolve themselves.
You want to wrap your code in a transaction, then SQL server will handle locking the appropriate rows or tables.
begin transaction
--Grab the next message id
declare #MessageId uniqueidentifier
set #MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = #MessageId
commit transaction
--Select the entire message
...