Maybe someone has an idea what I can check:
using (var cnn = new SqlConnection(connection))
{
cnn.Open();
using (var cmd = new SqlCommand())
{
cmd.Connection = cnn;
cmd.CommandText = "IF EXISTS (SELECT * FROM kruserprofile WHERE lactorid=#1 AND strname=#2) " +
" UPDATE kruserprofile SET txtvalue=#3 WHERE lactorid=#1 AND strname=#2 " +
"ELSE " +
" INSERT INTO kruserprofile (lactorid, strname, txtvalue) " +
" VALUES (#1, #2, #3)";
cmd.CommandTimeout = 120;
cmd.Parameters.AddWithValue("#1", actorId);
cmd.Parameters.AddWithValue("#2", ident);
cmd.Parameters.AddWithValue("#3", _mXml.ToString());
cmd.ExecuteNonQuery();
}
cnn.Close();
}
As you can see in this piece of code I do check if records exists and update or insert.
This works pretty fine on SQL Server systems.
On Azure SQL I do sometimes but not reproduce able the error:
Violation on PRIMARY KEY... where the constraint is on lactorid and strname.
Table:
CREATE TABLE dbo.kruserprofile (
lactorid int NOT NULL,
strname nvarchar(254) NOT NULL,
txtvalue nvarchar(max) NULL
);
GO
ALTER TABLE dbo.kruserprofile ADD CONSTRAINT PK__kruserpr__6E092EE804688C07 PRIMARY KEY (lactorid, strname);
GO
So I did not find any reason why, but sometimes it seems that the EXISTSreturns false so it try to insert which fails then. Any idea what I can check?
update:
Solution1:
We also have found the solution here.
Solution2:
I've created a test about using MERGE in Azure SQL.
Create table:
CREATE TABLE dbo.kruserprofile (
lactorid int NOT NULL,
strname nvarchar(254) NOT NULL,
txtvalue nvarchar(max) NULL
);
GO
ALTER TABLE dbo.kruserprofile ADD CONSTRAINT PK__kruserpr__6E092EE804688C07 PRIMARY KEY (lactorid, strname);
GO
Create a Table-valued parameter named dbo.kruserprofile_type, it will be used in my stored procedure:
create TYPE dbo.kruserprofile_type AS TABLE(
lactorid int NOT NULL,
strname nvarchar(254) NOT NULL,
txtvalue nvarchar(max)
)
GO
Create a Stored procedure, it will merge the same records and insert new records based on the primary key:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create PROCEDURE [dbo].[spUpsertKruserprofile]
#profile dbo.kruserprofile_type READONLY
AS
BEGIN
MERGE dbo.kruserprofile AS target_sqldb
USING #profile AS source_tblstg
ON (target_sqldb.lactorid = source_tblstg.lactorid and target_sqldb.strname = source_tblstg.strname )
WHEN MATCHED THEN
UPDATE SET
txtvalue = source_tblstg.txtvalue
WHEN NOT MATCHED THEN
INSERT (
lactorid,
strname,
txtvalue
)
VALUES (
source_tblstg.lactorid,
source_tblstg.strname,
source_tblstg.txtvalue
);
END
GO
After that, we can execute the stored procedure by following code:
DECLARE #profileVar AS dbo.kruserprofile_type;
/* Add data to the table variable. */
INSERT INTO #profileVar (lactorid, strname, txtvalue) values (1, 'tom','wednesday');
exec [dbo].[spUpsertKruserprofile] #profileVar
That's all.
Related
Let's say that I have a transact-SQL query that does something like this:
INSERT INTO Table1 -- Table1 has an identity column for the primary key
(
Table1Value1,
Table1Value2
)
VALUES
(
#Table1Value1, -- SqlParameter
#Table1Value2 -- SqlParameter
)
DECLARE #table1ID int = SCOPE_IDENTITY();
INSERT INTO Table2 -- Table2 also has and identity column for the PK
(
Table1ID -- Must have foreign key to Table1 record
Table2Value1,
Table2Value2
)
VALUES
(
#table1ID,
#Table2Value1, -- SqlParameter
#Table2Value2 -- SqlParameter
)
DECLARE #table2ID int = SCOPE_IDENTITY();
INSERT INTO Table3
(
Table1ID, -- Foreign Key to Table1 record
Table2ID, -- Foreign Key to Table2 record
Table3Value1,
Table3Value2
)
VALUES
(
#table1ID, -- Foreign Key to Table1 record
#table2ID, -- Foreign Key to Table2 record
#Table3Value1, -- SqlParameter
#Table3Value2 -- SqlParameter
)
Is there a way to make this work? Can you escape the local transact-sql variable names so they're ignored by whatever figures out the location of the parameters? Can you tell SqlClient to use something other than # to identify parameters? Is there a way to make this work without using a stored proc or having to run subqueries?
Thanks.
You can have client code like this (using C# as the example):
var cmd = new SqlCommand("exec Procedure #Var1, #Var2", connection);
cmd.Parameters.Add("#Var1", SqlDbType.NVarChar, 40).Value = var1Value;
cmd.Parameters.Add("#Var2", SqlDbType.NVarChar, 40).Value = var2Value;
connection.Open();
cmd.ExecuteNonQuery();
With a procedure including code like this:
-- declare an additional variable in the procedure
DECLARE #VAR3 NVarChar(40) = 'test Value';
SELECT #Var1, #Var2, #Var3;
The trick is if you want to declare a variable in your SQL code or procedure for use only within the procedure or script, don't also provide a parameter for it from the client, because that would be declaring the same variable twice.
It had to have been a typo. I changed it back and now it works. The only lesson here is to not work 40 hours in a 48 hour period and that you can mix parameters with transact-sql local variables. :-)
I wouldn't be opposed to deleting this.
I open my SqlConnection, then use a foreach loop to collect the data and with my stored procedure. I use Parameters.AddWithValue to send the data into my stored procedure, but I keep get an error
System.Data.SqlClient.SqlException: 'Procedure or function spInsertLeafPickup has too many arguments specified.
Any reason why?
Stored procedure:
CREATE TYPE Templeafpickup AS TABLE
(
ID INT PRIMARY KEY,
Address NVARCHAR(50),
SeasonType NVARCHAR(50),
NextPickupdate NVARCHAR(10)
)
CREATE PROCEDURE spInsertLeafPickup
#Templeafpickup Templeafpickup READONLY
AS
BEGIN
INSERT INTO LeafPickup([ID], [Address], [SeasonType], [NextPickupdate])
SELECT
[ID], [Address], [SeasonType], [NextPickupdate]
FROM
#Templeafpickup
END
C# class:
string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
using (SqlConnection con1 = new SqlConnection(cs))
{
con1.Open();
foreach (DataRow dr1 in dt.Rows)
{
SqlCommand cmd = new SqlCommand("spInsertLeafPickup", con1);
cmd.CommandType = CommandType.StoredProcedure;
//Insert stored procedure
cmd.Parameters.AddWithValue("#Address", dr1["PCOMBINED"]);
cmd.Parameters.AddWithValue("#SeasonType", dr1["PSSSTREET"]);
cmd.Parameters.AddWithValue("#NextPickupdate", dr1["ZST"]);
cmd.ExecuteNonQuery(); <= Error System.Data.SqlClient.SqlException: 'Procedure or function spInsertLeafPickup has too many arguments specified.'
}
con1.Close();
}
You are sending 3 parameters via c# code but you have declared 1 parameter only in stored procedure. Please check once how stored procedure is defined.
https://www.mssqltips.com/sqlservertutorial/160/sql-server-stored-procedure-tutorial/
create proc spInsertLeafPickup
#Address varchar,
#SeasonType int,
#NextPickupdate date
As
Begin
insert into LeafPickup([ID],[Address],[SeasonType],[NextPickupdate])
Values(NEWID(),#Address,#SeasonType,#NextPickupdate]
End
If the table definition for LeafPickup has the ID as an INT and IDENTITY, something like:
Create Table dbo.LeafPickup
(
ID int IDENTITY(1,1) NOT NULL primary key,
Address nvarchar(50),
SeasonType nvarchar(50),
NextPickupdate nvarchar(10)
)
Then your stored proc would not insert the ID - it will be auto generated for you. So the stored proc would be:
create proc dbo.spInsertLeafPickup
#Address nvarchar(50),
#SeasonType nvarchar(50),
#NextPickupdate nvarchar(10)
As
Begin
insert into LeafPickup(Address, SeasonType, NextPickupdate)
Values(#Address, #SeasonType, #NextPickupdate]
End
I've tried a lot of googling and following some tutorials. I'm using MS SQL and trying to make a simple User table and salt and hashing the password, haven't gotten to the salting part, yet.
The table looks like this:
CREATE TABLE dbo.[User] (
UserID INT IDENTITY(1,1) NOT NULL,
Email NVARCHAR(45) NOT NULL,
Password BINARY(64) NOT NULL,
FirstName NVARCHAR(45) NULL,
LastName NVARCHAR(45) NULL,
CONSTRAINT [PK_User_UserID] PRIMARY KEY CLUSTERED (UserID ASC)
)
And the procedure looks like this:
ALTER PROCEDURE dbo.[uspAddUser]
#pEmail NVARCHAR(45),
#pPassword NVARCHAR(45),
#pFirstName NVARCHAR(45) = NULL,
#pLastName NVARCHAR(45) = NULL,
#responseMessage NVARCHAR(250) OUTPUT
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
INSERT INTO dbo.[User] (Email, Password, FirstName, LastName)
VALUES (#pEmail, HASHBYTES('SHA_512', #pPassword), #pFirstName, #pLastName)
SET #responseMessage = 'Success'
END TRY
BEGIN CATCH
SET #responseMessage = ERROR_MESSAGE()
END CATCH
END
When I try to insert with the procedure it won't work:
DECLARE #responseMessage NVARCHAR(250)
EXEC dbo.[uspAddUser]
#pEmail = N'Admin#email.com',
#pPassword = N'123',
#pFirstName = N'Admin',
#pLastName = N'Administrator',
#responseMessage=#responseMessage OUTPUT
SELECT *
FROM [dbo].[User]
No results are shown. After inserting normally and then retrieving the results it shows the newly created tuple but with the ID auto incremented like other tuples were created. I'm not really sure where the problem is and would be grateful if someone else understood.
EDIT:
SOLVED
Apparently the fault was I used the wrong kind of sha hash. Instead of using SHA_512 I should've used SHA2_512.
HASHBYTES('SHA_512', #pPassword) returns null so that insert query gets aborted. Instead, you can use algorithms SHA2_512 or SHA2_256 or any other encryption algorithm you want.
Check out this link for more info.
Take the following script:
EXEC sp_MSforeachtable #command1 = "DROP TABLE ?";
GO
CREATE TABLE _adminServices (
[ServiceID] INT CHECK ([ServiceID] > 0) NOT NULL IDENTITY,
[ServiceName] NVARCHAR(255) DEFAULT NULL,
[ManagerStaffID] INT CHECK ([ManagerStaffID] > 0) DEFAULT NULL,
[ODMStaffID] INT CHECK ([ODMStaffID] > 0) DEFAULT NULL,
[ReferralInactivityDays] INT DEFAULT NULL,
[TargetSupportHours] INT DEFAULT NULL,
[ShowInLists] SMALLINT NOT NULL DEFAULT '1',
[RecordEntryDate] DATETIME2(0) DEFAULT NULL,
[RecordModDate] DATETIME2(0) DEFAULT NULL,
PRIMARY KEY ([ServiceID])
) ;
CREATE INDEX [ManagerStaffID] ON _adminServices ([ManagerStaffID]);
CREATE INDEX [ODMStaffID] ON _adminServices ([ODMStaffID]);
CREATE INDEX [ShowInLists] ON _adminServices ([ShowInLists]);
GO
EXEC sp_MSforeachtable #command1 = 'IF (
select COUNT(TABLE_NAME)
from INFORMATION_SCHEMA.COLUMNS
where COLUMNPROPERTY(object_id(TABLE_SCHEMA+''.''+TABLE_NAME), COLUMN_NAME, ''IsIdentity'') = 1
AND TABLE_SCHEMA+''.''+TABLE_NAME = ''?''
) = 1
BEGIN
SET IDENTITY_INSERT ? ON;
END;';
GO
INSERT INTO _adminServices (ServiceID, ServiceName, ManagerStaffID, ODMStaffID, ReferralInactivityDays, TargetSupportHours, ShowInLists, RecordEntryDate, RecordModDate) VALUES
(1, 'Service 1', 16, 18, 0, NULL, 1, '2017-07-21 11:59:56', '2017-10-25 09:38:02');
GO
When I execute the aforementioned script in SSMS I get the following error:
Msg 544, Level 16, State 1, Line 36
Cannot insert explicit value for identity column in table '_adminServices' when IDENTITY_INSERT is set to OFF.
Can anyone tell me why?
EDIT: My goal is the following: I have multiple tables and multiple inserts.
For the Inserts I have the scripts. Since I don't want to write the SET IDENTITY_INSERT ON and OFF for every table since I just have the INSERT INTO queries on a file, I want a way to do the following:
Delete all the tables in the DB
Create all the tables (I have the SQL for this)
Set all the required identities to ON
Run the Inserts (I have the SQL for this)
Set all the required identities to OFF
sp_MSforeachtable run on another session in relation to your insert
At any time, only one table in a session can have the IDENTITY_INSERT property set to ON.
General scheme of inserting the original value:
CREATE TABLE
SET IDENTITY INSERT ON
INSERT VALUES
SET IDENTITY INSERT OFF
If you table not have single structure sp_MSforeachtable you will not be suitable
I am trying to add a column (MSSQL 2005) to a table (Employee) with a default constraint of a primary key of another table (Department). Then I am going to make this column a FK to that table. Essentially this will assign new employees to a base department based off the department name if no DepartmentID is provided.
This does not work:
DECLARE #ErrorVar INT
DECLARE #DepartmentID INT
SELECT #DepartmentID = DepartmentID
FROM Department
WHERE RealName = 'RocketScience'
ALTER TABLE [Employee]
ADD [DepartmentID] INT NULL
CONSTRAINT [DepartmentIDOfAssociate] DEFAULT (#DepartmentIDAssociate)
SELECT #ErrorVar = ##Error
IF (#ErrorVar <> 0)
BEGIN
GOTO FATAL_EXIT
END
The Production, Test, and Development databases have grown out of synch and the DepartmentID for the DepartmentName = ‘RocketScience’ may or may not be the same so I don’t want to just say DEFAULT (somenumber). I keep getting “Variables are not allowed in the ALTER TABLE statement” no matter which way I attack the problem.What is the correct way to do this? I have tried nesting the select statement as well which gets “Subqueries are not allowed in this context. Only scalar expressions are allowed.”
In Addition, what would be really great I could populate the column values in one statement instead of doing the {ALTER null} {Update values} {ALTER not null} steps. I read something about the WITH VALUES command but could not get it to work.
Thanks!!!
The accepted answer worked great (Thanks marc_s) but after I thought about it for a while I decided to go another route.Mainly because there has to be a function left on the server which I think ends up being called every time an employee is added.If someone messed with the function later then no one could enter an employee and the reason would not be obvious.
(Even if that is not true then there are still extra functions on the server that do not need to be there)
What I did was assemble the command dynamically in a variable and then call that using the EXECUTE command.
Not only that but since I used the DEFAULT keyword with NOT NULL the table was back populated and I didn't have to run multiple commands to get it done. I found that one out by luck...
DECLARE #ErrorVar INT
DECLARE #DepartmentIDRocketScience INT
DECLARE #ExecuteString NVARCHAR(MAX)
SELECT #DepartmentIDRocketScience = DepartmentID
FROM Department
WHERE RealName = 'RocketScience'
SET #ExecuteString = ''
SET #ExecuteString = #ExecuteString + 'ALTER TABLE [Employee] '
SET #ExecuteString = #ExecuteString + 'ADD [DepartmentID] INT NOT NULL '
SET #ExecuteString = #ExecuteString + 'CONSTRAINT [DF_DepartmentID_RocketScienceDepartmentID] DEFAULT ' +CAST(#DepartmentIDAssociate AS NVARCHAR(MAX))
EXECUTE (#ExecuteString)
SELECT #ErrorVar = ##Error
IF (#ErrorVar <> 0)
BEGIN
GOTO FATAL_EXIT
END
You could wrap the code to find your department ID into a stored function and use that in your DEFAULT constraint statement:
CREATE FUNCTION dbo.GetDepartment()
RETURNS INT
AS
BEGIN
DECLARE #DepartmentID INT
SELECT #DepartmentID = DepartmentID
FROM Department
WHERE RealName = 'RocketScience'
RETURN #DepartmentID
END
And then:
ALTER TABLE [Employee]
ADD [DepartmentID] INT NULL
CONSTRAINT [DepartmentIDOfAssociate] DEFAULT (dbo.GetDepartment())
Does that help?
Marc
If you apply your foreign key constraint after adding the column, you can use any value for the default value when altering the table. Then you can run an update statement with your variable value.
ALTER TABLE Employee
ADD DepartmentID INT NOT NULL
Default -1;
UPDATE Employee
SET Employee.DepartmentID = #DepartmentID
WHERE DepartmentID = -1;
ALTER TABLE Employee
ADD CONSTRAINT fk_employee_to_department FOREIGN KEY (DepartmentID)
REFERENCES Department;