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.
Related
Yesterday suddenly a report occurred that someone was not able to get some data anymore because the issue Msg 2628, Level 16, State 1, Line 57 String or binary data would be truncated in table 'tempdb.dbo.#BC6D141E', column 'string_2'. Truncated value: '!012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678'. appeared.
I was unable to create a repro without our tables. This is the closest as I can get to:
-- Create temporary table for results
DECLARE #results TABLE (
string_1 nvarchar(100) NOT NULL,
string_2 nvarchar(100) NOT NULL
);
CREATE TABLE #table (
T_ID BIGINT NULL,
T_STRING NVARCHAR(1000) NOT NULL
);
INSERT INTO #table VALUES
(NULL, '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'),
(NULL, '!0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789!');
WITH abc AS
(
SELECT
'' AS STRING_1,
t.T_STRING AS STRING_2
FROM
UT
INNER JOIN UTT ON UTT.UT_ID = UT.UT_ID
INNER JOIN MV ON MV.UTT_ID = UTT.UTT_ID
INNER JOIN OT ON OT.OT_ID = MV.OT_ID
INNER JOIN #table AS T ON T.T_ID = OT.T_ID -- this will never get hit because T_ID of #table is NULL
)
INSERT INTO #results
SELECT STRING_1, STRING_2 FROM abc
ORDER BY LEN(STRING_2) DESC
DROP TABLE #table;
As you can see the join of #table cannot yield any results because all T_ID are NULL nevertheless I am getting the error mentioned above. The result set is empty.
That would be okay if a text with more than 100 characters would be in the result set but that is not the case because it is empty. If I remove the INSERT INTO #results and display the results it does not contain any text with more than 100 characters. The ORDER BY was only used to determine the faulty text value (with the original data).
When I use SELECT STRING_1, LEFT(STRING_2, 100) FROM abc it does work but it does not contain the text either that is meant to be truncated.
Therefore: What am I missing? Is it a bug of SQL Server?
-- this will never get hit is a bad assumption. It is well known and documented that SQL Server may try to evaluate parts of your query before it's obvious that the result is impossible.
A much simpler repro (from this post and this db<>fiddle):
CREATE TABLE dbo.t1(id int NOT NULL, s varchar(5) NOT NULL);
CREATE TABLE dbo.t2(id int NOT NULL);
INSERT dbo.t1 (id, s) VALUES (1, 'l=3'), (2, 'len=5'), (3, 'l=3');
INSERT dbo.t2 (id) VALUES (1), (3), (4), (5);
GO
DECLARE #t table(dest varchar(3) NOT NULL);
INSERT #t(dest) SELECT t1.s
FROM dbo.t1
INNER JOIN dbo.t2 ON t1.id = t2.id;
Result:
Msg 2628, Level 16, State 1
String or binary data would be truncated in table 'tempdb.dbo.#AC65D70E', column 'dest'. Truncated value: 'len'.
While we should have only retrieved rows with values that fit in the destination column (id is 1 or 3, since those are the only two rows that match the join criteria), the error message indicates that the row where id is 2 was also returned, even though we know it couldn't possibly have been.
Here's the estimated plan:
This shows that SQL Server expected to convert all of the values in t1 before the filter eliminated the longer ones. And it's very difficult to predict or control when SQL Server will process your query in an order you don't expect - you can try with query hints that attempt to either force order or to stay away from hash joins but those can cause other, more severe problems later.
The best fix is to size the temp table to match the source (in other words, make it large enough to fit any value from the source). The blog post and db<>fiddle explain some other ways to work around the issue, but declaring columns to be wide enough is the simplest and least intrusive.
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;
I am trying to write a stored procedure to compute the differences between two input tables.
Stored procedure is used to calculate differences between two tables (both tables have the same predefined table structure), the stored procedure will provide records added, removed or updated when comparing table 1 to table 2.
Example:
table 1 New has 3 records: A, B and C
table 2 has 3 records: B', C and D
B' denotes a change to one or multiple fields within the record B
The output of this stored procedure call will be
A-addition
B-update
D-Removal
I have written a query to compute the difference between two tables, but finding it hard to translate to stored procedure.
Table structure:
X varchar (10)
Y int
Z datetime
SELECT
table1.*, ChangeType = 'Addition'
FROM
table1
WHERE
NOT EXISTS (SELECT *
FROM table2
WHERE table1.x = table2.x)
UNION ALL
SELECT
table2.*, ChangeType = 'Removal'
FROM
table2
WHERE
NOT EXISTS (SELECT *
FROM table1
WHERE table1.x = table2.x)
UNION ALL
SELECT
table1, ChangeType = 'Update'
FROM
table2
INNER JOIN
table1 ON table1.x = table2.x
WHERE
table1.Y <> table2.Y OR table1.Z <> table2.Z
Please also include the stored procedure execution script as well.
I think you are looking for the MERGE sentence. You can put table1 as target and table2 as source based on certain values and decide what to do in case the match or not: https://msdn.microsoft.com/en-us/library/bb510625.aspx
In your case it would be something like:
MERGE table1 AS target
USING table2 AS source (x, y, z)
ON (target.x= source.x)
WHEN MATCHED
--do something
WHEN NOT MATCHED BY TARGET
--do something different
WHEN NOT MATCHED BY SOURCE
--something else
As to how to receive a table as a parameter in a SP, you need to follow the next steps:
Create a DATA TYPE
CREATE TYPE tableExample (X varchar (10),
Y int,
Z datetime)
Pass it to the SP:
CREATE PROC sp_mysp #table1 tableExample, #table2 tableExample
AS ...
I prefer a single pass, using a case statement to classify the action.
CREATE PROCEDURE CompareTables
AS
BEGIN
SELECT ChangeType = CASE
WHEN table2.x IS NULL THEN
'Addition'
WHEN table1.x IS NULL THEN
'Removal'
WHEN table1.Y <> table2.Y
OR table1.Z <> table2.Z THEN
'Update'
ELSE
'No Change'
END,
table1.*,
table2.*
FROM table2
FULL OUTER JOIN table1
ON table1.x = table2.x
WHERE table2.x IS NULL
OR table1.x IS NULL
OR NOT ( table1.Y = table2.Y
AND table1.Z = table2.Z
);
END;
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.
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