How to do a conditional JOIN with table valued parameter? - sql-server

Okay so I have spent some time researching this but cannot seem to find a good solution.
I am currently creating a stored procedure that takes a set of optional parameters. The stored procedure will act as the "universal search query" for multiple tables and columns.
The stored procedure looks something like this (Keep in mind that this is just a stripped down version and the actual stored procedure has more columns etc.)
The '#ProductIdsParam IntList READONLY' is an example table valued parameter that I would like to JOIN if it is not empty. In other words, the query should only search by parameters that are not null/empty.
Calling the procedure and parsing the other parameters works just like it should. I might however have misunderstood and should not do a "universal search query" like this at all.
CREATE PROCEDURE [dbo].[usp_Search]
#ProductIdParam INT = NULL,
#CustomerNameParam NVARCHAR(100) = NULL,
#PriceParam decimal = NULL,
-- THIS IS WHAT I'D LIKE TO JOIN. BUT THE TABLE CAN BE EMPTY
#ProductIdsParam IntList READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT DISTINCT
CustomerTransactionTable.first_name AS FirstName,
CustomerTransactionTable.last_name AS LastName,
ProductTable.description AS ProductDescription,
ProductTable.price as ProductPrice
FROM dbo.customer AS CustomerTransactionTable
-- JOINS
LEFT JOIN dbo.product AS ProductTable
ON CustomerTransactionTable.product_id = ProductTable.id
WHERE
(ProductTable.id = #ProductIdParam OR #ProductIdParam IS NULL)
AND (CustomerTransactionTable.first_name = #CustomerNameParam OR #CustomerNameParam IS NULL)
AND (CustomerTransactionTable.price = #PriceParam OR #PriceParam IS NULL)
END

You can add the int table in LEFT join and then add a where condition based on the record count in the filter table. If #ProductIdsParam is declared as table, you should first count records in it and store the result in a varaible.
AND COALESCE(#ProductIdsParam.id, 0) = (CASE WHEN #ProductIdsCount = 0 THEN 0 ELSE ProductTable.id END)
In case #ProductIdsCount = 0 then you get always 0 = 0 so you get all the records, else you select only records where the productId in the filter table equals the ProductTable.id.
There are other (maybe cleaner) approaches possible though but I think this works.

Related

Check null parameter in IN Clause

I have a stored procedure with a few parameters. One of them is a varchar containing a possible list of IDs (comma separated values i.e. 1, 2, 5, 9). I need the procedure to ignore the parameter when it is NULL. This is the way I actually do:
CREATE PROCEDURE PROC_TEST
#MAIN_ID INT = NULL,
#DETAIL_IDs VARCHAR(2000)
AS
BEGIN
SELECT ..... FROM TABLE1 INNER JOIN TABLE2 ON ...
WHERE
TABLE1.ID = ISNULL(#MAIN_ID, TABLE1.ID) AND
(
#DETAIL_IDs IS NULL
OR TABLE2.ID IN
(SELECT VALUE FROM STRING_SPLIT(#DETAIL_IDs,','))
)
END
It seems like the #DETAIL_IDs IS NULL part of the code requires a lot of time to execute: What am I doing wrong here?
Even if I remove the OR clause and simply add AND #DETAIL:IDs IS NULL it takes a long time.
The tables have more than 1 million records each.

select query on a table is hanging when a stored procedure to update the table is run

I've a simple stored procedure to update a table as follows:
This sp is updating the table properly. But when I execute select query on po_tran table, its hanging.
Is there any mistake in the stored procedure..?
alter procedure po_tran_upd #locid char(3)
as
SET NOCOUNT ON;
begin
update t
set t.lastndaysale = (select isnull(sum( qty)*-1, 0)
from exp_tran
where exp_tran.loc_id =h.loc_id and
item_code = t.item_code and
exp_tran.doc_date > dateadd(dd,-30,getdate() )
and exp_tran.doc_type in ('PI', 'IN', 'SR')),
t.stk_qty = (select isnull(sum( qty), 0)
from exp_tran
where exp_tran.loc_id =h.loc_id and
item_code = t.item_code )
from po_tran t, po_hd h
where t.entry_no=h.entry_no and
h.loc_id=#locid and
h.entry_date> getdate()-35
end
;
Try the following possible ways to optimize your procedure.
Read this article, where I have explained the same example using CURSOR, Here I also have updated a field of the table using CURSOR.
Important: Remove Subquery, As I can see you have used a subquery to update the field.
You can use Join or Save the result of your query in the temp variable and you can use that variable while update.
i.g
DECLARE #lastndaysale AS FLOAT
DECLARE #stk_qty AS INT
select #lastndaysale = isnull(sum( qty)*-1, 0) from exp_tran where exp_tran.loc_id =h.loc_id and
item_code = t.item_code and exp_tran.doc_date > dateadd(dd,-30,getdate() ) and exp_tran.doc_type in ('PI', 'IN', 'SR')
select #stk_qty = isnull(sum( qty), 0) from exp_tran where exp_tran.loc_id =h.loc_id and item_code = t.item_code
update t set t.lastndaysale =#lastndaysale,
t.stk_qty = #stk_qty
from po_tran t, po_hd h where t.entry_no=h.entry_no and h.loc_id=#locid and h.entry_date> getdate()-35
This is just a sample example you can do need full changes in that.
I added a possibly more performant update, however, I do not fully understand your question. If "any" query is running slow against the po_tran, then I suggest you examine the indexing on that table and ensure it has a proper clustered index. If "this" query is running slow then I suggest you look into "covering indexes". The two fields entry_no and item_code seem like good candidates to include in a covering index.
update t
set t.lastndaysale =
CASE WHEN e.doc_date > dateadd(dd,-30,getdate() AND e.doc_type in ('PI', 'IN', 'SR') THEN
isnull(sum(qty) OVER (PARTITION BY e.loc_id, t.item_code) *-1, 0)
ELSE 0
END,
t.stk_qty = isnull(SUM(qty) OVER (PARTITION BY e.loc_id, t.item_code),0)
from
po_tran t
INNER JOIN po_hd h ON h.entry_no=t.entry_no AND h.entry_date> getdate()-35
INNER JOIN exp_tran e ON e.loc_id = h.loc_id AND e.itesm_code = t.item_code
where
h.loc_id=#locid

Dynamic T-SQL stored procedure in where clause

I need to write a sample stored procedure with 2 parameters like this:
sp_list_customers #locationid int, #category varchar
Users can pass in the #locationid = 0 to list down customers in ALL locations or specific location ID. Also they can pass null or empty string to p to slow ALL categories.
Here is my SQL code:
create procedure ...
as
select *
from customers
where locationid = (case
when isnull(#locationid, 0) = 0
then locationid
else #locationid
end)
and category = (case
when isnull(#category, '') = ''
then category
else #category
end)
However this kind of codes is running toooo slow with more parameters
I was looking for ways to fix the issue and then found out the dynamic T-SQL is the acceptable solution. But to migrate all my stored procedures is a nightmare job and error prone.
I need to write another user defined function to help me. What is the best user-defined function code for this?
Thanks
Try if the below simplified code works better...
create procedure ... as select * from customers where (nullif(#locationid,'') is null
or locationid = #locationid)
and ( nullif(#category,'') is null or category = #category)

How to loop with different values in T-SQL query?

I have some specific set of values that I want to filter on a column, I don't want to do an 'in' clause in SQL Server. I want to use loop to pass in different set of values each time.
For example if there is a name column in my data, and I want to run query 5 times with different filter value.
Please look at the loop query attached below.
DECLARE #cnt INT = 1;
WHILE #cnt < 94
BEGIN
SELECT Name, COUNT(*) AS Number_of_Names
FROM Table
WHERE name IN ('John')
AND value IS NOT NULL
GROUP BY Name
SET #cnt = #cnt + 1;
END;
I want to pass in different values under 'name' column at each loop like john in the case above, then mary in the next loop likewise based on set of values I pass in the variable like #values = John,Mary,Nicole,matt etc..
Considering the comments on your question, this should give you an idea on how to achieve a solution without using loops and still get all the names even when the name is not present on the table.
SELECT Name,
COUNT(value) AS Number_of_Names --Only count when value is not null
FROM (VALUES('John'), ('Mary'), ('Nicole'), ('Matt'))Names(name) --This can be replaced by a table-valued parameter or temp table.
LEFT JOIN Table t ON Names.name = t.name
--WHERE name IN ('John') /*No longer needed*/
--AND value IS NOT NULL /*Removed this because it would make the OUTER JOIN behave as an INNER JOIN*/
GROUP BY Name;

Stored procedure that accepts multiple id values (where parameter accepts null)

This question would be addendum on the last answer in T-SQL stored procedure that accepts multiple Id values
I am passing a few list of ids as a parameter to a stored procedure. Each of them default to null if no data is sent in. For instance, I want food products with ids 1, 2, 5, 7, 20 returned by my stored procedure. I also send in a list of color ids, and production location ids. I am passing in a comma delimited list of these ids. Similar to the last answer in the question referenced above, I create a temp table with the data from each of the parameters. I then want to have a select statement that would be something like this:
SELECT * FROM Candies
INNER JOIN #TempColors
ON Candies.ColorsID = #TempColors.ColorID
INNER JOIN Locations
ON Candies.LocationID = Locations.LocationID
This only works when the parameters are populated and LEFT OUTER JOINS will not filter properly. What is the way to filter while accepting null as a valid parameter?
You could use
some join condition OR #param IS NULL
in your join, it would return all results if a null was supplied - though as far as I can see you don't specify what behaviour you want when null is passed
(when I say param I mean temp table column doing this on my phone and it's not easy ;))
Edit:
This one worked for me:
http://sqlfiddle.com/#!3/c7e85/26
e.g.
-- Assume this is your values string which is populating the table
DECLARE #Values varchar(50)
-- Your code to populate the table here: assume the string is NULL when no values are passed
INSERT INTO #TempColors BLAH BLAH...
-- Select statement
SELECT * FROM Candies
LEFT JOIN #TempColors
ON Candies.ColorsID = #TempColors.ColorID
WHERE 1 = CASE
WHEN Candies.ColorsID IS NULL AND #Values IS NULL THEN 1
WHEN Candies.ColorsID IS NOT NULL AND #Values IS NOT NULL THEN 1
ELSE 0
END
This way the NULLs will be filtered out with a NON-NULL parameter, but kept in for a NULL parameter

Resources