SQL Script Needs improvements, Could use some tips - sql

I recently got a test to figure out how to optimize this SQL Script, I have never made a script, and only create queries to get data at my work. This is the Script.
The tables have valid data and joins, the #GUID is already declared and has NVARCHAR values.
This is the SQL script
I am pretty new to this, I need to optimize this script, and from what I know, its better to Join the query instead of using Sub-queries. (correct me if I am wrong)
From What I understand from this script, it is asking information from 2 different Databases
CUSTOMER_CARDNUMBER and PROFILE
I can't see the information in these Fake databases, but that is not part of this test.
In these databases it is asking for multiple information GENERATED_CARDNUMBER, and CUSTOMER_CARDNUMBER with the values = #GUID (which I don't completely understand)
IT WIll Begin a Update if the REVISION_Status = A, if GUID = #GUID what I understand this Script is looking for in both databases. and If it does not find it, it will set a REVISION_STATUS with a Date if the query does not find GUID = #GUID; ( Please correct me if wrong)
I could use help, in improving this script, how and why.. even if there are little mistakes.
Thanks!!
IF EXISTS
(
SELECT *
FROM CUSTOMER_CARDNUMBER
WHERE GENERATED_CARDNUMBER IN
(
SELECT CARDNUMBER
FROM Profile
WHERE Cardnumber IN
(
SELECT GENERATED_CARDNUMBER
FROM CUSTOMER_CARDNUMBER
WHERE CUSTOMER_CARDNUMBER = #GUID
)
)
)
BEGIN
UPDATE GUID_ACTION
SET
REVISION_STATUS = 'A',
REVISION_DATE_CREATED = GETDATE()
WHERE GUID = #GUID;
END;
ELSE
BEGIN
UPDATE GUID_ACTION
SET
REVISION_STATUS = 'F',
REVISION_DATE_CREATED = GETDATE()
WHERE GUID = #GUID;
END;

``UPDATE GUID_ACTION
SET REVISION_STATUS =
CASE WHEN (0 < select count(*) from CUSTOMER_CARDNUMBERCUSTOMER_CARDNUMBER
inner join PROFILE
ON PROFILE.Cardnumber = CUSTOMER_CARDNUMBER.GENERATED_CARDNUMBER
AND PROFILE.Cardnumber = #GUID
)
THEN 'A'
ELSE 'F'
END;
,GUID_ACTION.REVISION_DATE_CREATED = GETDATE()
WHERE CUSTOMER_CARDNUMBER.GUID = #GUID;
This is a another more cleaner way, the query script with the subquery was difficult to understand with no insight of the tables or declared #GUID

Related

Need tips to optimize SQL Server stored procedure

This is my stored procedure, it's taking some time to execute even though running with local database.
Please suggest changes in order to improve the performance
BEGIN TRY
DECLARE #COUNTRY_CD INT
SET #COUNTRY_CD =(SELECT COUNTRY_CD FROM COUNTRY WHERE COUNTRY_DESC = LTRIM(RTRIM(#COUNTRY_DESC)))
DECLARE #COMPANNY_CD INT
SET #COMPANNY_CD =(SELECT COMPANY_CD FROM COMPANY WHERE COMPANY_DESC = LTRIM(RTRIM(#COMPANY_DESC)))
BEGIN TRANSACTION
DELETE FROM PACK
WHERE COUNTRY_CD = #COUNTRY_CD
AND COMPANY_CD = #COMPANNY_CD
AND PACK_DESC = LTRIM(RTRIM(#PACK_DESC))
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF(##TRANCOUNT > 0)
ROLLBACK TRANSACTION
DECLARE #ErrMsg nvarchar(4000),
#ErrSeverity int
SELECT #ErrMsg = ERROR_MESSAGE(),#ErrSeverity = ERROR_SEVERITY()
RAISERROR(#ErrMsg, #ErrSeverity, 1)
END CATCH
Try to evaluate the values of the variables #COUNTRY_CD and
#COMPANNY_CD in a separate proc and pass them as i/p parameter to this
proc and see if it helps. I have seen this issue in the past and the
solution that I just mentioned solved the issue.
Hard to say exactly without knowing more about your database schema. A few initial ideas might be to cleanup the *_DESC variables right away rather than doing the LTRIM and RTRIM in the WHERE clause. Maybe consider or add to an index on the PACK table that includes COUNTRY_CD/COMPANY_CD (NOT description though, assuming it's long string text. I would think COMPANY and COUNTRY are pretty small tables, but hopefully you have the proper indexes on those fields. Might also be worth trying to join to those tables in DELETE rather than doing lookups ahead of time.
-- clenaup variables
-- these should be new vars, not input parms
SELECT #COUNTRY_DESC = LTRIM(RTRIM(#COUNTRY_DESC))
,#COMPANY_DESC = LTRIM(RTRIM(#COMPANY_DESC))
,PACK_DESC = LTRIM(RTRIM(#PACK_DESC ))
-- delete
DELETE PACK
FROM PACK
JOIN COUNTRY ON PACK.COUNTRY_CD = COUNTRY.COUNTRY_CD
JOIN COMPANY ON PACK.COMPANY_CD = COMPANY.COMPANY_CD
WHERE COUNTRY.COUNTRY_DESC = #COUNTRY_DESC
AND COMPANY.COMPANY_DESC = #COMPANY_DESC
AND PACK.PACK_DESC = #PACK_DESC
Try right clicking within your stored procedure and check the Estimated Executon Plan. You can see how "expensive" your SP will be.
If need be you could try
https://stackoverflow.com/a/797968/1504882
Make sure COMPANY is indexed on company_cd, COUNTRY on country_cd and PACK on company_cd,country_cd,pack_desc.
Delete from a large table would take some time without the right index.

Nested if statements in SQL Server stored procedure SELECT statement

I'm new here, and relatively new to stored procedures, so please bear with me! I've checked related questions on here and can't find anything that works in this instance.
I am trying to build a stored procedure (MS SQL Server 2005) that takes a number of passed in values and in effect dynamically builds up the SQL as you would with inline SQL.
This is where I've come unstuck.
We have (somewhat simplified for clarity):
#searchf1 varchar(100), -- search filter 1
#searchr1 varchar(100), -- search result 1
#searchf2 varchar(100), -- search filter 2
#searchr2 varchar(100), -- search result 2
#direction char(1), -- direction to order results in
AS
set nocount on
set dateformat dmy
SELECT *
FROM database.dbo.table T
WHERE T.deleted = 'n'
ORDER BY CASE #direction
WHEN 'A' THEN T.id
WHEN 'D' THEN T.id DESC
END
END
set nocount off
I have also tried the lines from ORDER BY as:
IF #direction = 'N' THEN
ORDER BY
T.id
ELSE
ORDER BY
T.id DESC
Both approaches give me an error along the lines:
"Incorrect syntax near the keyword 'DESC'." (which references the line id DESC following the final ORDER BY
As part of this stored procedure I also want to try to feed in matched pairs of values which reference a field to look up and a field to match it to, these could either be present or ''. To do that I need to add into the SELECT section code similar to:
WHERE
deleted = 'n'
IF #searchf1 <> '' THEN
AND fieldf1 = #searchf1 AND fieldr1 = #searchr1
This however generates errors like:
Incorrect syntax near the keyword 'IF'.
I know dynamic SQL of this type isn't the most elegant. And I know that I could do it with glocal IF ELSE statements, but if I did the SP would be thousands of lines long; there are going to up to 15 pairs of these search fields, together with the direction and field to order that direction on.
(the current version of this SP uses a passed in list of IDs to return generated by some inline dynamic SQL, through doing this I'm trying to reduce it to one hit to generate the recordset)
Any help greatly appreciated. I've hugely simplified the code in the above example for clarity, since it's the general concept of a nested IF statement with SELECT and ORDER BY that I'm inquiring about.
For this I would try to go with a more formal Dynamic SQL solution, something like the following, given your defined input parameters
DECLARE #SQL VARCHAR(MAX)
SET #SQL = '
SELECT
FROM
database.dbo.table T
WHERE
T.deleted = ''n'' '
--Do your conditional stuff here
IF #searchf1 <> '' THEN
SET #SQL = #SQL + ' AND fieldf1 = ' + #searchf1 + ' AND fieldr1 = ' + #searchr1 + ''' '
--Finish the query
SET #SQL = #SQL + ' ORDER BY xxx'
EXEC(#SQL)
DISCLAIMER: The use of Dynamic SQL is NOT something that should be taken lightly, and proper consideration should be taken in ALL circumstances to ensure that you are not open to SQL injection attacks, however, for some dynamic search type operations it is one of the most elegant route.
Try it this way:
SELECT * FROM database.dbo.table T WHERE T.deleted = 'n'
ORDER BY
CASE WHEN #direction='A' THEN T.id END ASC,
CASE WHEN #direction='D' THEN T.id END DESC
Source Article:
http://blog.sqlauthority.com/2007/07/17/sql-server-case-statement-in-order-by-clause-order-by-using-variable/
Another option that you might have, depending on the data type of your field, if nulls are NOT allowed, would be to do something like this.
SELECT *
FROM database.dbo.table T
WHERE T.deleted = 'n'
AND fieldf1 = COALESCE(#searchf1, fieldf1)
AND fieldr1 = COALESCE(#searchr1, fieldr1)
--ETC
ORDER BY fieldf1
This way you are not using dynamic SQL and it is fairly readable, just have the variable be null when you are looking to omit the data.
NOTE: As I mentioned this route will NOT work if any of the COALESCE columns contain null values.

How would you build one Select stored procedure to handle any request from a table?

I want to build a single select stored procedure for SQL 2005 that is universal for any select query on that table.
**Columns**
LocationServiceID
LocationID
LocationServiceTypeID
ServiceName
ServiceCode
FlagActive
For this table I may need to select by LocationServiceID, or LocationID, or LocationServiceTypeID or ServiceName or a combination of the above.
I'd rather not have a separate stored procedure for each of them.
I assume the best way to do it would be to build the 'WHERE' statement on NOT NULL. Something like
SELECT * FROM LocationServiceType WHERE
IF #LocationID IS NOT NULL (LocationID = #LocationID)
IF #LocationServiceID IS NOT NULL (LocationServiceID = #LocationServiceID)
IF #LocationServiceTypeID IS NOT NULL (LocationServiceTypeID = #LocationServiceTypeID)
IF #ServiceName IS NOT NULL (ServiceName = #ServiceName)
IF #ServiceCode IS NOT NULL (ServiceCode = #ServiceCode)
IF #FlagActive IS NOT NULL (FlagActive = #FlagActive)
Does that make sense?
here is the most extensive article I've ever seen on the subject:
Dynamic Search Conditions in T-SQL by Erland Sommarskog
here is an outline of the article:
Introduction
The Case Study: Searching Orders
The Northgale Database
Dynamic SQL
Introduction
Using sp_executesql
Using the CLR
Using EXEC()
When Caching Is Not Really What You Want
Static SQL
Introduction
x = #x OR #x IS NULL
Using IF statements
Umachandar's Bag of Tricks
Using Temp Tables
x = #x AND #x IS NOT NULL
Handling Complex Conditions
Hybrid Solutions – Using both Static and Dynamic SQL
Using Views
Using Inline Table Functions
Conclusion
Feedback and Acknowledgements
Revision History
First of all, your code will not work. It should look like this:
SELECT * FROM LocationServiceType WHERE
(#LocationID IS NULL OR (LocationID = #LocationID)
... -- all other fields here
This is totally valid and known as 'all-in-one query'. But from a performance point of view this is not a perfect solution as soon as you don't allow SQL Server to select optimal plan. You can see more details here.
Bottom line: if your top priority is 'single SP', then use this approach. In case you care about the performance, look for a different solution.
SELECT *
FROM LocationServiceType
WHERE LocationServiceID = ISNULL(#LocationServiceID,LocationServiceID)
AND LocationID = ISNULL(#LocationID,LocationID)
AND LocationServiceTypeID = ISNULL(#LocationServiceTypeID,LocationServiceTypeID)
AND ServiceName = ISNULL(#ServiceName,ServiceName)
AND ServiceCode = ISNULL(#ServiceCode,ServiceCode)
AND FlagActive = ISNULL(#FlagActive,FlagActive)
If a null value is sent in it will cancel out that line of the where clause, otherwise it will return rows that match the value sent in.
What I've always done is is set the incoming parameters to null if should be ignored in query
then check variable for null first, so if variable is null condition short circuits and filter is not applied. If variable has value then 'or' causes filter to be used. Has worked for me so far.
SET #LocationID = NULLIF(#LocationID, 0)
SET #LocationServiceID = NULLIF(#LocationServiceID, 0)
SET #LocationServiceTypeID = NULLIF(#LocationServiceTypeID, 0)
SELECT * FROM LocationServiceType WHERE
(#LocationID IS NULL OR LocationID = #LocationID)
AND (#LocationServiceID IS NULL OR LocationServiceID = #LocationServiceID)
AND (#LocationServiceTypeID IS NULL OR #LocationServiceTypeID = #LocationServiceTypeID)
etc...

In SQL, how do I allow for nulls in the parameter?

I have what seems to be a really easy SQL query I can't figure out and its driving me nuts. This is SQL 2008. Basically, there is a status field where the can pick "pending", "satisfied" or all. If they send in "pending" or "satisfied" there's no problem. But when they pick all I'm having problems. Mostly because I can't figure out how to get the records where this field is null to show up (because it has to be 'is null' instead of '= null'. (This is the way the data will come over; I have no control over that.)
The code I've been using does not work for nulls.
SELECT *
FROM Payment_Table
where Payment.Status_code = #status_id
You can try
SELECT Col1, Col2,...,Coln --Required Columns
FROM Payment_Table
where (Payment.Status_code = #status_id OR #status_id IS NULL)
Try:
SELECT *
FROM Payment_Table
WHERE Payment.Status_code = ISNULL(#status_id, Status_code)
This will return all payments.
Try
WHERE
((#status_id IS NULL) OR (Payment.Status_code = #status_id))
WHERE Payment_Table.Status = ISNULL(#StatusID, Payment_Table.Status)
It usually works better then OR
Edit: you want to select rows where Payment_Table.Status = NULL when #StatusID = NULL!!
SELECT * FROM Payment_Table where Payment.Status_code = #status_id
UNION ALL
SELECT * FROM Payment_Table where Payment.Status_code IS NULL AND #StatusID IS NULL
OR
...
WHERE
Payment_Table.Status #StatusID
OR
(Payment.Status_code IS NULL AND #StatusID IS NULL)
You can use coalesce or IsNull on your Payment.StatusCode field, this will allow you to do a substitution for null with a specific value.
SELECT *
FROM Payment_Table
WHERE (Payment.Status_code is null or Payment.Status_code = #status_id)
There are many approaches depending which version of sql server you are using. This articles has an in-depth description: Dynamic Search Conditions in T-SQL
The best way to do this is below. However, you MUST watch out for parameter sniffing. This will become an issue as your table gets bigger and will affect your execution times randomly. This is an annoying issue that can pop up. Use the code below.
CREATE PROCEDURE GetPaymentStatus
#StatusID varchar(50)=NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #StatusId_local varchar(50)
SET #StatusID_local = #StatusId
SELECT MyField1, MyField2
FROM Payment_Table
WHERE Payment_Table.Status=#StatusID_local
OR (#StatusID_local IS NULL AND Payment_Table.Status IS NULL)
END
Check out this article or google sql parameter sniffing for more info.

SQL Server 2000 stored procedure branching with parameters

I want to create a stored procedure. If the parameter is -1 then there should not be a where clause on that column else there should be a WHERE clause. What's the best way to do it without a lot of IF branching?
I checked the archive. There are a few similar questions but not exactly the same.
CREATE PROCEDURE report
(
#site int,
#promo int,
#type int
)
AS
SET NOCOUNT ON
-- I want to avoid this:
IF #site = -1 AND #promo = -1 and #type = -1
BEGIN
SELECT * from table
END
IF #site > -1 AND #promo = -1 and #type = -1
BEGIN
SELECT * from table WHERE site = #site;
END
... -- other cases
ELSE -- all parameters are > -1
BEGIN
SELECT * from table
WHERE site = #site AND promo = #promo AND type = #type
END
This works in many cases, (despite what the comments will say without trying it) because the optimiser will ignore the ISNULL bit. Only works for non-null columns
SELECT #site = NULLIF(#site, -1) ...
SELECT * from table
WHERE site = ISNULL(#site, site) ..
Otherwise, conditional WHERE which is usually bad because OR can not be optimised
SELECT * from table
WHERE (#site = -1 OR site = #site) AND (...
Or separate stored procedures (don't think you want that either)
Or use sp_executesql (avoids dynamic SQL)
How about:
SELECT * FROM table WHERE
((site = #site) OR (#site = -1)) AND
((promo = #promo) OR (#promo = -1)) AND
((type = #type) OR (#type = -1))
One caveat, though, you may find that SQL is not very intelligent in optimizing this sort of query.
why fight against the obvious, simplest solution?
seriously, the branching solution make the intent clear, and can easily be understood by others.