How to solve this stored procedure issue? - sql-server

I created this stored procedure in SQL Server 2017, and apparently it worked at first but suddenly it just stopped working.
This is supposed to validate whether a car has already entered in a parking lot or not. If it hasn't it creates the row and in the vehicle table and creates the row in vehicle payment table. If this is not the case it just creates the row in in the vehicle payment table and updates the state to (meaning it's currently in).
--table names, fields and variables are in spanish
CREATE TABLE Estacionamiento.Vehiculo
(
id INT IDENTITY (1, 1) NOT NULL
CONSTRAINT PK_Vehiculo_Estacionamiento_id
PRIMARY KEY CLUSTERED (id),
tipoVehiculo INT NOT NULL,
placa NVARCHAR(8) NOT NULL UNIQUE,
CONSTRAINT CHK_Formato_Placa_Vehiculo
CHECK (placa LIKE '[A-Z][A-Z][A-Z]-[0-9][0-9][0-9][0-9]'),
estado BIT NOT NULL
)
GO
--Crear la tabla PagoVehiculo
CREATE TABLE Estacionamiento.PagoVehiculo
(
id INT IDENTITY(1, 1) NOT NULL
CONSTRAINT PK_Vehiculo_PagoVehiculo_id
PRIMARY KEY CLUSTERED (id),
vehiculo INT NOT NULL,
fechaHoraEntrada DATETIME NOT NULL,
fechaHoraSalida DATETIME NOT NULL,
total DECIMAL NOT NULL
)
GO
--The problem is here
CREATE PROCEDURE SP_InsercionVehiculosEntradasSalidas
(#placa NVARCHAR(8),
#tipoVehiculo NVARCHAR(15))
AS
BEGIN TRY
DECLARE #placaVehiculo INT
DECLARE #horaEntrada DATETIME
DECLARE #horaSalida DATETIME
IF NOT EXISTS(SELECT * FROM Estacionamiento.Vehiculo WHERE placa = #placa AND tipoVehiculo = #tipoVehiculo)
BEGIN
INSERT INTO Estacionamiento.Vehiculo (placa, tipoVehiculo, estado)
VALUES (#placa, #tipoVehiculo, 1)
SET #placaVehiculo = (SELECT id FROM Estacionamiento.Vehiculo WHERE placa = #placa)
INSERT INTO Estacionamiento.PagoVehiculo (vehiculo, fechaHoraEntrada, fechaHoraSalida, total)
VALUES (#placaVehiculo, GETDATE(), GETDATE(), 0)
END
ELSE IF EXISTS(SELECT * FROM Estacionamiento.Vehiculo WHERE placa = #placa)
BEGIN
SET #placaVehiculo = (SELECT id FROM Estacionamiento.Vehiculo WHERE placa = #placa)
PRINT(#placaVehiculo)
IF (SELECT estado FROM Estacionamiento.Vehiculo WHERE placa = #placa) = 0
BEGIN
INSERT INTO Estacionamiento.PagoVehiculo (vehiculo, fechaHoraEntrada, fechaHoraSalida, total)
VALUES (#placaVehiculo, GETDATE(), GETDATE(), 0)
UPDATE Estacionamiento.Vehiculo
SET estado = 1
END
ELSE IF (SELECT estado FROM Estacionamiento.Vehiculo WHERE placa = #placa) = 1
BEGIN
UPDATE Estacionamiento.PagoVehiculo
SET fechaHoraSalida = GETDATE()
WHERE vehiculo = #placaVehiculo
AND id = (SELECT MAX(id) FROM Estacionamiento.PagoVehiculo)
SET #horaEntrada = (SELECT MAX(fechaHoraEntrada)
FROM Estacionamiento.PagoVehiculo
WHERE vehiculo = #placaVehiculo)
SET #horaSalida = (SELECT MAX(fechaHoraSalida)
FROM Estacionamiento.PagoVehiculo
WHERE vehiculo = #placaVehiculo)
UPDATE Estacionamiento.Vehiculo
SET estado = 0
UPDATE Estacionamiento.PagoVehiculo
SET total = dbo.Fctn_CalculoPagoVehiculo(#horaEntrada, #horaSalida, #tipoVehiculo)
WHERE vehiculo = #placaVehiculo
AND id = (SELECT MAX(id)
FROM Estacionamiento.PagoVehiculo)
END
END
END TRY
BEGIN CATCH
DECLARE #error INT
SET #error = ##ERROR
RETURN #error
END CATCH
GO
I'm expecting to validate and insert and update when needed.

A few things that I am noticing as I read your proc:
In some places you check for a unique car in the vehiculo table by doing :
WHERE placa = #placa AND tipoVehiculo = #tipoVehiculo
and other places by doing just
WHERE placa = #placa
Be consistent in your code depending on which gives you the unique car
When the car is inserted into PagoVehiculo, you are populating both the entry and exit dates with the current date. While not technically wrong, I would expect the exit date to be null until the car exits. That will help with clarity of the data.
There are many places where you check select *, you can instead select 1, which will perform better
There is no ELSE statement to catch unexpected situations and report and issue.
At one point you are trying to get the id of the pagoVehiculo table for a particular car by doing:
id = (SELECT MAX(id) FROM Estacionamiento.PagoVehiculo)
There is no guaranty that will retrieve the correct car (specially since you could have multiple clients writting to the table at the same time. Add an additional clause to ensure you are pulling data for the right car. something like:
id = (SELECT MAX(id) FROM Estacionamiento.PagoVehiculo where vehiculo = #placaVehiculo)
In terms of what is going on with the proc, My guess is that is either failing silently or it is not falling into the IF or the ELSE IF and thus just skipping the whole thing. I would debug this as follows (after reviewing the comments :
Add and ELSE statement in the main If statement to catch unexpected situations
Add a print statement in the IF section , ELSE If and ELSE sections (something like print ('in IF statement') so that you know which branch is executing (or you can just debug it , if you are familiar with SQL Proc debugging)
Remove the try catch section and see if there is an exception that is being catched and not rethrown for some reason.
Oh and I just saw, tipoVehiculo is defined as INT in the table , but you are passing a NVARCHAR(15) in the proc, which is prob why you have the problem.

Related

Create trigger that compares dates in SQL Server

I'm very new to triggers and I can't seem to wrap my head around them. Let's assume I have two tables here:
CREATE TABLE Project
(
id INT NOT NULL IDENTITY(1, 1),
startDate DATETIME,
endDate DATETIME
);
CREATE TABLE Work
(
date DATETIME,
projectId INT
);
I insert some data:
INSERT INTO Project VALUES ('2017-04-18', '2017-05-01'); /*id = 1*/
INSERT INTO Work VALUES ('2017-04-17', 1);
Assuming there's only 1 project with id = 1, this should go well. However, it doesn't make much sense that my work starts the day before the project starts (not in this case). How would I create a trigger that basically says date cannot be < startDate OR > endDate?
Something like this should work:
CREATE TRIGGER t_CheckInterval ON dbo.Work
AFTER UPDATE, INSERT
AS
IF NOT EXISTS (
--if no records are returned then work date lies outside the project
-- (start, end) interval
SELECT 1
FROM inserted AS i
JOIN Project AS p
ON p.Id = i.projectId AND i.[date] BETWEEN p.startDate AND p.endDate
)
BEGIN
RAISERROR ('Error: Your error message here.', 16, 1)
ROLLBACK TRANSACTION
END
GO
In your case instead of using trigger for these kind of checkings, I suggest to use a CHECK CONSTRAINT, something like this:
CREATE FUNCTION dbo.ufn_CheckWorkDate
(
#WorkDate DateTime,
#ProjectID INT
)
RETURNS BIT
AS
BEGIN
DECLARE #Result BIT
IF EXISTS (SELECT * FROM Project WHERE id = #ProjectID AND #WorkDate BETWEEN startdate AND endDate)
SET #Result = 1
ELSE
SET #Result = 0
RETURN #Result
END
GO
ALTER TABLE Work
WITH CHECK ADD CONSTRAINT CK_CheckWorkDate
CHECK (dbo.ufn_CheckWorkDate(date, projectid) = 1)

Resume a WHILE loop from where it stopped SQL

I have a while loop query that I only want to run until 11PM everyday - I'm aware this can be achieved with a WAITFOR statement, and then just END the query.
However, on the following day, once I re-run my query, I want it to continue from where it stopped on the last run. So I'm thinking of creating a log table that will contain the last processed ID.
How can I achieve this?
DECLARE #MAX_Value BIGINT = ( SELECT MAX(ID) FROM dbo.TableA )
DECLARE #MIN_Value BIGINT = ( SELECT MIN(ID) FROM dbo.TableA )
WHILE (#MIN_Value < #MAX_Value )
BEGIN
INSERT INTO dbo.MyResults
/* Do some processing*/
….
….
….
SET #MIN_Value = MIN_Value + 1
/*I only want the above processing to run until 11PM*/
/* Once it’s 11PM, I want to save the last used #MIN_Value
into my LoggingTable (dbo.Logging) and kill the above processing.*/
/* Once I re-run the query I want my processing to restart from the
above #MIN_Value which is recorded in dbo.Logging */
END
Disclaimer: I do not recommend using WHILE loops in SQL Server but considering the comment that you want a solution in SQL, here you go:
-- First of all, I strongly recommend using a different way of assigning variable values to avoid scenarios with the variable being NULL when the table is empty, also you can do it in a single select.
-- Also, if something started running at 10:59:59 it will let the processing for the value finish and will not simply rollback at 11.
CREATE TABLE dbo.ProcessingValueLog (
LogEntryId BIGINT IDENTITY(1,1) NOT NULL,
LastUsedValue BIGINT NOT NULL,
LastUsedDateTime DATETIME NOT NULL DEFAULT(GETDATE()),
CompletedProcessing BIT NOT NULL DEFAULT(0)
)
DECLARE #MAX_Value BIGINT = 0;
DECLARE #MIN_Value BIGINT = 0;
SELECT
#MIN_Value = MIN(ID),
#MAX_Value = MAX(ID)
FROM
dbo.TableA
SELECT TOP 1
#MIN_Value = LastUsedValue
FROM
dbo.ProcessingValueLog
WHERE
CompletedProcessing = 1
ORDER BY
LastUsedDateTime DESC
DECLARE #CurrentHour TINYINT = HOUR(GETDATE());
DECLARE #LogEntryID BIGINT;
WHILE (#MIN_Value < #MAX_Value AND #CurrentHour < 23)
BEGIN
INSERT INTO dbo.ProcessingValueLog (LastUsedValue)
VALUE(#MIN_Value)
SELECT #LogEntryID = SCOPE_IDENTITY()
// Do some processing...
SET #MIN_Value = #MIN_Value + 1;
UPDATE dbo.ProcessingValueLog
SET CompletedProcessing = 1
WHERE LogEntryId = #LogEntryID
SET #CurrentHour = HOUR(GETDATE())
END

Reset the ID counter on a stored procedure in SQL Server

I'm developing a system that manages work orders for vehicles. The ID of work orders is composed as follows: OT-001-16.
Where OT- is a string, 001 is the counter, followed by - character and finally the number 16 is the current year.
Example:
If the current year is 2018, the ID should be OT-001-18.
The problem is when the year changes, the counter must restart from 001. I have a stored procedure to do that, but i think i'm doing a lot more work.
This is my stored procedure code:
CREATE PROCEDURE ot (#name varchar(100), #area varchar(100), #idate varchar(100), #edate varchar(100))
AS
BEGIN
SET NOCOUNT ON;
DECLARE #aux varchar(100);
DECLARE #aux2 varchar(100);
DECLARE #aux3 int;
DECLARE #aux4 varchar(100);
SELECT #aux = id_workorder FROM idaux;
IF (#aux IS NULL)
SET #aux = CONCAT('OT-000-', RIGHT(YEAR(GETDATE()), 2));
SET
#aux2 = SUBSTRING(
#aux, CHARINDEX('-', #aux) + 1,
LEN(#aux) - CHARINDEX('-', #aux) - CHARINDEX('-', REVERSE(#aux)));
SET #aux3 = CAST(#aux2 AS int) + 1;
SET #aux4 = #aux3;
IF #aux3 < 1000
IF #aux3 >= 10
SET #aux4 = CONCAT('0', #aux4);
ELSE
SET #aux4 = CONCAT('00', #aux4);
ELSE
SET #aux4 = #aux4;
DECLARE #f varchar(100);
DECLARE #y varchar(50);
SELECT TOP 1
#y = id_workorder
FROM workorder
WHERE (RIGHT(id_workorder, 2)) = (RIGHT(YEAR(GETDATE()), 2))
ORDER BY id_workorder DESC;
DECLARE #yy varchar(10);
SET
#yy = RIGHT(#y, 2);
DECLARE #yn varchar(10);
SET
#yn = RIGHT(YEAR(GETDATE()), 2);
BEGIN
IF #yn = #yy
BEGIN
DECLARE #laux varchar(20)
SET #f = 'OT-' + #aux4 + '-' + RIGHT(YEAR(GETDATE()), 2);
INSERT INTO workorder (id_workorder, name, area, initial_date, end_date)
VALUES (#f, #name, #area, #idate, #edate);
SELECT
#laux = id_workorder
FROM idaux
IF (#laux IS NULL)
BEGIN
INSERT idaux (id_workorder) VALUES (#f);
END
ELSE
BEGIN
UPDATE idaux SET id_workorder = #f;
END
END
ELSE
BEGIN
SET #f = CONCAT('OT-001-', (RIGHT(YEAR(GETDATE()), 2)));
INSERT INTO workorder (id_workorder, name, area, initial_date, end_date)
VALUES (#f, #name, #area, #idate, #edate);
SELECT #laux = id_workorder FROM idaux;
IF (#laux IS NULL)
BEGIN
INSERT idaux (id_workorder) VALUES (#f);
END
ELSE
BEGIN
UPDATE idaux SET id_workorder = #f;
END
END
END
END
Basically, i created an auxiliar table to save the last Work Order ID, then from this table called idaux i take the ID and i compared to new possible ID by a string handling. Then if the year of the last ID saved are equal to the current year the counter increases, but if not the counter is restarted to 001, the new ID is updated in the auxiliar table and the Work Order is inserted to the table workorder.
My stored procedure works, but i need your help to optimize the stored procedure. Any question post on comments.
Here is how I'd setup the stored procedure and the underlying table to keep track of your work orders:
create database tmpWorkOrders;
go
use tmpWorkOrders;
go
/*
The work order ID (as you wish to see it) and the
work order counter (per year) will be separated into
two separate columns (with a unique constraint).
The work order ID (you wish to see) is automatically
generated for you and stored "persisted":
http://stackoverflow.com/questions/916068/sql-server-2005-computed-column-is-persisted
*/
create table WorkOrders
(
SurrogateKey int identity(1, 1) primary key not null,
WorkOrderYear int not null,
WorkOrderCounter int not null,
WorkOrderID as
N'OT-' + right(N'000' + cast(WorkOrderCounter as nvarchar), 3)
+ N'-' + right(cast(WorkOrderYear as nvarchar), 2)persisted,
WorkOrderDescription nvarchar(200),
constraint UQ_WorkOrderIDs
unique nonclustered (WorkOrderYear, WorkOrderCounter)
);
go
create procedure newWorkOrder
(#WorkOrderYear int = null,
#WorkOderCounter int = null,
#WorkOrderDescription nvarchar(200) = null
)
as
begin
/*
If no year is given the the current year is assumed
*/
if #WorkOrderYear is null
begin
set #WorkOrderYear = year(current_timestamp);
end;
/*
If no work order counter (for the above year) is given
then the next available one will be given
*/
if #WorkOderCounter is null
begin
set #WorkOderCounter
= isnull(
(
select max(WorkOrderCounter)
from WorkOrders
where WorkOrderYear = #WorkOrderYear
) + 1,
0
);
end;
else
/*
If a work order counter has been passed to the
stored procedure then it must be validated first
*/
begin
/*
Does the work order counter (for the given year)
already exist?
*/
if exists
(
select 1
from dbo.WorkOrders as wo
where wo.WorkOrderYear = #WorkOrderYear
and wo.WorkOrderCounter = #WorkOderCounter
)
begin
/*
If the given work order counter already exists
then the next available one should be assigned.
*/
while exists
(
select 1
from dbo.WorkOrders as wo
where wo.WorkOrderYear = #WorkOrderYear
and wo.WorkOrderCounter = #WorkOderCounter
)
begin
set #WorkOderCounter = #WorkOderCounter + 1;
end;
end;
end;
/*
The actual insert of the new work order ID
*/
insert into dbo.WorkOrders
(
WorkOrderYear,
WorkOrderCounter,
WorkOrderDescription
)
values
(#WorkOrderYear,
#WorkOderCounter,
#WorkOrderDescription
);
end;
go
/*
Some test runs with the new table and stored procedure...
*/
exec dbo.newWorkOrder #WorkOrderYear = null,
#WorkOderCounter = null,
#WorkOrderDescription = null;
exec dbo.newWorkOrder #WorkOrderYear = null,
#WorkOderCounter = 3,
#WorkOrderDescription = null;
exec dbo.newWorkOrder #WorkOrderYear = null,
#WorkOderCounter = 0,
#WorkOrderDescription = null;
exec dbo.newWorkOrder #WorkOrderYear = null,
#WorkOderCounter = 0,
#WorkOrderDescription = null;
exec dbo.newWorkOrder #WorkOrderYear = null,
#WorkOderCounter = 0,
#WorkOrderDescription = null;
/*
...reviewing the result of the above.
*/
select *
from dbo.WorkOrders as wo;
Note, that the "next available" work order counter is once given (1) as the maximum + 1 and once (2) increased until it does not violate the unique key constraint on the table anymore. Like this you have two different possibilities to go about it.
There are a number of observations based on your code that you could alter to optimize and guarantee your results.
I am not aware of your Table Structure, but it seems you are using natural keys for your IDs.
Instead, use a surrogate key, such as INT/BIGINT to not only add efficiency in your table joins (no strings required), but potentially add another layer of security in your current design.
Alternatively, normalize the column into the flags they are. For example: OT-001-05 has three elements: OT is a type of work order, 001 is the ID, and 15 is the year. Presently, OT determines the ID which determines the year.
SELECT #aux = id_workorder FROM idaux;
idaux was not described. Is it a single value? If tabular, guarantee the result or it might break in the future.
Even if you add MAX(id_workorder), your result will not work as you think. Since this is a VARCHAR, the greatest value of the leftmost character not tied will return.
#aux, CHARINDEX('-', #aux) + 1,
LEN(#aux) - CHARINDEX('-', #aux) - CHARINDEX('-', REVERSE(#aux)));
This is fine, but overall you could make the code clearer and easier to debug by splitting all three of those elements into their own variable. Your still using your method, but simplified a little (personally, CHARINDEX can be a pain).
SET #aux = #Type -- 'OT'
SET #aux2 = #ID -- The result of your previous code
SET #aux3 = #Year -- your YY from GETDATE()
-- then join
SET #Work_Order = CONCAT(#aux, '-', #aux2, '-', #aux3)
Update:
Currently, your column in idaux has the ID in the MIDDLE of your column. This will produce disastrous results since any comparison of IDs will happen in the middle of the column. This means at best you might get away with PATINDEX but are still performing a table scan on the table. No index (save for FULLTEXT) will be utilized much less optimized.
I should add, if you put the ID element into its own column, you might find using BINARY collations on the column will improve its performance. Note I have not tested attempting a BINARY collation on a mixed column

SQL Server stored procedure for Insert runs, but does not insert values into the table

I've written this stored procedure to add line items into a table called InvoiceLineItems. The database does have a few test entries, and this procedure is based off of other insert stored procedures that are working.
For some reason, the query will execute successfully, but the values are not actually inserted into the table, and I can't figure out why, especially since the other insert procedures have worked.
Code that does successfully insert values into a table:
IF OBJECT_ID('sp_InsertInvoices') IS NOT NULL
DROP PROC sp_InsertInvoices;
GO
CREATE PROC sp_InsertInvoices
#CustomerID INT = NULL,
#TotalDue MONEY = NULL,
#Paid CHAR(3) = NULL
AS
BEGIN
IF #CustomerID IS NULL
THROW 50001, 'CustomerID cannot be null.', 1;
IF #TotalDue IS NULL
THROW 50001, 'TotalDue cannot be Null.', 1;
IF #TotalDue < 0
THROW 50001, 'TotalDue cannot be negative.', 1;
IF #Paid IS NULL
THROW 50001, 'Must specify if the balance is paid or not.', 1;
ELSE --All data is validated
INSERT INTO Invoices (CustomerID, TotalDue, Paid)
VALUES (#CustomerID, #TotalDue, #Paid)
END
GO
The code that doesn't insert values:
IF OBJECT_ID('sp_InsertInvoiceLineitems') IS NOT NULL
DROP PROC sp_InsertInvoiceLineitems;
GO
CREATE PROC sp_InsertInvoiceLineitems
#InvoiceID INT = NULL,
#ProductID INT = NULL,
#PQty TINYINT = NULL,
#ListPrice MONEY = NULL,
#ServiceID INT = NULL,
#ServiceCost MONEY = NULL,
#SQty TINYINT = NULL,
#TotalAmount MONEY = NULL
AS
BEGIN
IF #InvoiceID IS NULL
THROW 50001, 'Must enter an InvoiceID.', 1;
IF #ProductID IS NOT NULL AND #ProductID NOT IN (SELECT ProductID FROM Products)
THROW 50001, 'Please enter a valid ProductID.', 1;
IF #PQty IS NOT NULL AND #ProductID IS NOT NULL AND #PQty <= 0
THROW 50001, 'PQty must be greater than zero.', 1;
IF #ProductID IS NOT NULL
SET #ListPrice = (SELECT ListPrice FROM Products WHERE ProductID = #ProductID);
IF #ServiceID IS NOT NULL AND #ServiceID NOT IN (SELECT ServiceID FROM Services)
THROW 50001, 'Please enter a valid ServiceID.', 1;
IF #ServiceID IS NOT NULL
SET #ServiceCost = (SELECT ServiceCost FROM Services WHERE ServiceID = #ServiceID);
IF #SQty IS NOT NULL AND #ServiceID IS NOT NULL AND #Sqty <= 0
THROW 50001, 'SQty must be greater than zero.', 1;
IF #ProductID IS NOT NULL OR #ServiceID IS NOT NULL
SET #TotalAmount = ((#ListPrice*#PQty) + (#ServiceCost*#SQty));
ELSE --All data is verified
INSERT INTO InvoiceLineItems (InvoiceID, ProductID, PQty, ListPrice, ServiceID, ServiceCost, SQty, TotalAmount)
VALUES (#InvoiceID, #ProductID, #PQty, #ListPrice, #ServiceID, #ServiceCost, #SQty, #TotalAmount);
END
GO
Any thoughts on why it isn't inserting the values?
In IF...ELSE, the ELSE or supplemental IF are only executed when the preceding IF statement(s) are not satisfied. Therefore:
IF #ProductID IS NOT NULL SET #ListPrice = (SELECT ListPrice FROM Products WHERE ProductID = #ProductID)
If this evaluates to TRUE your statement will terminate, and you will have set a #ListPrice.
You need to check for ALL errors like you did in your first statement, and if ALL conditions are not met (there are no errors) then set your variables and update (insert) into your table.

SQL defaults - best practice?

What is the best thing to do in the CREATE and UPDATE stored procedures for a table with default constraints?
When I create a new column for a table, I try to set a propper default value (Default constraint).
Example:
CREATE TABLE Orders
(
O_ID INT NOT NULL
,State INT DEFAULT 0 -- 0 => Not Verified, 1 => Verified, 2 => Processing ....
,P_ID INT
,OrderDate DATE DEFAULT GETDATE()
)
What is the best thing to do in the CREATE and UPDATE stored procedures for this table?
Use the same defaults as in the constraint?
CREATE PROCEDURE UpdateOrder
(
#O_ID INT
,#State INT = 0
,#P_ID INT
,#OrderDate DATE
)
AS
UPDATE
Orders
SET
State = #State
,P_ID = #PID
,OrderDate = #OrderDate
WHERE
O_ID = #O_ID
That would be kind of repetitive, as it's already defaulted in the table.
On the other hand, it allows your parameters to be optional. I would say your choices are to default them to the same as the table (as you suggest), or to default them to null and the table will fill in the default values. The second way is less repetitive and error-prone.
If you want OrderDate to be updated during UPDATE, you need to include it in the UPDATE statement and use getdate() in place of #OrderDate
UPDATE
Orders
SET
State = #State ,
P_ID = #PID ,
OrderDate = getdate()
WHERE O_ID = #O_ID
To comply with DRY, one workaround would be to store your defaults in a table maybe:
CREATE TABLE dbo.MyDefaults
(
OrderState INT
);
INSERT dbo.MyDefaults(OrderState) SELECT 0;
You can't really do this with GETDATE(), so let's just leave that as is - not something you're likely to change, anyway. So now we can pull our default value from the table, instead of hard-coding it. Let's create a scalar function, because we can't use a subquery in a default constraint:
CREATE FUNCTION dbo.DefaultOrderState()
RETURNS INT
AS
BEGIN
RETURN (SELECT TOP (1) OrderState FROM dbo.MyDefaults);
END
GO
(If you have a lot of these, you might consider an EAV approach instead of dedicated columns.)
So now we can have our Orders table, and note that the constant "0" is never mentioned:
CREATE TABLE dbo.Orders
(
O_ID INT NOT NULL,
[State] INT NOT NULL DEFAULT (dbo.DefaultOrderState()),
P_ID INT,
OrderDate DATE NOT NULL DEFAULT (SYSDATETIME())
);
GO
And our update procedure can also grab the defaults (except you haven't defined whether you really want to reset the state to 0 if it is not currently 0 and no value is supplied to the procedure). Again the "0" constant is not mentioned.
CREATE PROCEDURE dbo.UpdateOrder
#O_ID INT,
#State INT = NULL,
#P_ID INT,
#OrderDate DATE = NULL
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Orders
SET [State] = COALESCE(#State, dbo.DefaltOrderState()),
P_ID = #P_ID,
OrderDate = COALESCE(#OrderDate, OrderDate, SYSDATETIME())
WHERE
O_ID = #O_ID;
END
GO
For the update, you could send in null for "No Change" (or another sentinel if the column is nullable). A similar approach would work for inserts.
CREATE PROCEDURE UpdateOrder
(
#O_ID INT
,#State INT
,#P_ID INT = -1
,#OrderDate DATE
)
AS
UPDATE
Orders
SET
State = IsNull(#State,State)
,P_ID = case when #P_ID < 0 then P_ID else #P_ID end -- assuming this int is not nullable and something like -1 is the default value
,OrderDate = COALESCE(#OrderDate,OrderDate)
WHERE
O_ID = #O_ID

Resources