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.
| 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 |
 |
|
|
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 |
 |
|
|
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? |
 |
|
|
Kristen
Test
22859 Posts |
Posted - 2005-10-13 : 09:26:15
|
| Not that I can think of, no.Kristen |
 |
|
|
madhivanan
Premature Yak Congratulator
22864 Posts |
|
|
Marioi
Posting Yak Master
132 Posts |
|
|
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 |
 |
|
|
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? |
 |
|
|
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. |
 |
|
|
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 |
 |
|
|
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. |
 |
|
|
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 |
 |
|
|
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? |
 |
|
|
Kristen
Test
22859 Posts |
|
|
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. |
 |
|
|
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 |
 |
|
|
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. |
 |
|
|
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 ScaffoldOur 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 |
 |
|
|
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 tablesIn 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. |
 |
|
|
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? KristenStandard SProcs mechanically generatedWe 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'GOLog 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'GOIF 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_GetGOCREATE PROCEDURE dbo.nw_SP_SOP_ORD_GetAll 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 columnsStandard 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 ONSET XACT_ABORT ONSET ARITHABORT ON-- System variablesThese are used by every SprocDECLARE @intErrNo int, @strErrMsg varchar(255), @intRetVal int, @sm_yppl_ID int, @dtNow datetime, @strParameters varchar(1000), @intRowCount int-- Local variablesThese 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. ' ENDnw_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] = @strParametersUpdate 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 RIGSet 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=1SELECT TOP 10 * FROM dbo.Orders */--================== nw_SP_SOP_ORD_Get ==================--GOIF 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 IISGOPRINT 'Create procedure nw_SP_SOP_ORD_Get DONE'GOPRINT 'Create procedure nw_SP_SOP_ORD_Save'GOEXEC dbo.kk_SM_SP_LogScriptRun 'nw_SP_SOP_ORD_Save', '051017'GOIF 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_SaveGOCREATE PROCEDURE dbo.nw_SP_SOP_ORD_Save @ysvl_ID int, -- Session Visit Log IDAn 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 VARCHARIf 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 NULLBecause 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 columnsThe _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 ADDValidation 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 ONSET XACT_ABORT ONSET ARITHABORT ON-- System variablesDECLARE @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 intNon-String parameters are converted to their native datatypes-- Local variables to hold conversion of STRING parameters to correct data typeDECLARE @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', @strParametersThe 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 ENDNo 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 SectionPerform 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 SectionUse 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? BEGINNew 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 BEGINExisting 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 ENDnw_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 ENDnw_SP_SOP_ORD_Save_EXIT: -- Return any Validation Errors to the user IF @intSilent=0 OR @intDebug >= 1 BEGINIf any Validation Errors AND we are not Silent Running, return a ResultSet to the ApplicationWe 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 needEXEC @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 recordsSELECT TOP 10 * FROM dbo.Orders */--================== nw_SP_SOP_ORD_Save ==================--GOIF 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 IISGOPRINT 'Create procedure nw_SP_SOP_ORD_Save DONE'GOPRINT 'Create procedure nw_SP_SOP_ORD_Del'GOEXEC dbo.kk_SM_SP_LogScriptRun 'nw_SP_SOP_ORD_Del', '051017'GOIF 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_DelGOCREATE 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 ONSET XACT_ABORT ONSET ARITHABORT ON-- System variablesDECLARE @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. ' ENDnw_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 ENDnw_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=1SELECT TOP 10 * FROM dbo.Orders */--================== nw_SP_SOP_ORD_Del ==================--GOIF 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 IISGOPRINT 'Create procedure nw_SP_SOP_ORD_Del DONE'GO |
 |
|
|
Next Page
|
|
|
|
|