Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2000 Forums
 Transact-SQL (2000)
 Capturing auto-generated GUID after insert

Author  Topic 

Marioi
Posting Yak Master

132 Posts

Posted - 2005-10-12 : 14:47:05
I am new to SQL server so I may be overseeing something totally obvious to you. After an insert, how do I get the value of a GUID that was generated by SQL Server (default value in a uniqueidentifier column set up as '(newid())'). For an identity column, I see that one can use @@IDENTITY. Is there something similar for GUIDs?

tkizer
Almighty SQL Goddess

38200 Posts

Posted - 2005-10-12 : 15:06:03
No such option exists for GUID, so you'll have to select from the table to determine what was just done.

Tara
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-12 : 15:24:33
... or store a NewID() in an @Variable and use that during the INSERT so that you know the value ahead of time and can return it to the client application (or generate a GUID at the Client end and use that instead - again, so that it is know ahead-of-time)

Kristen
Go to Top of Page

Marioi
Posting Yak Master

132 Posts

Posted - 2005-10-12 : 16:36:28
quote:
Originally posted by Kristen

... or store a NewID() in an @Variable and use that during the INSERT


Great idea. I would like to make this universally available. Could I assign (and capture) NewID in a trigger and somehow make that ID available after the insert? Can one define global variables? Something like user-defined @@ functions perhaps?
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-13 : 09:26:15
Not that I can think of, no.

Kristen
Go to Top of Page

madhivanan
Premature Yak Congratulator

22864 Posts

Posted - 2005-10-13 : 09:31:47
See if this helps you
http://vyaskn.tripod.com/retrieve_guid_value_like_identity.htm

Madhivanan

Failing to plan is Planning to fail
Go to Top of Page

Marioi
Posting Yak Master

132 Posts

Posted - 2005-10-13 : 12:34:47
quote:
Originally posted by madhivanan

See if this helps you
http://vyaskn.tripod.com/retrieve_guid_value_like_identity.htm



Very useful! I like idea 2 - doing it in a trigger.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-13 : 13:13:34
"Very useful! I like idea 2 - doing it in a trigger"


Agggggggghhhhhhhhhhhhhhhhhhhhhhh!!!!

Everything that inserts into a table will get a resultset back. I really hate that idea, for that reason.

If you really need to do it that way then I suggest you make a VIEW onto the TABLE and put an INSTEAD OF TRIGGER on the view, instead of a trigger onto the underlying table.

Much better would be to insert via a Stored Procedure that takes care of assigning the GUID and returning it to the client application as well.

Or generating it in the client application and using that as part of the data in the INSERT statement.

Kristen
Go to Top of Page

Marioi
Posting Yak Master

132 Posts

Posted - 2005-10-13 : 14:04:53
Kristen,

This is such a stupid problem. Our DB must support replication and we want to support native ways of working with SQL Server as much as possible. The database must support dynamic schema and be very open to access from multiple channels. If I force everyone to use sp_Insert, how do I elegantly prevent them from using INSERT?
Go to Top of Page

blindman
Master Smack Fu Yak Hacker

2365 Posts

Posted - 2005-10-14 : 00:47:39
quote:
Originally posted by Marioi

I am new to SQL server so I may be overseeing something totally obvious to you.

Yeah, I think you may be missing the "out of the box" solution. I assume your GUID defaults to newid(), but remember that this is just a default, and is only created if your application does not submit a value. So the beauty of GUIDs (GLOBALLY UNIQUE IDENTIFIERS, remember...) is that you can create them in your interface and still be confident that they will not conflict with existing IDs. And since you can create them before submitting the record for insert, there is no need to request the ID from the server. Many, if not most programming languages include function for generating GUIDs.
This is one of the reasons I love using GUIDs as surrogate keys.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-14 : 07:23:47
"If I force everyone to use sp_Insert, how do I elegantly prevent them from using INSERT?"

I would Grant EXECUTE permission to the SProc and DENY both INSERT and UPDATE permissions on the underlying TABLE (maybe DELETE too) - i.e. they can still do SELECT direct from the table.

(You could probably just give them SELECT permissions, but if you use the inbuilt DATA_READER / DATA_WRITER roles for you users they will inherently have Select, Insert, Update and Delete on all tables, so you'd need to explicitly use DENY)

"and still be confident that they will not conflict with existing IDs"

Just being pedantic for the moment, since using the NIC as a seed for the GUIDs was deemed to be "Too intrusive of personal privacy" GUIDs are no longer guaranteed to be unique - however, it is very long odds that one will be created that is a duplicate, but at the very least you should have Unique Constraint on all GUID columns, and for the very paranoid/mission critical a "loop and retry" when a duplicate is created!

Kristen
Go to Top of Page

Marioi
Posting Yak Master

132 Posts

Posted - 2005-10-14 : 11:09:07
quote:
Originally posted by Kristen

"[i]I would Grant EXECUTE permission to the SProc and DENY both INSERT and UPDATE permissions on the underlying TABLE


How would you structure parameters for the insert sproc to allow handling multiple (possibly a few hundred) fields and their values (possibly long)? Is there something like a rowset that could be passed as a parameter? I see that in Yukon varchar(max) can handle up to 2GB, and there is also an xml data type that could be used for this, but this is not available in 2000. We could go to Yukon but were hoping to maintain compatibility with 2000.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-14 : 11:49:01
Can you outline what the Sproc does, and indicate why the large number of parameters & large data values - might give us some food for thought for other solutions.

Kristen
Go to Top of Page

blindman
Master Smack Fu Yak Hacker

2365 Posts

Posted - 2005-10-14 : 11:59:47
quote:
Just being pedantic for the moment, since using the NIC as a seed for the GUIDs was deemed to be "Too intrusive of personal privacy" GUIDs are no longer guaranteed to be unique...

Curious. Do you have a white paper or link documenting this?
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-14 : 13:33:06
"Do you have a white paper or link documenting this"

http://www.sqlteam.com/Forums/topic.asp?TOPIC_ID=52891&SearchTerms=GUIDs%20not%20guaranteed%20to%20be%20unique

Kristen
Go to Top of Page

Marioi
Posting Yak Master

132 Posts

Posted - 2005-10-14 : 13:36:07
quote:
Originally posted by Kristen

Can you outline what the Sproc does


Our app is a tool for building a CRM solution as a metadata-driven application. The strategy is to perform validation and run business processes in a trigger-like fashion. If we front INSERTs we need to accommodate being able to insert records that may contain hundreds of fields.

In the current version of the app all data access goes through an ActiveX API, so we have our own rowset object in which you set field values and them commit the rowset. I am trying to figure out how this would be best implemented in SQL Server. One idea is to front SQL Server with a middle tier and re-write our API in DLLs. If we go this way, however, we'll have to front many of the facilities that are native to SQL Server (and usable out of the box), and time to market is really important for us. We don't have a requirement to run on any back end so committing to SQL Server is OK.

From what I hear using triggers and sprocs is the most efficient way of providing a data management "API", both from the standpoint of performance and the ability to utilize a lot of existing facilities that most of the world knows how to work with.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-14 : 14:51:31
I've changed my mind about triggers somewhat.

We have triggers that set the Update Date of the record (so it is sure to be reset each time the row is saved), and convert some columns from BlankString to NULL. These are pretty generic.

Then we have a few - a VERY few - triggers that do things like set the Total Order Value on an OrderHeader table (i.e. an Insert/Update trigger on the Order Items table).

But the Update Date and Blank String stuff our standard "Save" Sprocs are supposed to do anyway ...

So I recently decided that the "cost" of a Trigger that basically does an UPDATE on the table-in-question to tidy up some columns is a flagrant waste of CPU cycles - you wind up doing the original UPDATE and a second UPDATE via the trigger. 99% of the time the SProc is doing the job anyway.

So we are in the process of changing to only using triggers to RAISERROR when the data is incorrect (and we plan to only use these in Development), that will leave a very few Triggers for real-world-stuff - like the running-totals on the Order Header table.

We have always used Sprocs for GET, SAVE and DELETE - plus some other "standard" ones for FIND and a few other functions. These are mechanically generated, and in about 5% of cases we hand modify the code (it would be fair to say that the thing that mechanically generates the SProcs has been maturing for 6 or 7 years old, so has gotten pretty sophisticated).

So our equivalent of your "API Layer" is a set of generic Sprocs that we religiously use to Get, Save and Delete rows.

They have some "standard" parameters for things like:

Validation Error Count (non zero = data entry errors the user should be able to "fix")
Validation Error Messages (Suitable to display to the user)
Error Number (non zero = critical error / abend)
Error Message (Suitable to display to a developer to diagnose the problem)
Debug Mode (display diagnostic data)

this lot allows us to create application stuff generically - so what we do is make a form - that involves picking a bunch of Columns from a list of available columns. That creates a "snippet" of HTML suitable for a web page, which can be edited if necessary, but 95% of the time is OK as it stands.

With that HTML it also assigns the standard GET and SAVE Sprocs to that particular "form", which again we can override with a "bespoke" Sproc - but we only do that 5% of the time.

We do quite often override the "Get" Sproc - because we want to grab associated "Lookup" data, or display additional recordsets (e.g. on a Customer Edit Form we may want to display a list of their orders or somesuch).

But basically this approach allows us to make New/Edit/Delete forms for database records without doing very much!

The standard "Save" SProcs do get enhanced with appropriate "business rules", so encapsulate a lot of the work flow stuff, and that seems to work well for us.

If I've read between the lines of your Post you do something similar, but using a different set of mechanicals, so I've rabbited on in case it gives you some ideas!

Your point about "lots of parameters" doesn't arise for us, and maybe you could solve that problem the same way: our mechanically generated "Save" SProcs have parameters for every column in the table they relate to - the code that creates them doesn't care how many columns there are, of course!

Kristen
Go to Top of Page

Marioi
Posting Yak Master

132 Posts

Posted - 2005-10-14 : 15:55:08
Kristen, your model is similar to ours and it looks to me like a layer of sprocs will do what we want without precluding doing some direct data access here and there. So far as data integrity, I think I would still prefer to have the critical stuff coded in triggers (at least by raising errors, like you do).

The way our app seems different from yours is that the data schema is dynamic (metadata-driven) and we abstract the whole UI layer: tabular displays of data (we call them views), dashboards (which have multiple views), forms, and definitions for HTML and Word templates used for generating formatted documents for sending, including mail merge. There are workgroup-level settings, user permissions, and user-level options. Where it gets interesting is that we support custom client-side scripting (FormOnLoad, FormOnSave, FormOnTabClick, OnEnterField, OnLeaveField, etc) and server-side scripting (DatabaseOnOpen, DatabaseOnClose, RecordOnSave, RecordOnDelete). These scripts have the ability to abort the event that triggers them.

The way I imagined this would "translate" to SQL Server is that there would be a set of generic sProcs for adding, editing, and deleting records in any table, and each of those would then call a matching "custom functionality" sProc. This would preclude ever doing an INSERT, UPDATE, or DELETE on a data table directly.

Why not put custom functionality in main sprocs? Because we need to be able to sell version upgrades. We would distribute upgraded core sProcs and custom sProcs would survive. This of course means that custom sprocs are always additive in their nature and can't prevent the core stuff from being executed. But that is exactly what our custom functionality ends up doing 95% of time anyway. In some cases, customizations cancel out what the generic functions do. In our environment that is acceptable performance-wise.

Another characteristic of our app is that it encourages many-to-many linking (involving link tables). We call that "hyper-connectivity". Not only can every record be linked to multiple records in any other table, but there are multiple links between same table pairs (ex: Activity - Main - Companies, Activity - Involved - Companies or Project - Team - Users, Project - Support - Users, Project - Engineer - Users). Again, this is a capability we support generically. Customers can add or remove these links. This means a lot of inner joins in dynamic SQL queries.

Don't know whether this gives you enough of an idea of the kind of environment we have. For us, building an app means building metadata, which the engine makes come to life. A critical decision for us is how to structure that engine in SQL Server and ASP.net so that we can deliver quickly and be able to grow with it for years to come.

This is just a very preliminary investigation. We intend to hire a "guru", but from what I see it will be hard to find someone who's had adequate experience in such an abstract environment. If you know someone interested in guiding us, please let me know.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-15 : 01:42:01
"sprocs will do what we want without precluding doing some direct data access here and there"

The "here and there" bit will mean that the user has to have permissions on the tables (which would also be true if the SProcs had to use any dynamic SQL to access the tables).

Worth considering if you can use only Sprocs, no dynamic SQL, in order to avoid having to grant permissions on the tables (well, SELECT may be tolerable, but give up INSERT/UPDATE/DELETE reluctantly!)

"hard to find someone who's had adequate experience in such an abstract environment"

I find that too - working with a framework which is unconventional. We've evolved ours - which is a grand way of saying we complain lots about those areas of our code which I can only politely refer to as "legacy code" But our troops, particularly newbies, find it a rather surprising way to work! I suppose what we've been doing for mean years is similar to what Ruby on Rails and Scaffold

Our SAVE SProcs have an unusual "flavour":

The SAVE SProcs, mechanically generated, are happy to receive any/all columns in the table. Any parameter which is NULL causes the column to be left alone, with its original value; a parameter which is an empty string causes the column to be set to NULL, and any other value becomes the new value for that column.

Parameters to the SProc default to NULL, so any that are missed off by the caller cause those columns to remain "untouched" (e.g. the caller didn't need to process that column, or for new columns that the caller is not yet modified to handle)

(Note that in order to achieve this all parameters have to be varchar() - you can't have an "empty string date" - we convert them to appropriate datatype at the top of the SProc, and then use that representation within the SProc body (except for checking if the original varchar was an empty string, rather than NULL, at the point of deciding whether to retain a column's value, or replace it with NULL)

The SProcs Inserts a new row if none found, otherwise it updates the existing. There is an optional parameter which enforces "Must NOT already exist" or "Must ALREADY exist" - which we use most of the time (i.e. where we did a GET before allowing a user to modify the record).

We also have a "version number" on every table; that is incremented on each save, and a parameter to the Save and Delete SProcs - so they can make sure that no further edits have happened compared to the data the user was working from (although this is a bit tricky with Web pages where the user can press Back, modify the data, and re-submit!)

One place where we DO use Triggers is to duplicate rows into an Audit Table - we only store Deleted rows - the current version is in the main table, no point doubling our disk storage to keep that in Audit as well!

One thing to consider is how you regenerate the standard Sprocs - e.g. when you add another column, or decide there is Yet Another Global Change you want to implement!

We generate to a temporary file and use a nifty file comparison tool to merge the desired changes in - leaving the custom changes alone. We've though about storing meta-hints so that more of the Sprocs will be correctly generated by the mechanical tools, but for now we make few enough changes that it doesn't seem worth it.

Kristen
Go to Top of Page

Marioi
Posting Yak Master

132 Posts

Posted - 2005-10-17 : 10:33:24
The "here and there" bit will mean that the user has to have permissions on the tables

In our current environment that is the system user. Why do you say that this wouldn't work with dynamic SQL? Of course, the goal would be to abstract all data access through sProcs, system or not.

It would be really interesting for me to see your save sprocs. How do you pass all columns to it? As individual parameters? The way you deal with dates is also interesting.

Also, it's interesting how you perform text comparison and merge procedure code.

although this is a bit tricky with Web pages where the user can press Back, modify the data, and re-submit!)

Having lived in a replicated environment for 8 years, we take the last in wins approach to all saves. We wrote all kinds of warnings and marge UI, but nobody was using it. Since we don't process true transactions (price quotes are the closest to that), we can afford that approach.

One thing to consider is how you regenerate the standard Sprocs - e.g. when you add another column, or decide there is Yet Another Global Change you want to implement!

What are the issues? I thought we'd drop/create a procedure.

Thank you for sharing your valuable insights.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2005-10-18 : 03:56:56
"Why do you say that this wouldn't work with dynamic SQL"

An Sproc that Inserts/Updates a table does so with the permissions of the developer-user who created the Sproc - the end-user only has to have Execute permissions on the SProc.

However, if the SProc uses dynamic SQL then it will use the end-user permissions to access the underlying table(s)

"It would be really interesting for me to see your save sprocs"

I've generated a set below for Northwind.customers - this is "straight out of the box" but I've added some comments in blue to indicate what some of the bits do.

"What are the issues? I thought we'd drop/create a procedure"

Sorry, what I mean was if you add a Column to a table then the "SAVE" Sproc needs to become aware of that column - so the automatically generated SProcs need to be re-generated - but if the SProcs have been hand edited in the meantime then the new code needs to be merge with the existing core code.

We use the Merge tools in the Text Editor we use (Visual Slick Edit). A new version of the Sprocs are auto-generated to a temporary file, and that is compared against the "core code" version of the file.

"Also, it's interesting how you perform text comparison and merge procedure code"

Was that a question?

Kristen

Standard SProcs mechanically generated

We require three parts to the name - "system" : 'nw' (Northwind), "Sub-system" : 'SOP' (Sales order processing), "Table" : 'ORD' (Orders)


PRINT 'Create procedure nw_SP_SOP_ORD_Get'
GO
Log the fact that this SProc was run, together with a version (hand edited but based on modification date)
EXEC dbo.kk_SM_SP_LogScriptRun 'nw_SP_SOP_ORD_Get', '051017'
GO
IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'[dbo].[nw_SP_SOP_ORD_Get]')
AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE dbo.nw_SP_SOP_ORD_Get
GO

CREATE PROCEDURE dbo.nw_SP_SOP_ORD_Get
All Sprocs have a Session ID as the first parameter - helps with any logging
@ysvl_ID int, -- Session Visit Log ID

@OrderID int,
--ADD consider any new columns

Standard parameters next
- Do we care if the record is not found?
- Can we report errors as a recordset?
- Show DEBUG data?

@blnIgnoreNotFound int=0, -- 1=Do NOT report an error if not found

@intSilent int=0, -- 1=Silent on all ERROR resultsets
-- (only used if this SP can be called from another SP,
-- rather than an ASP page where it is desirable to see
-- errors during debugging)
@intDebug int=0 -- 0=No debugging data, 1+ = debugging at increasing levels of details

/* WITH ENCRYPTION */
AS
/*
* nw_SP_SOP_ORD_Get Get a record from Orders
*
* Returns:
*
* Record columns
*
* ERRORS:
*
All TODO comments are to be acted on, and then removed
-- TODO Need error return codes, and their descriptions
* -1 No rows found
* -2 Multiple rows found
*
* HISTORY:
*
* 17-Oct-2005 KRIS Started
*/

SET NOCOUNT ON
SET XACT_ABORT ON
SET ARITHABORT ON
-- System variables
These are used by every Sproc
DECLARE @intErrNo int,
@strErrMsg varchar(255),
@intRetVal int,
@sm_yppl_ID int,
@dtNow datetime,
@strParameters varchar(1000),
@intRowCount int

-- Local variables
These are specific to THIS sproc
--TODO Declare any local variables here
-- DECLARE @strMyVar1 varchar(255),
-- @intMyVar2 int

SELECT @dtNow = GetDate(),
@intErrNo = 0, -- Assume no error
@strErrMsg = '',
@intRetVal = 0 -- Return value (Assume no error)

Create a list of all parameters, for logging (See _SAVE Sproc below for longer list)
SELECT @strParameters = left(
COALESCE(CONVERT(varchar(20), @OrderID), 'NULL')
--ADD any new columns

, 1000)

Log that this Sproc was run, and log its parameters
EXEC @sm_yppl_ID = dbo.kk_SM_SP_LogSP @ysvl_ID, 'nw_SP_SOP_ORD_Get', @strParameters

IF @intDebug >= 1 SELECT [nw_SP_SOP_ORD_Get DEBUG(1)] = 'Select'
Select required columns for the specific record
SELECT
OrderID,
CustomerID,
EmployeeID,
OrderDate,
RequiredDate,
ShippedDate,
ShipVia,
Freight,
ShipName,
ShipAddress,
ShipCity,
ShipRegion,
ShipPostalCode,
ShipCountry
--ADD any new columns
-- SELECT TOP 10 *
FROM dbo.Orders
WHERE
Orders.OrderID = @OrderID
-- ORDER BY not appropriate - single record retrieved
-- ORDER BY
-- Orders.OrderID
SELECT @intErrNo = @@ERROR, @intRowCount = @@ROWCOUNT

-- Record error if no record found (unless error supressed)
IF @intErrNo = 0 AND @intRowCount = 0 AND @blnIgnoreNotFound = 0
BEGIN
SELECT @intRetVal = -1,
@strErrMsg = @strErrMsg + 'No rows found. '
END
IF @intErrNo = 0 AND @intRowCount > 1
BEGIN
SELECT @intRetVal = -2,
@strErrMsg = @strErrMsg + 'Multiple rows found. '
END

nw_SP_SOP_ORD_Get_EXIT:
Display an "Error" record set, unless we have NO error, or we are using "Silent Running"
IF (@intRetVal <> 0 AND @intSilent=0) OR @intDebug >= 1 SELECT [ERROR] = @strErrMsg,
[RetVal] = @intRetVal, [ErrNo] = @intErrNo, [SP] = 'nw_SP_SOP_ORD_Get',
[Params] = @strParameters

Update Log with details of our "exit" - include any useful status information
EXEC dbo.kk_SM_SP_UpdateSPLog @sm_yppl_ID, @intRetVal, @strErrMsg, NULL, @intRowCount, @intErrNo

IF @intDebug >= 1 SELECT [nw_SP_SOP_ORD_Get]='RETURN', [@intRetVal]=@intRetVal,
[Elapsed (sec)] = DATEDIFF(Second, @dtNow, GetDate()),
[@intErrNo]=@intErrNo, [@@TRANCOUNT]=@@TRANCOUNT, [@strErrMsg]=@strErrMsg, *
FROM dbo.kk_SM_yPPL_PageProcedureLog WHERE yppl_ID = @sm_yppl_ID

SET NOCOUNT OFF

RETURN @intRetVal -- Return error number, 0=No error

/* TEST RIG
Set up useful test examples here.
This code IS included in the SProc, so can be seen with sp_helptext etc.


--TODO Set up test sample here:

EXEC nw_SP_SOP_ORD_Get 123, 'MyPK1'
,@intDebug=1

SELECT TOP 10 * FROM dbo.Orders

*/
--================== nw_SP_SOP_ORD_Get ==================--
GO

IF exists (SELECT * FROM sysobjects WHERE id = object_id(N'[dbo].[nw_SP_SOP_ORD_Get]')
AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
GRANT EXECUTE ON dbo.nw_SP_SOP_ORD_Get TO IIS
GO
PRINT 'Create procedure nw_SP_SOP_ORD_Get DONE'
GO

PRINT 'Create procedure nw_SP_SOP_ORD_Save'
GO
EXEC dbo.kk_SM_SP_LogScriptRun 'nw_SP_SOP_ORD_Save', '051017'
GO
IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'[dbo].[nw_SP_SOP_ORD_Save]')
AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE dbo.nw_SP_SOP_ORD_Save
GO

CREATE PROCEDURE dbo.nw_SP_SOP_ORD_Save
@ysvl_ID int, -- Session Visit Log ID

An Edit Number "Version" column is used on All tables.
Northwind does not have that of course! so some of the code below is a little squiffy.
The Sprocs also automatically handle Create/Update Date & User (also not in the Customer table)

--TODO No EditNo column found
@EditNo int=NULL, -- NULL=UPSERT, 0=INSERT, n=EditNumber (UPDATE), -1=UPDATE (any)
All non-string columns in the table are provided as VARCHAR
If they are given as (or DEFAULT to) NULL then the existing value is retained.
If they are given as "" [emtpy string] then they are coersced to NULL
Because OrderID is an IDENTITY column it is automatically given an OUTPUT type

@OrderID varchar(20) OUTPUT, -- Actual type=int
@CustomerID nchar(5) = NULL,
@EmployeeID varchar(20) = NULL, -- Actual type=int
@OrderDate varchar(24) = NULL, -- Actual type=datetime
@RequiredDate varchar(24) = NULL, -- Actual type=datetime
@ShippedDate varchar(24) = NULL, -- Actual type=datetime
@ShipVia varchar(20) = NULL, -- Actual type=int
@Freight varchar(20) = NULL, -- Actual type=money
@ShipName nvarchar(40) = NULL,
@ShipAddress nvarchar(60) = NULL,
@ShipCity nvarchar(15) = NULL,
@ShipRegion nvarchar(15) = NULL,
@ShipPostalCode nvarchar(10) = NULL,
@ShipCountry nvarchar(15) = NULL,
--ADD any new columns

The _SAVE sproc will either INSERT or UPDATE - it can be forced to "Add" mode,
i.e. no record can already exist, otherwise it will check if the record exists

@intMode int=0, -- Mode 0=Normal, 1=Forced ADD

Validation errors, if any, will be returned as a recordset that the application can use
to display user-friendly errors

@intValErrCount int = NULL OUTPUT, -- Count of Validation Errors
@strValMsg varchar(8000) = NULL OUTPUT, -- Validation Message(s) - User Friendly

@intSilent int=0, -- 1=Silent on all ERROR resultsets
-- (only used if this SP can be called from another SP,
-- rather than an ASP page where it is desireable to see
-- errors during debugging)
@intDebug int=0 -- 0=No debugging data, 1+ = debugging at increasing levels of details

/* WITH ENCRYPTION */
AS
/*
* nw_SP_SOP_ORD_Save Save a record to Orders
*
* Returns:
*
* Result code (0=No error)
*
* ERRORS:
*
-- TODO Need error return codes, and their descriptions
* -4 Error INSERTING
* -5 Error UPDATING
* -10 Validation Error - Duplicate record
*
* HISTORY:
*
* 17-Oct-2005 KRIS Started
*/

SET NOCOUNT ON
SET XACT_ABORT ON
SET ARITHABORT ON
-- System variables
DECLARE @intErrNo int,
@strErrMsg varchar(255),
@intRetVal int,
@ysvl_UsrID varchar(4),
@sm_yppl_ID int,
@dtNow datetime,
@strParameters varchar(1000),
@intRowCount int

-- Local variables
--TODO Declare any local variables here
-- DECLARE @strMyVar1 varchar(255),
-- @intMyVar2 int

Non-String parameters are converted to their native datatypes
-- Local variables to hold conversion of STRING parameters to correct data type
DECLARE
@int_OrderID int,
@int_EmployeeID int,
@dt_OrderDate datetime,
@dt_RequiredDate datetime,
@dt_ShippedDate datetime,
@int_ShipVia int,
@mny_Freight money
--ADD any new columns (i.e. non varchar/text)

SELECT @dtNow = GetDate(),
@intErrNo = 0, -- Assume no error
@strErrMsg = '',
@intValErrCount = COALESCE(@intValErrCount, 0),
@intRetVal = 0 -- Return value (Assume no error)

SELECT @strParameters = left(
COALESCE(@OrderID, 'NULL')
+ '~' + COALESCE(@CustomerID, 'NULL')
+ '~' + COALESCE(@EmployeeID, 'NULL')
+ '~' + COALESCE(@OrderDate, 'NULL')
+ '~' + COALESCE(@RequiredDate, 'NULL')
+ '~' + COALESCE(@ShippedDate, 'NULL')
+ '~' + COALESCE(@ShipVia, 'NULL')
+ '~' + COALESCE(@Freight, 'NULL')
+ '~' + COALESCE(@ShipName, 'NULL')
+ '~' + COALESCE(@ShipAddress, 'NULL')
+ '~' + COALESCE(@ShipCity, 'NULL')
+ '~' + COALESCE(@ShipRegion, 'NULL')
+ '~' + COALESCE(@ShipPostalCode, 'NULL')
+ '~' + COALESCE(@ShipCountry, 'NULL')
--ADD any new columns

, 1000)

EXEC @sm_yppl_ID = dbo.kk_SP_SM_LogSPUserGet @ysvl_ID, 'nw_SP_SOP_ORD_Save', @strParameters
The logging SProc can return the currently logged in UserID for the current session,
if any (useful for logging)

, @ysvl_UsrID=@ysvl_UsrID OUTPUT

-- Assign not-string parameters to local variables (to force data type change)
Convert String parameters to native data type
SELECT
@int_OrderID = CASE WHEN @OrderID = '' THEN NULL
ELSE CONVERT(int, @OrderID) END,
@int_EmployeeID = CASE WHEN @EmployeeID = '' THEN NULL
ELSE CONVERT(int, @EmployeeID) END,
@dt_OrderDate = CASE WHEN @OrderDate = '' THEN NULL
ELSE CONVERT(datetime, @OrderDate) END,
@dt_RequiredDate = CASE WHEN @RequiredDate = '' THEN NULL
ELSE CONVERT(datetime, @RequiredDate) END,
@dt_ShippedDate = CASE WHEN @ShippedDate = '' THEN NULL
ELSE CONVERT(datetime, @ShippedDate) END,
@int_ShipVia = CASE WHEN @ShipVia = '' THEN NULL
ELSE CONVERT(int, @ShipVia) END,
@mny_Freight = CASE WHEN @Freight = '' THEN NULL
ELSE CONVERT(money, @Freight) END
--ADD any new columns (i.e. non varchar/text)

IF @intMode = 1
BEGIN
SELECT @EditNo = 0 -- Force ADD
END
No EditNumber provided, find the current edit number, or force INSERT if no record found
IF @EditNo IS NULL -- Not provided?
BEGIN
SELECT @EditNo = EditNo
FROM dbo.Orders T
WHERE T.OrderID = @int_OrderID
IF @@ROWCOUNT=0 SELECT @EditNo = 0 -- New record
END
SELECT @EditNo = COALESCE(@EditNo, 0) -- NULL will not work

-- Validation Section
Perform any appropriate field validation.
This is obviously table-by-table, but code for "PK Already exists" is auto-generated for "new" records

IF @EditNo = 0
BEGIN
-- New record validation
IF EXISTS
(
SELECT *
FROM dbo.Orders T
WHERE T.OrderID = @int_OrderID
)
BEGIN
SELECT @intValErrCount = @intValErrCount + 1,
@strValMsg = COALESCE(@strValMsg + '<br/>', '') + 'Record already exists'
END
END

IF @intRetVal = 0 AND @intValErrCount = 0 -- No validation errors
BEGIN
-- Save Section
Use two-step transaction to allow ROLLBACK without getting the Transaction Count out of step
BEGIN TRANSACTION nw_SP_SOP_ORD_Save_01
SAVE TRANSACTION nw_SP_SOP_ORD_Save_02
IF @EditNo = 0 -- New record?
BEGIN
New record, INSERT
IF @intDebug >= 1 SELECT [nw_SP_SOP_ORD_Save DEBUG(1)]='INSERT new record'
INSERT INTO dbo.Orders
(
-- OrderID, -- Identity colunm cannot be inserted
CustomerID,
EmployeeID,
OrderDate,
RequiredDate,
ShippedDate,
ShipVia,
Freight,
ShipName,
ShipAddress,
ShipCity,
ShipRegion,
ShipPostalCode,
ShipCountry
--ADD any new columns
) VALUES
(
For items which are EmptyString store NULL otherwise store the value in the parameter
(or any default constraint defined for the table)

-- Identity colunm
-- CASE WHEN @OrderID = '' THEN NULL ELSE @int_OrderID END,
CASE WHEN @CustomerID = '' THEN NULL ELSE @CustomerID END,
CASE WHEN @EmployeeID = '' THEN NULL ELSE @int_EmployeeID END,
CASE WHEN @OrderDate = '' THEN NULL ELSE @dt_OrderDate END,
CASE WHEN @RequiredDate = '' THEN NULL ELSE @dt_RequiredDate END,
CASE WHEN @ShippedDate = '' THEN NULL ELSE @dt_ShippedDate END,
CASE WHEN @ShipVia = '' THEN NULL ELSE @int_ShipVia END,
CASE WHEN @Freight = '' THEN NULL WHEN @Freight IS NULL THEN (0)
ELSE @mny_Freight END,
CASE WHEN @ShipName = '' THEN NULL ELSE @ShipName END,
CASE WHEN @ShipAddress = '' THEN NULL ELSE @ShipAddress END,
CASE WHEN @ShipCity = '' THEN NULL ELSE @ShipCity END,
CASE WHEN @ShipRegion = '' THEN NULL ELSE @ShipRegion END,
CASE WHEN @ShipPostalCode = '' THEN NULL ELSE @ShipPostalCode END,
CASE WHEN @ShipCountry = '' THEN NULL ELSE @ShipCountry END
--ADD any new columns
)
SELECT @intErrNo = @@ERROR, @intRowCount = @@ROWCOUNT, @OrderID = scope_identity()
if @intErrNo <> 0
BEGIN
SELECT @intRetVal = -4,
@strErrMsg = @strErrMsg + 'Error INSERTING Orders. '
END
END
ELSE
BEGIN
Existing record UPDATE
IF @intDebug >= 1 SELECT [nw_SP_SOP_ORD_Save DEBUG(2)]='UPDATE existing record'
UPDATE T
-- Primary Key field
-- OrderID = CASE WHEN @OrderID = '' THEN NULL
-- ELSE COALESCE(@int_OrderID, OrderID) END,
SET CustomerID = CASE WHEN @CustomerID = '' THEN NULL
ELSE COALESCE(@CustomerID, CustomerID) END,
EmployeeID = CASE WHEN @EmployeeID = '' THEN NULL
ELSE COALESCE(@int_EmployeeID, EmployeeID) END,
OrderDate = CASE WHEN @OrderDate = '' THEN NULL
ELSE COALESCE(@dt_OrderDate, OrderDate) END,
RequiredDate = CASE WHEN @RequiredDate = '' THEN NULL
ELSE COALESCE(@dt_RequiredDate, RequiredDate) END,
ShippedDate = CASE WHEN @ShippedDate = '' THEN NULL
ELSE COALESCE(@dt_ShippedDate, ShippedDate) END,
ShipVia = CASE WHEN @ShipVia = '' THEN NULL
ELSE COALESCE(@int_ShipVia, ShipVia) END,
Freight = CASE WHEN @Freight = '' THEN NULL
ELSE COALESCE(@mny_Freight, Freight) END,
ShipName = CASE WHEN @ShipName = '' THEN NULL
ELSE COALESCE(@ShipName, ShipName) END,
ShipAddress = CASE WHEN @ShipAddress = '' THEN NULL
ELSE COALESCE(@ShipAddress, ShipAddress) END,
ShipCity = CASE WHEN @ShipCity = '' THEN NULL
ELSE COALESCE(@ShipCity, ShipCity) END,
ShipRegion = CASE WHEN @ShipRegion = '' THEN NULL
ELSE COALESCE(@ShipRegion, ShipRegion) END,
ShipPostalCode = CASE WHEN @ShipPostalCode = '' THEN NULL
ELSE COALESCE(@ShipPostalCode, ShipPostalCode) END,
ShipCountry = CASE WHEN @ShipCountry = '' THEN NULL
ELSE COALESCE(@ShipCountry, ShipCountry) END
--ADD any new columns
FROM dbo.Orders T
WHERE T.OrderID = @int_OrderID
--TODO No EditNo column found
-- AND (@EditNo = -1 OR T.xxx_zEditNo = @EditNo)
-- Include the EditNo column when one exists
SELECT @intErrNo = @@ERROR, @intRowCount = @@ROWCOUNT
if @intErrNo <> 0
BEGIN
SELECT @intRetVal = -5,
@strErrMsg = @strErrMsg + 'Error UPDATING Orders. '
END
END

IF @intRetVal = 0 AND @intRowCount <> 1 -- No error but rowcount wrong
BEGIN
SELECT @intRetVal = -1, -- Incorrect row count (should be 1).
@strErrMsg = @strErrMsg + 'Row count error ['
+ CONVERT(varchar(20), @intRowCount) + '] '
IF @intDebug >= 1 SELECT [nw_SP_SOP_ORD_Save DEBUG(11)]='',
[@EditNo]=@EditNo /* , EditNo */ FROM dbo.Orders WHERE OrderID = @int_OrderID
END

nw_SP_SOP_ORD_Save_ABORT:
IF @intRetVal = 0 AND @intValErrCount = 0
BEGIN
COMMIT TRANSACTION nw_SP_SOP_ORD_Save_01
END
ELSE
BEGIN
ROLLBACK TRANSACTION nw_SP_SOP_ORD_Save_02
COMMIT TRANSACTION nw_SP_SOP_ORD_Save_01
IF @intDebug >= 1 SELECT [nw_SP_SOP_ORD_Save DEBUG(9)]='ROLLBACK',
[@intRetVal]=@intRetVal
END
END

nw_SP_SOP_ORD_Save_EXIT:
-- Return any Validation Errors to the user
IF @intSilent=0 OR @intDebug >= 1
BEGIN
If any Validation Errors AND we are not Silent Running, return a ResultSet to the Application
We use a special First Column Name which is detected by the "Next RecordSet" function and acted upon

SELECT
[_RK_COMMAND] = 'VAL_ERROR',
[VAL_COUNT] = @intValErrCount,
[VAL_ERROR] = @strValMsg,
[LOG_SP] = 'nw_SP_SOP_ORD_Save'
Additional columns can be returned to Application. Any IDENTITY column is automatically included
, [OrderID] = @OrderID
END
IF @intSilent<>0 AND @intValErrCount<>0 AND @intRetVal=0 SELECT @intRetVal=-999

--TODO Activate next line if a ResultSet (rather than a Return Value) is required
-- SELECT [intRetVal] = @intRetVal, [intRowCount] = @intRowCount

IF (@intRetVal NOT IN (0, -999) AND @intSilent=0) OR @intDebug >= 1
SELECT [ERROR] = @strErrMsg, [RetVal] = @intRetVal, [ErrNo] = @intErrNo,
[SP] = 'nw_SP_SOP_ORD_Save', [Params] = @strParameters

EXEC dbo.kk_SM_SP_UpdateSPLog @sm_yppl_ID, @intRetVal, @strErrMsg, NULL, @intRowCount, @intErrNo

IF @intDebug >= 1 SELECT [nw_SP_SOP_ORD_Save]='RETURN', [@intRetVal]=@intRetVal,
[Elapsed (sec)] = DATEDIFF(Second, @dtNow, GetDate()), [@intValErrCount]=@intValErrCount,
[@intErrNo]=@intErrNo, [@@TRANCOUNT]=@@TRANCOUNT, [@strValMsg]=@strValMsg,
[@strErrMsg]=@strErrMsg, *
FROM dbo.kk_SM_yPPL_PageProcedureLog WHERE yppl_ID = @sm_yppl_ID

SET NOCOUNT OFF

RETURN @intRetVal -- Return error number, 0=No error

/* TEST RIG

--TODO Set up test sample here:

--Note: All parameters are optional, leave out anything you don't need
EXEC @intErrNo = dbo.nw_SP_SOP_ORD_Save
@ysvl_ID,
@OrderID = '', -- int REQUIRED.
@CustomerID = '', -- nchar(5)
@EmployeeID = '', -- int
@OrderDate = '', -- datetime
@RequiredDate = '', -- datetime
@ShippedDate = '', -- datetime
@ShipVia = '', -- int
@Freight = '', -- money
@ShipName = '', -- nvarchar(40)
@ShipAddress = '', -- nvarchar(60)
@ShipCity = '', -- nvarchar(15)
@ShipRegion = '', -- nvarchar(15)
@ShipPostalCode = '', -- nvarchar(10)
@ShipCountry = '', -- nvarchar(15)
--ADD any new columns
-- @intMode = 0,
@intValErrCount = @intValErrCount OUTPUT,
@strValMsg = @strValMsg OUTPUT,
@intSilent = 1
,@intDebug = @intDebug

-- Recent records
SELECT TOP 10 * FROM dbo.Orders

*/
--================== nw_SP_SOP_ORD_Save ==================--
GO

IF exists (SELECT * FROM sysobjects WHERE id = object_id(N'[dbo].[nw_SP_SOP_ORD_Save]')
AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
GRANT EXECUTE ON dbo.nw_SP_SOP_ORD_Save TO IIS
GO
PRINT 'Create procedure nw_SP_SOP_ORD_Save DONE'
GO

PRINT 'Create procedure nw_SP_SOP_ORD_Del'
GO
EXEC dbo.kk_SM_SP_LogScriptRun 'nw_SP_SOP_ORD_Del', '051017'
GO
IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'[dbo].[nw_SP_SOP_ORD_Del]')
AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE dbo.nw_SP_SOP_ORD_Del
GO

CREATE PROCEDURE dbo.nw_SP_SOP_ORD_Del
@ysvl_ID int, -- Session Visit Log ID

@OrderID int,
--TODO No EditNo column found
-- @EditNo int, -- n=EditNumber/version of record to be deleted
-- (PK and EditNo MUST match the record to be deleted)
--ADD any new columns

@intSilent int=0, -- 1=Silent on all ERROR resultsets
-- (only used if this SP can be called from another SP,
-- rather than an ASP page where it is desireable to see
-- errors during debugging)
@intDebug int=0 -- 0=No debugging data, 1+ = debugging at increasing levels of details

/* WITH ENCRYPTION */
AS
/*
* nw_SP_SOP_ORD_Del Delete a record from Orders
*
* Returns:
*
* Result code (0=No error)
*
* ERRORS:
*
-- TODO Need error return codes, and their descriptions
* -1 Error during DELETE
* -2 No record deleted
* -3 Multiple records deleted
*
* HISTORY:
*
* 17-Oct-2005 KRIS Started
*/

SET NOCOUNT ON
SET XACT_ABORT ON
SET ARITHABORT ON
-- System variables
DECLARE @intErrNo int,
@strErrMsg varchar(255),
@intRetVal int,
@sm_yppl_ID int,
@dtNow datetime,
@strParameters varchar(1000),
@intRowCount int

-- Local variables
--TODO Declare any local variables here
-- DECLARE @strMyVar1 varchar(255),
-- @intMyVar2 int

SELECT @dtNow = GetDate(),
@intErrNo = 0, -- Assume no error
@strErrMsg = '',
@intRetVal = 0 -- Return value (Assume no error)

SELECT @strParameters = left(
COALESCE(CONVERT(varchar(20), @OrderID), 'NULL')
--ADD any new columns

, 1000)

EXEC @sm_yppl_ID = dbo.kk_SM_SP_LogSP @ysvl_ID, 'nw_SP_SOP_ORD_Del', @strParameters

BEGIN TRANSACTION nw_SP_SOP_ORD_Del_01
SAVE TRANSACTION nw_SP_SOP_ORD_Del_02

DELETE D
FROM dbo.Orders D
WHERE
D.OrderID = @OrderID
--TODO No EditNo column found
-- AND D.xxx_zEditNo = @EditNo -- Include the EditNo column when one exists
SELECT @intErrNo = @@ERROR, @intRowCount = @@ROWCOUNT

IF @intErrNo <> 0
BEGIN
SELECT @intRetVal = -1,
@strErrMsg = @strErrMsg + 'Error during DELETE. '
END

IF @intErrNo = 0 AND @intRowCount = 0 -- No error but rowcount wrong
BEGIN
SELECT @intRetVal = -2,
@strErrMsg = @strErrMsg + 'No record deleted. '
SELECT @strErrMsg = @strErrMsg
--TODO No EditNo column found
-- + CASE WHEN EditNo <> @EditNo THEN 'EditNo '
-- + COALESCE(CONVERT(varchar(20), EditNo), 'NULL') + ' <> '
-- + COALESCE(CONVERT(varchar(20), @EditNo), 'NULL') + '. '
-- ELSE '' END
FROM dbo.Orders
WHERE OrderID = @OrderID
IF @@ROWCOUNT = 0 SELECT @strErrMsg = @strErrMsg + 'Record does not exist '
+ 'OrderID = ' + COALESCE(''''
+ CONVERT(varchar(20), @OrderID) + '''', 'NULL')
+ ' '
END

IF @intErrNo = 0 AND @intRowCount > 1 -- No error but rowcount wrong
BEGIN
SELECT @intRetVal = -3,
@strErrMsg = @strErrMsg + 'Multiple records deleted. '
END

nw_SP_SOP_ORD_Del_ABORT:
IF @intRetVal = 0
BEGIN
COMMIT TRANSACTION nw_SP_SOP_ORD_Del_01
END
ELSE
BEGIN
ROLLBACK TRANSACTION nw_SP_SOP_ORD_Del_02
COMMIT TRANSACTION nw_SP_SOP_ORD_Del_01
END
nw_SP_SOP_ORD_Del_EXIT:

IF (@intRetVal <> 0 AND @intSilent=0) OR @intDebug >= 1 SELECT [ERROR] = @strErrMsg,
[RetVal] = @intRetVal, [ErrNo] = @intErrNo, [SP] = 'nw_SP_SOP_ORD_Del',
[Params] = @strParameters

EXEC dbo.kk_SM_SP_UpdateSPLog @sm_yppl_ID, @intRetVal, @strErrMsg, NULL, @intRowCount, @intErrNo

IF @intDebug >= 1 SELECT [nw_SP_SOP_ORD_Del]='RETURN', [@intRetVal]=@intRetVal,
[Elapsed (sec)] = DATEDIFF(Second, @dtNow, GetDate()), [@intErrNo]=@intErrNo,
[@@TRANCOUNT]=@@TRANCOUNT, [@strErrMsg]=@strErrMsg, *
FROM dbo.kk_SM_yPPL_PageProcedureLog WHERE yppl_ID = @sm_yppl_ID

SET NOCOUNT OFF

RETURN @intRetVal -- Return error number, 0=No error

/* TEST RIG

--TODO Set up test sample here:

EXEC nw_SP_SOP_ORD_Del 123, 'MyPK1', 1 ,@intDebug=1

SELECT TOP 10 * FROM dbo.Orders

*/
--================== nw_SP_SOP_ORD_Del ==================--
GO

IF exists (SELECT * FROM sysobjects WHERE id = object_id(N'[dbo].[nw_SP_SOP_ORD_Del]')
AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
GRANT EXECUTE ON dbo.nw_SP_SOP_ORD_Del TO IIS
GO
PRINT 'Create procedure nw_SP_SOP_ORD_Del DONE'
GO
Go to Top of Page
    Next Page

- Advertisement -