Assigne a string to a variable in a case statement in the THEN clause - sql

Here's what I have. I have tried quotes instead of =, I have removed the variable and tried just the select statement wrapped in quotes. Loosing my mind trying to figure this out.
Yes, it is homework, I'm not asking for you to write it for me, just let me know why I can't seem to get it to work and what I'm doing wrong.
The Case is about 7 Whens long, But I can't get passed the first one.
Please HELP.
I know people don't like doing homework, so think of this as teaching me instead. Thank you,
CREATE PROC usr_spChooseReport(#ReportNum int)
AS
declare #sqlString nvarchar(500)
SELECT CASE(#ReportNum)
WHEN 1 THEN #sqlString =''select book.Title, Book_Copies.No_of_Copies, Library_Branch.BranchName from Book inner join Book_Copies on book.BookID=Book_Copies.BookID inner join Library_Branch on Book_Copies.BranchID=Library_Branch.BranchID where ONVERT(NVARCHAR(MAX),Library_Branch.BranchName) = 'Sharptown'
and CONVERT(NVARCHAR(MAX), Book.Title) ='The Lost Tribe'''
ELSE 'Unknown'
END
But if I try this,
CREATE PROC usr_spChooseReport(#ReportNum int)
AS
SELECT CASE(#ReportNum)
WHEN 1 THEN (select book.Title, Book_Copies.No_of_Copies, Library_Branch.BranchName from Book inner join Book_Copies on book.BookID=Book_Copies.BookID inner join Library_Branch on Book_Copies.BranchID=Library_Branch.BranchID where CONVERT(NVARCHAR(MAX),Library_Branch.BranchName) = 'Sharptown'
and CONVERT(NVARCHAR(MAX), Book.Title) ='The Lost Tribe')
ELSE 'Unknown'
END
I get this error:
Only one expression can be specified in the select list when the
subquery is not introduced with EXISTS.

After reading your question a second time I think I understand. They want you to use a case statement to set a variable #sqlString to a string which is the select statement. Then at some point they will EXEC (#sqlString)
You need to follow your first example for the remaining cases.
WHEN 2 THEN #sqlString = 'SELECT * FROM Somewhere'
WHEN 3 THEN #sqlString = 'SELECT * FROM SomewhereElse'
The difference between your first example and the second is that in the first they are assigning a string value to a variable #sqlString. In your second example you are running a query that returns multiple expressions. Two totally different things.

First off, this is a funky problem. If you follow this pattern, will you have a completely different query for each of the 7 numbers?
I would hope it's all the same query with simply different parameters passed. Then I would just create a lookup table like this:
DECLARE #LookUpTable TABLE (ID INT PRIMARY KEY, LibraryBranch VARCHAR(25), Book_Name VARCHAR(25))
INSERT INTO #LookUpTable
VALUES (1,'Sharptown','The Lost Tribe'),
(2,'Other Branch','Other book');
DECLARE #ReportNum INT = 1;
select book.Title, Book_Copies.No_of_Copies, Library_Branch.BranchName
FROM Book
inner join Book_Copies
on book.BookID=Book_Copies.BookID
inner join Library_Branch
on Book_Copies.BranchID=Library_Branch.BranchID
inner join #LookUpTable LT
on Library_Branch.BranchName = LT.LibraryBranch
AND Book.Title = LT.Book_Name
WHERE LT.ID = #ReportNum
BUT if you actually do have different queries for each case, then either try Jchao's method and EXEC(#sqlString) at the end OR you could use if statements. Note: ELSE IF means it will run only one of the queries so once it finds a match it will run that one query and then be done, but if NO match is found then the ELSE at the end will run
DECLARE #ReportNum INT = 1;
IF (#ReportNum = 1)
BEGIN
SELECT 1 --query 1
END
ELSE IF (#ReportNum = 2)
BEGIN
SELECT 2 --query 2
END
--etc to 7
ELSE
BEGIN
SELECT 'Unknown' --query 8 for unknown
END

Related

Making a variable that is a list, and using null as an if/else selector

I want to create a variable that holds a list of UPCs that we need to search over. Instead of using:
select * from foo
where upc in (3410015215, 3410015217, 3410015243)
I want to say something like:
Declare #UPCList Varchar(255) = (3410015215, 3410015217, 3410015243)
--If you want to check all stores, swap the commentation status of the next two lines
--Declare #Store int = NULL
Declare #Store int = 203
My second issue is, I want to search fleem for a specific store, but if someone wants to search all stores, then they should be able to make #Store equal to NULL .
select * from foo p
inner join fleem f on p.upc = f.upc_id
if (#Store is NULL)
begin
where f.BOH = 1
end
else (#Store is not NULL)
begin
where f.store = #Store AND f.BOH = 1
end
Are these things possible?
If so, what is the best way to go about doing them?
There is no 'list' type in SQL. In SQL a list would just be a table or a temporary table with a single column and however many rows.
To solve your first issue you could store the values in the temporary table and then join against that to create the filter you are looking for.
CREATE TABLE #MyList (
upcs VARCHAR(MAX)
)
INSERT INTO #MyList (upcs)
VALUES
(123),
(456),
(789)
And then use #MyList in an inner join with foo,
SELECT * FROM foo AS f
INNER JOIN #MyList AS ml
ON f.upcs = ml.upcs
Then for your second issue all you need to do is include a WHERE clause with a CASE statement after your join,
select * from foo p
inner join fleem f on p.upc = f.upc_id
WHERE f.BOH = CASE
WHEN #Store IS NULL THEN f.BOH
ELSE #Store
END
Pretty sure this should really be two separate questions, but here are my answers. I'm assuming you're using SQL Server, but I could be wrong as you haven't tagged a DBMS. The code below may work on other systems, but I don't make any promises:
Q1: Table Variables
You can use table variables to do the sort of thing you've asked in your first question. See the code below.
DECLARE #UPCList as TABLE (upc int)
INSERT INTO #UPCList
VALUES
(3410015215),
(3410015217),
(3410015243)
After this you can do with #UPCList all the things you could do with an ordinary table.
Q2: Set Logic
You're not allowed to use IF/THEN inside a query. However, the logic you've described is easy to turn into a standard WHERE clause.
SELECT
*
FROM
foo p
JOIN fleem f on p.upc = f.upc_id
WHERE
f.boh = 1
AND (f.store = #Store
OR #Store is NULL)

Explain syntax: select #au_id = au_id from #mytemp (Equals sign in SELECT clause) [duplicate]

What are the differences between the SET and SELECT statements when assigning variables in T-SQL?
Quote, which summarizes from this article:
SET is the ANSI standard for variable assignment, SELECT is not.
SET can only assign one variable at a time, SELECT can make multiple assignments at once.
If assigning from a query, SET can only assign a scalar value. If the query returns multiple values/rows then SET will raise an error. SELECT will assign one of the values to the variable and hide the fact that multiple values were returned (so you'd likely never know why something was going wrong elsewhere - have fun troubleshooting that one)
When assigning from a query if there is no value returned then SET will assign NULL, where SELECT will not make the assignment at all (so the variable will not be changed from its previous value)
As far as speed differences - there are no direct differences between SET and SELECT. However SELECT's ability to make multiple assignments in one shot does give it a slight speed advantage over SET.
I believe SET is ANSI standard whereas the SELECT is not. Also note the different behavior of SET vs. SELECT in the example below when a value is not found.
declare #var varchar(20)
set #var = 'Joe'
set #var = (select name from master.sys.tables where name = 'qwerty')
select #var /* #var is now NULL */
set #var = 'Joe'
select #var = name from master.sys.tables where name = 'qwerty'
select #var /* #var is still equal to 'Joe' */
When writing queries, this difference should be kept in mind :
DECLARE #A INT = 2
SELECT #A = TBL.A
FROM ( SELECT 1 A ) TBL
WHERE 1 = 2
SELECT #A
/* #A is 2*/
---------------------------------------------------------------
DECLARE #A INT = 2
SET #A = (
SELECT TBL.A
FROM ( SELECT 1 A) TBL
WHERE 1 = 2
)
SELECT #A
/* #A is null*/
Aside from the one being ANSI and speed etc., there is a very important difference that always matters to me; more than ANSI and speed. The number of bugs I have fixed due to this important overlook is large. I look for this during code reviews all the time.
-- Arrange
create table Employee (EmployeeId int);
insert into dbo.Employee values (1);
insert into dbo.Employee values (2);
insert into dbo.Employee values (3);
-- Act
declare #employeeId int;
select #employeeId = e.EmployeeId from dbo.Employee e;
-- Assert
-- This will print 3, the last EmployeeId from the query (an arbitrary value)
-- Almost always, this is not what the developer was intending.
print #employeeId;
Almost always, that is not what the developer is intending. In the above, the query is straight forward but I have seen queries that are quite complex and figuring out whether it will return a single value or not, is not trivial. The query is often more complex than this and by chance it has been returning single value. During developer testing all is fine. But this is like a ticking bomb and will cause issues when the query returns multiple results. Why? Because it will simply assign the last value to the variable.
Now let's try the same thing with SET:
-- Act
set #employeeId = (select e.EmployeeId from dbo.Employee e);
You will receive an error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
That is amazing and very important because why would you want to assign some trivial "last item in result" to the #employeeId. With select you will never get any error and you will spend minutes, hours debugging.
Perhaps, you are looking for a single Id and SET will force you to fix your query. Thus you may do something like:
-- Act
-- Notice the where clause
set #employeeId = (select e.EmployeeId from dbo.Employee e where e.EmployeeId = 1);
print #employeeId;
Cleanup
drop table Employee;
In conclusion, use:
SET: When you want to assign a single value to a variable and your variable is for a single value.
SELECT: When you want to assign multiple values to a variable. The variable may be a table, temp table or table variable etc.
Surround everything in select with ().
Make sure you are only returning 1 item
eg
ET #sql_update = (select left(#sql_update, len(#sql_update)-1))
SET #Telephone2 = (SELECT REPLACE(LTRIM(REPLACE(#Telephone2, '0', ' ')), ' ', '0'))

(T-SQL): How do I use result set A to calculate result set B?

I have 2 queries, A and B, which I currently use independently from each other. They both return an employee ID and some metric values. I now want to use the employee IDs returned from result set A in query B.
A's query is structured like this:
select employee_id from employee where employee_team = 1 and employee_role = 1
B's query is structured like this:
declare #tester int = 123450 --plug in employee ID
select employee_id
,employee_name
,sum(case
when notes.note_author!=employee_id and logs.log_date<#today
then 1 else 0 end) as metric
from notes
inner join note_to_log_bridge as br on notes.note_id=br.note_id
inner join logs on br.log_id=logs.log_id
inner join employee on employee_id=#Tester
If I want to get B's metrics for 5 employees, I have to run query B 5 times, changing the #Tester variable each time. I'd like to instead find some way of automating that, so that I get the metrics for query B for every employee_id in result set A.
I tried stored result set A as a CTE and using a while loop to run through query B:
declare #line=1
with cte (employee_id) as <query_a>
while (#line<=count(cte.employee_id))
begin <query b>...
I never finished this query because I discovered that while cannot follow the creation of a CTE.
I tried using a table variable:
declare #set_a (employee_id int)
insert into #set_a <query a>
but when I try to use #set_a in query B, I get a message saying that I need to declare the scalar variable #set_a.
I tried using a temp table and got a "could not be bound" error.
I am out of ideas. Am I approaching this problem in anything resembling the right direction? Is this even possible?
Thank you!
Yes, you can use cursor and it will work perfectly fine.
But, if you have significantly more than 5 rows you may consider using CROSS APPLY to make it all in one query. It may work faster than cursor.
select
employee.employee_id
,CA.*
from
employee
CROSS APPLY
(
<put your query B here
and replace all references to parameter #Tester
with employee.employee_id>
) AS CA
where employee.employee_team = 1 and employee.employee_role = 1
You can think of this operator like this: for each row in the main outer query A CROSS APPLY runs the inner query B with the possibility to reference values of the row from the outer query A (in this case employee.employee_id).
Use Cursor ?
If I want to get B's metrics for 5 employees, I have to run query B 5
times, changing the #Tester variable each time.
DECLARE #empid int;
DECLARE vend_cursor CURSOR
FOR select employee_id from employee where employee_team = 1 and employee_role = 1
OPEN vend_cursor
FETCH NEXT FROM vend_cursor into #empid;
WHILE ##FETCH_STATUS = 0
BEGIN
// your query with #mpid
FETCH NEXT FROM db_cursor INTO #name
END
CLOSE vend_cursor
DEALLOCATE vend_cursor

What is the most efficient way in T-SQL to compare answer strings to answer keys for scoring an exam

These exams typically have about 120 questions. Currently, they strings are compared to the keys and a value of 1 or 0 assigned. When complete, total the 1's for a raw score.
Are there any T-SQL functions like intersect or diff or something all together different that would handle this process as quickly as possible for 100,000 examinees?
Thanks in advance for your expertise.
-Steven
Try selecting the equality of a question to its correct answer. I assume you have the student's tests in one table and the key in another; something like this ought to work:
select student_test.student_id,
student_test.test_id,
student_test.question_id,
(student_test.answer == test_key.answer OR (student_test.answer IS NULL AND test_key.answer IS NULL))
from student_test
INNER JOIN test_key
ON student_test.test_id = test_key.test_id
AND student_test.question_id = test_key.question_id
WHERE student_test.test_id = <the test to grade>
You can group the results by student and test, then sum the last column if you want the DB to give you the total score. This will give a detailed "right/wrong" analysis of the test.
EDIT: The answers being stored as a continuous string make it much harder. You will most likely have to implement this in a procedural fashion with a cursor, meaning each student's answers are loaded, SUBSTRINGed into varchar(1)s, and compared to the key in an RBAR (row by agonizing row) fashion. You could also implement a scalar-valued function that compared string A to string B one character at a time and returned the number of differences, then call that function from a driving query that will call this function for each student.
Something like this might work out for you:
select student_id, studentname, answers, 0 as score
into #scores from test_answers
declare #studentid int
declare #i int
declare #answers varchar(120)
declare #testkey varchar(120)
select #testkey = test_key from test_keys where test_id = 1234
declare student_cursor cursor for
select student_id from #scores
open student_cursor
fetch next from student_cursor into #studentid
while ##FETCH_STATUS = 0
begin
select #i = 1
select #answers = answers from #scores where student_id = #studentid
while #i < len(#answers)
begin
if mid(#answers, #i, 1) = mid(#testkey, #i, 1)
update #scores set score = score + 1 where student_id = #studentid
select #i = #i + 1
end
fetch next from student_cursor into #studentid
end
select * from #scores
drop table #scores
I doubt that's the single most efficient way to do it, but it's not a bad starting point at least.

Bypass last INNER JOIN in query

I have the following stored procedure which takes a user ID, a starting date, an end date, and a list of codes in a comma-delimited list, and it returns all activity records between those two dates which match one of the codes in the list.
ALTER PROCEDURE [dbo].[ActivitiesSummary]
#UserID varchar(30),
#StartDate datetime,
#EndDate datetime,
#Codes varchar(100)
AS
BEGIN
SET NOCOUNT ON;
SELECT act.SectionID, act.UnitID, act.ActivityCode
FROM dbo.Activities act INNER JOIN ConvertCodeListToTbl(#Codes) i ON act.ActivityCode = i.code
WHERE act.ActivityDate>=#Startdate AND act.ActivityDate<#EndDate
GROUP BY act.SectionID, act.UnitID, act.ActivityCode
ORDER BY act.SectionID, act.UnitID, act.ActivityCode
END
ConvertCodeListToTbl(#Codes) is a function that takes a comma-delimited list of codes (e.g., 'A0001, B0001, C0001') and returns a table with one code per row:
A0001
B0001
C0001
This method works really well except when no codes have been selected. When that occurs, I receive no records back because #Codes='' and the last INNER JOIN returns no records.
What I want to happen: if #Codes='', ignore the last INNER JOIN, or otherwise find a way to return all records regardless of code.
What are my options?
Is sounds like you need to change the INNER JOIN line to:
FROM dbo.Activities act INNER JOIN ConvertCodeListToTbl(#Codes) i
ON (act.ActivityCode = i.code OR #Codes = '')
I'm not sure of a way to do this in SQL that wouldn't screw up the query plan (eg. you could do it with dynamic sql, but then you'd be producing two very different queries).
Your calling application must know that #codes is empty before it calls the stored procedure, so why not have it call a different stored procedure that doesn't do the join?
You could add another condition to the join statement. This probably isn't valid sql server syntax, but doing something like
... i ON (act.ActivityCode = i.code) OR IF(#codes = '', 1, 0)
would essentially join everywhere if #codes is empty.
In SQL server, you can set a default value for a parameter in a stored procedure.
Change your line at the top to:
#Codes varchar(100) = '*'
Then your other proc ConvertCodeListToTbl to return all values if it is passed '*' as it's parameter.
I don't know how the performance will compare, but this should be an otherwise identical query that does what you want:
SELECT act.SectionID, act.UnitID, act.ActivityCode
FROM dbo.Activities act
WHERE act.ActivityDate>=#Startdate AND act.ActivityDate<#EndDate
AND (#Codes = '' OR
EXISTS
(SELECT *
FROM ConvertCodeListToTbl(#Codes)
WHERE act.ActivityCode = code))
GROUP BY act.SectionID, act.UnitID, act.ActivityCode
ORDER BY act.SectionID, act.UnitID, act.ActivityCode
Another option is to have ConvertCodeListToTbl return SELECT ActivityCode FROM dbo.Activities when its input is empty.