Using cursor or while loop or any other way? - sql-server-2008

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

Related

Set then Select

Can somebody please help me with this simple (but I don't know how) task?
In order to avoid wasting time fixing every single line, I'm trying to do something like this
SET #auditor = a, b, c;
SELECT *
FROM Audits
WHERE auditor in (#auditor)
My query indeed is really long and this part "WHERE auditor in (#auditor)" will be repetitive. Therefore, doing some kind of Set first will make it faster to update when there are some changes in the work force (we hire "d" and/or "b" left)
Thanks in advance
I recommend using a temporary table at the top of your query instead:
CREATE TEMPORARY TABLE FilteredAudits AS (SELECT * FROM Audits WHERE Auditor IN (a,b,c))
Then replace Audits with FilteredAudits as needed.
The benefits to this approach is that you guarantee the filter will only be applied once, meaning the rest of the query will JOIN to the smallest data set possible, and you don't have to repeat your IN() filter throughout the query.
You can use an array variable and FIND_IN_SET
SET #auditors = 'a,b,c';
SELECT *
FROM Audits
WHERE FIND_IN_SET(auditor,#auditors)
Explanation:
FIND_IN_SET(auditor,#auditors) is true is auditor is found in #auditors and in this case it returns the index.
But I would consider saving the auditors in a separate table rather than in a variable to better keep track of them.
you cannot set a parameter like an array. you will have to do something like
set #auditor='a,b,c'
now if auditor is a number this should work but if it is anything other then you will have to create a funciton dbo.[tfn_SplitList] on your system you will use it like below
auditor in(select List from dbo.[tfn_SplitList](#auditor ,','))

selectively UPDATING old rows when INSERTING new ones into table (trigger issue) in MySQL

I've come across a slight issue when designing a trigger statement in our MySQL DB today:
We have the following relevant tables:
quote (quote_key, date, discount_rate, discount_period, supplier_key)
part (part_key, part_name, ...)
part_quote (pq_key, part_price, fk_quote_key, fk_part_key)
part_approval (papproval_key, is_approved, fk_part_quote_key)
Now for an explanation of the logic:
Every supplier issues quotes for different inputs into a manufacturing process (e.g. parts and components). If the price for a part is about right, they will be approved for manufacturing and can thus be used by our engineers. Since we want to be able to receive quotes from different suppliers for the same parts to enable a comparison, I've tried to model this process by using the part_quote and part_approval table.
On to my problem: If I want to approve a new part_quote, I would like the BOOL flag "is_approved" in all (or the most recent) old quotes to be automatically set to FALSE.
I tried to get this done by issuing a trigger statement:
CREATE TRIGGER update_approval BEFORE INSERT ON part_approval
FOR EACH ROW --- ??
I have some problems selecting the right rows to update:
How do I select the part_key, which will ultimately identify all relevant rows that require updating?
How do I select only the old (or most recent) rows?
I would have loved to include a screener but unfortunately, I do not have 10reps yet :/
Thank you so much in advance,
All the best,
Marius
First I will answer your question as-is to the best of my ability since it is helpful to know more about how to use triggers. Then I will explain why you shouldn't actually be using triggers.
Triggers provide access to two special aliases, which are not otherwise available: OLD and NEW. NEW lets you access the new values and works in update/insert triggers. OLD lets you access the old values and works in update/delete triggers.
For your case you would probably want something like this:
CREATE TRIGGER `update_approval` BEFORE INSERT ON `part_approval`
FOR EACH ROW
UPDATE `table_x` SET `value` = y WHERE `z` = NEW.a;
For more information and some useful examples of triggers, I would suggest reading this: http://dev.mysql.com/doc/refman/5.0/en/trigger-syntax.html
HOWEVER, triggers in MySQL cannot update the same table which they were triggered on, which is what you are looking to do. There is a good question someone had relating to this here: MySQL - Trigger for updating same table after insert and a good answer that you should use a stored procedure in this case.
I'm not sure this will solve my problem entirely, but based on your suggestions I came across a couple of helpful posts, that were able to implement similar updates within a trigger with multiple statements.
CREATE TRIGGER update_approval BEFORE INSERT ON part_approval
FOR EACH ROW BEGIN
DECLARE part_id INT;
SELECT part_key INTO part_id
FROM part p, part_quote pq, part_approval a
WHERE NEW.part_quote_key=pq.part_quote_key AND pq.part_key = p.part_key;
UPDATE part p, part_quote pq, part_approval a
SET a.is_approved=DEFAULT
WHERE pq.part_key=part_id AND a.approval_date<NEW.approval_date;
END;
will only be able to try it out on monday after the DB has been populated.
thanks for the help!

TSQL Use record currently being inserted to select data in another table

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.

MySql COUNT(*) is different in Stored Procedure

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.

In SQL, how do I exclude result from SELECT * FROM ...?

I know my title is not very descriptive... let me explain in details here.
Let say, if a table has 26 fields. e.g. field_a ... field_z. and I only want a select query to return me 15 fields only.
So, normally, I will do SELECT field_a, field_b ... field_o FROM myTable.
Which is tedious. Is there a way in MYSQL that I can do a SELECT * and tell it not to return certain fields?
e.g. soemthing like SELECT * exclude (field_p, field_q .. field_z) FROM myTable?
Thanks all for the answers. :)
SELECT * is evil.
You should never use it in production code.
You should always specify the columns you want returned, like so:
SELECT `column1`, `column2`, `someothercolumn`
FROM `myTable`
As always, the documentation can help with the nitty gritty!
SQL itself does not support the functionality you are asking for. (If it did, it would have the same problems as select *. See other's comments)
This is what I do when I'm using MySQL:
Fire up mysql command line client
Perform a describe my_table
Copy the data in Field column (mouse select)
Paste data in my editor (TextPad)
Manually remove the columns I don't need
Run a macro that substitutes new line for a comma (and insert a table alias)
All in all, it takes around 20-30 seconds to create a select list regardless of the number of columns. This way ensures that I don't misspell or forget any columns.
delimiter //
DROP PROCEDURE IF EXISTS `getColumnNames`//
CREATE PROCEDURE `getColumnNames` (db_name CHAR(255), t_name CHAR(255), ex_name CHAR(255))
BEGIN
SELECT group_concat(column_name) FROM `information_schema`.`COLUMNS` C
WHERE
table_schema = db_name
AND
table_name = t_name
AND
column_name not in (ex_name)
GROUP BY table_schema,table_name;
END
//
delimiter ;
call getColumnNames("Db_name", "tbl_name", "col_to_exclude");
No use the fields you want (select * shouold not appear in production code even if you want allthe fields). I don't know about mySQL but in SQL Server I can drag and drop the columns which makes it easy to specify, is there a way to make this less tedious by dragging and dropping?
There's no way to do this because 'SELECT *' is a bad idea in a production environment anyhow. Just think of all the extra error-catching that would need to be done if something like this existed -- what if the excluded field didn't exist in the table? Does this create an error? A warning?
Enumerating all your fields is indeed tedious, but it's the correct way to do it. It makes your code (a little bit more) self-documenting, and helps stop errors early in the cycle. For one example, if 'user_name' is eventually renamed to 'username' for whatever reason, the SQL statement will fail and you won't have a strange data lolling around in your code waiting to be traced down.
I hope that there's none way to do this. Even if there is one, I'd suggest not to use it.
Kindof solution is to create a view that excludes the one row you want to have excluded, and you can select * from the view.