First of all I am new to SQL, yet I have a great Java background. My problem is that I am trying to make this procedure return a varchar, yet it is not letting me.
I tried using the RETURN statement (I now know it only returns INTS) and the SELECT statement, but for some reason it continues to return an int.
Here is my code
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE get_TopGuildLeader
AS
--Variables
DECLARE #LeaderUID VARCHAR(20);
DECLARE #GuildID INT
DECLARE #MaxPoints INT
--Selecting Leader
SET #MaxPoints = (SELECT MAX(GuildPoint)
FROM PS_GameData.dbo._GuildRankPoint)
SET #GuildID = (SELECT GuildID
FROM PS_GameData.dbo._GuildRankPoint
WHERE GuildPoint = #MaxPoints);
SET #LeaderUID = (SELECT MasterName
FROM PS_GameData.dbo._GuildsBack
WHERE GuildID = #GuildID);
--Return Leader Name
SELECT #LeaderUID;
You need to return data using OUTPUT parameters instead of SELECT.
Refer link on how to achieve this:
https://technet.microsoft.com/en-us/library/ms187004(v=sql.105).aspx
Hope this helps!
You can use an output parameter. However, be careful with your SQL query. I'm not familiar with your data model, but it looks like your query could result in a run time error. For example, is it possible that there could be more than one GuildID with the same number of GuildPoints? If so, the second query in your stored procedure will fail and return a message like below.
Msg 512, Level 16, State 1, Line 2
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, , >= or when the subquery is used as an expression.
There are many ways to approach and protect against that potential issue. Below is an example of how to consolidate the logic down to one compact query and guarantee that there will always be one row returned.
create proc dbo.GetTopGuildLeader
#LeaderId varchar(20) output
as
set #LeaderId = (
select top 1 gb.MasterName
from PS_GameData.dbo._GuildRankPoint grp
join PS_GameData.dbo._GuildsBack gb
on grp.GuildID = gb.GuildID
order by GuildPoint);
go
Hope this helps.
Related
I have the following procedure to retrieve some data, based by the year, which is input by the user. However, I always get a 0 back. I'm still fairly new to SQL, but this seemed like it should work
Create PROCEDURE [dbo].[Yearly]
#year int
AS
BEGIN
DECLARE #yearly Datetime
DECLARE #summ int
SELECT #summ = SUM([dbo].[Out].[OutPcs]), #yearly = [dbo].[Out].[DateTime]
FROM [dbo].[Out]
WHERE YEAR(#yearly) = #year
GROUP BY [Out].[DateTime]
END;
Should I have used nested select statements? I suspect something is wrong in that part of the procedure.
You have DECLARE #yearly Datetime.
You attempt to set it in SELECT ... #yearly = Out.Datetime FROM Out, but then you have this WHERE statement: YEAR(#yearly) = #year
This returns nothing since #yearly is NULL when called by YEAR()
This makes the statement equivalent to WHERE NULL = 2018
Which will never be true.
To fix this, you need to set yearly before calling it in your WHERE clause or use something else there.
It looks like you want to use YEAR(Dbo.Out.Datetime) instead there
Since it looks like you're new to SQL I will add some extra explanation. This is an oversimplification.
Most programming languages run top to bottom. Executing the line1 first, line2 second, line3 third, and so on. SQL does not do this.
The command SELECT Name FROM Employee WHERE EmpID = 1 Runs in the following order.
First - FROM Employee --> Load the Employee table
Second - WHERE EmpID = 1 --> Scan Employee for the records where EmpID = 1
Third - SELECT Name --> Display the `Name` field of the records I found.
Your command looks like this to the SQL compiler
First - FROM dbo.Out --> Load Out table
Second - WHERE YEAR(#yearly) = #year --> Scan for records that meet this req.
Third - SELECT ... #yearly = dbo.Out.Datetime --> Set #yearly to the [Datetime] field associated to the record(s) I found.
Note that if your statement had returned multiple records, then SQL would have tried to set your 1-dimensional variable to an array of values. It would fail and give you something like
Too many records returned. Have me only return 1 record.
Why your code is not working is well explained by #Edward
Here is a working code:
Create PROCEDURE [dbo].[Yearly]
#year int
AS
BEGIN
SELECT SUM([dbo].[Out].[OutPcs])
FROM [dbo].[Out]
WHERE YEAR([dbo].[Out].[DateTime]) = #year
END;
You forgot to return "summ":
And #yearly var is not necessary.
Group by Year is not necessary too.
Create PROCEDURE [dbo].[Yearly]
#year int
AS
BEGIN
DECLARE #summ int
SELECT #summ = SUM([dbo].[Out].[OutPcs])
FROM [dbo].[Out]
WHERE YEAR([dbo].[Out].[DateTime]) = #year
Return #summ
END;
I have started creating a stored procedure that will search through my database table based on the passed parameters. So far I already heard about potential problems with kitchen sink parameter sniffing. There are a few articles that helped understand the problem but I'm still not 100% that I have a good solution. I have a few screens in the system that will search different tables in my database. All of them have three different criteria that the user will select and search on. First criteria are Status that can be Active,Inactive or All. Next will be Filter By, this can offer different options to the user depends on the table and the number of columns. Usually, users can select to filter by Name,Code,Number,DOB,Email,UserName or Show All. Each search screen will have at least 3 filters and one of them will be Show All. I have created a stored procedure where the user can search Status and Filter By Name,Code or Show All. One problem that I have is Status filter. Seems that SQL will check all options in where clause so If I pass parameter 1 SP returns all active records if I pass 0 then only inactive records. The problem is if I pass 2 SP should return all records (active and inactive) but I see only active records. Here is an example:
USE [TestDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROC [dbo].[Search_Master]
#Status BIT = NULL,
#FilterBy INT = NULL,
#Name VARCHAR(50) = NULL,
#Code CHAR(2) = NULL
WITH RECOMPILE
AS
DECLARE #MasterStatus INT;
DECLARE #MasterFilter INT;
DECLARE #MasterName VARCHAR(50);
DECLARE #MasterCode CHAR(2);
SET #MasterStatus = #Status;
SET #MasterFilter = #FilterBy;
SET #MasterName = #Name;
SET #MasterCode = #Code;
SELECT RecID, Status, Code, Name
FROM Master
WHERE
(
(#MasterFilter = 1 AND Name LIKE '%'+#MasterName+'%')
OR
(#MasterFilter = 2 AND Code = #MasterCode)
OR
(#MasterFilter = 3 AND #MasterName IS NULL AND #MasterCode IS NULL)
)
AND
(
(#MasterStatus != 2 AND MasterStatus = #Status)
OR
(#MasterStatus = 2 AND 1=1)
);
Other than problem with Status filter I'm wondering if there is any other issues that I might have with parameter sniffing? I found a blog that talks about preventing sniffing and one way to do that is by declaring local variables. If anyone have suggestions or solution for Status filter please let me know.
On your Status issue, I believe the problem is that your BIT parameter isn't behaving as you're expecting it to. Here's a quick test to demonstrate:
DECLARE #bit BIT;
SET #bit = 2
SELECT #bit AS [2=What?];
--Results
+---------+
| 2=What? |
+---------+
| 1 |
+---------+
From our friends at Microsoft:
Converting to bit promotes any nonzero value to 1.
When you pass in your parameter as 2, the engine does an implicit conversion from INTEGER to BIT, and your non-zero value becomes a 1.
You'll likely want to change the data type on that parameter, then use some conditional logic inside your procedure to deal with the various possible values as you want them to be handled.
On the issue of parameter sniffing, 1) read the article Sean suggests in the comments, but 2) if you keep that WITH RECOMPILE on your procedure, parameter sniffing can't happen.
The issue (but still read the article) is that SQL Server uses the first set of parameters you send through the proc to store an execution plan, but subsequent parameters require substantially different plans. Adding WITH RECOMPILE is forcing a new execution plan on every iteration, which has some overhead, but is may well be exactly what you want to do in your situation.
As a closing thought, SQL Server 2008 ended mainstream support in 2014 and extended support ends on 7/9/2019. An upgrade might be a good idea.
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'))
Could not find any solution how to use the last inserted ID of a certain table and use this ID to select another field of the same table.
I tried this but always get syntax errors:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION dbo.sfLastCode ()
AS
BEGIN
SET NOCOUNT ON;
-- Declare the return variable here
declare #LastCode nvarchar(2)
-- Add the T-SQL statements to compute the return value here
#LastCode=select tblevents.CODE from tblEvents where Evid=(select IDENT_CURRENT('dbo.tblEvents'))
returns #LastCode
GO
For example I would like to get as aresult the code '123' form the record with the ID 90 .
For sure the syntax is wrong at all, but I do not know where and what.
I am not sure if this should be done in a SP or in a scalar function. I start the code form Access by a PT-query.
EDIT:
Meanwhile I tried
select ident_Current('dbo.tblEvents') as LastEvID
and got a STRANGE result, because the number is 96, although the highest EvID is 90.
6 records have not been saved due to tests and errors in the code.
Why does IDENT_Current('dbo.tblEvents') does not give me number 90?
As I read Max(EvID) is not the best choice I tried IDENT_Current('dbo.tblEvents'), but when IDENT_Current also counts not saved records it is of no use for me.
Thanks a lot
Michael
Try updating your code to
SET #LastCode=(select tblevents.CODE from tblEvents where Evid=(select IDENT_CURRENT('dbo.tblEvents')))
Can't you just do this?:
DECLARE #LastCode VARCHAR(2)
SET #LastCode=
(
SELECT TOP 1
tblEvents.CODE
FROM
tblEvents
ORDER BY tblevents.EvID DESC
)
SELECT #LastCode;
I'm converting some data in SQL Server 2005. I have a table update like this:
update Invoices set Invoices.InvoiceReference = 'NewRef'
where Invoices.InvoiceReference='Unknown'
But what I'd like to plug in instead of 'NewRef' is the output from a stored procedure that uses parameters from the columns of the Invoices table. The stored procedure itself does updates to another table. Is it possible? Something like this below (which is wrong of course :)
DECLARE #Ref nvarchar(20)
update Invoices set Invoices.InvoiceReference = (
EXEC InvoiceGenerateRef
#ClientCode = Invoices.ClientCode,
#EventCode = Invoices.EventCode,
#Ref = #Ref OUTPUT
SELECT #Ref)
where Invoices.InvoiceReference='Unknown'
Do I need to use a cursor or is the syntax just wrong?
Thanks,
Chris.
I think you would be better off changing your stored procedure into either a function or a view (depending on what you actually do in the proc).
I think what you are after is to join to the resultset of a stored proc which would not work.
You are almost there, the correct way to achieve what you are looking to do would be to define an output parameter as part of your stored procedure definition.
This paramter can then be used as part of your update statement.
DECLARE #Ref nvarchar(20)
EXEC InvoiceGenerateRef
#ClientCode = N'ABC2',
#EventCode = N'X1'
#Ref = #Ref OUTPUT
update Invoices
set Invoices.InvoiceReference = #Ref
where Invoices.InvoiceReference='Unknown'
by using OPENROWSET you can query your stored procedure results just like a view:
http://blogs.technet.com/wardpond/archive/2005/08/01/the-openrowset-trick-accessing-stored-procedure-output-in-a-select-statement.aspx
so for your case this might be useful.
1) You could change InvoiceGenerateRef so that it could optionally save the generated Ref into InvoiceReference field. Presumably you would also have to provide parameters to define the selection
2) You could us e cursor to step round each row in
SELECT ...
FROM Invoices
WHERE Invoices.InvoiceReference='Unknown'
and pass the details to InvoiceGenerateRef and then update the row. This is bad IMHO and will be slow (Your best bet is a set-based solution)
3) You could select the appropriate Invoices.ID's into a temporary table which would be in scope for the InvoiceGenerateRef so that it could iterate that (i.e. the choice of WHICH rows to update is external to the SProc, but the SProc does the actual updating)
CREATE TABLE #TEMP
(
T_ID int NOT NULL
)
INSERT INTO #TEMP (T_ID)
SELECT ID
FROM Invoices
WHERE Invoices.InvoiceReference='Unknown'
EXEC InvoiceGenerateRef #ACTION='UpdateFromTemporaryTable'
4) You could change InvoiceGenerateRef to a function that performed the same task:
UPDATE U
SET U.InvoiceReference =
dbo.MyInvoiceGenerateRefFunction(U.ClientCode, U.EventCode)
FROM Invoices AS U
WHERE U.InvoiceReference='Unknown'
MyInvoiceGenerateRefFunction would have to be deterministic (I think!)
This would be my preferred choice