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;
Related
I'm fairly new to MySQL but I'd like to create a function to validate a JSON objects that are stored in my database tables.
I looked up information on creating a function, but must be missing something as I can't seem to get it to work. It doesn't seem like it would be overly complicated but perhaps I'm not using the appropriate syntax.
Here is my code:
DELIMITER //
CREATE FUNCTION CHECKJSON( DB_NAME varchar(255), TABLE_NAME varchar(255), JSON_COLUMN varchar(255))
RETURNS varchar(300)
BEGIN
DECLARE notNullCount int;
DECLARE validJSONCount int;
DECLARE result varchar(300);
SET notNullCount = (SELECT count(*) FROM DB_NAME.TABLE_NAME WHERE JSON_COLUMN IS NOT NULL);
set validJSONCount = (SELECT count(*) FROM DB_NAME.TABLE_NAME WHERE JSON_VALID(JSON_COLUMN) > 0);
CASE
WHEN (validJSONCount = notNullCount) THEN
SET result = CONCAT('VALID JSON COUNT: ', validJSONCount)
ELSE
SET result = CONCAT('INVALID JSON COUNT: ', (notNullCount - validJSONCount))
END;
RETURN result;
END //
DELIMITER ;
When I try to run this code, I get the following error message:
"Error Code: 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 'ELSE SET result = CONCAT('INVALID JSON COUNT: ', (notNullCount - validJSONC' at line 14"
Any thoughts on how I might improve this code? Thanks!
Since MySQL 5.7 you have a pretty and simple function for this:
JSON_VALID(value)
Returns 0 or 1 to indicate whether a value is valid JSON. Returns NULL if the argument is NULL.
https://dev.mysql.com/doc/refman/5.7/en/json-attribute-functions.html#function_json-valid
You're missing a couple of ; and to end the case it should be END CASE.
DELIMITER //
CREATE FUNCTION CHECKJSON( DB_NAME varchar(255), TABLE_NAME varchar(255), JSON_COLUMN varchar(255))
RETURNS varchar(300)
BEGIN
DECLARE notNullCount int;
DECLARE validJSONCount int;
DECLARE result varchar(300);
SET notNullCount = (SELECT count(*) FROM DB_NAME.TABLE_NAME WHERE JSON_COLUMN IS NOT NULL);
set validJSONCount = (SELECT count(*) FROM DB_NAME.TABLE_NAME WHERE JSON_VALID(JSON_COLUMN) > 0);
CASE
WHEN (validJSONCount = notNullCount) THEN
SET result = CONCAT('VALID JSON COUNT: ', validJSONCount) ;
ELSE
SET result = CONCAT('INVALID JSON COUNT: ', (notNullCount - validJSONCount)) ;
END CASE;
RETURN result;
END //
DELIMITER ;
I am needing to grab data from one table and use a relationship to place it into another table. Long story short, I need to get back an array of IDs so I created a function to return them. I am only retrieving data which is IDs so all int values. The problem is when I invoke my function I get the error:
Conversion failed when converting the varchar value '/' to data type int.
But everything is cast as varchar so I have NO idea why this is happening. I know its dirty code but it is a high priority project with a tight deadline so I just need it to work. I am using SQL Server 2012. Any thoughts?
Function:
CREATE FUNCTION FactPersonList (#FactID varchar(100),
#delimiter char(1))
RETURNS varchar(8000)
AS
BEGIN
DECLARE #CharIDList varchar(8000)
DECLARE #id int
DECLARE #FinalTable TABLE (
factid int,
charid int
)
SET #CharIDList = CHAR(08) + #delimiter
INSERT INTO #FinalTable
SELECT DISTINCT
#FactID,
CharacterId
FROM KeyFactsCharacters
WHERE KeyFactID = #FactID
WHILE ((SELECT
COUNT(*)
FROM #FinalTable)
> 0)
BEGIN
SET #Id = (SELECT TOP 1
charid
FROM #FinalTable)
SET #CharIDList = CAST(#CharIDList + #Id + #delimiter AS varchar(8000))
END
SET #CharIDList = CAST(#CharIDList + CHAR(08) + '~' AS varchar(8000))
RETURN CAST(#CharIDList AS varchar(8000))
END
I invoke it using this:
SELECT dbo.FactPersonList(KeyFact.KeyFactId, '/') FROM KeyFactsCharacters, KeyFact
I'm not familiar with MS SQL (experience almost exclusively in MySQL), but I would guess that #CharIDList + #Id + #delimiter is causing the error; since #Id is defined as an int, MS SQL may be trying to coerce the other operands to int values as well (for addition rather than concatenation).
I'm writing a stored procedure that needs to clean up some data if an insert fails. I'd like it to perform the clean up, but return the original error if this insert fails (primarily for logging as I want to see exactly why the insert failed). Basically like a throw; in C#. Is there a simple way to do this?
BEGIN TRY
Insert into table (col1) values ('1")
END TRY
BEGIN CATCH
--do clean up here
--then throw original error
END TRY
Is this feasible/good practice? In the application code that calls the proc, I'm handling the error from an application standpoint, but the clean up statements seem to better fit inside the proc.
I usually do something like this:
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[procedure_name]') AND ObjectProperty(id, N'IsProcedure') = 1)
DROP PROCEDURE [dbo].[procedure_name]
GO
CREATE PROCEDURE [dbo].[procedure_name]
(
#param1 VARCHAR(100)
,#param2 INT
)
AS
/*
*******************************************************************************
<Name>
[procedure_name]
</Name>
<Purpose>
[Purpose]
</Purpose>
<Notes>
</Notes>
<OutsideRef>
Called From: [Called From]
</OutsideRef>
<ChangeLog>
Change No: Date: Author: Description:
_________ ___________ __________ _____________________________________
001 [DATE] [YOU] Created.
</ChangeLog>
*******************************************************************************
*/
BEGIN
SET NOCOUNT ON
SET XACT_ABORT OFF -- Allow procedure to continue after error
-- *****************************************
-- Parameter string. Used for error handling
-- *****************************************
DECLARE #ErrorNumber INT
,#ErrorMessage VARCHAR(400)
,#ErrorSeverity INT
,#ErrorState INT
,#ErrorLine INT
,#ErrorProcedure VARCHAR(128)
,#ErrorMsg VARCHAR(2000)
,#NestedProc BIT = 1
,#Params VARCHAR(255); -- String representing parameters, make it an appropriate size given your parameters.
--Be Careful of the CONVERT here, GUIDs (if you use them) need 36 characters, ints need 10, etc.
SET #Params = ''
+ CHAR(13) + '#param1 = ' + COALESCE(CONVERT(VARCHAR(100), #param1), 'NULL')
+ CHAR(13) + '#param2 = ' + COALESCE(CONVERT(VARCHAR(10), #param2), 'NULL')
BEGIN TRY
--If you're using transactions, and want an 'all or nothing' approach, use this so that
--you only start a single transaction in the outermost calling procedure (or handle
--that through your application layer)
IF ##TRANCOUNT = 0
BEGIN
SET #NestedProc = 0
BEGIN TRANSACTION
END
INSERT INTO [TABLE]
(
COL1
,COL2
)
VALUES
(
#param1
,#param2
);
--Commit the transaction if this is the outtermost procedure and if there is a transaction to rollback.
IF ##TRANCOUNT > 0 AND #NestedProc = 0
BEGIN
COMMIT TRANSACTION
END
END TRY
BEGIN CATCH
--Roll back the transaction if this is the outtermost procedure and if there is a transaction to rollback.
IF ##TRANCOUNT > 0 AND #NestedProc = 0
BEGIN
ROLLBACK TRANSACTION
END
-- Execute the error retrieval routine.
SELECT
#ErrorNumber = ERROR_NUMBER(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorProcedure = ERROR_PROCEDURE(),
#ErrorState = ERROR_STATE(),
#ErrorLine = ERROR_LINE(),
#ErrorMessage = ERROR_MESSAGE();
SET #ErrorMsg = 'Error Number : ' + CAST(#ErrorNumber AS VARCHAR(5)) + CHAR(13)
+ 'Procedure Name : ' + #ErrorProcedure + CHAR(13)
+ 'Procedure Line : ' + CAST(#ErrorLine AS VARCHAR(5)) + CHAR(13)
+ 'Error Message : ' + #ErrorMessage + CHAR(13)
+ 'Parameters : ' + CHAR(13) + #Params + CHAR(13);
--Raise the exception.
RAISERROR (#ErrorMsg, #ErrorSeverity, #ErrorState);
END CATCH
END
GO
This type of procedure allows you to have nesting procs with transactions (so long as the desired effect is that if an error is thrown anywhere, you'll eventually throw back up to the outer procedure and then rollback). A pretty important scenario that I don't think this template handles is the case where an error that's severe enough to completely kill the procedure is thrown. Perhaps someone else could chime in on that front.
Assuming we are using a table MyTable defined as
CREATE TABLE [dbo].[MyTable](
[Col1] [int] NOT NULL
) ON [PRIMARY]
The I would use a procedure similar to the one below.
In the case of insert failure the code will enter the Catch block where a check for the error number/message can be perform and assigned.
Once assigned the transaction can be rolled back and the error number/message returned.
You may need to change the SQL Server Error number in the RAISERROR error line depending on what you are doing.
CREATE PROCEDURE [dbo].[zTestProc]
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#LocalError INT,
#ErrorMessage VARCHAR(4000)
BEGIN TRY
BEGIN TRANSACTION TestTransaction
Insert into MyTable(col1) values ('01/01/2002')
COMMIT TRANSACTION TestTransaction
END TRY
BEGIN CATCH
SELECT #LocalError = ERROR_NUMBER(),
#ErrorMessage = ERROR_MESSAGE()
IF( XACT_STATE()) <>0
BEGIN
ROLLBACK TRANSACTION TestTransaction
END
RAISERROR ('TestSP: %d: %s', 16, 1, #LocalError, #ErrorMessage) ;
RETURN(0)
END CATCH
END
Try the following snippet.
DECLARE #errNum int
DECLARE #rowCount int
BEGIN TRY
INSERT INTO [TABLE] (COL1) VALUES ('1")
END TRY
BEGIN CATCH
SET #errNum = ##ERROR
SET #rowCount = ##ROWCOUNT
RAISEERROR(#errNum)
END CATCH
I have a stored procedure (that I didn't write) that uses openquery to populate a temporary table. The problem is that we have an expected error (it hits active directory for a user that no longer exists) that is stopping the entire procedure. What I was hoping to do is catch the error, fill in some default values and allow the cursor to continue. Currently, I'm catching the error, but the proc is stopping at that point. Is there a way I can force it to continue? Here's the piece of the proc:
BEGIN
SET #SQL=N'INSERT INTO #AD_Display_Names (GUID, Display_Name)
SELECT objectGUID, displayName
FROM OPENQUERY(ADSI,''SELECT objectGUID, displayName
FROM ''''LDAP://<GUID=' + CONVERT (VARCHAR (MAX), #GUID) + '>''''
WHERE objectCategory = ''''Person'''' AND objectClass = ''''user'''''')'
BEGIN TRY
EXEC SP_EXECUTESQL #SQL
END TRY
BEGIN CATCH
SET #SQL=N'INSERT INTO #AD_Display_Names (GUID, Display_Name)
VALUES(''00000000-0000-0000-0000-000000000000'', ''Unknown'')'
EXEC SP_EXECUTESQL #SQL
END CATCH
FETCH NEXT FROM [User_Names_Cursor]
INTO #GUID
END
Why not do something like this?
-- cursor stuff here
BEGIN
DECLARE #objectGUID UNIQUEIDENTIFIER
DECLARE #displayName VARCHAR(100)
SELECT #objectGUID = objectGUID, #displayName = displayName
FROM OPENQUERY(ADSI, N'SELECT objectGUID, displayName
FROM ''LDAP://<GUID=' + CONVERT (VARCHAR (MAX), #GUID) + '>''
WHERE objectCategory = ''Person'' AND objectClass = ''user'''
IF(#objectGUID IS NULL)
BEGIN
SET #objectGUID = '00000000-0000-0000-0000-000000000000'
SET #displayName = 'Unknown'
END
INSERT INTO #AD_Display_Names (GUID, Display_Name)
VALUES(#objectGUID, #displayName)
FETCH NEXT FROM [User_Names_Cursor]
INTO #GUID
END
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.