I am working on a 'grading' system, and am trying to make sure that a person is not able to submit a grade twice by using a stored procedure that will check if a person has graded a particular item before allowing a new grade to be saved. The odd thing is, I am passing a user ID and object ID, but when my stored procedure selects the COUNT(*), I get the wrong number.
Here is my stored procedure:
CREATE PROCEDURE `ADD_GRADE`(IN objectID int, IN grader int)
BEGIN
DECLARE gradeCount INT;
SELECT COUNT(*)
FROM GRADES
WHERE Obj_ID = objectID AND Grader = grader
INTO gradeCount;
IF gradeCount <= 0 THEN INSERT INTO Grades (Obj_ID, Grader) VALUES (objectID, grader);
END IF;
The IF statement is working, but for some reason my gradeCount appears to be ignoring the Grader ID and just checking based upon the Obj_ID. I've added selects to be sure my parameters are staying at the correct value and they are. If I copy just the select and do it elsewhere manually, I get the correct number.
Though new to MySql, I'm not new to SQL itself. Am I just going crazy? Or am I doing something wrong?
Thanks
Even if this code worked (I don't know why it does not) it is not the proper way to make sure something is only entered once.
The proper way is to apply a unique constraint on the objectID and grader columns. Then try inserting the row. If the row inserts then the values are unique. If you get a unique violation then the values have already been entered.
If you do not have unique constraints available you should lock the table to make sure no other updates are happening between your commands.
My guess is because MySQL is case insensitive for field names, and the code
Grader = grader
is just comparing the column with itself, which is always true. you may need to rename the parameter you are passing in so that it doesn't have the same name as an existing column. I usually have all my stored procedure arguments proceeded with __ (douple underscore) so I don't run into situations like this.
Related
After I defined a trigger and its procedure, to diminish the amount of books on stock by an amount specified on a separate table (both being variable), it gives me error 1054, of an unknown column in the procedure.
The code that defines the trigger and procedure is the following:
CREATE DEFINER=`root`#`localhost` TRIGGER reduce_available
AFTER INSERT ON book_customer FOR EACH ROW
UPDATE stock SET stock.current_amount = stock.current_amount-book_customer.amount;
Usually to process the type of interactions that would warrant this type of trigger instead I generate an entirely new table that records the purchases and sellings, with aditional useful data. However, in this specific case I don't have such a table, and would like to know how to work around it.
Some clarification:
The procedure ins't meant to update more than 1 table.
The trigger only triggers in one table, as it should.
I'm searching for a way to fix my procedure usage, not alternative solutions.
Additional information:
The code works when I set the "minus" value to 1, which means that the unknown table is the one that gives the error:
UPDATE stock SET stock.current_amount = stock.current_amount-1;
I can't simply do this because then I've got the issue of not being able to rent a customer more than 1 book at a time, due to not having a "sell" table that creates cronological dependence while in the same operation.
Both data values(stock.current_amount and book_customer.amount) are the same type (INT).
Due to this SQL program being made because of a very small assignment, neither me nor my team looked-up proper "coding design", instead relying on a self-stablished method. (Maybe this matters, I dunno)
The error I recieve is "1054", but if I mess around it changes between "1063" and a "11xx" I can't seem to replicate.
Edits:
Solved!
Rookie mistake; I used an uncomplete sintaxis without a "where" clause.
The reason I didn't find it while searching was that I didn't know there could be more than 1 sintaxis in the first place.
I forgot to say many thanks to Linoff!
Improving readability of the question.
Do you just want to refer to new? If so, I assume you only want to update one row in stock rather than all of them. You need a where clause
CREATE DEFINER=`root`#`localhost` TRIGGER reduce_available
AFTER INSERT ON book_customer FOR EACH ROW
BEGIN
UPDATE stock s
SET s.current_amount = s.current_amount - new.amount
WHERE s.book_id = new.book_id;
END;
I was attempting to run a simple Insert into my CustomerOrders table and got the error: #1109 - Unknown table 'ConcertDetails' in field list
I did some searches on this and looked at about 7 different stack overflow posts on it but still not sure what is wrong. I also looked up information on triggers and there seems to be different syntax on different sites. The weird thing is this trigger used to work just fine, not sure what has been altered since 4 days ago.
I tried changing some things, for instance I removed my trigger and it let me insert, but when I put the trigger back I couldn't insert any more, so there must be something wrong with the trigger. This is what I have for the trigger:
DROP TRIGGER IF EXISTS `alterPurchasePrice`;
DELIMITER //
CREATE TRIGGER `alterPurchasePrice` BEFORE INSERT ON `CustomerOrders`
FOR EACH ROW BEGIN
IF new.DiscountCode = 'yes' THEN
SET new.PurchasePrice = ConcertDetails.Cost - 10;
END IF;
END
//
DELIMITER ;
The purpose of the trigger is to lower the price by $10 if the user types 'yes' into the DiscountCode field.
This involves the tables:
CustomerOrders: ConcertID, CustomerName, Discount Code, OrderID,
PurchasePrice ConcertDetails: ConcertDate, ConcertID, Cost
I think you need a SELECT to retrieve values from other tables.
To get the value of "cost" from "ConcertDetails" table, for a specific concert, we could write a query like this:
SELECT ConcertDetails.cost
FROM ConcertDetails
WHERE ConcertDetails.concertid = ?
assuming that "concertid" is the primary key (or unique key) of the ConcertDetails table, we would be guaranteed that the query would return at most one row.
To put that to use in the trigger, we should be able to do something like this
SET NEW.PurchasePrice
= ( SELECT d.cost - 10 AS discount_price
FROM ConcertDetails d
WHERE d.concertid = NEW.concertid
);
The value of the concertid column of the row to be inserted from the CustomerOrders table will be supplied for the query through the reference to NEW.concertid. The return from that query will be assigned to the purchaseprice column.
If the query returns more than one row (which could happen if we don't have any kind of guarantee that "concertid" is unique in the "ConcertDetails" table), the trigger will throw a "too many rows" error.
If there are no rows returned, we'd expect a NULL value to be assigned would be assigned to "purchaseprice". We would also get a NULL returned if the "cost" column is set to NULL.
Is there some sort of guarantee that "cost" will never be less than 10? If the returned Cost is 6, then the value assigned to "purchaseprice" would be -4. If we want the value assigned to "purchaseprice" to never be less than zero, we could do something like this:
SET NEW.purchaseprice
= ( SELECT GREATEST(d.cost-10,0) AS discount_price
FROM ConcertDetails d
WHERE d.concertid = NEW.concertid
);
Other notes:
I'd recommend a different naming convention for your triggers. With multiple tables and multiple triggers, it can get kind of confusing, when looking for triggers on CustomerOrders table, to remember that the name of the BEFORE INSERT trigger is "alterPurchasePrice". Especially if you (or someone else) is coming back to work on the system six months or six years from now.
The convention we follow for trigger names is to use the name of the table, followed by an underscore, followed by one of: bi, bu, bd, ai, au, ad (for Before/After Insert/Update/Delete). (Since MySQL doesn't allow more than one trigger for each of those, we don't get naming collisions. And it makes it easier to check whether a BEFORE INSERT trigger exists on a table, before someone writes a BEFORE INSERT trigger that does something else.)
I also mention, in regards to the use of CamelCase table names... the MySQL Reference Manual says this:
To avoid problems ... it is best to adopt a consistent convention, such as always creating and referring to databases and tables using lowercase names. This convention is recommended for maximum portability and ease of use.
https://dev.mysql.com/doc/refman/5.7/en/identifier-case-sensitivity.html
Then again, these are just conventions. We also follow a convention to name tables by naming what a single row represents. If we had a requirement to create this table, we would assign the name customer_order. And the BEFORE INSERT trigger on the table would be named customer_order_bi
Very new to TSQL...
I have the following table called "tblinit":
Account_Num UserID Task_Percent
----------- ------------ ------------
1 john.smith 0.75
I would like to update the "Task Percent" value in "tblRaw" below.
Account_Num UserID Task_Percent
----------- ------------ ------------
1 john.smith 0.5
2 mary.mickle 0.9
3 don.donalds 1
My plan is to use a TSQL stored procedure executed by a trigger on insert into "tblinit". The stored procedure will move the data into "tblRaw" (either a merge or a delete and insert) and then truncate "tblinit" when the procedure is done. tblInit is only used to stage incoming data.
I have read about SCOPE_IDENTITY and ##IDENTIY but don't fully grasp the concept. Is the scope defined by the trigger which executes the stored procedure? In attempting my own SELECT statements using SCOPE_IDENTITY and ##IDENTITY I always return with a "NULL" result. The referenced MSDN article seems to return primary keys that don't correlate to the data specified in the article's example. Clearly I am reading something incorrectly. I want to grab the record that was just inserted and use it in my query.
In essence, how do I update john.smith's new percentage value automatically on insert or, alternatively, how do I add a new record entirely?
how do I update john.smith's new percentage value automatically on insert
This trigger could be used to do exactly that:
create trigger tblinit_to_tblRaw
on tblinit
for insert
as
begin
update r
set r.Task_Percent = i.Task_Percent
from inserted i
join tblRaw r on i.UserID = r.UserID -- Join on Account_Num instead?
end
This does not take into account new records (no existing match in tblRaw). For that you might want to run if exists(... or merge.
I must confess to being a bit confused as to your intent given the various concepts you've referred to.
If you want to delete from the original table after your trigger fires in the update/delete then you're doing it wrong.
If you just want to keep a running total in another table for performance reasons then check out "indexed views".
If you want to add something to one table then update another and remove from the original then you are either looking for a queue or simply a stored procedure to perform the update on the appropriate table. You do not need to do complex steps with triggers and stuff.
No idea where the IDENTITY stuff comes from. Pretty sure you don't need it here.
I think you're making it more complex than it needs be.
I could be wrong - feel free to elaborate.
I've gone through several discussions related (not exact) to this but I want to know the correct solution to my situation hence posting this question.
I need to select some records from a table based on some condition and pass each record values to a SP.
I thought of using Cursor to loop through the record set, after some googling I found while loop is also used and many discussions on which one is best are there and most of them said that it depends on the situation. Now I'm not able to judge my situation hence putting in front of experts.
Here is the while loop I thought of:
Table Test
{
id int
value int
-- Some more fields
}
WHILE EXISTS(SELECT TOP 1 id FROM Test WHERE isValid=1)
BEGIN
DECLARE #id AS INT
DECLARE #value AS INT
SELECT TOP 1 #id=id, #value=value FROM Test WHERE isValid=1
EXEC SP_SomeProcessingSP #id, #value -- Some more fields passed to it from above table
-- After execution of the above SP I need to update the record to invalid
UPDATE Test SET isValid = 0 WHERE id=#id
END
Is this approach better than cursor? It would be great if anyone can come up with better solution without while and cursor (I want to avoid both).
Edit: Modified the while block and gave example table as well. Now the SP which is called inside this while block accesses data from couple of tables and do some processing and insert data into couple of other tables.
Looks like a round about way to do:
UPDATE Test SET isValid = 0 WHERE isValid = 1
As for what approach is best, you have not supplied enough detail for an answer.
It entirely depends on what you are trying to do. Perhaps a recursive CTE would solve your problems without the need to call a stored procedure. Perhaps a good join would solve it. Perhaps a different solution would be best.
You need to explain what exactly you are trying to achieve.
You could modify the stored procedure to take tables as inputs and then select into a table variable. For the update you can join back on that table variable.
Check out Table Valued Parameters here: http://msdn.microsoft.com/en-us/library/bb510489.aspx
I have a table A (SQL Server 2008) that contains sets of min and max numbers. In one of my stored procedures I use this table joined with a product table B to find an available product number that's between min and max and then insert a new product with this product number. So I want the next free number that's between min/max.
Between finding out the next available number and inserting the product row I want a lock to prevent anyone of finding the same number (and giving a duplicate).
How should I think in this situation? Should I get an update lock on the A table even though I never modify it? The lock should be released after I do the insert into table B and the transaction finishes? Will this update lock prevent other transactions from reading table A?
Edit: The min/max table is a table for different product series. Depending on which serie you want i want to try and find an available number in this sequence. The productnr is not unique, it would be possible to make it unique in combination with a second column though. Simplified sp:
CREATE PROCEDURE [dbo].[InsertProduct]
(
#Param1 int,
#Param2 bit,
...
#Param20 int) AS
BEGIN
DECLARE #ProductNr int
--Here I do a query to determine which ProductNr I should have. Checking that the number is between max/min in the series and that no other product has this productnr.
--Now insert the row
INSERT INTO Products VALUES (#Param1, #ProductNr, ...., #Param2
END
Your question is kind of obscure, it would help if you included some sample data.
Regardless, a tactic that I have used in the past is to try and wrap everything withing a single statement -- here, it would be an INSERT. Every SQL statement is de-facto wrapped in its own implicit transaction (that's atomicity, what the "A" in the ACID of relational database properties fame stands for). In psuedo-code, it'd look something like:
INSERT MyTable (Id, Plus, Other, Columns)
select CalcForNewId, Plus, Other, Columns
from [what may be a pertty convoluted query to determine the "next" valid Id]
This only works if you can write your business logic as a single reasonable query (where "reasonable" means it doesn't lock, block, or deadlock either the current or any other user for an unreasonable length of time). That can be a pretty tall order, but I'd take that over having to write complex BEGIN TRANSACTION/COMMIT/ROLLBACK code intermixed with TRY...CATCH blocks any day. (That will of course work, and I've upvoted #Scott Bruns accordingly.)
Just insert the next number into table B. If it commits it is yours to use. If you get a Key Violation it means that a different process has just entered the new number. In that case increment the new number and try again.
The database will automatically handle the concerency for you. No manual locking is required.