What is wrong with the syntax on this case statement? - sql

I am trying to insert values into a temp table via a cursor and if they have a period on the end, I would like to remove the period. Here is the code I am having syntax issues with.
WHILE ##FETCH_STATUS = 0
BEGIN
CASE SELECT CHARINDEX('.',REVERSE(#Category))
WHEN 1
THEN INSERT INTO #Category VALUES (SUBSTRING(#Category,1,LEN(#Category)-1)))
END;
What am I doing incorrectly here? I'm open to more efficient answers but would also like to know how to solve it this way.

CASE yields an expression, not a statement. You are looking for the IF statement.
As for a better way to do this: Scrap the cursor and use an INSERT INTO ... SELECT ... WHERE CHARINDEX('.', REVERSE(category)) = 1 statement.

CASE is used to compute an expression, not control program flow. Use IF instead:
WHILE ##FETCH_STATUS = 0
BEGIN
IF CHARINDEX('.',REVERSE(#Category)) = 1
INSERT INTO #Category VALUES (SUBSTRING(#Category,1,LEN(#Category)-1)))
END;
Although if #Category is computed from an expression off of your cursor then you may see significant improvement by using an INSERT INTO instead of looping and executing individual INSERT statements.

Related

How to selectively return rows inside a stored procedure on SQL Server?

I have a base stored procedure simply returning a select from the database, like this:
CREATE PROCEDURE MyProcedure
AS
BEGIN
SELECT * FROM MyTable
END
GO
But now I need to execute some logic for every row of my select. According to the result I need to return or not this row. I would have my select statement running with a cursor, checking the rule and return or not the row. Something like this:
CREATE PROCEDURE MyProcedure
AS
BEGIN
DECLARE CURSOR_MYCURSOR FOR SELECT Id, Name FROM MyTable
OPEN CURSOR_MYCURSOR
FETCH NEXT FROM CURSOR_MYCURSOR INTO #OUTPUT1, #OUTPUT2
WHILE (##FETCH_STATUS=0)
BEGIN
IF (SOME_CHECK)
SELECT #OUTPUT1, #OUTPUT2
ELSE
--WILL RETURN SOMETHING ELSE
END
END
GO
The first problem is that everytime I do SELECT #OUTPUT1, #OUTPUT2 the rows are sent back as different result sets and not in a single table as I would need.
Sure, applying some logic to a row sounds like a "FUNCTION" job. But I can't use the result of the function to filter the results being selected. That is because when my check returns false I need to select something else to replace the faulty row. So, I need to return the faulty rows so I can be aware of them and replace by some other row.
The other problem with this method is that I would need to declare quite a few variables so that I can output them through the cursor iteration. And those variables would need to follow the data types for the original table attributes and somehow not getting out of sync if something changes on the original tables.
So, what is the best approach to return a single result set based on a criteria?
Thanks in advance.
I recommend use of cursors but easy solution to your question would be to use table variable or temp table
DECLARE #MyTable TABLE
(
ColumnOne VARCHAR(20)
,ColumnTwo VARCHAR(20)
)
CREATE TABLE #MyTable
(
ColumnOne VARCHAR(20)
,ColumnTwo VARCHAR(20)
)
than inside your cursors you can insert records that match your logic
INSERT INTO #MyTable VALUES (#Output1, #Output2)
INSERT INTO #MyTable VALUES (#Output1, #Output2)
after you done with cursor just select everything from table
SELECT * FROM #MyTable
SELECT * FROM #MyTable

Execute SQL statements while looping a table

I want to create a table with a few records in it and then run a set of sql statements for every record in that table. I would use the data in the table to set values in the sql statement.
This should allow me to write the SQL just once and then run it for whatever data I put in the table.
But, I'm not sure how to go about doing this. Should I use a cursor to loop the table? Some other way?
Thanks for any help or advice you can give me.
CURSOR will have an overhead associated with it, but can be a good method to walk through your table. They are not a totally unnecessary evil and have their place.
With the limited information that WilliamB2 provided, it sounds like a CURSOR set may be a good solution for this problem to walk through his data and generate the multiple downstream INSERTs.
Yes you can use a cursor. You can also use a while loop
declare #table as table(col1 int, col2 varchar(20))
declare #col1 int
declare #col2 varchar(50)
declare #sql varchar(max)
insert into #table
SELECT col1, col2 FROM OriginalTable
while(exists(select top 1 'x' from #table)) --as long as #table contains records continue
begin
select top 1 #col1=col1, #col2=col2 from #table
SET #sql = 'INSERT INTO Table t VALUES('+cast(#col1 as varchar)+')'
delete top (1) from #table --remove the previously processed row. also ensures no infinite loop
end
I think cursor has an overhead attached to it.
With this second approach you are not working on the original table
Maybe you could use INSERT...SELECT instead of the loop:
INSERT INTO target_table
SELECT
some_col,
some_other_col,
'Some fixed value',
NULL,
42,
you_get_the_idea
FROM source_table
WHERE source_table.you_get_the_idea = 1
The columns on your SELECT should match the structure of the target table (you can omit an int/identity pk like id if you have one).
If the best option is this or the loop depends on how many tables you want to populate inside the loop. If it's just a few, I usually stick with INSERT...SELECT.

SQL Server 2000: weird error: cannot convert VARCHAR to INT

I'm having a weird error that happens under weird circumstances.
The list of skill names I receive in the cursor curLongSkills is to be inserted into the table tbl_new_skill_overview if and only if they don't already exist. So I loop through the cursor as usual, and check whether it already exists before inserting.
The weird thing is that I receive the error Syntax error converting the varchar value 'Some Random Skill' to a column of data type int. on the line SELECT #iCount = COUNT(ID).
However, this does not happen if I remove the WHERE clause in that statement. So if I comment or remove WHERE Name = #sSkillName, it won't give the error. It's as if it thinks that I'm assigning #sSkillName to #iCount just because I'm using #sSkillName in the WHERE clause of the same query.
Other ways of doing this will suffice provided that I can tell whether or not the skill has already been inserted into tbl_new_skill_overview. I don't necessarily have to do it this way.
I've also tried the following, which gives the same error:
SET #iCount = (
SELECT COUNT(ID) AS Line_Count
FROM tbl_new_skill_overview
WHERE Name = #sSkillName
);
The server is running Microsoft SQL Server 2000 (I know, I know...).
Following is the entire SQL script.
DECLARE #sSkillName VARCHAR(200);
DECLARE #iCount INT;
DECLARE curLongSkills CURSOR FOR (
SELECT DISTINCT Name
FROM tbl_new_skill
WHERE Profile = 'long'
AND Parent_ID IS NULL
)
OPEN curLongSkills;
FETCH curLongSkills INTO #sSkillName;
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #iCount = COUNT(ID)
FROM tbl_new_skill_overview
WHERE Name = #sSkillName; -- No error if this line removed.
IF #iCount = 0 BEGIN
PRINT #sSkillName;
-- TODO: Insert skill
END;
FETCH curLongSkills INTO #sSkillName;
END;
CLOSE curLongSkills;
DEALLOCATE curLongSkills;
I've never liked cursors - but as a cheeky alternative, you should be able to accomplish what you want without a cursor.
insert into tbl_new_skill_overview
select //columnNames
from tbl_new_skill
WHERE Profile = 'long'
AND Parent_ID IS NULL
and name not in
(select name from tbl_new_skill)
The problem was stupidity.
The Name column in tbl_new_skill_overview was mistakenly put in as an INT, not a VARCHAR.
Thanks to all who responded, particularly bobs for asking me to show the database structure, at which point I realized the mistake.
That's a strange occurrence for sure. I have no idea what's causing it, but to get around it, perhaps you could do something like this:
if not exists (select * from tbl_new_skill_overview where Name = #sSkillName) begin
print #sSkillName;
-- TODO: Insert skill
end
That is assuming you don't use #iCount for anything else later.

Why is ##Identity returning null?

I have a .NET 2010 app hitting a SQL2000 db. The code is pretty basic. When I insert a record, the record is inserted, but the id is not returned. The id column is an int and it is an Idetity. Here is the stored proc...
ALTER PROCEDURE Insert_Vendor
#CorpID as varchar(255),
#TaxpayerID as varchar(255)
AS
Insert into dbo.Vendor
(
vdr_CorpID,
vdr_TaxpayerID
)
values
(
#CorpID,
#TaxpayerID
)
IF ##error <> 0
BEGIN
RETURN -1
END
ELSE
RETURN ##Identity
GO
And on the receiving end...
int myID = (int)(db.ExecuteScalar(dbCommand));
You should always use SCOPE_IDENTITY()
NULL can't be returned via RETURN from a stored proc. You'd get a SQL warning and it would return zero.
ExecuteScalar looks for the 1st row, 1st column of a recordset. There is no recordset above
... So you'd use SELECT SCOPE_IDENTITY() not RETURN SELECT SCOPE_IDENTITY()
ExecuteScalar
executes the query, and returns the
first column of the first row in the
result set returned by the query
So you need to re-write the RETURN statements as
SELECT -1
and (since scope_indentity() returns numeric(38,0))
SELECT CAST(SCOPE_IDENTITY() AS INT)
respectively
you have to call ##IDENTITY right after the insert, use Scope_identity() instead.
Because your question leaves out a lot of details I will just mention a few possible ways around this as it seems impossible to answer a question without all the details. But it's your first time here so you'll get better. You will right?
Anyways first I would say you should always use scope_identity as it is safer. There could be things going on behind the scenes with triggers that could cause this real problems. Stick with scope_identity and you shouldn't have to worry.
Second I would suggest instead of
RETURN
use
SELECT SCOPE_IDENTITY()
Lastly I would say why not just use an OUTPUT parameter vs returning a result. I don't have anything to support this next statement but I would think it is better. Again no proof on that but it just seems like less overhead with output parameter vs resultset that comes with schema.
Just my thoughts.
I personally would recommend using SCOPE_IDENTITY instead of ##IDENTITY. That being said the problem is in the stored procedure. Devio above was correct the execute scalar is looking for the first column of the first row. The RETURN statement will not do this so you will need to use either one of the below items:
IF ##error <> 0
BEGIN
Select -1
END
ELSE
Select ##Identity
or:
IF ##error <> 0
BEGIN
Select -1
END
ELSE
Select SCOPE_IDENTITY()

Handling the data in an IN clause, with SQL parameters?

We all know that prepared statements are one of the best way of fending of SQL injection attacks. What is the best way of creating a prepared statement with an "IN" clause. Is there an easy way to do this with an unspecified number of values? Take the following query for example.
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (1,2,3)
Currently I'm using a loop over my possible values to build up a string such as.
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (#IDVAL_1,#IDVAL_2,#IDVAL_3)
Is it possible to use just pass an array as the value of the query paramter and use a query as follows?
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (#IDArray)
In case it's important I'm working with SQL Server 2000, in VB.Net
Here you go - first create the following function...
Create Function [dbo].[SeparateValues]
(
#data VARCHAR(MAX),
#delimiter VARCHAR(10)
)
RETURNS #tbldata TABLE(col VARCHAR(10))
As
Begin
DECLARE #pos INT
DECLARE #prevpos INT
SET #pos = 1
SET #prevpos = 0
WHILE #pos > 0
BEGIN
SET #pos = CHARINDEX(#delimiter, #data, #prevpos+1)
if #pos > 0
INSERT INTO #tbldata(col) VALUES(LTRIM(RTRIM(SUBSTRING(#data, #prevpos+1, #pos-#prevpos-1))))
else
INSERT INTO #tbldata(col) VALUES(LTRIM(RTRIM(SUBSTRING(#data, #prevpos+1, len(#data)-#prevpos))))
SET #prevpos = #pos
End
RETURN
END
then use the following...
Declare #CommaSeparated varchar(50)
Set #CommaSeparated = '112,112,122'
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (select col FROM [SeparateValues](#CommaSeparated, ','))
I think sql server 2008 will allow table functions.
UPDATE
You'll squeeze some extra performance using the following syntax...
SELECT ID,Column1,Column2 FROM MyTable
Cross Apply [SeparateValues](#CommaSeparated, ',') s
Where MyTable.id = s.col
Because the previous syntax causes SQL Server to run an extra "Sort" command using the "IN" clause. Plus - in my opinion it looks nicer :D!
If you would like to pass an array, you will need a function in sql that can turn that array into a sub-select.
These functions are very common, and most home grown systems take advantage of them.
Most commercial, or rather professional ORM's do ins by doing a bunch of variables, so if you have that working, I think that is the standard method.
You could create a temporary table TempTable with a single column VALUE and insert all IDs. Then you could do it with a subselect:
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (SELECT VALUE FROM TempTable)
Go with the solution posted by digiguru. It's a great reusable solution and we use the same technique as well. New team members love it, as it saves time and keeps our stored procedures consistent. The solution also works well with SQL Reports, as the parameters passed to stored procedures to create the recordsets pass in varchar(8000). You just hook it up and go.
In SQL Server 2008, they finally got around to addressing this classic problem by adding a new "table" datatype. Apparently, that lets you pass in an array of values, which can be used in a sub-select to accomplish the same as an IN statement.
If you're using SQL Server 2008, then you might look into that.
Here's one technique I use
ALTER Procedure GetProductsBySearchString
#SearchString varchar(1000),
as
set nocount on
declare #sqlstring varchar(6000)
select #sqlstring = 'set nocount on
select a.productid, count(a.productid) as SumOf, sum(a.relevence) as CountOf
from productkeywords a
where rtrim(ltrim(a.term)) in (''' + Replace(#SearchString,' ', ''',''') + ''')
group by a.productid order by SumOf desc, CountOf desc'
exec(#sqlstring)