Default nvarchar parameter not respected - sql-server-2008

I have a stored procedure in SQL Server 2008 R2 with the following parameter values declared:
#UN nvarchar(30),
#SN nvarchar(8),
#GG uniqueidentifier,
#Ss irnapp.SymbolTableType READONLY,
#SD date,
#ED date,
#IR nvarchar(1),
#ID nvarchar(1),
#NR int = NULL,
#GP nvarchar(1) = N'N'
It was my intention that if the #GP value is not supplied, then it should be given a value of N'N'. However, the procedure only returns the expected results when I explicitly pass in N'N' for #GP.
I've attempted searching for examples of SQL stored procedures with default parameter values, but the only examples I've found for nvarchar are defaults of NULL, which is not feasible for my application.
Would anyone know if the above is a legal default parameter declaration?
UPDATE:
Thanks Aaron for the quick response. I was hoping this would be a simple catch, as the code is quite lengthy. That said, here goes:
BEGIN TRY
DECLARE #GI int;
EXEC irn.GetGroupID #UN, #SN, #GG, #GI OUT;
DECLARE #CUID int;
IF #GP = N'Y'
BEGIN
SELECT #CUID = UserID
FROM Users
WHERE Uname = #UN
AND SNum = #SN;
END;
DECLARE #NoteIDs irn.NoteIDTableType;
INSERT INTO #NIDs (NO, NID)
SELECT *
FROM GetNIDs(#GI, #Ss, #SD, #ED, #IR, #ID, #NR, #GP, #CUID);
EXEC GetNsByNIDs #NIDs, N'N';
END TRY
BEGIN CATCH
EXEC irn.CreateProcedureErrorLog
EXEC irn.RaiseProcedureError
END CATCH;
ALTER FUNCTION [i].[GetNIDs] (
#GID int,
#Ss SymbolTableType READONLY,
#SD date,
#ED date,
#IR nvarchar(1),
#ID nvarchar(1),
#NR int,
#GP nvarchar(1) = N'N',
#CUID int = NULL)
RETURNS #TopOrderedMatchingNote TABLE (
NO int NOT NULL PRIMARY KEY,
NID int NOT NULL UNIQUE)
AS
BEGIN
INSERT INTO #MN (NID)
SELECT NID
FROM N
WHERE GID = #GID
AND ND >= #FDate
AND ND <= #TDate
AND IP = #GP
AND ((IP = N'Y' AND CUID = #CUID) OR (IP = N'N'))
AND IsDeleted = CASE #IncludeDeleted
WHEN N'N' THEN N'N'
ELSE IsDeleted
END;
END;
...snip...
Hope this is helpful and thanks again

Yes, your default parameter declaration example is valid and legal. Here is a quick repro:
USE tempdb;
GO
CREATE PROCEDURE dbo.splunge
#GP nvarchar(1) = N'N'
AS
BEGIN
SET NOCOUNT ON;
SELECT COALESCE(#GP, N'Y');
END
GO
EXEC dbo.splunge;
EXEC dbo.splunge #GP = N'Y';
Results:
----
N
----
Y
If you're having problems getting this to work, you'll need to post more of your code to demonstrate what that means.

Related

Incorrect syntax while parsing JSON with OPENJSON

This is my SP, I am trying to parse this and the error
Incorrect syntax near '$.Role'
was shown.
The JSON is stored in a tables's column. What am I doing wrong?
CREATE PROCEDURE [dbo].[sp_GetKeyPersonRoleMinMax]
#SectionID INT,
#ProposalID INT
AS
SET NOCOUNT ON
Declare #FldKPRoleRequirementsList NVARCHAR(MAX)
Declare #FldName varchar(50)
DEclare #FldIncl varchar(50)
Declare #FldRequired varchar(50)
Declare #FldLabel varchar(max)
Declare #FldList varchar(max)
CREATE Table #RoleMinMaxTemp
(
ID INT IDENTITY(1, 1) ,
Role nvarchar(1000),
MinRoleCount INT,
MaxRoleCount INT
)
Declare Fld_Cursor Cursor For
SELECT FldName, FldIncl, FldRequired, FldLabel,FldList from tblFld where FldParent = 1367 AND FldName like 'FldKPRoleRequirementsList%'
SET NOCOUNT ON
Open Fld_Cursor
WHILE (##Fetch_Status = 0)
BEGIN
if (#FldName = 'FldKPRoleRequirementsList')
BEGIN
SET #FldKPRoleRequirementsList = #FldList
END
FETCH next from Fld_Cursor into #FldName, #FldIncl, #FldRequired, #FldLabel,#FldList
END
Close Fld_Cursor
Deallocate Fld_Cursor
IF(#FldKPRoleRequirementsList IS NOT NULL and Len(#FldKPRoleRequirementsList) >0)
BEGIN
INSERT INTO #RoleMinMaxTemp
SELECT *
FROM OPENJSON(#FldKPRoleRequirementsList,'$.FldRole')
WITH (
Role nvarchar(1000) '$.Role',
MinRoleCount INT '$.MinRoleCount',
MaxRoleCount INT '$.MaxRoleCount'
);
END;
What is the reason for this error? I am using SQL Server 2016.
Try changing you compactibility of SQL SERVER to 130. Your must be below that.
ALTER DATABASE <DatabaseName> SET COMPATIBILITY_LEVEL = 130
Use this script to change it.

Mysql Stored Proc not returning a VARCHAR out parameter

Below is my stored procedure. It works fine but my problem is I can't get the output parameter as VARCHAR.
The part where I'm having problem is the assignment of #curcName to the out parameter op_resultMessage
BEGIN
SET op_resultMessage = #curcName;
END;
Here's the Stored Procedure.
CREATE DEFINER=`root`#`localhost` PROCEDURE `addCurriculum`(
IN p_curcName varchar(100),
IN p_description TEXT,
IN p_yearLevel VARCHAR(50),
IN p_syStart INT,
IN p_syEnd INT,
IN p_creator VARCHAR(50),
OUT op_resultMessage VARCHAR(50))
BEGIN
DECLARE curcName VARCHAR(20) ;
IF EXISTS
(SELECT #curcName := `name`
FROM curriculum
WHERE
yearLevel = p_yearLevel
AND syStart = p_syStart
AND syEnd = p_syEnd )
THEN --
BEGIN
SET op_resultMessage = #curcName;
END;
ELSE
BEGIN
INSERT INTO curriculum(`name`, description, yearLevel, syStart, syEnd, creator)
VALUES(p_curcName,p_description,p_yearLevel,p_syStart,p_syEnd,p_creator);
END;
END IF;
END
I'm trying to return a message IF name EXISTS
So it should go something like
SET op_resultMessage = #curcName 'already uses the school year and year level you're trying to insert';
But I don't know how to properly concatenate and assign values. I'm still confused with := SET and = operators. I guess that's where I'm having problems with.
If I change the out parameter's type to an INT like
OUT op_resultMessage VARCHAR(50)
then assigns a number to op_resultMessage like SET op_resultMessage = 1;
It returns the number 1 as out parameter values. It just won't work with varchar.
So when I try to call the procedure
CALL `enrollmentdb`.`addCurriculum`
('Test Curriculum ','Test ','Grade 1',2015,2016,'jordan',#outputMsg);
SELECT #outputMsg; -- this doesn't return any value even if Grade 1, 2015 and 2016 exists
I'd appreciate any help. I actually just learned mysql recently.
Thanks.
drop procedure if exists addCurriculum;
delimiter $$
CREATE PROCEDURE `addCurriculum`(
IN p_curcName varchar(100),
IN p_description TEXT,
IN p_yearLevel VARCHAR(50),
IN p_syStart INT,
IN p_syEnd INT,
IN p_creator VARCHAR(50),
OUT op_resultMessage VARCHAR(50))
BEGIN
DECLARE curcName VARCHAR(20) ;
SELECT `name` into #curcName
FROM curriculum
WHERE
yearLevel = p_yearLevel
AND syStart = p_syStart
AND syEnd = p_syEnd
LIMIT 1;
-- Note change above. When selecting into a variable (or more than 1)
-- then 0 or 1 rows can come back max or an Error occurs
IF #curcName is not null then
SET op_resultMessage = #curcName;
ELSE
BEGIN
INSERT INTO curriculum(`name`, description, yearLevel, syStart, syEnd, creator)
VALUES(p_curcName,p_description,p_yearLevel,p_syStart,p_syEnd,p_creator);
END;
SET op_resultMessage = 'GEEZ I am right here'; -- Drew added this
END IF;
END$$
delimiter ;
Note the commentary in the stored procedure, especially the part of only 0 or 1 rows returning else an Error will occur with a select into var pattern. So LIMIT 1. That may or may not be the row you want (limit 1), but that is where it is at right now.

MYSQL Stored Procedures: Variable Declaration and Conditional Statements

I have looked over numerous tutorials, manuals and documentations, but I still can not get this to work.
I am trying to create a stored procedure using phpMyAdmin.
I cant seem to find the errors here, the sql errors are so vague...
CREATE PROCEDURE insertToonOneShot(IN locale CHAR(2), IN name VARCHAR(16), IN realm VARCHAR(24), IN faction CHAR(1), IN toon_level INT, IN class_name INT)
BEGIN
DECLARE #realmID INT;
DECLARE #classID INT;
DECLARE #toonID INT;
SET #realmID = SELECT id FROM realms WHERE realms.name = realm;
SET #classID = SELECT id FROM classes WHERE classes.name = class_name;
IF NOT #realmID IS NULL AND NOT #classID IS NULL AND #toonID IS NULL THEN
INSERT INTO
toon (`locale`, `name`, `realm_id`, `faction`, `level`, `class_id`)
VALUES
(locale, name, #realmID, faction, toon_level, #classID);
END IF;
END;
The error I am getting right now is:
#1064 - You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use
near #realmID INT; DECLARE #classID INT; DECLARE #toonID INT; SET #rea
at line 3
Probably one of the more frustrating things I have ever had to do...
I have seen many tutorials online that show using the # symbol in variable declaration, and others not using it, I have even seen some that use VAR instead of DECLARE. What is the right syntax?...
This question, and its answers, seem to be confusing session variables (which are prefixed with #) with procedural variables (which are not prefixed.) See the answers to this question for more information.
The accepted solution resolves the error by using session variables, but it could introduce problems related to variable scope. For example, if a variable called realmID has been defined outside the procedure, its value will be overwritten when the procedure is run.
The correct way to resolve the problem is to use only procedural variables. These are DECLAREd at the start of the procedure without the # prefix.
DELIMITER $$
CREATE PROCEDURE insertToonOneShot(
IN locale CHAR(2),
IN name VARCHAR(16),
IN realm VARCHAR(24),
IN faction CHAR(1),
IN toon_level INT,
IN class_name INT
)
BEGIN
DECLARE realmID INT;
DECLARE classID INT;
SELECT id INTO realmID FROM realms WHERE realms.name = realm LIMIT 1;
SELECT id INTO classID FROM classes WHERE classes.name = class_name LIMIT 1;
IF realmID IS NOT NULL AND classID IS NOT NULL THEN
INSERT INTO toon (`locale`, `name`, `realm_id`, `faction`, `level`, `class_id`)
VALUES (locale, name, realmID, faction, toon_level, classID);
END IF;
END$$
DELIMITER ;
When you have a subquery, it needs to have parentheses. These lines:
SET #realmID = SELECT id FROM realms WHERE realms.name = realm;
SET #classID = SELECT id FROM classes WHERE classes.name = class_name;
Should be:
SET #realmID = (SELECT id FROM realms WHERE realms.name = realm);
SET #classID = (SELECT id FROM classes WHERE classes.name = class_name);
Or, better yet, you don't need the set:
SELECT #realmID := id FROM realms WHERE realms.name = realm;
SELECT #classID := id FROM classes WHERE classes.name = class_name;
This does the trick:
CREATE PROCEDURE insertToonOneShot(IN locale CHAR(2), IN name VARCHAR(16), IN realm VARCHAR(24), IN faction CHAR(1), IN toon_level INT, IN class_name VARCHAR(12))
BEGIN
SELECT #realmID := id FROM realms WHERE realms.name = realm;
SELECT #classID := id FROM classes WHERE classes.name = class_name;
SELECT #toonID := id FROM toon WHERE toon.name = name AND toon.realm_id = #realmID;
IF NOT #realmID IS NULL AND NOT #classID IS NULL AND #toonID IS NULL
THEN
INSERT INTO toon (`locale`, `name`, `class_id`, `realm_id`, `faction`, `level`)
VALUES (locale, name, #classID, #realmID, faction, toon_level);
END IF;
END;
//
Apparently the declare statements were not required... Who would have known?
Thanks to Gordon Linoff for pointing me in the right direction.

Porting MySQL Stored Procedure to Oracle

I'm trying to port a stored procedure from MySQL to Oracle, and I'm having a lot of trouble. I've gone through Oracle documentation, and I'm having trouble doing very basic things like declaring variables properly. I was hoping someone could show me how to properly declare and set variables.
My stored procedure is used to add values to two different tables and ensure that it's being mapped properly and the foreign keys aren't being violated.
Here is my MySQL Code:
CREATE DEFINER=root#% PROCEDURE proc_add_entry(IN theName vARCHAR(50), IN theKey VARCHAR(50), IN theOtherData VARCHAR(50), IN theOtherData2 INT, IN theStartDate DATE, IN theEndDate DaTE, IN theReferenceDate DaTE)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
BEGIN
declare theNameID int ;
declare theKeyID int ;
declare theOtherDataID int default null;
declare error bool default false;
declare continue handler for SQLEXCEPTION
set error = true;
set theKeyID = (select KeyID from map_alias ma where ma.alias = trim(theKey));
set theOtherDataID = (select theOtherDataID from map_otherdata mc where mc.otherdata = trim(theOtherData));
set theNameID = (select max(nameID) from inserttable);
set theNameID = theNameID + 1;
insert into inserttable values (theNameID , theKeyID , theOtherDataID , theOtherData2, theStartDate ,
theEndDate , theReferenceDate);
if error = true then
insert into errors_inserttable values (theNameID , theKeyID , theOtherDataID , theOtherData2, theStartDate ,
theEndDate , theReferenceDate);
end if;
set error = false;
insert into map_inserttable (theNameID , datasourceid, theName) values (theNameID , 1, theName);
if error = true then
insert into errors_map_inserttable (theNameID , datasourceid, theName) values (theNameID , 1, theName);
end if;
END
In Oracle, my last statement are being ignored (ORA-00922: Missing or invalid option). It should be a local variable, so I'm not sure why i'm getting that particular error.
I'm struggling to declare the continue handler as well. I'm getting the error:
Error(16,27): PLS-00103: Encountered the symbol "FOR" when expecting one of the following: := . ( # % ; not null range default character.
Here is my oracle code so far:
CREATE OR REPLACE PROCEDURE PROC_ADD_ENTRY
(
THENAME IN VARCHAR2
, THEKEY IN VARCHAR2
, THEOTHERDATA IN VARCHAR2
, THEOTHERDATA2 IN NUMBER
, THEFIRSTDATE IN DATE
, THELASTDATE IN DATE
, THEREFERENCEDATE IN DATE
) AS
THENAMEID INT;
THEKEYID INT;
THEOTHERDATAID int;
ERROR bool default false;
BEGIN
declare continue HANDLER FOR SQLEXCEPTION set error = true;
set THEKEYID = (select KEYID from map_INSERTTABLE mc where mc.Key = trim(THEKEY));
END PROC_ADD_ENTRY;
I'm sure this is stupidly simple for someone that uses oracle, but I'm reading the documentation and I'm seeing conflicting information on where and how to declare variables, continue handlers, and assign values to variables. (is it := or = to assign values? Do i use the word declare after the begin statement to declare variables, or do I do it the way I show below?)
If someone could show me:
a) where to declare a local variable
b) how to assign a value to it (i.e. 1 to an int)
c) how to assign a value from the DB to a variable (set var = select number from table_number tn where tn.number = 1)
d) how to declare a continue handler properly
I would really appreciate it.
You've go the basic structure fine.
create or replace procedure <name> (<param list>) as
<local variables>
begin
<body>
end <name>;
To address your specific questions:
a) where to declare a local variable
I've marked this section up above.
b) how to assign a value to it (i.e. 1 to an int)
You would use := for assignment.
eg. thenameid := 1;
The data type you want will typically match the sql data types (eg. NUMBER for the above) though there are PL/SQL-specific data types such as PLS_INTEGER. See the PL/SQL data types documentation for more details.
c) how to assign a value from the DB to a variable (set var = select number from table_number tn where tn.number = 1)
You would use the into keyword with a locally defined variable to store the value in. eg.
l_num_rows number;
select count(*) into l_num_rows from user_objects;
d) how to declare a continue handler properly
If I'm reading understanding your code correctly, you want set error = true to be executed every time there is a problem with an sql statement and then you want the stored procedure to carry on.
Exception handling is what you are after. You would wrap any or SQL PL/SQL statements that you think may have errors in an exception block like this, with as many exception cases as needed (eg. NO_DATA_FOUND):
begin
<statements that may fail>
exception when <exception name> then
<action>
...
exception when others then
<action>
end;
"other" is the catchall. You can have just this case handled but as with any error handling it is better practise to catch specific cases first.
For completion, here's roughly what your example procedure would look like. I've removed the error code flag as it is not needed and also changed the ints to numbers:
create or replace procedure proc_add_entry (
in thename varchar(50),
in thekey varchar(50),
in theotherdata varchar(50),
in theotherdata2 number,
in thestartdate date,
in theenddate date,
in thereferencedate date
) as
thenameid number;
thekeyid number;
theotherdataid number default null;
begin
begin
select keyid into thekeyid from map_alias ma where ma.alias = trim(thekey);
select theotherdataid into theotherdataid from map_otherdata mc where mc.otherdata = trim(theotherdata);
select max(nameid) into thenameid from inserttable;
thenameid := thenameid + 1;
insert into inserttable values (thenameid, thekeyid, theotherdataid, theotherdata2, thestartdate, theenddate, thereferencedate);
exception when others then
insert into errors_inserttable values (thenameid, thekeyid, theotherdataid, theotherdata2, thestartdate, theenddate, thereferencedate);
end;
begin
insert into map_inserttable (thenameid, datasourceid, thename) values (thenameid, 1, thename);
exception when others then
insert into errors_map_inserttable (thenameid, datasourceid, thename) values (thenameid, 1, thename);
end;
end proc_add_entry;

Check constraint to validate IP address field

I'm working on a project involving C# and a SQL Server 2008 database.
In one of the tables, I have a field (nvarchar(15)) which will contain an IP address.
I'd like to add a check constraint which will validate that the input value is actually an IP address.
I wanted to use a regex to do that, but it seems that this feature is not supported by default. I saw things about writing a customm dll with UDF inside (MSDN tutorial), but I don't really understand how it works (i.e. where should I place the dll ?)
Is there a "simple" way to add such a constraint ?
Any solution is welcome.
Thanks in advance !
There are several way of doing this - the most performant one would probably be a CLR function in the database.
This is because SQL has fairly poor text manipulation tooling and no native RegEx in SQL Server.
As other have said, this is better handled by an application before insertion to the DB.
It shouldn't be handled in the database, it should be handled first and foremost in the application.
There's no harm in then adding a check to the database, but leaving it up to the DB to filter input is very sketchy.
The easiest way I can think of is to create a function like fnCheckIP and use this function in the constraint.
There's no need to use UDF.
create function fnCheckIP(#ip varchar(15)) returns bit
AS
begin
if (#ip is null)
return null
declare #num1 int
declare #num varchar(15)
declare #pos int
while (#ip is not null)
begin
set #pos = IsNull(NullIf(charindex('.', #ip), 0), Len(#ip) + 1)
set #num = substring(#ip, 1, #pos - 1)
if (isnumeric(#num) = 0) or (not cast(#num as int) between 0 and 255)
return cast(0 as bit)
if (len(#ip) - #pos <= 0)
set #ip = null
else
set #ip = NullIf(substring(#ip, #pos + 1, len(#ip) - #pos), '')
end
return cast (1 as bit)
end
go
select dbo.fnCheckIP('127.0.0.1')
select dbo.fnCheckIP('127.0.0.300')
This solution is similar to Paulo's but using either approach will require getting rid of the comma character because isnumeric allows commas which will throw a cast to int error.
CREATE FUNCTION fn_ValidateIP
(
#ip varchar(255)
)
RETURNS int
AS
BEGIN
DECLARE #Result int = 0
IF
#ip not like '%,%' and
len(#ip) <= 15 and
isnumeric(PARSENAME(#ip,4)) = 1 and
isnumeric(PARSENAME(#ip,3)) = 1 and
isnumeric(PARSENAME(#ip,2)) = 1 and
isnumeric(PARSENAME(#ip,1)) = 1 and
cast(PARSENAME(#ip,4) as int) between 1 and 255 and
cast(PARSENAME(#ip,3) as int) between 0 and 255 and
cast(PARSENAME(#ip,2) as int) between 0 and 255 and
cast(PARSENAME(#ip,1) as int) between 0 and 255
set #Result = 1
ELSE
set #Result = 0
RETURN #Result
END
select dbo.fn_ValidateIP('127.0.0.1')
This may not be entirely practical, but one way would be to store the converted string ###-###-###-### into a binary(4) data type. Let the interface fuss around with hyphens and deal with converting the four numbers to binary and back (and this could probably even be done by a caluclated column.) A bit extreme, yes, but with binary(4) you will always be able to turn it into an IP address.
At last about 10 yrs after Oracle, sqlserver got native compilation (with limitations)
ALTER function fn_ValidateIPv4
(
#ip varchar(255)
)
RETURNS int
--WITH EXECUTE AS OWNER, SCHEMABINDING, NATIVE_COMPILATION
AS
BEGIN
--ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')
/* only sql2016 native Compilation **/
DECLARE #len_ip as int;
SET #len_ip = len(#ip);
DECLARE #firstBlock varchar(4) = '';
DECLARE #secondBlock varchar(4) = '';
DECLARE #thirdBlock varchar(4) = '';
DECLARE #fourthBlock varchar(4) = '';
DECLARE #countDot as smallint = 0;
DECLARE #l_i as smallint = 0;
DECLARE #l_curChar varchar(1) = 'X';
DECLARE #Result int = 0
IF (#len_ip <= 15)
BEGIN
WHILE (#l_i < #len_ip)
BEGIN
set #l_i += 1;
set #l_curChar = substring(#ip,#l_i,1);
if #l_curChar = '.'
SET #countDot += 1
ELSE
BEGIN
IF #l_curChar IN ( '0','1','2','3','4','5','6','7','8','9' )
BEGIN
IF #countDot = 0
SET #firstBlock = #firstBlock + #l_curChar;
IF #countDot = 1
SET #secondBlock = #secondBlock + #l_curChar;
IF #countDot = 2
SET #thirdBlock = #thirdBlock + #l_curChar;
IF #countDot = 3
SET #fourthBlock = #fourthBlock + #l_curChar;
IF #countDot > 3
set #firstBlock = 'AAA'; -- force error
END
ELSE set #firstBlock = 'AAA'; -- force error
END;
END;
IF ( #countDot = 3 and
cast(#fourthBlock as int) between 1 and 255 and
cast(#thirdBlock as int) between 0 and 255 and
cast(#secondBlock as int) between 0 and 255 and
cast(#firstBlock as int) between 0 and 255
)
set #Result = 1;
END;
/*
select dbo.fn_ValidateIPv4( '127.0.0.258' );
*/
RETURN #Result
END;
I had to remove not de-supported built functions isnumeric etc...