I have a SP in MSSQL that will insert and error log to the SQL
ALTER PROCEDURE [dbo].[TestLog]
#PrimaryName nvarchar(255),
#ERROR nvarchar(4000)
AS
BEGIN
insert into Process_Log
(
PrimaryName ,
ERROR
) VALUES
(
#PrimaryName ,
#ERROR
)
END
My sample error message is this:
Violation of PRIMARY KEY constraint 'PK__AP__2EC21549E681BC94'. Cannot insert duplicate key in object 'dbo.Test'. The duplicate key value is (215009).
The statement has been terminated.
as I have the ' in string I have to use double quotes and SET QUOTED_IDENTIFIER OFF.
I am unable to change the error message or edit it as it is from other application.
EDIT:
My Process table structure:
CREATE TABLE [dbo].[Process_Log](
[PrimaryName] [nvarchar](255) NULL,
[ERROR] [nvarchar](max) NULL
)
In your query replace:
insert into Process_Log
(
PrimaryName ,
ERROR
) VALUES
(
#PrimaryName ,
#ERROR
)
with:
insert into Process_Log
(
PrimaryName ,
ERROR
) VALUES
(
#PrimaryName ,
REPLACE(#ERROR, '''', '''''')
)
This is NOT changing the error message, just "escaping" the single quotes so that SQL Server won't get confused.
You will also need to get rid of the QUOTED_IDENTIFIER bit and forget about double quotes.
I'm starting to think that this isn't SQL Server, as that "stuff" you added in your comment below certainly isn't SQL as I know it... and it doesn't do anything if I try running it through SSMS.
Try this:
DECLARE #test TABLE (error VARCHAR(MAX));
--Doesn't work due to single quotes
--INSERT INTO #test SELECT 'Violation of PRIMARY KEY constraint 'PK__AP__2EC21549E681BC94'. Cannot insert duplicate key in object 'dbo.Test'. The duplicate key value is (215009). The statement has been terminated.';
INSERT INTO #test SELECT 'Violation of PRIMARY KEY constraint ''PK__AP__2EC21549E681BC94''. Cannot insert duplicate key in object ''dbo.Test''. The duplicate key value is (215009). The statement has been terminated.';
SELECT * FROM #test;
If the column does not support strings with, then you will need to alter your TestLog table and increase the size of the column which was truncated. You can learn how to alter a table in MS SQL here:
https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql?view=sql-server-2017
You will need to change your parameter from
#ERROR nvarchar(4000)
to
#ERROR nvarchar(MAX)
Also, you will need to take a look at how many characters your error message is and act accordingly.
And also, you will need to make sure your quotes are properly escaped.
I have found a solution for that: I have to execute the SP with SET QUOTED_IDENTIFIER OFF before exec SP, I had SET QUOTED_IDENTIFIER OFF - in my SP - but it has to be executed every time I execute SP. (SP is executed in the software.)
Example:
SET QUOTED_IDENTIFIER OFF
Exec [TestLog]
#PrimaryName = "test"
,#ERROR = "Violation of PRIMARY KEY constraint 'PK__AP__2EC21549E681BC94'. Cannot insert duplicate key in object 'dbo.Test'. The duplicate key value is (215009).
The statement has been terminated."
Then I will avoid a a 128 len probmem.
Related
I have created two tables: workouts.session and workouts.exercises.
I have created a stored procedure to enter workout sessions into the log. However, I could not figure out how to prevent a user from entering invalid or incomplete data. How should I change my code? For instance, I need SQL to throw an error when a user enters invalid exercise id, or when all entries are null.
Tried different things, but everything failed. Believe I must add a transaction statement, and raiseerror statement. Tried different things, but I do not have enough experience with this :(
Original code:
CREATE PROCEDURE Workouts.AddSession
#SessionDate datetime,
#Exercise_ID int,
#Weight_Time int,
#Unit char(7),
#Reps int,
#Set int,
#RestSeconds int,
#Comment char(256)
AS
BEGIN
INSERT INTO workouts.Session (SessionDate, Exercise_ID, Weight_Time,
Unit, Reps, "Set", Comment)
VALUES (#SessionDate, #Exercise_ID, #Weight_Time,
#Unit, #Reps, #Set, #RestSeconds, #Comment)
END
To answer as close as possible to your question, I recommend try-transaction block with raiserror:
CREATE PROCEDURE Workouts.AddSession
#SessionDate datetime,
#Exercise_ID int,
#Weight_Time int,
#Unit char(7),
#Reps int,
#Set int,
#RestSeconds int,
#Comment char(256)
AS
BEGIN
set xact_abort on
begin try
begin transaction
-- start business here
if #Excercise_ID not in (5,7,8 /*...valid ids here....*/)
raiserror('Exercise_ID %i is invalid',16,0,#Exercise_ID)
if #Weight_Time is null and #Unit is null and #Reps is null and......
raiserror('All gym parameters are null',16,0)
INSERT INTO workouts.Session (SessionDate, Exercise_ID, Weight_Time,
Unit, Reps, "Set", Comment)
VALUES (#SessionDate, #Exercise_ID, #Weight_Time,
#Unit, #Reps, #Set, #RestSeconds, #Comment)
-- end business here
commit
end try
begin catch
if ##trancount > 0 rollback
declare #rethrow nvarchar(4000)='Error >> num '+convert(nvarchar(max),error_number())+', line '+convert(nvarchar(max),error_line())+', message: '+error_message()
raiserror(#rethrow, 16, 0)
end catch
However, you have to think if the stuff of your question apply not only to this procedure, but to the table workouts.Session regardless of source. In that case, in order to ensure data integrity, you should use constraints instead.
Making sure no set of columns are all-null can be done with a CHECK CONSTRAINT:
alter table workouts.Session add constraint CHK__WORKOUTS_SESSION__NOT_ALL_NULL CHECK(not (Weight_Time is NULL asnd Unit is NULL and Reps is NULL and ....))
CHK__WORKOUTS_SESSION__NOT_ALL_NULL is just the constraint's name, use whatever you want. Using a prefix like CHK is a good idea
About having valid Exercise_ID, you can do this with a check constraint too. Same syntax as above, but instead CHECK(Exercise_ID in (5,6,7....))
Notice that, unlike the same check in the procedure, in a check constraint you can not use another table for the IN clause. The best way for valid values is to have a master table with Exercise_ID as primary key, have all possible values there, then add a FOREIGN KEY there:
alter table workouts.Session add constraint FK__WORKOUTS_SESSION__EXERCISE_ID__REF__OTHERTABLE__ID
foreign key(Exercise_ID) references othertable(id)
I have an SQL Server 2008 table with a structure similar to the following:
ID int PRIMARY KEY IDENTITY(1,1)
Name nvarchar(100)
LongText ntext
What I am trying to achieve is simple. Before inserting data inside this table, I want to encrypt the LongText using AES_192 algorithm. I am using the following SP to encrypt data:
create proc sp_Encrypt_LongText
#rawText ntext = null,
#encryptedText nvarchar(max) output
as
begin
OPEN SYMMETRIC KEY Encryption_Symmetric_Key
DECRYPTION BY CERTIFICATE Encryption_Certificate WITH PASSWORD = 'mypassword'
set #encryptedText = ENCRYPTBYKEY(KEY_GUID(N'Encryption_Symmetric_Key'), cast(#rawText as nvarchar(max)))
CLOSE SYMMETRIC KEY Encryption_Symmetric_Key
end
and for decryption, I have created the following SP:
alter proc sp_Decrypt_LongText
#encryptedText ntext = null,
#decryptedText varchar(max) output
as
begin
OPEN SYMMETRIC KEY Encryption_Symmetric_Key
DECRYPTION BY CERTIFICATE Encryption_Certificate WITH PASSWORD = 'mypassword'
set #decryptedText = cast(DECRYPTBYKEY(cast(#encryptedText as nvarchar(max))) as varchar(max))
CLOSE SYMMETRIC KEY Encryption_Symmetric_Key
end
The procedures seem to work fine when I use the exec command. So far, so good. The problem is that the data is inserted and fetched inside the table using stored procedures; one each for insert and select. What I have as of now is as follows:
For insertion:
create proc sp_InsertData
#Name nvarchar(100),
#LongText ntext = NULL
as
INSERT INTO TABLE tbl VALUES (#Name, #LongText)
For fetching
create proc sp_FindDataById
#Id int
as
SELECT ID, Name, LongText from tbl where ID=#Id
My question is, how do I plug the encryption/decryption procedures inside these SPs to make them work?. I have looked into several articles for achieving this, but I keep running into one issue or another; mostly because of the ntext datatype. Or maybe I might be going on the wrong path here. Any kind of help is appreciated.
PS: Due to some reasons specified by the DBAs, I can't change the data type of LongText from ntext to nvarchar or varchar. Hence, all the casting is applied in the procedures.
Okay, so I managed to convince the DBAs to have the data transferred to a new column with varbinary(max) data type. Then I transferred the values into this new column after encrypting them, and then dropped the older column and renamed the new one to the old one's name. Took some work, but everything is running smoothly now. I managed to create a stored procedure and two functions to further modularize the scripts.
For opening the symmetric key
CREATE PROCEDURE sp_OpenEncryptionKeys
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
OPEN SYMMETRIC KEY Encryption_Symmetric_Key
DECRYPTION BY CERTIFICATE Encryption_Certificate
END TRY
BEGIN CATCH
--catch
END CATCH
END
For encrypting:
CREATE FUNCTION Encrypt
(
#ValueToEncrypt varchar(max)
)
RETURNS varbinary(max)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result varbinary(max)
SET #Result = EncryptByKey(Key_GUID('My_Encryption_Symmetric_Key'), #ValueToEncrypt)
-- Return the result of the function
RETURN #Result
END
For decrypting:
CREATE FUNCTION Decrypt
(
#ValueToDecrypt varbinary(max)
)
RETURNS varchar(max)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result varchar(max)
SET #Result = DecryptByKey(#ValueToDecrypt)
-- Return the result of the function
RETURN #Result
END
For inserting
exec sp_OpenEncryptionKeys
INSERT INTO tbl VALUES ('Name', Encrypt('some text here'))
For fetching
exec sp_OpenEncryptionKeys
SELECT ID, Decrypt(LongText) from tbl
Hope this helps someone.
I am adding check constraint to column like below. I am getting different results with both ways of using it. Can anybody explain what makes the difference?
ALTER TABLE [dbo].[state] WITH CHECK ADD CONSTRAINT [chk_State_partition_42] CHECK ([Code]>='42' AND [Code]<'43')
ALTER TABLE dbo.State WITH CHECK ADD CONSTRAINT [chk_state_partition_42] CHECK ([code]>=42 AND [code]<43)
--Actually above second query is result of following dynamic sql code
DECLARE #code varchar(2) = '42'
, #SQLStr varchar(max)
--SET #SQLStr = 'ALTER TABLE dbo.state WITH CHECK ADD CONSTRAINT [chk_state_partition_'+#APIState+'] CHECK ([code]>='+#APIState+' AND [code]<'+CAST(CAST(#code as int)+1 as varchar(2))+')'
Select #SQLStr
SQL Server is able to cast some types implicitly Find details here
This depends on the datatype of your column Code. Is it numeric (e.g. INT), the engine will cast the 42 to a number implicitly. If not, the comparison will be done on string level.
You must be aware, that alphanumerical comparison would - probably - not work as expected. 9 is higher than 42 in this case...
UPDATE
You just added, that the data type of your code column is varchar... If you expect a comparison of numbers, the code with qoutes >'42' is dangerously wrong!
So I'm running the SqlRestore utility created by AppHarbor, which can be found here:
https://github.com/appharbor/AppHarbor-SqlServerBulkCopy
The first step of the utility is to wipe out the data from the destination database, using these commands:
// http://stackoverflow.com/questions/155246/how-do-you-truncate-all-tables-in-a-database-using-tsql/156813#156813
StringBuilder commandBuilder = new StringBuilder();
commandBuilder.Append(
#"
-- disable all constraints
EXEC sp_msforeachtable ""ALTER TABLE ? NOCHECK CONSTRAINT all""
-- delete data in all tables
EXEC sp_msforeachtable ""DELETE FROM ?""
-- enable all constraints
exec sp_msforeachtable ""ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all""
");
When I run this (both via the program, and also manually via Management Studio), the delete statement is throwing an error saying invalid column name schoolid
At first, I had no idea which table was throwing the error, so I re-wrote the delete step to a cursor, shown here:
declare tableCursor cursor local forward_only for
select [name]
from sys.objects
where [type] = 'U'
declare #tableName varchar(50)
open tableCursor
fetch next from tableCursor into #tableName
while (##FETCH_STATUS = 0) begin
print 'trying to delete from ' + #tableName
exec('delete from ' + #tableName)
print 'deleted from ' + #tableName
fetch next from tableCursor into #tableName
end
close tableCursor
deallocate tableCursor
Executing the script this way tells me that it was "trying to delete from table X", but when I look at the table definition of table X, that column DOES NOT EXIST (and never has)!
So next, I decide to just manually delete the table, then use VS Sql Server Schema comparison to re-generate the table to my destination database, since it was possibily corrupt somehow.
Once it's re-generated, I re-run that delete script, AND IT STILL THROWS THE ERROR!
Here's the table definition:
CREATE TABLE [dbo].[TourneyPoolMatchResult] (
[TourneyPoolMatchResultId] INT IDENTITY (1, 1) NOT NULL,
[TournamentTypeId] INT NOT NULL,
[WinningWrestlerId] INT NOT NULL,
[LosingWrestlerId] INT NOT NULL,
[WinType] VARCHAR (5) NOT NULL,
[Score] VARCHAR (20) NULL,
[BonusPoints] DECIMAL (2, 1) NOT NULL,
[AdvancementPoints] DECIMAL (2, 1) NOT NULL,
[PlacementPoints] INT NOT NULL,
[LosingWrestlerPlacementPoints] INT NOT NULL,
PRIMARY KEY CLUSTERED ([TourneyPoolMatchResultId] ASC)
);
GO
CREATE trigger [dbo].[trg_TourneyPoolMatchResult_Change]
on [dbo].[TourneyPoolMatchResult]
after insert, update, delete
as
exec [UpdateTeamPoints];
exec [UpdatePointsForSubmittal];
As you can see, nowhere in that table definition does it have anything about SchoolId.
What in the world is going on here?
You use the delete statement and you have some triggers on delete. Maybe your trigger function (e.g. UpdateTeamPoints or UpdatePointsForSubmittal) reference a column named schoolid? Please search your sql code, is there any schoolid anywhere?
If your tables haven't Foreign Key constraints instead of
DELETE FROM table_name;
use:
TRUNCATE TABLE table_name;
From TRUNCATE:
Removes all rows from a table or specified partitions of a table,
without logging the individual row deletions. TRUNCATE TABLE is
similar to the DELETE statement with no WHERE clause; however,
TRUNCATE TABLE is faster and uses fewer system and transaction log
resources.
and
TRUNCATE TABLE cannot activate a trigger because the operation does
not log individual row deletions. For more information, see CREATE
TRIGGER (Transact-SQL).
Note: the highest linked question does not solve the problem for system stored procedures, but it's close. With help of the commenters, I came to a working answer.
Trying to use statements such as the following for sp_spaceused, throws an error
SELECT * INTO #tblOutput exec sp_spaceused 'Account'
SELECT * FROM #tblOutput
The errors:
Must specify table to select from.
and:
An object or column name is missing or empty. For SELECT INTO statements, verify each column has a name. For other statements, look for empty alias names. Aliases defined as "" or [] are not allowed. Change the alias to a valid name.
When I fully declare a table variable, it works as expected, so it seems to me that the stored procedure does return an actual table.
CREATE TABLE #tblOutput (
name NVARCHAR(128) NOT NULL,
rows CHAR(11) NOT NULL,
reserved VARCHAR(18) NOT NULL,
data VARCHAR(18) NOT NULL,
index_size VARCHAR(18) NOT NULL,
unused VARCHAR(18) NOT NULL)
INSERT INTO #tblOutput exec sp_spaceused 'Response'
SELECT * FROM #tblOutput
Why is it not possible to use a temp table or table variable with the result set of EXECUTE sp_xxx? Or: does a more compact expression exist than having to predefine the full table each time?
(incidentally, and off-topic, Googling for the exact term SELECT * INTO #tmp exec sp_spaceused at the time of writing, returned exactly one result)
TL;DR: use SET FMTONLY OFF with OPENQUERY, details below.
It appears that the link provided by Daniel E. is only part of the solution. For instance, if you try:
-- no need to use sp_addlinkedserver
-- must fully specify sp_, because default db is master
SELECT * FROM OPENQUERY(
[SERVERNAME\SQL2008],
'exec somedb.dbo.sp_spaceused ''Account''')
you will receive the following error:
The OLE DB provider "SQLNCLI10" for linked server "LOCALSERVER\SQL2008" supplied inconsistent metadata for a column. The name was changed at execution time.
I found the solution through this post, and then a blog-post on OPENQUERY, which in turn told me that until SQL2008, you need to use SET FMTONLY OFF. The final solution, which is essentially surprisingly simple (and easier to accomplish since there is no need to specify a loopback linked server), is this:
SELECT * FROM OPENQUERY(
[SERVERNAME\SQL2008],
'SET FMTONLY OFF
EXEC somedb.dbo.sp_spaceused ''Account''')
In addition, if you haven't set DATA-ACCESS, you may get the following error:
Server 'SERVERNAME\SQL2008' is not configured for DATA ACCESS.
This can be remedied by running the following command:
EXEC sp_serveroption 'SERVERNAME\SQL2008', 'DATA ACCESS', TRUE
We cannot SELECT from a stored procedure thats why SELECT * INTO ..Exec sp_ will not work.
To get the result set returned from a store procedure we can INSERT INTO a table.
SELECT INTO statement creates a table on fly and inserts data from the source table/View/Function. The only condition is source table should exist and you should be able to Select from it.
Sql Server doesn't allow you to use SELECT from sp_ therefore you can only use the INSERT INTO statement when executing a stored procedure this means at run time you can add the returned result set into a table and Select from that table at later stage.
INSERT INTO statement requires the destination table name, An existing table. Therefore whether you use a Temp Table, Table variable or Sql server persistent table you will need to create the table first and only they you can use the syntax
INSERT INTO #TempTable
EXECUTE sp_Proc
Using [YOUR DATABASE NAME]
CREATE TABLE [YOURTABLENAME]
(Database_Name Varchar(128),
DataBase_Size VarChar(128),
unallocated_Space Varchar(128),
reserved Varchar(128),
data Varchar(128),
index_size Varchar(128),
unused Varchar(128)
);
INSERT INTO dbo.[YOUR TABLE NAME]
(
Database_Name,
DataBase_Size,
unallocated_Space,
reserved,
data,
index_size,
unused
)
EXEC sp_spaceused #oneresultset = 1
--To get it to return it all as one data set add the nonresultset=1 at the end and viola good to go for writing to a table. :)