SQL Server 2008 - Keyword search using table Join - sql-server

Ok, I created a Stored Procedure that, among other things, is searching 5 columns for a particular keyword. To accomplish this, I have the keywords parameter being split out by a function and returned as a table. Then I do a Left Join on that table, using a LIKE constraint.
So, I had this working beautifully, and then all of the sudden it stops working. Now it is returning every row, instead of just the rows it needs.
The other caveat, is that if the keyword parameter is empty, it should ignore it.
Given what's below, is there A) a glaring mistake, or B) a more efficient way to approach this?
Here is what I have currently:
ALTER PROCEDURE [dbo].[usp_getOppsPaged]
#startRowIndex int,
#maximumRows int,
#city varchar(100) = NULL,
#state char(2) = NULL,
#zip varchar(10) = NULL,
#classification varchar(15) = NULL,
#startDateMin date = NULL,
#startDateMax date = NULL,
#endDateMin date = NULL,
#endDateMax date = NULL,
#keywords varchar(400) = NULL
AS
BEGIN
SET NOCOUNT ON;
;WITH Results_CTE AS
(
SELECT opportunities.*,
organizations.*,
departments.dept_name,
departments.dept_address,
departments.dept_building_name,
departments.dept_suite_num,
departments.dept_city,
departments.dept_state,
departments.dept_zip,
departments.dept_international_address,
departments.dept_phone,
departments.dept_website,
departments.dept_gen_list,
ROW_NUMBER() OVER (ORDER BY opp_id) AS RowNum
FROM opportunities
JOIN departments ON opportunities.dept_id = departments.dept_id
JOIN organizations ON departments.org_id=organizations.org_id
LEFT JOIN Split(',',#keywords) AS kw ON
(title LIKE '%'+kw.s+'%' OR
[description] LIKE '%'+kw.s+'%' OR
tasks LIKE '%'+kw.s+'%' OR
requirements LIKE '%'+kw.s+'%' OR
comments LIKE '%'+kw.s+'%')
WHERE
(
(#city IS NOT NULL AND (city LIKE '%'+#city+'%' OR dept_city LIKE '%'+#city+'%' OR org_city LIKE '%'+#city+'%'))
OR
(#state IS NOT NULL AND ([state] = #state OR dept_state = #state OR org_state = #state))
OR
(#zip IS NOT NULL AND (zip = #zip OR dept_zip = #zip OR org_zip = #zip))
OR
(#classification IS NOT NULL AND (classification LIKE '%'+#classification+'%'))
OR
((#startDateMin IS NOT NULL AND #startDateMax IS NOT NULL) AND ([start_date] BETWEEN #startDateMin AND #startDateMax))
OR
((#endDateMin IS NOT NULL AND #endDateMax IS NOT NULL) AND ([end_date] BETWEEN #endDateMin AND #endDateMax))
OR
(
(#city IS NULL AND
#state IS NULL AND
#zip IS NULL AND
#classification IS NULL AND
#startDateMin IS NULL AND
#startDateMax IS NULL AND
#endDateMin IS NULL AND
#endDateMin IS NULL)
)
)
)
SELECT *
FROM Results_CTE
WHERE RowNum >= #startRowIndex
AND RowNum < #startRowIndex + #maximumRows;
END

Query -
Can you show us the Previous version that was working?
Are you using any source control to keep track of the changes made?
My Suggestions
Like Predicate with %% seems very expensive and slow process. Is it possible for you to user Full Text Index ?
Declare local variable for each Input Parameter. Assign the corresponding column name to this variable. In case of City, as three columns are being used, so declare three variables for City and assign the corresponding column names, Like below...
Set #Local_city = 'city'
Set #Local_dept_city = 'dept_city'
finally you can use it in the Where clause. Doing this will exclude the below lines of code.
OR
(
(#city IS NULL AND
#state IS NULL AND
#zip IS NULL AND
#classification IS NULL AND
#startDateMin IS NULL AND
#startDateMax IS NULL AND
#endDateMin IS NULL AND
#endDateMin IS NULL)
So, I had this working beautifully, and then all of the sudden it
stops working. Now it is returning every row, instead of just the rows
it needs.
Are you sure about the operators being used in the query? I mean everywhere you have OR operator.
The other caveat, is that if the keyword parameter is empty, it should
ignore it.
Please check my above suggestion.
Final Query
ALTER PROCEDURE [dbo].[usp_getOppsPaged]
#startRowIndex int,
#maximumRows int,
#city varchar(100) = NULL,
#state char(2) = NULL,
#zip varchar(10) = NULL,
#classification varchar(15) = NULL,
#startDateMin date = NULL,
#startDateMax date = NULL,
#endDateMin date = NULL,
#endDateMax date = NULL,
#keywords varchar(400) = NULL
AS
BEGIN
SET NOCOUNT ON;
Declare #Local_city varchar(100)
declare #Local_dept_city varchar(100)
declare #Local_org_city varchar(100)
Declare #Local_state char(2)
Declare #Local_dept_state char(2)
Declare #Local_org_state char(2)
Declare #Local_zip varchar(10)
Declare #Local_dept_zip varchar(10)
Declare #Local_org_zip varchar(10)
Declare #Local_classification varchar(15)
Declare #Local_startDateMin date
Declare #Local_startDateMax date
Declare #Local_endDateMin date
Declare #Local_endDateMin date
Declare #Local_endDateMax date
Set #Local_city = 'city'
Set #Local_dept_city = 'dept_city'
Set #Local_org_city = 'org_city'
Set #Local_state = 'state'
Set #Local_dept_state = 'dept_state'
Set #Local_org_state = 'org_state'
Set #Local_zip = 'zip'
Set #Local_dept_zip = 'dept_zip'
Set #Local_org_zip = 'org_zip'
Set #Local_classification = 'classification'
Set #Local_startDateMax = 'startDateMax'
Set #Local_endDateMin = 'endDateMin'
Set #Local_endDateMin = 'endDateMin'
Set #Local_endDateMax = 'endDateMax'
;WITH Results_CTE AS
(
SELECT opportunities.*,
organizations.*,
departments.dept_name,
departments.dept_address,
departments.dept_building_name,
departments.dept_suite_num,
departments.dept_city,
departments.dept_state,
departments.dept_zip,
departments.dept_international_address,
departments.dept_phone,
departments.dept_website,
departments.dept_gen_list,
ROW_NUMBER() OVER (ORDER BY opp_id) AS RowNum
FROM opportunities
JOIN departments ON opportunities.dept_id = departments.dept_id
JOIN organizations ON departments.org_id=organizations.org_id
LEFT JOIN Split(',',#keywords) AS kw ON
(title LIKE '%'+kw.s+'%' OR
[description] LIKE '%'+kw.s+'%' OR
tasks LIKE '%'+kw.s+'%' OR
requirements LIKE '%'+kw.s+'%' OR
comments LIKE '%'+kw.s+'%')
WHERE
(
(#city IS NOT NULL AND (city LIKE '%'+#Local_city+'%' OR dept_city LIKE '%'+#Local_dept_city+'%' OR org_city LIKE '%'+#Local_org_city+'%'))
OR
(#state IS NOT NULL AND ([state] = #Local_state OR dept_state = #Local_dept_state OR org_state = #Local_org_state))
OR
(#zip IS NOT NULL AND (zip = #Local_zip OR dept_zip = #Local_dept_zip OR org_zip = #Local_org_zip))
OR
(#classification IS NOT NULL AND (classification LIKE '%'+#Local_classification+'%'))
OR
((#startDateMin IS NOT NULL AND #Local_startDateMax IS NOT NULL) AND ([start_date] BETWEEN #Local_startDateMax AND #Local_startDateMax))
OR
((#endDateMin IS NOT NULL AND #Local_endDateMax IS NOT NULL) AND ([end_date] BETWEEN #Local_endDateMin AND #Local_endDateMax))
)
)
SELECT *
FROM Results_CTE
WHERE RowNum >= #startRowIndex
AND RowNum < #startRowIndex + #maximumRows;
END

Related

How to make the stored procedure run faster without using several SPs with different names instead of 1?

I have a stored procedure with all the fields of a table as input values as following. By sending any of items and letting other input items NULL from application, we can get the required data.
ALTER procedure [APP].[CRM_Action_Select]
#ID int = null ,
#DashbordRefrenceID int = null ,
#Refrence int = null ,
#Description NVARCHAR(max) = null ,
#AttemptCode UNIQUEIDENTIFIER = null ,
#AttemptName NVARCHAR(100),
#OrgAttemptCode INT = null ,
#OrgAttemptName NVARCHAR(100) = null ,
#OP_CheckerCode UNIQUEIDENTIFIER =NULL,
#OP_CheckerName NVARCHAR(100)=NULL,
#Org_ChekerCode INT=NULL,
#Org_ChekerName NVARCHAR(100)=NULL,
#ActionDate VARCHAR(50)=NULL,
#CheckDate VARCHAR(50)=NULL,
#ActionDescription NVARCHAR(max),
#RealyAction int = null,
#IsRead int = null
as
begin
select *
from [app].[CRM_Action] with(nolock)
where
(#ID is null or ID = #ID) and
(#DashbordRefrenceID is null or DashbordRefrenceID = #DashbordRefrenceID) AND
(#Refrence is null or Refrence = #Refrence) and
(#Description is null or Description = #Description) and
(#AttemptCode is null or AttemptCode = #AttemptCode) and
(#AttemptName is null or AttemptName = #AttemptName) AND
(#OrgAttemptCode is null or OrgAttemptCode = #OrgAttemptCode) and
(#OrgAttemptName is null or OrgAttemptName = #OrgAttemptName) and
(#OP_CheckerCode is null or OP_CheckerCode = #OP_CheckerCode) and
(#OP_CheckerName is null or OP_CheckerName = #OP_CheckerName) and
(#Org_ChekerCode is null or Org_ChekerCode = #Org_ChekerCode) AND
(#Org_ChekerName is null or Org_ChekerName = #Org_ChekerName) and
(#ActionDate is null or ActionDate = #ActionDate) and
(#CheckDate is null or CheckDate = #CheckDate) and
(#ActionDescription is null or #ActionDescription = #ActionDescription) and
(#RealyAction is null or RealyAction = #RealyAction) and
(#IsRead is null or IsRead = #IsRead)
order by ID
end
The problem is as the table grows this procedure slows down. By making other procedures, I found out that checking the IS NULL condition in big tables makes it slow down.
I searched for overloading stored procedures, but it seems it is not possible in SQL Server.
Is there any solution for this without having several stored procedures with different names?
This is an ideal candidate for the query hint OPTION(RECOMPILE), which will take into account the actual values of the parameters rather than create an execution plan for any possible parameter.
If the actual values are taken into account, the execution plan can eliminate the parts that should not be evaluated. E.g. if #ID is in fact NULL, that condition can be removed. That is because (TRUE OR X) is always TRUE, and in a series of conditions in the style of X AND Y AND Z ..., if X is TRUE this is equivalent to Y AND Z ....
At the end of the query, add the following:
OPTION(RECOMPILE)
See query hints for more details.
You can simply create a dynamic query by setting a base query and extending content based on parameters specified:
#Query = 'select * from [app].[CRM_Action] with(nolock)'
IF (#Id IS NOT NULL)
#Query += ' WHERE Id = #Id '
IF (#DashbordRefrenceID IS NOT NULL)
#Query += ' AND DashbordRefrenceID = #DashbordRefrenceID '
IF (#Description IS NOT NULL)
#Query += ' AND Description = #Description '
At the end only execute #Query:
EXECUTE sp_executesql #Query

Is this T-SQL stored procedure written correctly?

I am new to SQL Server and T-SQL, but I do have some experience building applications in MS Access.
This stored procedure runs fine when I execute it from the application, however when I am debugging it in SSMS, I get an error
Unable to Step. Invalid Operation.
but it will allow me to step through. Based on my research, it seems like I am creating a race condition but I have not been able to correctly fix the issue. I would also appreciate any advice to optimize this or fix any issues that are apparent.
What the code does:
This code is to enter a new customer into the database. The first select statement looks for an existing ID. If the ID is null, it will add a new customer. If the ID is not null, it will not add the customer. The same goes for the second IF statement to check if #pName2 and #pName3 are null before inserting these values.
Here is my code:
#pUID nvarchar(16) = null,
#pName1 nvarchar(50) = null,
#pName2 nvarchar(50) = null,
#pName3 nvarchar(50) = null,
#pAddress1 nvarchar(30) = null,
#pAddress2 nvarchar(30) = null,
#pAddress3 nvarchar(30) = null,
#pZipCode nvarchar(30) = null
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ID INT
SELECT #ID = ID FROM tblCustomer WHERE strUID = #pUID
IF #ID IS NULL
BEGIN
DECLARE #Customer_ID INT
INSERT INTO tblCustomer(strUID, strName)
VALUES(#pUID, #pName1)
SET #Customer_ID = ##IDENTITY
IF (#pName2 <> '') OR (#pName3 <> '')
BEGIN
INSERT INTO tblSecondaryCustomer(CustomerID, strName2, strName3)
VALUES(#Customer_ID, #pName2, #pName3)
END
INSERT INTO tblAddress(CustomerID, strAddress1, strAddress2, strAddress3, strZipCode)
VALUES(#Customer_ID, #pAddress1, #pAddress2, #pAddress3, #pZipCode)
END
END
Try replacing your IF statement with the following:
IF NOT EXISTS(SELECT ID FROM tblCustomer WHERE strUID = #pUID)
It doesn't seem your using #ID other than a check for existence...and you can use the ISNULL function to make sure you cover NULL cases...
IF (ISNULL(#pName2,'') <> '') OR (ISNULL(#pName3,'') <> '')
HTH
Dave

Stored procedure Date From and Date To

I am trying to create a stored procedure for filtering orders. Basically the users have the option of filtering the order by date from and date to. So they can do search via date from, date to or use both if it makes sense?
Anyhow here is my SQL Server stored procedure so far
ALTER PROCEDURE [dbo].[CN_GetOrderItemByCustID]
#CustomerID int,
#OrderItemWRClass varchar(max) = NULL,
#OrderItemSKUName varchar(50) = NULL,
#OrderItemDateFrom Datetime,
#OrderItemDateTo Datetime
AS
BEGIN
SET NOCOUNT ON;
IF DATEDIFF(d, #OrderItemDateFrom, '01/01/1970') = 0
SET #OrderItemDateFrom = null
IF DATEDIFF(d, #OrderItemDateTo, '01/01/1970') = 0
SET #OrderItemDateTo = null
-- Insert statements for procedure here
SELECT
COM_OrderItem.OrderItemID, COM_Order.OrderID,
COM_Order.OrderDate, COM_OrderItem.OrderItemUnitCount,
COM_OrderItem.OrderItemStatus, COM_OrderItem.OrderItemSKUNAME,
COM_OrderItem.OrderItemSKUID
FROM
COM_OrderItem
INNER JOIN
COM_Order ON COM_Order.OrderID = COM_OrderItem.OrderItemOrderID
WHERE
COM_Order.OrderCustomerID = #CustomerID
OR COM_OrderItem.OrderItemWRClass LIKE #OrderItemWRClass + '%'
OR COM_OrderItem.OrderItemSKUName LIKE #OrderItemSKUName + '%'
OR CONVERT(VARCHAR, COM_Order.OrderDate, 120) LIKE #OrderItemDateFrom + '%'
ORDER BY
COM_Order.OrderDate DESC
However I am not sure on how to put the date from (OrderItemDateFrom) and date to (OrderItemDateTo) in the final SQL statement?
Should I be using OR CONVERT(VARCHAR, COM_Order.OrderDate, 120) LIKE #OrderItemDateFrom + '%' -- which gives me an error
Conversion failed when converting date and/or time from character string.
I know in a normal SQL query I would use Between OrderItemDateFrom and OrderItemDateTo
Thanks
Use this logic
ALTER PROCEDURE [dbo].[CN_GetOrderItemByCustID]
-- Add the parameters for the stored procedure here
#CustomerID int,
#OrderItemWRClass varchar(max) = NULL,
#OrderItemSKUName varchar(50) = NULL,
#OrderItemDateFrom Datetime,
#OrderItemDateTo Datetime
AS
BEGIN
SET NOCOUNT ON;
IF DATEDIFF(d,#OrderItemDateFrom,'01/01/1970')=0 SET #OrderItemDateFrom = '01/01/1970';
IF DATEDIFF(d,#OrderItemDateTo,'01/01/1970')=0 SET #OrderItemDateTo = '31/12/2199';
-- Insert statements for procedure here
SELECT COM_OrderItem.OrderItemID, COM_Order.OrderID, COM_Order.OrderDate, COM_OrderItem.OrderItemUnitCount, COM_OrderItem.OrderItemStatus, COM_OrderItem.OrderItemSKUNAME,
COM_OrderItem.OrderItemSKUID
FROM COM_OrderItem
INNER JOIN COM_Order ON COM_Order.OrderID = COM_OrderItem.OrderItemOrderID
WHERE COM_Order.OrderCustomerID = #CustomerID OR COM_OrderItem.OrderItemWRClass LIKE #OrderItemWRClass + '%' OR COM_OrderItem.OrderItemSKUName LIKE #OrderItemSKUName + '%'
OR (COM_OrderDate>=#OrderItemDateFrom && COM_OrderDate<=#OrderItemDateTo )
ORDER BY COM_Order.OrderDate DESC
Try it . It should work.
Your logic can be simplified a little by allowing NULL values for #OrderItemDateFrom and #OrderItemDateTo. Also, if filters values and column values are all DATETIMEs, you should try to compare directly to allow indexes usages (if any applied on the DATETIME column):
ALTER PROCEDURE [dbo].[CN_GetOrderItemByCustID]
#CustomerID int,
#OrderItemWRClass varchar(max) = NULL,
#OrderItemSKUName varchar(50) = NULL,
#OrderItemDateFrom Datetime = NULL, -- TODO: change caller to not provide parameter, or leave it to null
#OrderItemDateTo Datetime = NULL -- TODO: change caller to not provide parameter, or leave it to null
AS
BEGIN
SET NOCOUNT ON;
-- when working with dates try to use an unambiguous format like 'YYYY-MM-DD'
SET #OrderItemDateFrom = ISNULL(#OrderItemDateFrom, '1970-01-01')
-- assign a very large date to act like not provided
-- going one day after to catch DATETIMEs with provided time
SET #OrderItemDateTo = DATEADD(day, 1, ISNULL(#OrderItemDateTo, '3000-01-01'))
-- Insert statements for procedure here
SELECT
COM_OrderItem.OrderItemID, COM_Order.OrderID,
COM_Order.OrderDate, COM_OrderItem.OrderItemUnitCount,
COM_OrderItem.OrderItemStatus, COM_OrderItem.OrderItemSKUNAME,
COM_OrderItem.OrderItemSKUID
FROM COM_OrderItem
INNER JOIN COM_Order ON COM_Order.OrderID = COM_OrderItem.OrderItemOrderID
WHERE COM_Order.OrderCustomerID = #CustomerID
OR COM_OrderItem.OrderItemWRClass LIKE #OrderItemWRClass + '%'
OR COM_OrderItem.OrderItemSKUName LIKE #OrderItemSKUName + '%'
-- between can be used
OR (COM_OrderDate BETWEEN #OrderItemDateFrom AND #OrderItemDateTo)
ORDER BY
COM_Order.OrderDate DESC
END
Another option is to use dynamic SQL and construct it based on parameters values (i.e. insert WHERE condition if filter value is provided). This is particularly useful when filters numbers is relatively low compared to the total number of filters, as ORs are not performance friendly.
NOTE: shouldn't your filters apply in conjuction (i.e. use AND instead of OR)? It would make sense to allow the user to filter by several value in the same time.
ALTER PROCEDURE [dbo].[CN_GetOrderItemByCustID]
#CustomerID int,
#OrderItemWRClass varchar(max) = NULL,
#OrderItemSKUName varchar(50) = NULL,
#OrderItemDateFrom Datetime,
#OrderItemDateTo Datetime
AS
BEGIN
SET NOCOUNT ON;
IF DATEDIFF(d, #OrderItemDateFrom, '01/01/1970') = 0
SET #OrderItemDateFrom = null
IF DATEDIFF(d, #OrderItemDateTo, '01/01/1970') = 0
SET #OrderItemDateTo = null
-- Insert statements for procedure here
SELECT
COM_OrderItem.OrderItemID, COM_Order.OrderID,
COM_Order.OrderDate, COM_OrderItem.OrderItemUnitCount,
COM_OrderItem.OrderItemStatus, COM_OrderItem.OrderItemSKUNAME,
COM_OrderItem.OrderItemSKUID
FROM
COM_OrderItem
INNER JOIN
COM_Order ON COM_Order.OrderID = COM_OrderItem.OrderItemOrderID
WHERE
COM_Order.OrderCustomerID = #CustomerID
OR COM_OrderItem.OrderItemWRClass LIKE #OrderItemWRClass + '%'
OR COM_OrderItem.OrderItemSKUName LIKE #OrderItemSKUName + '%'
OR (#OrderItemDateFrom IS NULL OR COM_Order.OrderDate >=#OrderItemDateFrom)
OR (#OrderItemDateTo IS NULL OR COM_Order.OrderDate <=#OrderItemDateTo)
ORDER BY
COM_Order.OrderDate DESC
You should Try this.
OR (#OrderItemDateFrom IS NULL OR COM_Order.OrderDate >=#OrderItemDateFrom)
OR (#OrderItemDateTo IS NULL OR COM_Order.OrderDate <=#OrderItemDateTo)
Just Edit and try this condition..

how to write update procedure with scope identity

My insert procedure is working fine the way i want. But update is not working with scope identity.
SET ANSI_NULLS ON
ALTER PROCEDURE [dbo].[spr_unitCreation]
(
#Unit_Name VARCHAR(50) = NULL,
#Unit_Abbreviation VARCHAR(50) = NULL,
#Unit_type VARCHAR(50) = NULL,
#Decimal_Places VARCHAR(50) = NULL,
#Description VARCHAR(50) = NULL,
#Super_Unit VARCHAR(50) = NULL,
#Per_Unit VARCHAR(50) = NULL,
#unit_Id INT OUTPUT,
#abc VARCHAR(50) = NULL
)
AS
BEGIN
IF #abc = 'update' BEGIN
SET NOCOUNT ON;
SELECT #unit_Id AS SCOPE_IDENTITY
UPDATE tbl_UnitCreation
SET Unit_Name = #Unit_Name,
Unit_Abbreviation = #Unit_Abbreviation,
Unit_type = #Unit_type,
Decimal_Places = #Decimal_Places,
Description = #Description,
Super_Unit = #Super_Unit,
Per_Unit = #Per_Unit
WHERE unit_Id = #unit_Id
END
END
SELECT * FROM tbl_UnitCreation
SCOPE_IDENTITY returns the last identity value inserted into an identity column. You are not inserting a row.
To update a row all you need is to pass a value to #unit_Id when executing [spr_unitCreation]. Also remove the line "SELECT #unit_Id AS SCOPE_IDENTITY" from your code.
Based on the comments, you need to find the correct id by searching on relevant details. So you can get the id like this:
SELECT #unit_Id = unit_Id
FROM tbl_UnitCreation
WHERE Unit_Name=#Unit_Name -- NB: Ensure this column contains your relevant details
Another commonly used option is to use the OUTPUT clause of the UPDATE statement, inserting all the updated/"inserted" primary keys and Unit_name into a tablevariable.
https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-2017

SQL Server procedure update and coalesce issue

So I am making a very simple update procedure, like I have done before, but when I run the execution through it, it does not update. I have looked this over several times and do not see any error in why it wouldn't cause the update to pass through it. My manual updates are working fine, so I am thinking I am either missing something super-obvious or this is something going on with the coalesce function. Any help would be great. The more eyes the merrier.
USE AdventureWorks2012
GO
CREATE PROCEDURE UpdateCreditCard
#CreditCardID INT,
#CardType nvarchar(50) = NULL,
#CardNumber nvarchar(25) = NULL,
#ExpMonth tinyint = NULL,
#ExpYear smallint = NULL
AS
BEGIN
UPDATE [Sales].[CreditCard]
SET
#CardType = COALESCE(#CardType,CardType),
#CardNumber = COALESCE(#CardNumber,CardNumber),
#ExpMonth = COALESCE(#ExpMonth,ExpMonth),
#ExpYear = COALESCE(#ExpYear,ExpYear)
FROM Sales.CreditCard
WHERE #CreditCardID = CreditCardID
END
EXECUTE UpdateCreditCard
#CreditCardID = 19267,
#CardType = 'MasterCard',
#CardNumber = '99999999',
#ExpMonth = 4,
#ExpYear = 2025
You are updating the variables not the columns in CreditCard table.
.....
SET
#CardType = COALESCE(#CardType,CardType), -- here you are updating the #CardType variable
.....
Try this.
CREATE PROCEDURE UpdateCreditCard
#CreditCardID INT,
#CardType nvarchar(50) = NULL,
#CardNumber nvarchar(25) = NULL,
#ExpMonth tinyint = NULL,
#ExpYear smallint = NULL
AS
BEGIN
UPDATE [Sales].[CreditCard]
SET
CardType = COALESCE(#CardType,CardType),
CardNumber = COALESCE(#CardNumber,CardNumber),
ExpMonth = COALESCE(#ExpMonth,ExpMonth),
ExpYear = COALESCE(#ExpYear,ExpYear)
FROM Sales.CreditCard
WHERE CreditCardID=#CreditCardID
END

Resources