I have the following piece of code within my stored procedure, I know it doesn't work and should not work but I wanted to illustrate my intentions:
declare #ErrorMessages varchar;
set #ErrorMessages = 'An existing deposit on this property ends after the intended start date for the new deposit. ' +
'Existing End Date: ' + #PreviousDepositEndDate + '. Intended Start Date: ' + #TenancyAgreementStartDate
raiserror 50002 #ErrorMessages
Can anyone tell me what I should be doing? Or any links on creating this type of string.
EDIT: Forgot to say that the #Dates are both of datetime, the error message is that it cannot be converted from datetime to string
Try this:
declare #ErrorMessages varchar(255);
Using just #ErrorMessages varchar; gives you a varchar(1).
set #ErrorMessages =
'An existing deposit on this property ends after the intended start date for the new deposit. ' +
'Existing End Date: ' +
#PreviousDepositEndDate + '. Intended Start Date: ' + #TenancyAgreementStartDate
raiserror(#ErrorMessages, 16, 1)
If you wan't to specify the error number, you must first use sp_addmessage and define the error message, which you can reference in raiserror. You may have to insert some casts, depending on what types #PreviousDepositEndDate and #TenancyAgreementStartDate are.
Here's a slightly different version which some people like because it emulates C printf style:
-- Test data
declare #PreviousDepositEndDate varchar(30) = cast(getdate() - 1 as varchar(30))
, #TenancyAgreementStartDate varchar(30) = cast(getdate() as varchar(30))
-- Throw
raiserror (N'An existing deposit on this property ends after the intended start date for the new deposit. Existing End Date: %s. Intended Start Date: %s',
16, -- Severity,
1, -- State,
#PreviousDepositEndDate, -- First argument.
#TenancyAgreementStartDate) -- Second argument.
More info can be found in this MSDN link: http://msdn.microsoft.com/en-us/library/ms178592.aspx
Related
I have a Visual studio based stored procedure that generates a report for a monthly audit process. In the database being queried, all data for each month lives in its own individual table (Contacts_month_1, Contacts_month_2, etc.)
The SQL used in this report generation has some minor logic included, to allow it to work dynamically, rather than use hard coded dates. The problem arose at the start of January 2017, when I started receiving not just the results for the prior month, but additionally the prior year as well. To be specific, the audit report for December 2016 included data for both 12/2016 and 12/2015. Initially I thought it was a fluke of some kind based on the turn of the year, and we have not had this automated process during the turn as of yet. Unfortunately when I came in to the office today, inside the output file for January 2017, I also received the results for January 2016.
I attempted to include a year check to the process, however I am still getting the same result output. Any ideas would be appreciated.
Declare #GetMonth TinyInt
,#SessionTable varchar(50)
,#ContactTable varchar(50)
,#TableVersion varchar(2)
Declare #GetYear SmallInt
,#SessionTable_year varchar(50)
,#ContactTable_year varchar(50)
,#TableVersion_year varchar(4)
Set #GetMonth=MONTH(cast(GetDate() as Datetime))-1
Set #GetYear=YEAR(cast(GetDate() as Datetime))
If (#getmonth=0) Set #GetMonth=12 + (#GetYear-1)
Set #TableVersion=CAST(#getMonth as varchar(2))
Set #SessionTable='[CentralDWH].[dbo].[Sessions_month_' +#tableversion +']'
Set #ContactTable ='[CentralDWH].[dbo].[Contacts_month_' +#tableversion +']'
-- Select #GetMonth,#GetYear (DEBUGGING STATEMENT)
-- Select #SessionTable,#ContactTable (DEBUGGING STATEMENT)
Exec('SELECT [PBX_id] as AgentID
,[p22_value] as Skill
,''Athens'' as Location
,Convert(varchar(20),c.[local_start_time],120) as local_start_time
,convert(varchar(20),c.[local_end_time],120) as local_end_time
,U.[USER_NAME]
,call_id
FROM '+#SessionTable +' S
Inner join dbo.Users U on S.user_key=U.user_key
inner Join '+ #ContactTable+' C on S.contact_key=C.contact_key
Where is_screen > 0
And Unit_Num between 398003 and 398005
and P22_value is not null
and c.[local_start_time] > ' + #GetYear
+ ' order by local_start_time')
As I understand, the #GetMonth variable is used for returning the previous month
Set #GetMonth = MONTH(CAST(GetDate() AS Datetime)) - 1
After a quick look after you procedure my first issue was this line of code:
IF (#getmonth = 0)
SET #GetMonth = 12 + (#GetYear - 1)
I don't understand why are you setting the #GetMonth variable to 12 + current year -1 and I assume this is the cause to the problem.
Did you want to get the 12th month of the previous year when the current month is 1 (January)? If yes then you can easily change the If block to
If #GetMonth = 0
Begin
Set #GetMonth = 12
Set #GetYear = #GetYear - 1
End
Other issues:
It's recommended to keep the consistency of the names of the variables #GetMonth, #getmonth, this will cause an error if the database collation is case sensitive.
#GetMonth is declared as TinyInt and this will cause an arithmetic overflow if you try to store the year
I recommend testing the dynamic SQL statement that you are composing here with some hard coded values to check the results returned, you can use January and 2016 to check if the actual issue in your procedure or it's in your query.
Hope it helps
Thanks for your help, I figured out the root of the problem, and it was because i was not casting the GetYear as a varchar when trying to run the T-SQL statement. This in turn caused the variable to be completely ignored. I also cleaned up the query a little bit after realizing i was goofing up pretty hard.
Below is the cleaned up functional query, so that it may help someone in the future:
Declare #GetMonth SmallInt,
#SessionTable varchar(50),
#ContactTable varchar(50),
#TableVersion varchar(2),
#GetYear SmallInt,
#YearCheck varchar(4)
Set #GetMonth=MONTH(cast(GetDate() as Datetime))-1
Set #GetYear=YEAR(cast(GetDate() as Datetime))-1
If (#GetMonth=0)
Begin
Set #GetMonth =12
Set #GetYear =#GetYear - 1
End
Set #TableVersion=CAST(#GetMonth as varchar(2))
Set #SessionTable='[CentralDWH].[dbo].[Sessions_month_' +#tableversion +']'
Set #ContactTable ='[CentralDWH].[dbo].[Contacts_month_' +#tableversion +']'
Set #YearCheck=CAST(#GetYear as varchar(4))
--Select #GetMonth,#GetYear,#YearCheck (DEBUGGING STATEMENT)
-- Select #SessionTable,#ContactTable (DEBUGGING STATEMENT)
Exec('SELECT
[PBX_id] as AgentID,
[p22_value] as Skill,
''Athens'' as Location,
Convert(varchar(20),c.[local_start_time],120) as local_start_time,
convert(varchar(20),c.[local_end_time],120) as local_end_time,
U.[USER_NAME],
call_id
FROM '+#SessionTable +' S
Inner join dbo.Users U on S.user_key=U.user_key
inner Join '+ #ContactTable+' C on S.contact_key=C.contact_key
Where is_screen>0
And Unit_Num between 398003 and 398005
And P22_value is not null
And year(c.[local_start_time]) > '+#YearCheck+'
order by local_start_time')
Once I cleaned all this up and remembered to cast the year properly, everything fell into place.
I was wondering how to combine Varchar variables in a stored procedure. I want to combine email addresses into a single variable based of access level. I have tried doing a few things in my if statement.
For example I have tried both:
v_m1_email = Concat(v_m1_email, ' , ' , v_email)
and
v_m1_email = v_m1_email || ' , ' || v_email
My code:
CREATE PROCEDURE ALERTEMAIL (OUT p_m1_email VARCHAR(300),
OUT p_m2_email VARCHAR(300),
OUT p_m3_email VARCHAR(300),
OUT p_m4_email VARCHAR(300))
DYNAMIC RESULT SETS 1
P1: BEGIN
DECLARE v_email VARCHAR(50);
DECLARE v_access CHAR(5);
DECLARE v_m1_email VARCHAR(300);
DECLARE v_m2_email VARCHAR(300);
DECLARE v_m3_email VARCHAR(300);
DECLARE v_m4_email VARCHAR(300);
DECLARE SQLSTATE CHAR(5);
DECLARE cursor1 CURSOR WITH RETURN for
SELECT EMAIL,JOB_ID FROM PERSONNEL;
OPEN cursor1;
FETCH cursor1 INTO v_email, v_access;
WHILE (SQLSTATE = '00000') DO
IF v_access = 'Man1' THEN
SET v_m1_email = v_m1_email + ' , ' + v_email;
ELSEIF v_access = 'Man2' THEN
SET v_m2_email = v_m2_email + ' , ' + v_email;
ELSEIF v_access = 'Man3' THEN
SET v_m3_email = v_m3_email + ' , ' + v_email;
ELSEIF v_access = 'Man4' THEN
SET v_m4_email = v_m4_email + ' , ' + v_email;
END IF;
FETCH cursor1 INTO v_email, v_access;
END WHILE;
SET p_m1_email = v_m1_email;
SET p_m2_email = v_m2_email;
SET p_m3_email = v_m3_email;
SET p_m4_email = v_m4_email;
END P1
With regard to the first of what already had been tried from the OP, just as #I_am_Batman on 23-Apr-2016 already noted, the syntax for the CONCAT scalar >>-CONCAT--(--expression1--,--expression2--)------>< is limited to just the two arguments, so the expression coded as Concat(v_m1_email, ' , ' , v_email) would fail, presumably with a sqlcode=-170 suggesting something like "Number of arguments for function CONCAT not valid."
Which variant of DB2 was not noted [not in tag nor by comment in the OP], but I offer this link to some doc DB2 for Linux UNIX and Windows 9.7.0->Database fundamentals->SQL->Functions->Scalar functions->CONCAT
However there is nothing conspicuously incorrect with the second of what already had been tried from the OP; i.e. assuming the assignment and expression shown, had been coded just as shown in the body of the CREATE PROCEDURE, with a preceding SET and a trailing ;. In that case, the statement SET v_m1_email = v_m1_email || ' , ' || v_email; should have been able to pass both syntax-checking and data-type\validity-checking. Whereas what is shown in the OP as SET v_m1_email = v_m1_email + ' , ' + v_email; is not valid except when the values of both variables always would be valid string-representations of numbers; that is because the + operator is a numeric-operator rather than the [conspicuously as-desired] string-operator used to effect concatenation [i.e. for "combining strings"].
[ed: 22-Aug-2016] I forgot there was a constant\literal ' , ' in the above expression, so that string-literal also would have to evaluate as a numeric to allow that expression with the + as addition-operator to function at run-time. But of course, that literal could never be interpreted as a numeric; so while the expression could be treated as valid for compile-time [with implicit cast in effect and data-checking not examining the literal value], that expression never would be capable of being evaluated at run-time.
Therefore, if the || operator was properly coded [as seems so, given what was claimed to have been "tried"], yet did not effect what was desired, then the OP would need to be updated to state exactly what was the problem. For example, perhaps there was an error in compile\CREATE of the routine, or perhaps a run-time error for which the effect of the concatenation was perhaps untrimmed results or some other unexpected output, or something else.?.?
Note: as I already added in a comment to a prior answer, the use of CONCAT operator vs the equivalent || in SQL source enables use of that source in\across other code pages without a possible issue due to the use of a variant character.
p.s. A CASE statement might be preferred in place of the IF\ELSE constructs
p.p.s. Might be worth review if the SP really should return both the RS and, or just, the OUT parameters
String concatenation can be done with the || operator.
set vEmail = userName || '#' || domain || '.' || tld;
Give that a try.
When I run this SP, I get:
Msg 241, Level 16, State 1, Procedure PED_SP_PED_Updates, Line 22
Conversion failed when converting date and/or time from character string.
Here is the execution:
exec dbo.ped_sp_ped_updates
#CURRENTHICN='111111111A',
#DATERECEIVED = '20140904',
#FIELDTOBECHANGED='FIRST_NAME_MEMBER',
#CURRENTFIELDVALUE = 'MARY',
#NEWFIELDVALUE = 'MARYTEST',
#REQUESTEDBY = 'IPISORS',
#ID=156
I am not sure why, I'm casting the varchar back to a date for the comparison.
Please note, I have no problem being told a better way to do it, but it would be (I think) more helpful to my learning if I could, at least 'also', get a direct answer as to why my current proc isn't working. In addition to any helpful ideas as to why it should be done different, better, etc, etc. etc.
ALTER PROCEDURE [dbo].[PED_SP_PED_Updates]
#CurrentHicn VARCHAR(500),
#DateReceived VARCHAR(20),
#FieldToBeChanged VARCHAR(500),
#CurrentFieldValue VARCHAR(500),
#NewFieldValue VARCHAR (500),
#RequestedBy VARCHAR(10),
#ID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #CurrentDBNote VARCHAR(MAX)
DECLARE #NewNote VARCHAR(MAX)
DECLARE #CountofHicn INT
SET #NEWNOTE = 'Isaac Pisors | ' + GetDate() + ' | '
+ 'Changing field: ' + #FieldToBeChanged + ' from ' + #CurrentFieldValue + ' to ' + #NewFieldValue
+ ', per ' + #RequestedBy + ' request. Also changing any related DOCS/FAXES records to correspond'
SET #CurrentDBNote=
(SELECT NOTES_GENERAL FROM PED_APPLICATIONS WHERE HICN_MEDICARE_NUMBER=#CurrentHicn AND (Cast(ISNULL(DATE_RECEIVED,'1900-01-01') as DATE)=CAST(#DateReceived AS DATE)))
--NOW ADD THE TWO:
SET #NewNote = #CurrentDBNote + CHAR(13) + #CurrentDBNote
--SEE IF THERE IS STILL A MATCHING RECORD
SET #CountofHicn=
(SELECT COUNT(*) FROM PED_APPLICATIONS WHERE HICN_MEDICARE_NUMBER=#CurrentHicn AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=CAST(#DateReceived AS DATE)))
IF #CountofHicn=0 --THERE IS NO LONGER A MATCHING RECORD - INSERT THAT NOTE AND CALL IT A DAY
BEGIN
UPDATE PED_PEDUPDATES SET COMPLETEDON=GetDate(), COMPLETEDBY='SSIS',
EXCEPTIONNOTE='Could not locate any records where HICN is ' + #CurrentHicn + ' and Date Received is ' + CAST(#DateReceived AS VARCHAR)
WHERE [ID]=#ID
END
ELSE --GO AHEAD AND DO THE UPDATE
BEGIN
UPDATE PED_APPLICATIONS SET #FieldToBeChanged = #NewFieldValue
WHERE HICN_MEDICARE_NUMBER=#CurrentHicn AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=CAST(#DateReceived AS DATE))
END
IF #FieldToBeChanged='HICN_MEDICARE_NUMBER' --THEN WE HAVE TO UPDATE DOCS TABLE, TOO
BEGIN
UPDATE PED_DOCS SET HICN_MEDICARE_NUMBER=#NewFieldValue
WHERE
(HICN_MEDICARE_NUMBER=#CurrentFieldValue AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=#DateReceived)) or
(HICN_MEDICARE_NUMBER=#CurrentFieldValue AND DATE_RECEIVED IS NULL)
END
IF #FieldToBeChanged='HICN_MEDICARE_NUMBER' --THEN OUR WHERE CLAUSE-HICN IS THE *NEW* HICN
BEGIN
UPDATE PED_APPLICATIONS SET NOTES_GENERAL=#NewNote
WHERE HICN_MEDICARE_NUMBER=#NewFieldValue AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=CAST(#DateReceived AS DATE))
END
ELSE --ELSE OUR WHERE CLAUSE-HICN IS THE *OLD* HICN
BEGIN
UPDATE PED_APPLICATIONS SET NOTES_GENERAL=#NewNote
WHERE HICN_MEDICARE_NUMBER=#CurrentHicn AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=CAST(#DateReceived AS DATE))
END
--FINALLY, UPDATE RECORD AS COMPLETE:
UPDATE PED_PEDUPDATES SET COMPLETEDON=GetDate(),COMPLETEDBY='SSIS' WHERE [ID]=#ID
END
GO
Short Term Fix
Instead of CAST(#DateReceived AS DATE), use CONVERT(date, #DateReceived, 112)
The value 112 is the style code for the yyyymmdd formated varchar you're using. See the cast and convert documentation for more details.
Also, you should verify that all values in the DATE_RECEIVED column of your table are in the correct format. Even one value that is not convertible will cause this error.
Proper Fix
#DateReceived should be passed in to the procedure as a date instead of a varchar.
The DATE_RECEIVED field in your table should be declared as a date instead of a varchar.
In general, avoid treating dates or times as strings in a database when there are native types for that purpose.
I have created a scalar function that is supposed to spit out a date in a specific format based on a variable.
for some reason i get an error around the "else"
what am i doing wrong ?
CREATE FUNCTION [dbo].[fGetDateformat] (#datum datetime,#CNotation char(2))
/* accepts datum and CNotation
USAGE:
select fGetDateformat(datum, "EN" or "DE") */
returns varchar(25) AS
BEGIN
declare #ReturnStr varchar(25)
if #Cnotation = 'DE'
set language german
set #ReturnStr =
DATENAME(dd, #datum)+'. '+DATENAME(MM, #datum)+' '+ DATENAME(YEAR, #datum)
else
set #ReturnStr = DATENAME(mm, #datum)+' '+DATENAME(dd, #datum)+', '+ DATENAME(YEAR, #datum)
return #ReturnStr
END
GO
If a T-SQL block spans multiple lines, you have to enclose it in begin and end. For example, this won't work:
if 1=1
print 'One is one!'
print 'Yay'
else
print 'Huh?'
This will:
if 1=1
begin
print 'One is one!'
print 'Yay'
end
else
print 'Huh?'
Note that print 'Huh?' is still okay, since it is a single line.
I have a stored procedure that does some parameter validation and should fail and stop execution if the parameter is not valid.
My first approach for error checking looked like this:
create proc spBaz
(
#fooInt int = 0,
#fooString varchar(10) = null,
#barInt int = 0,
#barString varchar(10) = null
)
as
begin
if (#fooInt = 0 and (#fooString is null or #fooString = ''))
raiserror('invalid parameter: foo', 18, 0)
if (#barInt = 0 and (#barString is null or #barString = ''))
raiserror('invalid parameter: bar', 18, 0)
print 'validation succeeded'
-- do some work
end
This didn't do the trick since severity 18 doesn't stop the execution and 'validation succeeded' is printed together with the error messages.
I know I could simply add a return after every raiserror but this looks kind of ugly to me:
if (#fooInt = 0 and (#fooString is null or #fooString = ''))
begin
raiserror('invalid parameter: foo', 18, 0)
return
end
...
print 'validation succeeded'
-- do some work
Since errors with severity 11 and higher are caught within a try/catch block another approach I tested was to encapsulate my error checking inside such a try/catch block. The problem was that the error was swallowed and not sent to the client at all. So I did some research and found a way to rethrow the error:
begin try
if (#fooInt = 0 and (#fooString is null or #fooString = ''))
raiserror('invalid parameter: foo', 18, 0)
...
end try
begin catch
exec usp_RethrowError
return
end catch
print 'validation succeeded'
-- do some work
I'm still not happy with this approach so I'm asking you:
How does your parameter validation look like? Is there some kind of "best practice" to do this kind of checking?
I don't think that there is a single "right" way to do this.
My own preference would be similar to your second example, but with a separate validation step for each parameter and more explicit error messages.
As you say, it's a bit cumbersome and ugly, but the intent of the code is obvious to anyone reading it, and it gets the job done.
IF (ISNULL(#fooInt, 0) = 0)
BEGIN
RAISERROR('Invalid parameter: #fooInt cannot be NULL or zero', 18, 0)
RETURN
END
IF (ISNULL(#fooString, '') = '')
BEGIN
RAISERROR('Invalid parameter: #fooString cannot be NULL or empty', 18, 0)
RETURN
END
We normally avoid raiseerror() and return a value that indicates an error, for example a negative number:
if <errorcondition>
return -1
Or pass the result in two out parameters:
create procedure dbo.TestProc
....
#result int output,
#errormessage varchar(256) output
as
set #result = -99
set #errormessage = null
....
if <errorcondition>
begin
set #result = -1
set #errormessage = 'Condition failed'
return #result
end
I prefer to return out as soon an possible, and see not point to having everything return out from the same point at the end of the procedure. I picked up this habit doing assembly, years ago. Also, I always return a value:
RETURN 10
The application will display a fatal error on positive numbers, and will display the user warning message on negative values.
We always pass back an OUTPUT parameter with the text of the error message.
example:
IF ~error~
BEGIN
--if it is possible to be within a transaction, so any error logging is not ROLLBACK later
IF XACT_STATE()!=0
BEGIN
ROLLBACK
END
SET #OutputErrMsg='your message here!!'
INSERT INTO ErrorLog (....) VALUES (.... #OutputErrMsg)
RETURN 10
END
I always use parameter #Is_Success bit as OUTPUT. So if I have an error then #Is_success=0. When parent procedure checks that #Is_Success=0 then it rolls back its transaction(with child transactions) and sends error message from #Error_Message to client.
As you can see from this answer history I followed this question and accepted answer, and then proceeded to 'invent' a solution that was basically the same as your second approach.
Caffeine is my main source of energy, due to the fact that I spend most of my life half-asleep as I spend far too much time coding; thus I didn't realise my faux-pas until you rightly pointed it out.
Therefore, for the record, I prefer your second approach: using an SP to raise the current error, and then using a TRY/CATCH around your parameter validation.
It reduces the need for all the IF/BEGIN/END blocks and therefore reduces the line count as well as puts the focus back on the validation. When reading through the code for the SP it's important to be able to see the tests being performed on the parameters; all the extra syntactic fluff to satisfy the SQL parser just gets in the way, in my opinion.