How do I create this stored procedure? - sql-server

I have two tables:
CREATE TABLE [NEWS]
(
[ID] INT IDENTITY(1,1) NOT NULL,
[TITLE] VARCHAR(500) NULL,
[CONTENT] VARCHAR(800) NULL,
[CREATED] DATETIME DEFAULT(GETDATE())
PRIMARY KEY ([ID])
)
CREATE TABLE [LOG]
(
[ID] INT IDENTITY(1,1) NOT NULL,
[ACTION] VARCHAR(500) NULL,
[CREATED] DATETIME DEFAULT(GETDATE())
PRIMARY KEY ([ID])
)
I want to do the following procedure:
I have an input parameter #NewsId.
STEP 1
If NewsId is NULL : I want to save the row into the table (NEWS) .
If newsid is defined then I want to update the row.
STEP 2
I want to do step 1 and then save the record into a table named LOG.
INSERT INTO LOG ("Action") VALUES ("insert or update")
How can I do these two steps using stored procedure?
How can I make one step after the successful completion and go to step 2?

Here is a simple sample to get you going.
create procedure MyProc (#NewsId int) as
Begin
-- you should really pass these in?
declare #title varchar(500) = 'A title'
declare #content varchar(800) = 'A piece of content'
if #NewsId is null
begin
Begin Try
insert into News (Title, Content) values (#title, #content)
-- get the new id just inserted
set #NewsId = SCOPE_IDENTITY()
insert into Log (Action) values ('insert')
End Try
Begin Catch
.... handle error
end catch
end
else
begin
update News set Title = #title, Content = #content
where id = #NewsId
insert into Log (Action) values ('update')
end
end
from CodeProject:
Begin Try
The_Query_for_which_we_need_to_do_the_ Error_Handling
End Try
Begin Catch
If there is some error in the query within the Try block, this flow
will be passed to this Catch block.
End catch

Related

Inserts into a SQL database table that has a varbinary(max) column can be slow

We have the following stored procedure:
ALTER procedure [dbo].[spMergePPObjectBlobProperty]
(
#values dbo.udtPPObjectBlobProperty readonly
)
as
begin
begin try
declare #updatetime datetime
set #updatetime = GetUTCDate()
merge tblPPObjectBlobProperty as t
using (select * from #values) as s
on t.InsertionId = s.InsertionId and t.PropertyMapNameId = s.PropertyMapNameId
when matched then
update set UpdateTime = #updatetime, [Value] = s.BlobValue, UpdateId = s.UpdateId
when not matched then
insert (PropertyMapNameId, Value, UpdateId, UpdateTime, InsertionId)
values(s.PropertyMapNameId, s.BlobValue, s.UpdateId, #updatetime, s.InsertionId)
option (loop join);
end try
begin catch
declare #errormessage varchar(256)
-- Get the error message
select #errormessage = ERROR_MESSAGE()
-- Raise an error and return
raiserror('Error updating entries in the tblPPObjectBlobProperty Table. %s', 16, 1, #errormessage)
end catch
end
Occasionally, this sp can take a few seconds to run with only several rows to insert. Other times it is extremely fast.
We have a number of these sps and this is the only one that appears to be slow sometimes and it is the only one that inserts into a table with a varbinary(max) column.
The type used here is defined as follows:
CREATE TYPE [dbo].[udtPPObjectBlobProperty] AS TABLE(
[InsertionId] [bigint] NOT NULL,
[PropertyMapNameId] [int] NOT NULL,
[BlobValue] [varbinary](max) NULL,
[UpdateId] [bigint] NULL,
PRIMARY KEY CLUSTERED
(
[InsertionId] ASC,
[PropertyMapNameId] ASC
)WITH (IGNORE_DUP_KEY = OFF)
)
GO
Can this stored procedure be optimised in any way?

Trigger if value from one table changes from Null to other value

I have 2 tables House and AlarmInstall...
I need to create a trigger that will update a boolean Atribute "isInstaled" to false in my House table when i insert a uninstallDate (diferent then Null) in my Alarm Install Table.
I am a bit of a noob when it comes to trigger in SQL server. Any help will be appreciated!!!
These are the 2 tables
[dbo].[AlarmInstall](
[AlarmInstallId] [int] IDENTITY(1,1) NOT NULL,
[HouseId] [int] NOT NULL,
[InstallDate] [date] NOT NULL,
[uninstallDate] [date] NOT NULL,
[Model] [nchar](10) NOT NULL,
[dbo].[House](
[HouseId] [int] IDENTITY(1,1) NOT NULL,
[StreetId] [int] NOT NULL,
[DoorNr] [nchar](10) NOT NULL,
[CityId] [int] NOT NULL,
[IsInstalled] [bit] NULL,
Both tables are related thru HouseId that is PK in House Table and FK in AlarmInstall
tried with this trigger but all my Houses get flagged as true!!!
GO
CREATE TRIGGER STATECHANGE
ON dbo.AlarmInstall
AFTER INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
update dbo.House SET IsInstalled=1
From dbo.AlarmInstall a
inner join inserted i on a.HouseId=i.HouseId
and i.InstallDate is not null
END
I think this could help: https://www.mssqltips.com/sqlservertip/4024/sql-server-trigger-after-update-for-a-specific-value/
You could also use a calculated field for isInstalled with the following definition:
CREATE TABLE House(
HouseId int NOT NULL,
[...]
uninstallDate date NULL,
isInstalled bit AS CASE WHEN uninstallDate IS NULL THEN 0 ELSE 1 END
But I don't see the value of creating it like this (or using a trigger), because when you don't have this field, you can always calculate it in a query:
SELECT
HouseId,
[...]
uninstallDate,
CASE WHEN uninstallDate IS NULL THEN 0 ELSE 1 END AS isInstalled
FROM
House
// Added to originat answer
CREATE TRIGGER dbo.StateChange
ON dbo.AlarmInstall
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON
BEGIN
IF UPDATE(uninstallDate)
BEGIN
DECLARE #houseId int
DECLARE #uninstallDate date
SELECT #houseId = HouseId, #uninstallDate = uninstallDate FROM inserted
UPDATE dbo.House
SET IsInstalled = CASE WHEN #uninstallDate IS NULL THEN 0 ELSE 1 END
WHERE HouseId = #houseId
END
END

Exception when updating row with rowversion?

I have a table that looks like this :
CREATE TABLE [dbo].[akut_prioritering]
(
[behandling_id] [int] NOT NULL,
[akutstatus] [int] NOT NULL,
[nasta_dag] [bit] NOT NULL,
[sort_order] [bigint] NOT NULL,
[rowversion] [timestamp] NOT NULL,
CONSTRAINT [XPKakut_prioritering]
PRIMARY KEY CLUSTERED ([behandling_id] ASC)
) ON [PRIMARY]
And then I have this stored procedure that tries to update rows in this table :
ALTER PROCEDURE [dbo].[akutlistaSave]
#behandlingSortOrder dbo.akutlista_sortorder_tabletype READONLY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #behandlingId INT;
DECLARE #sortOrder BIGINT;
DECLARE #rowversion ROWVERSION;
DECLARE sortOrderCursor CURSOR LOCAL SCROLL STATIC FOR
SELECT behandling_id, sort_order FROM #behandlingSortOrder
OPEN sortOrderCursor
BEGIN TRAN
FETCH NEXT FROM sortOrderCursor INTO #behandlingId, #sortOrder, #rowversion
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS(SELECT *
FROM akut_prioritering ap
WHERE ap.behandling_id = #behandlingId
AND ap.rowversion = #rowversion)
BEGIN
UPDATE akut_prioritering
SET sort_order = #sortOrder
WHERE behandling_id = #behandlingId;
END
ELSE
BEGIN
RAISERROR ('Rowversion not correct.', 16, 1);
END
FETCH NEXT FROM sortOrderCursor INTO #behandlingId, #sortOrder, #rowversion
END
CLOSE sortOrderCursor
SELECT
ap.behandling_id, ap.rowversion
FROM
akut_prioritering ap
INNER JOIN
#behandlingSortOrder bso ON ap.behandling_id = bso.behandling_id;
DEALLOCATE sortOrderCursor
END
The inparameter type looks like this :
CREATE TYPE [dbo].[akutlista_sortorder_tabletype] AS TABLE
(
[behandling_id] [int] NULL,
[sort_order] [bigint] NULL,
[rowversion] [timestamp] NULL
)
When running this I get a SqlException :
Cannot insert an explicit value into a timestamp column. Use INSERT with a column list to exclude the timestamp column, or insert a DEFAULT into the timestamp column.
From what I understand the rowversion column should be updated with a new value automatically, there is no reason in my case to set it manual.
You can't set the rowversion value in dbo.akutlista_sortorder_tabletype because it is not updateable: it is auto generated
However, rowversion (a.k.a deprecated timestamp) is simply a (var)binary(8) with some special rules. You can define and set a (var)binary(8) in dbo.akutlista_sortorder_tabletype and compare on that in the UPDATE
From the first link
A nonnullable rowversion column is semantically equivalent to a binary(8) column. A nullable rowversion column is semantically equivalent to a varbinary(8) column.
It looks like you are trying to insert a timestamp value in a custom table type and then passing that to your stored procedure. As your error suggests, you cannot insert explicit timestamp values into timestamp columns.
You will need to find a different way of passing you table values to this stored procedure to work.

Alter scalar function in SQL server 2008 that referring in the computed column of a table

I have created a scalar function in SQL Server 2008 and the same I am referring in a computed column in few of my tables. Now I want to alter the function without dropping the table. But it throws an error:
Cannot ALTER 'dbo.GetStatus' because it is being referenced by object
'Order'.
Is is possible to alter the function? Or do I drop and create all dependable table first and then alter the function?
Here is my function:
CREATE FUNCTION [dbo].[GetStatus]
(
#FromDate datetime,
#ToDate datetime
)
RETURNS tinyint
AS
BEGIN
declare #ret tinyint;
if(#FromDate<=GETDATE() and (#ToDate>=GETDATE() or #ToDate is null))
set #ret= 1
else
set #ret= 0
return #ret
END
And it is referring in a table:
CREATE TABLE [dbo].[Order](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](200) NOT NULL,
[EffectiveFromDate] [datetime] NOT NULL,
[EffectiveToDate] [datetime] NULL,
[Status] AS ([dbo].[GetStatus]([EffectiveFromDate],[EffectiveToDate]))
)
This is by design. You should first drop all defaults/constraints, then alter your function and the add those constraints back. No need to drop the tables.
But you can work around this by the following trick:
add intermediate function that will call your actual function;
alter computed columns to call intermediate function instead of actual.
Example:
CREATE FUNCTION dbo.fnActual ( #p INT )
RETURNS INT
AS
BEGIN
RETURN #p + 1
END
GO
CREATE FUNCTION dbo.fnIntermediate ( #p INT )
RETURNS INT
AS
BEGIN
RETURN dbo.fnActual(#p)
END
GO
CREATE TABLE TestTable(id INT, fn AS dbo.fnIntermediate(id))
GO
Insert some value:
INSERT INTO dbo.TestTable VALUES ( 1 )
SELECT * FROM dbo.TestTable --selects 2
--throws exception
ALTER FUNCTION dbo.fnIntermediate ( #p INT )
RETURNS INT
AS
BEGIN
RETURN dbo.fnActual(#p)
END
GO
--succseeds
ALTER FUNCTION dbo.fnActual ( #p INT )
RETURNS INT
AS
BEGIN
RETURN #p + 2
END
GO
SELECT * FROM dbo.TestTable --selects 3
ALTER TABLE [dbo].[Order]
DROP COLUMN [Status]
GO
ALTER FUNCTION [dbo].[GetStatus] ...
GO
ALTER TABLE [dbo].[Order]
ADD [Status] AS ([dbo].[GetStatus]([EffectiveFromDate],[EffectiveToDate]))
GO
or even
ALTER TABLE [dbo].[Order]
DROP COLUMN [Status]
GO
ALTER TABLE [dbo].[Order]
ADD [Status] AS CAST(CASE WHEN [EffectiveFromDate] <= GETDATE() AND ([EffectiveToDate] >= GETDATE() OR [EffectiveToDate] IS NULL) THEN 1 ELSE 0 END as tinyint)
GO

SQL - Trigger for auto-incrementing number of signed in people

I am having a little bit of trouble with making a trigger in my SQL. I have two tables:
This one
Create table [user]
(
[id_user] Integer Identity(1,1) NOT NULL,
[id_event] Integer NULL,
[name] Nvarchar(15) NOT NULL,
[lastname] Nvarchar(25) NOT NULL,
[email] Nvarchar(50) NOT NULL, UNIQUE ([email]),
[phone] Integer NULL, UNIQUE ([phone]),
[pass] Nvarchar(50) NOT NULL,
[nick] Nvarchar(20) NOT NULL, UNIQUE ([nick]),
Primary Key ([id_user])
)
go
and this one
Create table [event]
(
[id_event] Integer Identity(1,1) NOT NULL,
[id_creator] Integer NOT NULL,
[name] Nvarchar(50) NOT NULL,
[date] Datetime NOT NULL, UNIQUE ([date]),
[city] Nvarchar(50) NOT NULL,
[street] Nvarchar(50) NOT NULL,
[zip] Integer NOT NULL,
[building_number] Integer NOT NULL,
[n_signed_people] Integer Default 0 NOT NULL Constraint [n_signed_people] Check (n_signed_people <= 20),
Primary Key ([id_akce])
)
Now I need a trigger for when I insert a new user with and id_event, or update existing one with one, to take the id_event I inserted, look in the table of events and increment the n_signed_people in a line with a coresponding id_event, until it is 20. When it is 20, it should say that the event is full. I made something like this, it is working when I add a new user with id, but now I need it to stop at 20 and say its full and also I am not sure if it will work, when I'll try to update existing user, by adding an id_event (I assume it was NULL before update).
CREATE TRIGGER TR_userSigning
ON user
AFTER INSERT
AS
BEGIN
DECLARE #idevent int;
IF (SELECT id_event FROM Inserted) IS NOT NULL --if the id_event is not empty
BEGIN
SELECT #idevent=id_event FROM Inserted; --the inserted id_event will be save in a local variable
UPDATE event SET n_signed_people = n_signed_people+1 WHERE #idevent = id_event;
END
END
go
Good evening,
I did notice some issues with your schema. I want to list the fixes I made in order.
1 - Do not use reserved words. Both user and event are reserved.
2 - Name your constraints. You will be glad they are not some random word when you want to drop one.
3 - I added a foreign key to make sure there is integrity in the relationship.
All this work was done in tempdb. Now, lets get to the fun stuff, the trigger.
-- Just playing around
use tempdb;
go
-- attendee table
if object_id('attendees') > 0
drop table attendees
go
create table attendees
(
id int identity (1,1) NOT NULL constraint pk_attendees primary key,
firstname nvarchar(15) NOT NULL,
lastname nvarchar(25) NOT NULL,
email nvarchar(50) NOT NULL constraint uc_email unique,
phone int NULL constraint uc_phone unique,
pass nvarchar(50) NOT NULL,
nick nvarchar(20) NOT NULL constraint uc_nick unique,
event_id int NOT NULL
)
go
-- events table
if object_id('events') > 0
drop table events
go
create table events
(
id int identity (1,1) NOT NULL constraint pk_events primary key,
creator int NOT NULL,
name nvarchar(50) NOT NULL,
planed_date datetime NOT NULL constraint uc_planed_date unique,
street nvarchar(50) NOT NULL,
city nvarchar(50) NOT NULL,
zip nvarchar(9) NOT NULL,
building_num int NOT NULL,
registered int
constraint df_registered default (0) NOT NULL
constraint chk_registered check (registered <= 20),
);
go
-- add some data
insert into events (creator, name, planed_date, street, city, zip, building_num)
values (1, 'new years eve', '20131231 20:00:00', 'Promenade Street', 'Providence', '02908', 99);
-- make sure their is integrity
alter table attendees add constraint [fk_event_id]
foreign key (event_id) references events (id);
I usually add all three options (insert, update, & delete). You coded for insert in the example above. But you did not code for delete.
Also, both the inserted and deleted tables can contain multiple rows. For instance, if two attendees decide to drop out, you want to minus 2 from the table.
-- create the new trigger.
CREATE TRIGGER [dbo].[trg_attendees_cnt] on [dbo].[attendees]
FOR INSERT, UPDATE, DELETE
AS
BEGIN
-- declare local variable
DECLARE #MYMSG VARCHAR(250);
-- nothing to do?
IF (##rowcount = 0) RETURN;
-- do not count rows
SET NOCOUNT ON;
-- deleted data
IF NOT EXISTS (SELECT * FROM inserted)
BEGIN
UPDATE e
SET e.registered = e.registered - c.total
FROM
[dbo].[events] e
INNER JOIN
(SELECT [event_id], count(*) as total
FROM deleted group by [event_id]) c
ON e.id = c.event_id;
RETURN;
END
-- inserted data
ELSE IF NOT EXISTS (SELECT * FROM deleted)
BEGIN
UPDATE e
SET e.registered = e.registered + c.total
FROM
[dbo].[events] e
INNER JOIN
(SELECT [event_id], count(*) as total
FROM inserted group by [event_id]) c
ON e.id = c.event_id;
RETURN;
END;
-- updated data (no counting involved)
END
GO
Like any good programmer, I need to test my work to make sure it is sound.
Lets add 21 new attendees. The check constraint should fire. This only works since the error generated by the UPDATE rollback the insert.
-- Add 21 attendees
declare #var_cnt int = 0;
declare #var_num char(2);
while (#var_cnt < 22)
begin
set #var_num = str(#var_cnt, 2, 0);
insert into attendees (firstname, lastname, email, phone, pass, nick, event_id)
values ('first-' + #var_num,
'last-' + #var_num,
'email-'+ #var_num,
5554400 + (#var_cnt),
'pass-' + #var_num,
'nick-' + #var_num, 1);
set #var_cnt = #var_cnt + 1
end
go
Last but not least, we need to test a DELETE action.
-- Delete the last row
delete from [dbo].[attendees] where id = 20;
go

Resources