Stored procedure never deletes rows when called - tsql

Inside a SQL Server 2012 stored procedure, I have the following lines of code that do the following:
Declares and sets a variable called #galaxyID
Then uses that #galaxyID to delete all rows from a table called galaxyObjects that have a particular galaxyID
I need to check to make sure that #galaxy is not null because sometimes the column is NULL in the table astroList.
I don't have any errors, but nothing is ever deleted and I'm not sure why.
Here is the code:
DECLARE #galaxyID UNIQUEIDENTIFIER
SET #galaxyID = (SELECT galaxyID FROM astroList WHERE astroID = #astroID)
DELETE FROM galaxyObjects
WHERE galaxyID = #galaxyID AND #galaxyID IS NOT NULL

You can't really use SET #var = (SELECT ...) unless there is no possible way for the SELECT to ever return more than one row. So anyone looking at this code that doesn't know the data model will assume this is a ticking time bomb. A safer option in SQL Server is to use a DELETE with a join (which works for any number of rows, and is a no-op when there is no match):
DELETE go
FROM dbo.astroList AS al
INNER JOIN dbo.galaxyObjects AS go
ON al.galaxyID = go.galaxyID
WHERE al.astroID = #astroID;
This will ignore rows where galaxyID in astroList is NULL.

Related

Calling stored procedure in select statement with Db2

I have a select statement that's working fine but I need one more value that comes from a stored procedure and I can't call it as a value in the select statement
select
recp.percent,
(call libraryEXT.get_rate_for_detail(detail_number))
from
mainTable.orders ORD
inner join
mainTable.receipts recp on ORD.rcptID = recp.rcptID
It works fine to only get recp.percent, but putting the next value in for calling the stored procedure says it doesn't expect "call"
If I run the stored procedure by itself it returns one record with the columns: name, rcptID, time and I need the rcptID from that
How can I properly call the stored procedure and make the value returned be my other value in the select statment?
As #mao said, this is not possible to call an SP in a select statement.
If you are the SP owner, try creating a table-function instead. This one can be called in a select statement.
Given the way you've used CALL libraryEXT.get_rate_for_detail(detail_number) it's apparently only returning a single value.
That being the case, you'd be better served by having it as a scaler User Defined Function (UDF).
Then this is what your code would look like:
select
recp.percent,
libraryEXT.get_rate_for_detail_UDF(detail_number) as rate
from
mainTable.orders ORD
inner join
mainTable.receipts recp on ORD.rcptID = recp.rcptID
I renamed the function just to be clear it's NOT the stored proc. If you created the stored proc, you could delete it and use the same name for the UDF.

Error during execution of trigger. How to rework select statement?

I'm relatively new to triggers so forgive me if this doesn't look how it should. I am creating a trigger that checks a user account for last payment date and sets a value to 0 if they haven't paid in a while. I created what I thought was a correct trigger but I get the error, "error during execution of trigger" when its triggered. From what I understand the select statement is causing the error as it selecting values which are in the process of being changed. Here is my code.
CREATE OR REPLACE TRIGGER t
BEFORE
UPDATE OF LASTLOGINDATE
ON USERS
FOR EACH ROW
DECLARE
USER_CHECK NUMBER;
PAYMENTDATE_CHECK DATE;
ISACTIVE_CHECK CHAR(1);
BEGIN
SELECT U.USERID, U.ISACTIVE, UP.PAYMENTDATE
INTO USER_CHECK, PAYMENTDATE_CHECK, ISACTIVE_CHECK
FROM USERS U JOIN USERPAYMENTS UP ON U.USERID = UP.USERID
WHERE UP.PAYMENTDATE < TRUNC(SYSDATE-60);
IF ISACTIVE_CHECK = 1 THEN
UPDATE USERS U
SET ISACTIVE = 0
WHERE U.USERID = USER_CHECK;
INSERT INTO DEACTIVATEDUSERS
VALUES(USER_CHECK,SYSDATE);
END IF;
END;
From what I thought, since the select is in the begin statement, it would run before an update, nothing would be changing about the tables until after if runs through the trigger. I tried but using :old in front of the select variables but that doesn't seem to be the right use.
And here is the update statement i was trying.
UPDATE USERS
SET LASTLOGINDATE = SYSDATE
WHERE USERID = 5;
Some issues:
The select you do in the trigger sets the variable isactive_check to a payment date, and vice versa. There is an accidental switch there, which will have a negative effect on the next if;
The same select should return exactly one record, which by the looks of it is not guaranteed, since you join with table userpayments, which may have several payments for the selected user that meet the condition, or none at all. Change that select to do an aggregation.
If a user has more than one payment record, the condition might be true for one, but not for another. So if you are interested only in users who have not paid in a long while, such user should not be included, even though they have an old payment record. Instead you should check whether all records meet the condition. This you can do with a having clause.
As the table users is mutating (the update trigger is on that table), you cannot perform every action on that same table, as it would otherwise lead to a kind of deadlock. This means you need to rethink what the purpose is of the trigger. As this is about an update for a specific user, you actually don't need to check the whole table, but only the record that is being changed. For that you can use the special new variable.
I would suggest this SQL instead:
SELECT MAX(UP.PAYMENTDATE)
INTO PAYMENTDATE_CHECK
FROM USERPAYMENTS
WHERE USERID = :NEW.USERID
and then continue with the checks:
IF :NEW.ISACTIVE = 1 AND PAYMENTDATE_CHECK < TRUNC(SYSDATE-60) THEN
:NEW.ISACTIVE := 0;
INSERT INTO DEACTIVATEDUSERS (USER_ID, DEACTIVATION_DATE)
VALUES(USER_CHECK,SYSDATE);
END IF;
Now you have avoided to do anything in the table users and have made the checks and modification via the :new "record".
Also, it is good practice to mention the column names in an insert statement, which I have done in above code (adapt column names as needed):
Make sure the trigger is compiled and produces no compilation errors.

Using a tsql trigger to keep data synced up in two tables

I have a Products table which contains an attribute that will get updated via an ERP update by an end user. When that happens I need the update to be replicated in another table. I am not at all experienced with creating T-SQL triggers but I believe it will accomplish my objective.
Example:
In the IC_Products table:
Productkey = 456
StockLocation = ‘GA-07-A250’
In the IC_ProductCustomFields table (will start out the same because I will run a script to make it so):
Productkey = 456
CustomFieldKey = 13
Value = ‘GA-07-A250’
When the IC_Products.StockLocation column gets updated then I want the value in new IC_ProductCustomFields.Value to also get updated automatically and immediately.
If a new record is created in IC_Products then I want a new record to also be created in IC_ProductCustomFields.
I would like to know how to write the trigger script as well as how to implement it. I am using SQL Server 2005.
You want something like this:
CREATE TRIGGER [dbo].[tr_Products_SyncCustomFields] ON [dbo].[IC_Products]
FOR INSERT, UPDATE
AS
-- First, we'll handle the update. If the record doesn't exist, we'll handle that second
UPDATE IC_ProductCustomFields
SET Value = inserted.StockLocation
FROM IC_ProductCustomFields cf
INNER JOIN inserted -- Yes, we want inserted. In triggers you just get inserted and deleted
ON cf.Productkey = inserted.Productkey AND CustomFieldKey = 13;
-- Now handle the insert where required. Note the NOT EXISTS criteria
INSERT INTO IC_ProductCustomFields (Productkey, CustomFieldKey, Value)
SELECT Productkey, CustomFieldKey, Value
FROM inserted
WHERE NOT EXISTS
(
SELECT *
FROM IC_ProductCustomFields
WHERE Productkey = inserted.Productkey AND CustomFieldKey = 13
);
GO
You could, I think, do separate triggers for insert and update, but this will also have the side-effect of restoring your (supposed?) invariants if the custom fields ever get out of sync; even in an update, if the custom field doesn't exist, this will insert the new record as required to bring it back into compliance with your spec.

Getting Affected Rows by UPDATE statement in RAW plpgsql

This has been asked multiple times here and here, but none of the answers are suitable in my case because I do not want to execute my update statement in a PL/PgSQL function and use GET DIAGNOSTICS integer_var = ROW_COUNT.
I have to do this in raw SQL.
For instance, in MS SQL SERVER we have ##ROWCOUNT which could be used like the following :
UPDATE <target_table>
SET Proprerty0 = Value0
WHERE <predicate>;
SELECT <computed_value_columns>
FROM <target>
WHERE ##ROWCOUNT > 0;
In one roundtrip to the database I know if the update was successfull and get the calculated values back.
What could be used instead of '##ROWCOUNT' ?
Can someone confirm that this is in fact impossible at this time ?
Thanks in advance.
EDIT 1 : I confirm that I need to use raw SQL (I wrote "raw plpgsql" in the original description).
In an attempt to make my question clearer please consider that the update statement affects only one row and think about optimistic concurrency:
The client did a SELECT Statement at first.
He builds the UPDATE and knows which database computed columns are to be included in the SELECT clause. Among other things, the predicate includes a timestamp that is computed each time the rows is updated.
So, if we have 1 row returned then everything is OK. If no row is returned then we know that there was a previous update and the client may need to refresh the data before trying to update clause again. This is why we need to know how many rows where affected by the update statement before returning computed columns. No row should be returned if the update fails.
What you want is not currently possible in the form that you describe, but I think you can do what you want with UPDATE ... RETURNING. See UPDATE ... RETURNING in the manual.
UPDATE <target_table>
SET Proprerty0 = Value0
WHERE <predicate>
RETURNING Property0;
It's hard to be sure, since the example you've provided is so abstract as to be somewhat meaningless.
You can also use a wCTE, which allows more complex cases:
WITH updated_rows AS (
UPDATE <target_table>
SET Proprerty0 = Value0
WHERE <predicate>
RETURNING row_id, Property0
)
SELECT row_id, some_computed_value_from_property
FROM updated_rows;
See common table expressions (WITH queries) and depesz's article on wCTEs.
UPDATE based on some added detail in the question, here's a demo using UPDATE ... RETURNING:
CREATE TABLE upret_demo(
id serial primary key,
somecol text not null,
last_updated timestamptz
);
INSERT INTO upret_demo (somecol, last_updated) VALUES ('blah',current_timestamp);
UPDATE upret_demo
SET
somecol = 'newvalue',
last_updated = current_timestamp
WHERE last_updated = '2012-12-03 19:36:15.045159+08' -- Change to your timestamp
RETURNING
somecol || '_computed' AS a,
'totally_new_computed_column' AS b;
Output when run the 1st time:
a | b
-------------------+-----------------------------
newvalue_computed | totally_new_computed_column
(1 row)
When run again, it'll have no effect and return no rows.
If you have more complex calculations to do in the result set, you can use a wCTE so you can JOIN on the results of the update and do other complex things.
WITH upd_row AS (
UPDATE upret_demo SET
somecol = 'newvalue',
last_updated = current_timestamp
WHERE last_updated = '2012-12-03 19:36:15.045159+08'
RETURNING id, somecol, last_updated
)
SELECT
'row_'||id||'_'||somecol||', updated '||last_updated AS calc1,
repeat('x',4) AS calc2
FROM upd_row;
In other words: Use UPDATE ... RETURNING, either directly to produce the calculated rows, or in a writeable CTE for more complex cases.
Generally the answer to this question depends on the type of the driver used.
PQcmdTuples() function does what is needed, if the application uses libpq. Other libraries on top of libpq need to have some wrapper on top of this function.
For JDBC the Statement.executeUpdate() method seems to the job.
ODBC provides SQLRowCount() function for the similar purpose.

How do I avoid using cursors in Sybase (T-SQL)?

Imagine the scene, you're updating some legacy Sybase code and come across a cursor. The stored procedure builds up a result set in a #temporary table which is all ready to be returned except that one of columns isn't terribly human readable, it's an alphanumeric code.
What we need to do, is figure out the possible distinct values of this code, call another stored procedure to cross reference these discrete values and then update the result set with the newly deciphered values:
declare c_lookup_codes for
select distinct lookup_code
from #workinprogress
while(1=1)
begin
fetch c_lookup_codes into #lookup_code
if ##sqlstatus<>0
begin
break
end
exec proc_code_xref #lookup_code #xref_code OUTPUT
update #workinprogress
set xref = #xref_code
where lookup_code = #lookup_code
end
Now then, whilst this may give some folks palpitations, it does work. My question is, how best would one avoid this kind of thing?
_NB: for the purposes of this example you can also imagine that the result set is in the region of 500k rows and that there are 100 distinct values of look_up_code and finally, that it is not possible to have a table with the xref values in as the logic in proc_code_xref is too arcane._
You have to have a XRef table if you want to take out the cursor. Assuming you know the 100 distinct lookup values (and that they're static) it's simple to generate one by calling proc_code_xref 100 times and inserting the results into a table
Unless you are willing to duplicate the code in the xref proc, there is no way to avoid using a cursor.
They say, that if you must use cursor, then, you must have done something wrong ;-) here's solution without cursor:
declare #lookup_code char(8)
select distinct lookup_code
into #lookup_codes
from #workinprogress
while 1=1
begin
select #lookup_code = lookup_code from #lookup_codes
if ##rowcount = 0 break
exec proc_code_xref #lookup_code #xref_code OUTPUT
delete #lookup_codes
where lookup_code = #lookup_code
end