Need help on SQL Server 2008 programming - sql-server

I am very new to SQL Server 2008 programming and trying to create a procedure.
Well, the requirement is 'The procedure returns data either based on the input parameter OR if no input data is given-it should do a default select and return all data qualifying'
I tried out with something like this-
CREATE PROCEDURE [dbo].[Proc_sampletestproc]
(#testid int)
AS
BEGIN
SET NOCOUNT OFF; ----I worked with Oracle PL/SQL and so do we need for mandatory this declarations
Here, I need to put a check that if the input testid has a value or not. If it exists, we have one case with a select or we do a default select.
Also, I am putting a direct SELECT with no joins on tables. How would I JOIN the tables along with OUTER JOINS because a testid may or may not have an insurance? I mean syntactically - syntax is quite different in SQL Server.
SELECT
T.TESTID, T.NAME, TI.INSURENAME
FROM
testinsured ti, test t, testinsuredHistory tih
WHERE
t.testid = ti.testid -----This entry may be there or not IN THE testinsured TABLE
AND tih.testinsuredid = ti.testinsuredid --A testid might have 2 Insurers whose history is stored here.
AND TIH.STARTDATE IS NOT NULL
AND TIH.ENDDATE IS NOT NULL --TO CHECK ACTIVE DATES FOR COVERAGE
Also, I want to do a group by on testid so that the name comes once but the InsuredPlanname comes accordingly once, twice as many as each Testid has.

if (#testid is not null)
begin
/* ... */
end
else
begin
SELECT T.TESTID,T.NAME,TI.INSURENAME
FROM test t
left outer join testinsured ti on t.testid = ti.testid -----This entry may be there or not IN THE testinsured TABLE
left outer join testinsuredHistory tih on ti.testinsuredid = tih.testinsuredid --A testid might have 2 Insurers whose history is stored here.
where TIH.STARTDATE IS NOT null AND TIH.ENDDATE IS NOT NULL
/* group by ... */
end

Related

choosing between two tables in SQL Server

Question for you - not sure if it is feasible however.
I have a scenario where I want to SELECT a set of data from one table if it exists there then if it doesn't I want to SELECT it from a different table where I know it will exist. The issue is they have slightly different field names in some cases. I am curious if a CASE clause would be the best way to do this?
i.e.:
SELECT example1,
example1a
FROM database 1 (if it exists)
if not SELECT from database 2 (where it will exist)
If this is column by column basis you can use this most likely.
SELECT
coalesce(db1.example1,db2.example1),
coalesce(db1.example1a, db2.example1a)
FROM
database1 db1
FULL OUTER JOIN
database2 db2 on
db1.id = db2.id
If you are wanting to choose one or the other, you can use exists()
if exists(select 1 from VW_ARUN_NORM_NEW WHERE REQ_CAT LIKE '%1000%' R REQ_CAT LIKE '%2000%')
begin
SELECT TOP
MATERIAL_NUMBER,
SALES_ORDER_NUMBER,
REQ_CAT,
PLANT,
REQUESTED_DELIV_DATE
FROM VW_ARUN_NORM_NEW
WHERE
REQ_CAT LIKE '%1000%'
OR REQ_CAT LIKE '%2000%'
end
else
begin
SELECT
MATERIAL,
SALES_ORDER_NUMBER,
REQUIREMENT_CATEGORY,
PLANT_CODE,
REQUESTED_DELIVERY_DATE
FROM
VW_MRP_ALLOCATION
WHERE
REQUIREMENT_CATEGORY LIKE '%5000%'
end
ISNULL() from an OUTER JOIN of the two tables is probably the best way.
you could use if exists clause example
IF EXISTS (select example1 from database1)
begin
--do something
end
else
begin
--do another query
end
or you can check if the table exists:
(select top 1 name from sys.tables where name = 'ACTIVSOC2')
but when you select the table of the query must exists.
Hope you help

SQL Server trigger to change NULL to empty strings

I'm migrating an Access database to a SQL Server 2014 Express back end and the application as it is is designed to put empty strings in some columns in some of the tables instead of NULL's (Access's behavior on attached forms).
It turns out I can't have the table not allow nulls in these columns because when attached to a bound form via ODBC attached tables Access explicitly is trying to insert NULL values when someone simply deletes the contents of a column, even if I have a default value defined on the table.
I want to say up front that I will be fixing this to handle NULLs properly, but for 'right now', my priority is to just get the back end converted to SQL operating the same as it was in Access, so I just want a trigger to change NULL values on a few fields to empty strings until such a time when I can look at all the application logic which is expecting empty strings in these fields right now and change it to handle NULLs.
I came up with the following:
CREATE TRIGGER TABLE1_ReplaceNulls
ON TABLE1
AFTER INSERT, UPDATE
AS
IF UPDATE(FIELDWNULL1)
BEGIN
UPDATE TABLE1
SET FIELDWNULL1 = ''
FROM inserted I
INNER JOIN TABLE1 ON I.PKFIELD = TABLE1.PKFIELD
WHERE I.FIELDWNULL1 IS NULL;
END;
This works fine for a single column. How best do I do this for multiple columns in the same table? I have one table with 4 columns that all could contain NULLS, but I want empty strings in place of them.
Should I do a separate IF block for each column that could contain the NULL or just handle it all at once? Of course, if I handle it all at once I would have to consider some of the columns might have legit values, but if I do separate statements then it could essentially run 4 updates after a column is inserted. Maybe it doesn't matter as this is all temporary, but just curious on other more experienced thoughts.
Using below update statement,update all four column in one trans. This code is not tested.
UPDATE TABLE1
SET FIELDWNULL1=iif(FIELDWNULL1 is null,'',FIELDWNULL1),
FIELDWNULL2=iif(FIELDWNULL2 is null,'',FIELDWNULL2),
FIELDWNULL3=iif(FIELDWNULL3 is null,'',FIELDWNULL3),
FIELDWNULL4=iif(FIELDWNULL4 is null,'',FIELDWNULL4)
FROM inserted I INNER JOIN TABLE1
ON I.PKFIELD = TABLE1.PKFIELD
New trigger code: With IIF statement
CREATE TRIGGER TABLE1_ReplaceNulls ON TABLE1
AFTER INSERT, UPDATE
AS
--IF UPDATE(FIELDWNULL1)
BEGIN
UPDATE TABLE1
SET FIELDWNULL1=iif(FIELDWNULL1 is null,'',FIELDWNULL1),
FIELDWNULL2=iif(FIELDWNULL2 is null,'',FIELDWNULL2),
FIELDWNULL3=iif(FIELDWNULL3 is null,'',FIELDWNULL3),
FIELDWNULL4=iif(FIELDWNULL4 is null,'',FIELDWNULL4)
FROM inserted I INNER JOIN TABLE1
ON I.PKFIELD = TABLE1.PKFIELD
--WHERE I.FIELDWNULL1 IS NULL;
END;
With ISNULL() function
CREATE TRIGGER TABLE1_ReplaceNulls ON TABLE1
AFTER INSERT, UPDATE
AS
--IF UPDATE(FIELDWNULL1)
BEGIN
UPDATE TABLE1
SET FIELDWNULL1=ISNULL(FIELDWNULL1,''),
FIELDWNULL2=ISNULL(FIELDWNULL2,''),
FIELDWNULL3=ISNULL(FIELDWNULL3,''),
FIELDWNULL4=ISNULL(FIELDWNULL4,'')
FROM inserted I INNER JOIN TABLE1
ON I.PKFIELD = TABLE1.PKFIELD
--WHERE I.FIELDWNULL1 IS NULL;
END;

SQL Server 2008 insert records then update fields based on another table

In my searches I have found many threads that deal with my issue in round about ways but I have not been able to find one answer that clearly defines the answer.
Yes I'm a noob to SQL Server.
Here is what I am trying to do And I need to know the best way to do it. For the purposes of this question I will keep it simple. Remember it is the functionality I want
I have table A
ClientName,
manager name,
Location
Table B
Manager name
Manager location
Table C
Random columns,
Client name,
Manager name
I want to
take the rows returned from SELECT [client name], [manager name] from table c
Insert those rows into table a with #clientname and #manager name parameters already defined.
Once the row is inserted it will make
tableA.location = Select [manager location]
from tableb
where #manager = tableb.[manager name]
These needs to happen in a batch for multiple rows in one call
This is point in time data so no lookups and no blanket update queries
Use functions?
Stored procedures?
Triggers may not be the answer since I will be inputting 500+ rows on each call
Thank you in advance
It is hard to believe you could not find something very closely related to your question. All you should have to do is
insert TableA
select C.ClientName, C.ManagerName, B.Location
from TableC C
join TableB B on B.ManagerName = C.ManagerName
where C.ManagerName = #ManagerName and C.ClientName = #clientname
Your stated usage case makes this look like you should wrap this inside of a stored proc. In fact, the statement above is the entire body of a stored proc
create proc sotest(#clientname varchar(40), #managername varchar(40)) as
begin
-- code above
end
It is not possible to avoid lookup of location from TableB as your have defined the problem (assuming you want to populated the Location column)

Use SQL variable for comparison in the same SELECT statement that sets it

How do I make the following T-SQL statement legal? I can copy the subquery that sets #Type variable for every CASE option, but I'd rather execute the subquery only once. Is it possible?
SELECT
#Type = (SELECT CustomerType FROM dbo.Customers WHERE CustomerId = (SELECT CustomerId FROM dbo.CustomerCategories WHERE CatId= #CatId)),
CASE
WHEN #Type = 'Consumer'THEN dbo.Products.FriendlyName
WHEN #Type = 'Company' THEN dbo.Products.BusinessName
WHEN #Type IS NULL THEN dbo.Products.FriendlyName
WHEN #Type = '' THEN dbo.Products.FriendlyName
END Product,
...
FROM
Products
INNER JOIN
Category
...
Edit: modified my example to be more concrete...have to run now...will be back tomorrow...sorry for signing off short but have to pick up kids :D will check back tomorrow. THX!!
Clarification: I can't separate the two: in the subquery's where-clasue, I need to refer to columns from tables that're used in the main query's join stmt. If I separate them, then #Type will lose relevance.
Why not just separate it into two operations? What do you think you gain by trying to glom them into a single statement?
SELECT #Type = (subquery);
SELECT CASE WHEN #type = 'Consumer'...
At the risk of sounding obtuse, do you really need the variable at all? Why not:
SELECT CASE WHEN col_form_subquery = 'Consumer' THEN ...
END Product
FROM (<subquery>) AS x;
With that form you'll need to decide whether you want to assign values to variables or retrieve results.
You can also pull multiple variables, e.g.
SELECT #Col1 = Col1, #Col2 = Col2
FROM (<subquery>) AS x;
-- then refer to those variables in your other query:
SELECT *, #Col1, #Col2 FROM dbo.Products WHERE Col1 = #Col2;
But this is all conjecture, because you haven't shared enough specifics.
EDIT okay, now that we have a real query and can understand a bit better what you're after, let's see if we can write you a new version. I'll assume that you were only trying to store the #Type variable so you can re-use it within the query, and that you weren't trying to store a value there to use later (after this query).
SELECT CASE
WHEN c.CustomerType = 'Company' THEN p.BusinessName
WHEN COALESCE(c.CustomerType, '') IN ('Consumer', '') THEN p.FriendlyName
END
--, other columns
FROM dbo.Products AS p
INNER JOIN dbo.Category AS cat
ON p.CatId = cat.CatId
INNER JOIN dbo.CustomerCategories AS ccat
ON ccat.CatId = cat.CatId
INNER JOIN dbo.Customers AS c
ON c.CustomerId = ccat.CustomerId
WHERE cat.CategoryId = #CatId;
Some notes:
I'm not sure why you thought subqueries are the right way to approach this. Usually it is much better (and clearer to other developers) to build proper joins and let SQL Server optimize the query overall instead of trying to be smart and optimize individual subqueries largely independent of the main query. A proper join will help to eliminate rows up front that would otherwise, through the subqueries, potentially be materialized - only to be discarded. Trust SQL Server to do its job, and in this case its job is to perform a join across multiple tables.
The join to dbo.Category might not be needed if the SELECT doesn't need to display the category name. If so then change the where clause and remove that join (join to CusomterCategories instead).
The second case can be changed to a simple ELSE if you've covered all the possible scenarios.
I made an assumption about the join between Products and Category (why is Category not plural like the others?). If this isn't it please fill us in.
You cannot not do that, you will get the following error
A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations.
separate the two and then return the variable as part of the select statement

SQL - Inserting and Updating Multiple Records at Once

I have a stored procedure that is responsible for inserting or updating multiple records at once. I want to perform this in my stored procedure for the sake of performance.
This stored procedure takes in a comma-delimited list of permit IDs and a status. The permit IDs are stored in a variable called #PermitIDs. The status is stored in a variable called #Status. I have a user-defined function that converts this comma-delimited list of permit IDs into a Table. I need to go through each of these IDs and do either an insert or update into a table called PermitStatus.
If a record with the permit ID does not exist, I want to add a record. If it does exist, I'm want to update the record with the given #Status value. I know how to do this for a single ID, but I do not know how to do it for multiple IDs. For single IDs, I do the following:
-- Determine whether to add or edit the PermitStatus
DECLARE #count int
SET #count = (SELECT Count(ID) FROM PermitStatus WHERE [PermitID]=#PermitID)
-- If no records were found, insert the record, otherwise add
IF #count = 0
BEGIN
INSERT INTO
PermitStatus
(
[PermitID],
[UpdatedOn],
[Status]
)
VALUES
(
#PermitID,
GETUTCDATE(),
1
)
END
ELSE
UPDATE
PermitStatus
SET
[UpdatedOn]=GETUTCDATE(),
[Status]=#Status
WHERE
[PermitID]=#PermitID
How do I loop through the records in the Table returned by my user-defined function to dynamically insert or update the records as needed?
create a split function, and use it like:
SELECT
*
FROM YourTable y
INNER JOIN dbo.splitFunction(#Parameter) s ON y.ID=s.Value
I prefer the number table approach
For this method to work, you need to do this one time table setup:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this function:
CREATE FUNCTION [dbo].[FN_ListToTableAll]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
(
----------------
--SINGLE QUERY-- --this WILL return empty rows
----------------
SELECT
ROW_NUMBER() OVER(ORDER BY number) AS RowNumber
,LTRIM(RTRIM(SUBSTRING(ListValue, number+1, CHARINDEX(#SplitOn, ListValue, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS ListValue
) AS InnerQuery
INNER JOIN Numbers n ON n.Number < LEN(InnerQuery.ListValue)
WHERE SUBSTRING(ListValue, number, 1) = #SplitOn
);
GO
You can now easily split a CSV string into a table and join on it:
select * from dbo.FN_ListToTableAll(',','1,2,3,,,4,5,6777,,,')
OUTPUT:
RowNumber ListValue
----------- ----------
1 1
2 2
3 3
4
5
6 4
7 5
8 6777
9
10
11
(11 row(s) affected)
To make what you need work, do the following:
--this would be the existing table
DECLARE #OldData table (RowID int, RowStatus char(1))
INSERT INTO #OldData VALUES (10,'z')
INSERT INTO #OldData VALUES (20,'z')
INSERT INTO #OldData VALUES (30,'z')
INSERT INTO #OldData VALUES (70,'z')
INSERT INTO #OldData VALUES (80,'z')
INSERT INTO #OldData VALUES (90,'z')
--these would be the stored procedure input parameters
DECLARE #IDList varchar(500)
,#StatusList varchar(500)
SELECT #IDList='10,20,30,40,50,60'
,#StatusList='A,B,C,D,E,F'
--stored procedure local variable
DECLARE #InputList table (RowID int, RowStatus char(1))
--convert input prameters into a table
INSERT INTO #InputList
(RowID,RowStatus)
SELECT
i.ListValue,s.ListValue
FROM dbo.FN_ListToTableAll(',',#IDList) i
INNER JOIN dbo.FN_ListToTableAll(',',#StatusList) s ON i.RowNumber=s.RowNumber
--update all old existing rows
UPDATE o
SET RowStatus=i.RowStatus
FROM #OldData o WITH (UPDLOCK, HOLDLOCK) --to avoid race condition when there is high concurrency as per #emtucifor
INNER JOIN #InputList i ON o.RowID=i.RowID
--insert only the new rows
INSERT INTO #OldData
(RowID, RowStatus)
SELECT
i.RowID, i.RowStatus
FROM #InputList i
LEFT OUTER JOIN #OldData o ON i.RowID=o.RowID
WHERE o.RowID IS NULL
--display the old table
SELECT * FROM #OldData order BY RowID
OUTPUT:
RowID RowStatus
----------- ---------
10 A
20 B
30 C
40 D
50 E
60 F
70 z
80 z
90 z
(9 row(s) affected)
EDIT thanks to #Emtucifor click here for the tip about the race condition, I have included the locking hints in my answer, to prevent race condition problems when there is high concurrency.
There are various methods to accomplish the parts you ask are asking about.
Passing Values
There are dozens of ways to do this. Here are a few ideas to get you started:
Pass in a string of identifiers and parse it into a table, then join.
SQL 2008: Join to a table-valued parameter
Expect data to exist in a predefined temp table and join to it
Use a session-keyed permanent table
Put the code in a trigger and join to the INSERTED and DELETED tables in it.
Erland Sommarskog provides a wonderful comprehensive discussion of lists in sql server. In my opinion, the table-valued parameter in SQL 2008 is the most elegant solution for this.
Upsert/Merge
Perform a separate UPDATE and INSERT (two queries, one for each set, not row-by-row).
SQL 2008: MERGE.
An Important Gotcha
However, one thing that no one else has mentioned is that almost all upsert code, including SQL 2008 MERGE, suffers from race condition problems when there is high concurrency. Unless you use HOLDLOCK and other locking hints depending on what's being done, you will eventually run into conflicts. So you either need to lock, or respond to errors appropriately (some systems with huge transactions per second have used the error-response method successfully, instead of using locks).
One thing to realize is that different combinations of lock hints implicitly change the transaction isolation level, which affects what type of locks are acquired. This changes everything: which other locks are granted (such as a simple read), the timing of when a lock is escalated to update from update intent, and so on.
I strongly encourage you to read more detail on these race condition problems. You need to get this right.
Conditional Insert/Update Race Condition
“UPSERT” Race Condition With MERGE
Example Code
CREATE PROCEDURE dbo.PermitStatusUpdate
#PermitIDs varchar(8000), -- or (max)
#Status int
AS
SET NOCOUNT, XACT_ABORT ON -- see note below
BEGIN TRAN
DECLARE #Permits TABLE (
PermitID int NOT NULL PRIMARY KEY CLUSTERED
)
INSERT #Permits
SELECT Value FROM dbo.Split(#PermitIDs) -- split function of your choice
UPDATE S
SET
UpdatedOn = GETUTCDATE(),
Status = #Status
FROM
PermitStatus S WITH (UPDLOCK, HOLDLOCK)
INNER JOIN #Permits P ON S.PermitID = P.PermitID
INSERT PermitStatus (
PermitID,
UpdatedOn,
Status
)
SELECT
P.PermitID,
GetUTCDate(),
#Status
FROM #Permits P
WHERE NOT EXISTS (
SELECT 1
FROM PermitStatus S
WHERE P.PermitID = S.PermitID
)
COMMIT TRAN
RETURN ##ERROR;
Note: XACT_ABORT helps guarantee the explicit transaction is closed following a timeout or unexpected error.
To confirm that this handles the locking problem, open several query windows and execute an identical batch like so:
WAITFOR TIME '11:00:00' -- use a time in the near future
EXEC dbo.PermitStatusUpdate #PermitIDs = '123,124,125,126', 1
All of these different sessions will execute the stored procedure in nearly the same instant. Check each session for errors. If none exist, try the same test a few times more (since it's possible to not always have the race condition occur, especially with MERGE).
The writeups at the links I gave above give even more detail than I did here, and also describe what to do for the SQL 2008 MERGE statement as well. Please read those thoroughly to truly understand the issue.
Briefly, with MERGE, no explicit transaction is needed, but you do need to use SET XACT_ABORT ON and use a locking hint:
SET NOCOUNT, XACT_ABORT ON;
MERGE dbo.Table WITH (HOLDLOCK) AS TableAlias
...
This will prevent concurrency race conditions causing errors.
I also recommend that you do error handling after each data modification statement.
If you're using SQL Server 2008, you can use table valued parameters - you pass in a table of records into a stored procedure and then you can do a MERGE.
Passing in a table valued parameter would remove the need to parse CSV strings.
Edit:
ErikE has raised the point about race conditions, please refer to his answer and linked articles.
If you have SQL Server 2008, you can use MERGE. Here's an article describing this.
You should be able to do your insert and your update as two set based queries.
The code below was based on a data load procedure that I wrote a while ago that took data from a staging table and inserted or updated it into the main table.
I've tried to make it match your example, but you may need to tweak this (and create a table valued UDF to parse your CSV into a table of ids).
-- Update where the join on permitstatus matches
Update
PermitStatus
Set
[UpdatedOn]=GETUTCDATE(),
[Status]=staging.Status
From
PermitStatus status
Join
StagingTable staging
On
staging.PermitId = status.PermitId
-- Insert the new records, based on the Where Not Exists
Insert
PermitStatus(Updatedon, Status, PermitId)
Select (GETUTCDATE(), staging.status, staging.permitId
From
StagingTable staging
Where Not Exists
(
Select 1 from PermitStatus status
Where status.PermitId = staging.PermidId
)
Essentially you have an upsert stored procedure (eg. UpsertSinglePermit)
(like the code you have given above) for dealing with one row.
So the steps I see are to create a new stored procedure (UpsertNPermits) which does
a) Parse input string into n record entries (each record contains permit id and status)
b) Foreach entry in above, invoke UpsertSinglePermit

Resources