Creating a Stored Procedure in MySQL with C# - c#

I'm trying to create a new stored procedure programatically using the following code:
using (MySqlConnection conn = new MySqlConnection(connectionString))
{
conn.Open();
using (MySqlTransaction trans = conn.BeginTransaction())
{
using (MySqlCommand command = conn.CreateCommand())
{
command.CommandText = query;
command.ExecuteNonQuery();
}
trans.Commit();
}
}
And the following text as the create statement copied from Mysql workbench:
static string query = #"
delimiter $$
CREATE PROCEDURE `GetParentIds`(IN `tempTableName` VARCHAR(255), IN `id` int)
BEGIN
DECLARE parId INT;
DECLARE curId INT;
DROP TEMPORARY TABLE IF EXISTS tempTableName;
CREATE TEMPORARY TABLE tempTableName (
node_id INT NOT NULL PRIMARY KEY
);
set curId := id;
get_parents_loop: LOOP
set parId := null;
set parId = (select ParentID from {TableName} where ID = curId);
IF parId is NULL THEN
LEAVE get_parents_loop;
END IF;
INSERT INTO tempTableName(node_id) Values (parId);
set curId := parId;
END LOOP get_parents_loop;
SELECT *
FROM tempTableName;
END$$";
This procedure is passed an ID of an object which has a parentID and it gets all of the parentIDs of all the parents of the given object and returns them. The problem comes when I try to run it and I get the following message:
"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 'delimiter $$
CREATE PROCEDURE GetParentIds(IN tempTableName VARCHAR(255),' at line 1"
Any and all Ideas are welcome!
* EDIT **
Thanks to all the answers below, this is what finally worked:
CREATE PROCEDURE GetParentIds(IN tempTableName VARCHAR(255), IN id int)
BEGIN
DECLARE parId INT;
DECLARE curId INT;
DROP TEMPORARY TABLE IF EXISTS tempTableName;
CREATE TEMPORARY TABLE tempTableName (node_id INT NOT NULL PRIMARY KEY );
set curId := id;
get_parents_loop: LOOP
set parId := null;
set parId = (select ParentID from TDOs where TDOID = curId);
IF parId is NULL THEN
LEAVE get_parents_loop;
END IF;
INSERT INTO tempTableName(node_id) Values (parId);
set curId := parId;
END LOOP get_parents_loop;
SELECT *
FROM tempTableName;
END;

Remove DELIMITER $$ at the beginning and $$ after last END
DELIMITER is a mysql client command that enables you to change its statement terminator temporarily while you define a stored routine.
If you are defining a stored routine from within a programming interface that does not use the semicolon as a statement terminator, semicolons within stored routine definitions do not present any special issues.

Related

Same results are getting updated in all rows by using where clause in SQL Server

I'm running a stored procedure inside a foreach loop in c#. After completion, all the rows of a column are getting updated with the top most value. Below is the stored procedure:
ALTER PROCEDURE [dbo].[getservername8]
#number varchar(255)
AS
DECLARE #server_name varchar(500)
SELECT #server_name = short_description
FROM [Event_alerts].[dbo].[event_alerts]
DECLARE #s AS varchar(50)
SELECT #s = SUBSTRING(#server_name, CHARINDEX('-', #server_name) + 15, 50)
UPDATE event_alerts
SET server_name = #s
WHERE number = #number
This is the C# code:
using (SqlCommand command2 = new SqlCommand("getservername8", conn))
{
command2.CommandType = CommandType.StoredProcedure;
command2.Parameters.AddWithValue("#number",number);
command2.Parameters["#number"].Direction = ParameterDirection.Input;
command2.ExecuteNonQuery();
}
Any help would be much appreciated
The culprit is because you are getting the same value of #server_name for each call. So #s si also same for all. that is why you are inserting the same value in the column
select #server_name = short_description from [Event_alerts].[dbo].[event_alerts]
declare #s as varchar(50)
select #s= SUBSTRING(#server_name, CHARINDEX('-', #server_name) + 15, 50)
Your code has a valid where clause, so it should only be updating the matching rows. I can only speculate that all rows have the same value for number.
However, you seem to have an error in the definition of #server_name -- and that might be the problem you are referring to. There is no where clause so it is set to an arbitrary value -- possibly from what you would call "the last row". Although that nomenclature is a misinterpretation of what happens.
Your SP is too complex anyway. I suspect that you intend:
alter procedure [dbo].[getservername8] (
#number varchar(255)
) as
begin
update ea
set server_name = substring(short_description, charindex('-', short_description) + 15, 50)
from event_alerts ea
where number = #number;
end; -- getservername8
Also note the good programming practices:
The statements end in a semicolon.
The body of the SP uses begin/end.
The end is tagged with the name of the SP.

Encrypt the stored procedure created by SqlDependency

I created a SqlDependency so that an event would fire when the results of a particular query change.
// Create a command
SqlConnection conn = new SqlConnection(connectionString);
string query = "SELECT MyColumn FROM MyTable;";
SqlCommand cmd = new SqlCommand(query, conn)
cmd.CommandType = CommandType.Text;
// Register a dependency
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += DependencyOnChange;
When this code executes, a stored procedure is automatically created with a name like
SqlQueryNotificationStoredProcedure-82ae1b92-21c5-46ae-a2a1-511c4f849f76
This procedure is unencrypted, which violates requirements I have been given. I have two options:
Convince the customer that it doesn't matter that the auto generated procedure is unencrypted because it only does cleanup work and contains no real information (thanks to ScottChamberlain for pointing this out).
Find a way to encrypt the stored procedure generated by SqlDependency.
How can I accomplish option 2?
Contents of the stored procedure in question:
CREATE PROCEDURE [dbo].[SqlQueryNotificationStoredProcedure-b124707b-23fc-4002-aac3-4d52a71c5d6b]
AS
BEGIN
BEGIN TRANSACTION;
RECEIVE TOP (0) conversation_handle
FROM [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
IF (
SELECT COUNT(*)
FROM [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b]
WHERE message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer'
) > 0
BEGIN
IF (
(
SELECT COUNT(*)
FROM sys.services
WHERE NAME = 'SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b'
) > 0
)
DROP SERVICE [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
IF (OBJECT_ID('SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b', 'SQ') IS NOT NULL)
DROP QUEUE [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
DROP PROCEDURE [SqlQueryNotificationStoredProcedure-b124707b-23fc-4002-aac3-4d52a71c5d6b];
END
COMMIT TRANSACTION;
END
GO
Create a DDL trigger that checks if a procedure with a name like "SqlQueryNotificationStoredProcedure-" is being created, and if so, immediately alter it WITH ENCRYPTION instead:
CREATE TRIGGER [TR_EncryptQueryNotificationProcedures]
ON DATABASE
AFTER CREATE_PROCEDURE, ALTER_PROCEDURE
AS
BEGIN
SET ARITHABORT ON;
SET NOCOUNT ON;
IF TRIGGER_NESTLEVEL() > 1 RETURN;
-- For debugging purposes only
PRINT CONVERT(NVARCHAR(MAX), EVENTDATA());
DECLARE #DatabaseName NVARCHAR(128);
SET #DatabaseName = EVENTDATA().value(
'(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(128)'
);
DECLARE #Schema NVARCHAR(128);
SET #Schema = EVENTDATA().value(
'(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(128)'
);
DECLARE #Name NVARCHAR(128);
SET #Name = EVENTDATA().value(
'(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(128)'
);
DECLARE #Definition NVARCHAR(MAX);
SELECT #Definition =
OBJECT_DEFINITION(
OBJECT_ID(
QUOTENAME(#DatabaseName) + '.' +
QUOTENAME(#Schema) + '.' +
QUOTENAME(#Name),
'P'
)
)
;
-- If the sproc is already encrypted, we can't do anything with it
IF #Definition IS NULL RETURN;
SELECT #Definition = STUFF(
#Definition,
CHARINDEX('CREATE', #Definition),
LEN('CREATE'),
'ALTER'
);
IF
#Name LIKE 'SqlQueryNotificationStoredProcedure-%' AND
-- this should always be false since we can't read encrypted definitions,
-- but just to make sure
#Definition NOT LIKE '%WITH ENCRYPTION AS BEGIN%'
BEGIN;
SET #Definition = REPLACE(
#Definition, 'AS' + CHAR(13) + CHAR(10) + 'BEGIN',
'WITH ENCRYPTION AS BEGIN'
);
EXEC (#Definition);
END;
END;
GO
ENABLE TRIGGER [TR_EncryptQueryNotificationProcedures] ON DATABASE;
Disclaimer: not tested against an actual dependency notification, but the basic idea is sound. It's quite brittle because it depends on the exact form of the procedure, of course -- making it more robust is possible, but tedious.

Loop through dynamic DataSet and insert Stored Procedure

I have a DataTable in C# that I need to insert into a table. The DataTable is completely dynamic (Columns are not pre-defined). I inserted this DataTable row by row using C# code but because it is inefficient I am sending the DataTable bulk into an SQL Stored Procedure. I need the Stored Procedure to loop through the bulk, insert row by row, and return the set of invalid data.
C# code:
SqlConnection sqlConnection = getDBConnection();
SqlCommand command = sqlConnection.CreateCommand();
command.CommandType = System.Data.CommandType.StoredProcedure;
command.CommandText = "[dbo].[saveDataIntoTable]";
SqlParameter parameter = new SqlParameter();
//The parameter for the SP must be of SqlDbType.Structured
parameter.ParameterName = "#Sample";
parameter.SqlDbType = System.Data.SqlDbType.Structured;
parameter.Value = dataTable;
command.Parameters.Add(parameter);
foreach (DataRow row in dataTable.Rows)
{
System.Diagnostics.Debug.WriteLine(row.ItemArray);
if (row.ItemArray[0] == null)
{
dataTable.Rows.Remove(row);
}
}
SqlDataReader dr = command.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dr);
//handling the dt DataTable here
Stored Procedure:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[saveDataIntoTable]
(
-- which accepts one table value parameter. It should be noted that the parameter is readonly
#Sample As [dbo].[SampleUserData] Readonly
)
AS
BEGIN
BEGIN TRY
Insert Into USER(USER_ID,EMAIL,PASSWORD,PASSWORD_HINT,PWD_CHANGED_DATE,
CREATED_BY,CREATED_DATE,UPDATED_BY,UPDATED_DATE,STATUS,VERSION,VALIDATE)
Select USER_ID, EMAIL,PASSWORD,PASSWORD_HINT,PWD_CHANGED_DATE,
CREATED_BY,CREATED_DATE,UPDATED_BY,UPDATED_DATE,STATUS,VERSION,VALIDATE From #Sample
END TRY
BEGIN CATCH
Select USER_ID, EMAIL,PASSWORD,PASSWORD_HINT,PWD_CHANGED_DATE,
CREATED_BY,CREATED_DATE,UPDATED_BY,UPDATED_DATE,STATUS,VERSION,VALIDATE From #Sample
END CATCH
END
I used a User-defined Table Type for the Stored Procedure:
-- Create a table data type
CREATE TYPE [dbo].[SampleUserData] As Table
(
--This type has structure similar to the DB table
USER_ID Nvarchar(20) ,
EMAIL Nvarchar(50),
PASSWORD Nvarchar(100),
PASSWORD_HINT Nvarchar(20),
PWD_CHANGED_DATE date,
CREATED_BY Nvarchar(20),
CREATED_DATE date,
UPDATED_BY Nvarchar(20),
UPDATED_DATE date,
STATUS Nvarchar(20),
VERSION Int,
VALIDATE Nvarchar(10)
);
Right now, my Stored Procedure inserts the whole bulk of data at once. When an exception occurs, it returns the whole DataSet (I do not know how to separate the rows).
PS: If there is any other method for the above scenario which is easier, please let me know.
Thanks.
This will do the same as your stored procedure one line at a time:
This was written for SQL Server 2005
BEGIN
--I'm only using your first three columns in this example
DECLARE #USER_ID as Nvarchar(20);
DECLARE #Email as Nvarchar(20);
DECLARE #Password as Nvarchar(20);
DECLARE #SampleCursor as CURSOR;
SET #SampleCursor = CURSOR FOR
SELECT USER_ID, EMAIL, PASSWORD
FROM #Sample;
OPEN #SampleCursor;
--Can't insert directly into table from table variable so save as scalar variable first
FETCH NEXT FROM #SampleCursor INTO #USER_ID, #Email, #Password;
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM #SampleCursor INTO #USER_ID, #Email, #Password;
BEGIN TRY
--then insert scalar variables into table
INSERT INTO USER (USER_ID, Email, Password) VALUES( #USER_ID, #Email, #Password)
END TRY
BEGIN CATCH
SELECT #USER_ID, #Email, #Password
END CATCH
END
CLOSE #SampleCursor;
DEALLOCATE #SampleCursor;
END
This might work. Just insert your source table one line at a time using TOP(1). The solution below creates a table temporarily so your source table isn't deleted.
--create copy of source table
SELECT * INTO TempTbl FROM Source_Table
--loop through temp table
WHILE EXISTS (SELECT * FROM TempTbl)
BEGIN
--insert first line of temp table into destination table
BEGIN TRY
INSERT INTO [USER] SELECT TOP (1) * FROM TempTbl
END TRY
BEGIN CATCH
SELECT TOP(1) FROM TempTbl
END CATCH
--remove inserted line from temp table
DELETE TOP (1) FROM TempTbl
END
DROP TABLE TempTbl
Update.
This works for me:
CREATE PROCEDURE SOProc
-- Add the parameters for the stored procedure here
#Source_Table_Name sysname = ''
AS
BEGIN
EXEC(
'SELECT * INTO TempTbl FROM ' + #Source_Table_Name)
WHILE EXISTS (SELECT * FROM TempTbl)
BEGIN
BEGIN TRY
INSERT INTO User_Table SELECT TOP (1) * FROM TempTbl
END TRY
BEGIN CATCH
SELECT TOP(1) * FROM TempTbl
END CATCH
DELETE TOP (1) FROM TempTbl
END
DROP TABLE TempTbl
END
GO
Why not use a where clause to validate the data
-- insert valid data
insert into [USER] (USER_ID,EMAIL,PASSWORD,PASSWORD_HINT,PWD_CHANGED_DATE,
CREATED_BY,CREATED_DATE,UPDATED_BY,UPDATED_DATE,STATUS,VERSION,VALIDATE)
select USER_ID, EMAIL,PASSWORD,PASSWORD_HINT,PWD_CHANGED_DATE,
CREATED_BY,CREATED_DATE,UPDATED_BY,UPDATED_DATE,STATUS,VERSION,VALIDATE
from #Sample
where USER_ID is not null
-- select invalid data
select USER_ID, EMAIL,PASSWORD,PASSWORD_HINT,PWD_CHANGED_DATE,
CREATED_BY,CREATED_DATE,UPDATED_BY,UPDATED_DATE,STATUS,VERSION,VALIDATE
from #Sample
where USER_ID is null
An example of a more detailed validation check
select USER_ID, EMAIL,PASSWORD,PASSWORD_HINT,PWD_CHANGED_DATE,
CREATED_BY,CREATED_DATE,UPDATED_BY,UPDATED_DATE,STATUS,VERSION,VALIDATE
from #Sample S
where USER_ID is not null
and EMAIL is not null
and PASSWORD is not null -- etc
-- check record is not duplicate
and not exists (select 1 from [USER] U where U.USER_ID = S.USER_ID)
and isnumeric(USER_ID)

Returning a scalar from SQL Server to C#

I am trying to return scalar from a database like this:
DbConnection cn = GetConnection2();
cn.Open();
// stored procedure
DbCommand cmd = GetStoredProcCommand(cn, "GetReason");
DbParameter param;
param = CreateInParameter("Reason_Number", DbType.String);
param.Value = number;
cmd.Parameters.Add(param);
param = CreateOutParameter("Result", DbType.String);
param.Direction = ParameterDirection.Output;
cmd.Parameters.Add(param);
cmd.ExecuteScalar();
string reason;
reason = cmd.Parameters["#Result"].Value.ToString();
if (cn.State == ConnectionState.Open)
cn.Close();
return reason;
Here is my stored procedure:
-- =============================================
-- Create date: Today
-- Description: Input Reason # and Return Full Reason Name
-- =============================================
ALTER PROCEDURE [dbo].[GetReason]
#Reason_Number nvarchar(50),
#Result nvarchar(50) output
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT #Result = Field1
FROM dbo.Reasons
WHERE Field1 LIKE #Reason_Number + '%';
END
I am getting an error on the ExecuteScalar line:
System.InvalidOperationException occurred
Message="String[1]: the Size property has an invalid size of 0."
What am I doing wrong?
If you want to use ExecuteScalar, your stored proc needs to return the single row, single column from a SELECT:
ALTER PROCEDURE [dbo].[GetReason]
#Reason_Number nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT Field1
FROM dbo.Reasons
WHERE Field1 LIKE #Reason_Number + '%';
END
and then your code needs to read that value:
var returnedValue = cmd.ExecuteScalar();
and use it from there. Of course, in that case, you also do not need an OUTPUT parameter in your C# code....
Word of warning: that SELECT in your stored proc could potentially return multiple rows. You might want to add a TOP 1 to your select - just to be safe:
SELECT TOP 1 Field1
FROM dbo.Reasons
WHERE Field1 LIKE #Reason_Number + '%';
Just add an other one statement to the end of your stored procedure and remove OUTPUT parameter
SELECT #Result as 'Result'

How to retrieve the result value and a select statement of a stored procedure at the same time in C#?

OK so this might sound a bit weird!
The thing is that in a project there are many stored procedure already written like this:
CREATE PROCEDURE [Status_Insert]
#StatusId int OUTPUT,
#Status nvarchar (50),
#IsDeleted bit = 0
AS
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRAN
INSERT INTO dbo.[Status]
(
[Status],
[IsDeleted]
)
VALUES
(
#Status,
#IsDeleted
)
SET #StatusId = ##IDENTITY
COMMIT TRAN
RETURN 1
END TRY
BEGIN CATCH
ROLLBACK TRAN
DECLARE #ErrorNumber_INT INT;
DECLARE #ErrorSeverity_INT INT;
DECLARE #ErrorProcedure_VC VARCHAR(200);
DECLARE #ErrorLine_INT INT;
DECLARE #ErrorMessage_NVC NVARCHAR(4000);
SELECT
#ErrorMessage_NVC = ERROR_MESSAGE(),
#ErrorSeverity_INT = ERROR_SEVERITY(),
#ErrorNumber_INT = ERROR_NUMBER(),
#ErrorProcedure_VC = ERROR_PROCEDURE(),
#ErrorLine_INT = ERROR_LINE()
RETURN -1
END CATCH
The primary key is an Identity and an output variable is used to retrieve its value after the insert statement. (By the way, is this approach a good practice?)
Now the return values are used to indicate the success or failure of the procedure. (IE. if the operation was successful 1 is returned and -1 is returned if the operation has failed)
If there is an error in the execution of the procedure, the last select statement will return the error to the user.
Now how can I call this procedure in C# and get these results at the same time?
What I want to do is, execute the procedure, and get the return value, if it was 1 then get the result of the last select statement (which contains more info about the error that has occured)
Thanks in advance.
You can use "ReturnValue" parameter in Command.
using(var connection = GetOpenConnection()) {
using(var cmd = connection.CreateCommand()){
cmd.CommandText = "Status_Insert";
cmd.CommandType = CommandType.StoredProcedure;
var prmReturnValue = new SqlParameter( "#ReturnValue", DBNull.Value );
prmReturnValue.Direction = ParameterDirection.ReturnValue;
using(var reader = cmd.ExecuteReader()) {
// process table result(s)
}
var returnValue = prmReturnValue.Value;
}
}
EDIT:
The problem is in select. If you select something into variable, the select will not be in result-sets.
If you want assign variable(s) from functions you don't need SELECT statement, you can use SET command or you can initialize values in DECLARE statement (from version 2008):
/* variable initialization in DECLARE statements >>> */
DECLARE #ErrorNumber_INT INT = ERROR_NUMBER();
DECLARE #ErrorSeverity_INT INT = ERROR_SEVERITY();
DECLARE #ErrorProcedure_VC VARCHAR(200) = ERROR_PROCEDURE();
/* <<< variable initialization in DECLARE statements */
/* variable initialization with SET statements >>> */
DECLARE #ErrorLine_INT INT;
DECLARE #ErrorMessage_NVC NVARCHAR(4000);
SET #ErrorLine_INT INT = ERROR_LINE();
SET #ErrorMessage_NVC = ERROR_MESSAGE();
/* <<< variable initialization with SET statements */
/* this select will be in result-set(s) >>> */
SELECT
#ErrorNumber_INT AS [ErrorNumber_INT],
#ErrorSeverity_INT AS [ErrorSeverity_INT],
#ErrorProcedure_VC AS [ErrorProcedure_VC],
#ErrorLine_INT AS [ErrorLine_INT],
#ErrorMessage_NVC AS [ErrorMessage_NVC]
/* <<< this select will be in result-set(s) */

Categories