SELECT * FROM CUSTOMERS
--WHERE ID='1'
I want the result like above statement, anyone can advice this can be declare comments or any other way?
DECLARE #TEST as VARCHAR(2) ='--'
SELECT * FROM CUSTOMERS
#TEST WHERE ID='1'
There is no preprocessor that can do what you want, and if there would be a preprocessor, it would probably only work in specific client software (like SQL Server Management Studio).
The best I can come up with is declaring a BIT variable and include it in your WHERE statement's logic.
DECLARE #TEST BIT = 0; -- Change to 1 for testing (which will show all records instead of only the record with ID = '1')
SELECT * FROM CUSTOMERS
WHERE ID = '1' OR #TEST = 1;
But using ORs in a WHERE-clause can become a performance issue (due to not using indexes), so using a UNION might be a solution in that case:
DECLARE #TEST BIT = 0; -- Change to 1 for testing (which will show all records instead of only the record with ID = '1')
SELECT * FROM CUSTOMERS
WHERE ID = '1'
UNION
SELECT * FROM CUSTOMERS
WHERE #TEST = 1;
I would personally go for the first option and fall back to the second option if that would produce a better query execution plan.
The latter solution does have the drawback that you have to duplicate your query logic, so changes have to be made in two places instead of one.
It's also the question if you would like to include such a testing variable at all. You can include a complete test query in comments:
/*
-- For testing purposes:
SELECT * FROM CUSTOMERS
*/
SELECT * FROM CUSTOMERS
WHERE ID = '1';
In SSMS, you can simply select the text inside the comment block and hit F5. Only the selected statement(s) will be parsed and executed.
Finally, if you have a large script with many testing cases, you could consider using a separate test script for that. In case of changes, you would have to maintain two scripts, of course, but everything related to testing is cleanly separated that way.
Try following statement:
DECLARE #TEST BIT = 1
DECLARE #ID NVARCHAR(50) = N'1'
SELECT *
CUSTOMERS as c
WHERE #TEST = 1 OR c.ID = #ID
So you can test without commenting out...
Another option is dynamic statements:
DECLARE #Statement NVARCHAR(4000) = NULL
DECLARE #TEST BIT = 1
IF #TEST = 1
BEGIN
SET #Statement = N'SELECT * FROM CUSTOMERS AS c'
END
ELSE
BEGIN
SET #Statement = N'SELECT * FROM CUSTOMERS AS c WHERE c.ID = #ID'
END
EXEC sp_executesql #Statement,
N'#ID NVARCHAR(50)',
#ID = N'1'
By Using Dynamic SQL Query.You can Set your Common SQL Command in One Variable and the based on your need/Requirement you can CONCAT the String and this can be achieved from by using Dynamic SQL
DECLARE #TEST VARCHAR(MAX),#Where VARCHAR(MAX)=' WHERE ID=''1'''
IF(Condition Check)
SET #TEST='SELECT * FROM CUSTOMERS' +#Where
ELSE
SET #TEST='SELECT * FROM CUSTOMERS'
SELECT #TEST
EXEC (#TEST)
Another Option:
DECLARE #TEST VARCHAR(2) =NULL
SET #TEST='1'
SELECT * FROM CUSTOMERS
WHERE (ID=#TEST OR #TEST IS NULL)
For quick debugging I'm using pattern like next:
declare #var int -- can be some function or procedure param
select f1, f2, ... -- can be update or delete too
--declare #var int = 7; select *
from mytable
where id = #var
Related
I have a table that have a list of "Reports" or "Correspondence Letters" (html files). This list will appear in a dropdown in a website. Some of these items will need a "check" to see if they are allowed to be added to the dropdown.
An example of how this table (I removed columns that was not necessary to display):
In this table there is a column "Cor_PolNeedCheck_ToShow". This value links to a different table where the query for that file is stored.
From here I need to create a query that will build the actual list that will be displayed. But in order to do that I need to run the query that is in the second table to perform the check. I've created the following query to do this and it brings back the expected results:
DECLARE #retvalue INT
DECLARE #Paramater NVARCHAR(20) = '241215'
DECLARE #Cor_GroupCde NVARCHAR(10) = 248
DECLARE #Statement NVARCHAR(500);
SELECT #Statement = (SELECT Lookup_Query + #Paramater FROM [dbo].[Ref_Lookup]
WHERE Lookup_ID = (Select Cor_PolNeedCheck_ToShow FROM dbo.Ref_Correspondence WHERE Cor_Group_Cde = #Cor_GroupCde));
EXEC #retvalue = sp_executesql #Statement;
PRINT #retvalue
This value needs to be passed into a query again to verify each record to show or not to show. This is the query that will run to show the items (I have added the #retvalue of the above in the query just to show what it needs to do). This query is in a stored proc where the #Paramater will be passed (above) from the application and then to be used as needed for the below (as above query needs to fit into this one).
SELECT Cor_Group_Cde, Cor_Desc
FROM Ref_Correspondence
WHERE Cor_Show = 'Y' AND Cor_Prod_List Like '%#' + #ProdID + '#%'
AND (Cor_PolNeedCheck_ToShow IS NULL OR --#retValue > 0)
The problem I'm facing is that I need to get the #retValue into the where clause.
I have though of a stored proc, but a stored proc cannot be called in a where clause. I then though of a user defined function, but the problem there is that you cannot call a stored proc ("sp_executesql") in a function.
Due to company standards I cannot do this within the application either. Is there a way around this or is there a way to do this that I maybe missed with the above?
-------------------- EDIT ---------------------
The function I created looks like this:
USE [DBName]
GO
/****** Object: UserDefinedFunction [dbo].[ufn_CorrespondenceCheckResult] Script Date: 4/11/2019 5:35:04 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ufn_CorrespondenceCheckResult]
(
#Paramater nvarchar(20),
#Cor_GroupCde nvarchar(10)
)
RETURNS int
AS
BEGIN
DECLARE #Statement nvarchar(MAX);
DECLARE #Return int;
set #Return = 0;
SELECT #Statement = (SELECT Lookup_Query + #Paramater FROM [dbo].[Ref_Lookup]
WHERE Lookup_ID = (Select Cor_PolNeedCheck_ToShow FROM dbo.Ref_Correspondence WHERE Cor_Group_Cde = #Cor_GroupCde));
EXEC #Return = sp_executesql #Statement;
return #Return;
END
Running the above function in my script gives me an error of "Only functions and some extended stored procedures can be executed from within a function.":
SELECT Cor_Group_Cde, Cor_Desc
FROM Ref_Correspondence
WHERE Cor_Show = 'Y' AND Cor_Prod_List Like '%#' + #ProdID + '#%'
AND (Cor_PolNeedCheck_ToShow IS NULL OR ((SELECT [dbo].[ufn_CorrespondenceCheckResult] (#Paramater,#Cor_GroupCde)) > 0))
It would be better to convert your Stored Procedure into a Function, if possible. Then you can use function in WHERE clause.
If you can't convert it into the function then better to execute the SP and store the full result in a Variable. Now you can use This table in WHERE clause.
You should store the result of the Stored Procedure in a variable in then use that in your next statement's condition. You can use an OUTPUT parameter to return the value of from the Stored Procedure.
DECLARE #count INT
EXEC FindProductByModel --CREATE a stored procedure FindProductByModel instead of a function
#Paramater = '241215',
#Cor_GroupCde = '248',
#retvalue= #count OUTPUT --#retval is an output paramater of your stored procedure
SELECT Cor_Group_Cde, Cor_Desc
FROM Ref_Correspondence
WHERE Cor_Show = 'Y' AND Cor_Prod_List Like '%#' + #ProdID + '#%'
AND (Cor_PolNeedCheck_ToShow IS NULL OR #count>0)
I have problem in optimizing an SQL query to do some data cleansing.
In fact, I have a table which is a sort of referential of a multiple special characters and word. Let's call it ABNORMAL(ID,PATTERN)
I have also another table INDIVIDUALS containing a column (NAME) which I want to clean by removing from it all characters that exist in the table ABNORMAL.
Currently, I have tried to use update statements, but I'm not sure if there is a better way to do this.
Approach one
Use a while loop to build a replace containing all characters from ABNORMALS by a blank '' and do one update using the built-in REPLACE
DECLARE #REPLACE_EXPRESSION nvarchar(max) ='REPLACE(NAME,'''','''')'
DECLARE #i int = 1
DECLARE #nbr int = (SELECT COUNT(*) FROM ABNORMAL)
-- CURRENT_CHARAC
DECLARE #CURRENT_CHARAC nvarchar(max)
-- NEW REPLACE EXPRESSION TO IMBRICATE INTO THE REPLACE EXPRESSION VARIABLE
DECLARE #CURR_REP NVARCHAR(max)
-- STRING TO BUILD AN SQL QUERY CONTAINING THE REPLACE EXPRESSION
DECLARE #UPDATE_QUERY nvarchar(max)
WHILE #i < #nbr
BEGIN
SELECT #CURRENT_CHARAC=PATTERN FROM CLEANSING_STG_PRISM_FRA_REF_UNSIGNIFICANT_VALUES WHERE ID_PATTERN=#i ;
SET #REPLACE_EXPRESSION = REPLACE(#REPLACE_EXPRESSION ,'NAME','REPLACE(NAME,'+''''+#CURRENT_CHARAC+''''+','''')')
set #i=#i+1
END
SET #UPDATE_QUERY = 'UPDATE INDIVIDUAL SET NAME ='+ #REPLACE_EXPRESSION
EXEC sp_executesql #UPDATE_QUERY
Approach two
Use a while loop to select every character in abnormal and do an update using replace containing the characters to remove:
DECLARE #i int = 1
DECLARE #nbr int = (SELECT COUNT(*) FROM ABNORMAL)
-- CURRENT_CHARAC
DECLARE #CURRENT_CHARAC nvarchar(max)
-- STRING TO BUILD AN SQL QUERY CONTAINING THE REPLACE EXPRESSION
DECLARE #UPDATE_QUERY nvarchar(max)
WHILE #i < #nbr
BEGIN
SELECT #CURRENT_CHARAC=PATTERN FROM CLEANSING_STG_PRISM_FRA_REF_UNSIGNIFICANT_VALUES WHERE ID_PATTERN=#i ;
UPDATE INDIVIDUAL
SET NAME = REPLACE(NAME,#CURRENT_CHARAC,'')
SET #i=#i+1
END
I already tested both approaches for 2 millions records, and I found that the first approach is faster than the second. I would know if you have already done something similar and new (better) ideas to try.
If you are using SQL Server 2017 you could use TRANSLATE and avoid dynamic SQL:
SELECT i.*
, REPLACE(TRANSLATE(i.NAME, f, REPLICATE('!', s.l)), '!', '') AS cleansed
FROM INDIVIDUALS i
OUTER APPLY (SELECT STRING_AGG(PATTERN, '') AS f
,LEN(STRING_AGG(PATTERN,'')) AS l
FROM ABNORMAL) AS s
DBFiddle Demo
Anyway 1st approach is better becasue you do one UPDATE, with second approach you remove characters one character at time (so you will have multiple UPDATE).
I would also track transaction log growth with both approaches.
If there's not too many characters that to be cleaned, then this trick might work.
Basically, you build 1 big update statement with a replace for each value in the table with the characters to be removed.
Example code:
Test data (using temp tables)
create table #ABNORMAL_CHARACTERS (id int identity(1,1), chr varchar(30));
insert into #ABNORMAL_CHARACTERS (chr) values ('!'),('&'),('#');
create table #INDIVIDUAL (id int identity(1,1), name varchar(30));
insert into #INDIVIDUAL (name) values ('test 1 &'),('test !'),('test 3');
Code:
declare #FieldName varchar(30) = 'name';
declare #Replaces varchar(max) = #FieldName;
declare #UpdateSQL varchar(max);
select #Replaces = concat('replace('+#Replaces+', ', ''''+chr+''','''')') from #ABNORMAL_CHARACTERS order by id;
set #UpdateSQL = 'update #INDIVIDUAL
set name = '+#Replaces + '
where exists (select 1 from #ABNORMAL_CHARACTERS where charindex(chr,name)>0)';
exec (#UpdateSQL);
select * from #INDIVIDUAL;
A test here on rextester
And if you would have a UDF that can do a regex replace.
For example here
Then the #Replaces variable could be simplified with only 1 RegexReplace function and a pattern.
I'm a C# developer trying to become more familiar with SQL Server stored procedures.
I'm a little confused as to why the syntax in "A" works and "B" does not work with Set #id. What is happening here that makes "B" require Select instead of Set?
Example A (works)
DECLARE #currDateTime DateTime
SET #currDateTime = GetDate()
SELECT #currDateTime
Example B (does not work)
DECLARE #id int
SET #id = ID FROM [MyTable] WHERE [Field1] = 'Test'
Example C (works)
DECLARE #id int
SELECT #id = ID
FROM [MyTable]
WHERE [Field1] = 'Test'
SELECT is a built-in type of SQL clause that runs a query and returns a result-set in the format of a table or it assigns variables to the results from a query.
SET is a clause that sets a variable.
The two are very different. SELECT has various other associated clauses, such as FROM, WHERE and so on; SET does not. SELECT returns values as a result table in its normal usage; SET does not.
Admittedly, both look the same in an expression such as:
set #currDateTime = GetDate();
select #currDateTime = GetDate();
However, it is really a coincidence that the syntax for setting a single value happens to look the same.
It doesn't work because it's incorrect SQL syntax, You need SELECT when fetching data from table/view/table function.
You could use SET when using an expression though i.e:
DECLARE #Id bigint
SET #Id = (SELECT TOP 1 Id
FROM MyTable
WHERE Field1 = 'Test')
This question may boil down to something simpler, but I am still curious as to how close SQL Server / TSQL can get to conditional WHERE clauses (and reasoning behind why they don't exist would also be interesting).
I have a stored procedure that, for a few parameters, takes in an enumeration array (which has been accordingly translated to a user-defined table type which essentially mocks an int array). For reference the data type is as follows:
CREATE TYPE myIntArray AS TABLE (
val INT
);
My stored procedure is along the following lines (altered to be more simplistic):
CREATE PROCEDURE myProc
#homeID INT,
#name VARCHAR(500),
#hometype_enum myIntArray READONLY,
#country_enum myIntArray READONLY
AS
BEGIN
SELECT * FROM my_table
WHERE name=#name
END
GO
What I am wanting to do is additionally filter the results of my query based upon the values of the enum arrays that were passed in as INT tables, IFF they even have values passed in (it is possible the tables might be empty). The pseudo code would look something like this:
SELECT *
FROM my_table
WHERE name = #name
IF((SELECT COUNT(val) FROM #hometype_enum) > 0)
BEGIN
AND hometype IN (SELECT val FROM hometype_enum)
END
IF((SELECT COUNT(val) FROM #country_enum ) > 0)
BEGIN
AND country IN (SELECT val FROM country_enum )
END
The two enums are independent of each other, so it's possible a search could be made and be filtered on no enum (both tables empty), either-or, or both enums.
My actual query involves multiple columns, tables, and unions (ugly, I know), so it's not as nice as just being able to copy/paste a 3-line SELECT for each scenario. I am currently using some pretty ugly temp table logic that I'll spare the reader's eyes from at the moment.
Aside from figuring out my particular problem, my main question is: does SQL Server support conditional WHERE clause statements (I am convinced it does not from my research)? Why is this (architectural, time complexity, space complexity issues)? Are there any more-or-less terse ways to go about emulating a conditional clause, such as taking advantage of conditional short-circuiting?
Thank you all for your insights. Another day of learning!
As suggested in the comments, the best way to handle this kind of conditional where clause would be to use dynamic-sql ..... Something like....
CREATE PROCEDURE myProc
#homeID INT,
#name VARCHAR(500),
#hometype_enum myIntArray READONLY,
#country_enum myIntArray READONLY
AS
BEGIN
SET NOCOUNT ON
Declare #Sql NVarchar(MAX);
SET #Sql = N' SELECT * FROM my_table '
+ N' WHERE name = #name '
+ CASE WHEN EXISTS (Select * FROM #hometype_enum)
THEN N' AND hometype IN (SELECT val FROM hometype_enum) ' ELSE N' ' END
+ CASE WHEN EXISTS (Select * FROM #country_enum)
THEN N' AND country IN (SELECT val FROM country_enum ) ' ELSE N' ' END
Exec sp_executesql #Sql
,N'#homeID INT , #name VARCHAR(500),
#hometype_enum myIntArray, #country_enum myIntArray'
,#homeID
,#name
,#hometype_enum
,#country_enum
END
GO
Using sp_executesql will allow sql server to store parameterised execution plans for the same stored procedure. It is different execution plans for different sets/combinations of a parameters for the same stored procedure for optimal performance.
The below one works fine too. There is no need for dynamic-sql. The MS SQL will handle it without issues (with good performance).
CREATE PROCEDURE myProc
#homeID INT,
#name VARCHAR(500),
#hometype_enum myIntArray READONLY,
#country_enum myIntArray READONLY
AS
BEGIN
SELECT * FROM my_table
WHERE name=#name
AND ( ( SELECT count(val) FROM #hometype_enum ) = 0
OR hometype IN (SELECT val FROM #hometype_enum) )
AND ( ( SELECT count(val) FROM #country_enum ) = 0
OR country IN (SELECT val FROM #country_enum) )
END
GO
The OR will make the job for you. When first part : SELECT count(val) FROM #hometype_enum ) = 0 will return true then the second one will not be executed at all - no error from empty IN clause. When the first part will return some value grater than 0 then the second one will be evaluated correctly.
I have an Stored Procedure that have an argument named Id:
CREATE PROCEDURE [TargetSp](
#Id [bigint]
)
AS
BEGIN
Update [ATable]
SET [AColumn] =
(
Select [ACalculatedValue] From [AnotherTable]
)
Where [ATable].[Member_Id] = #Id
END
So I need to use it for a list of Id's not for one Id like :
Exec [TargetSp]
#Id IN (Select [M].[Id] From [Member] AS [M] Where [M].[Title] = 'Example');
First: How can I Execute it for a list?
Second: Is there any Performance difference between I execute the sp many times or rewrite it in target script?
You could use a table-valued parameter (see http://msdn.microsoft.com/en-us/library/bb510489.aspx). Generally, if you send only one request to the server instead of a list of requests you will see a shorter execution time.
I normally pass in the information like that as XML, then you can use it just like it's a table... selecting, inserting, updating as necessary
DECLARE #IDS NVARCHAR(MAX), #IDOC INT
SET #IDS = N'<ROOT><ID>1</ID><ID>2<ID></ROOT>'
EXEC sp_xml_preparedocument #IDOC OUTPUT, #IDS
SELECT [ID] FROM OPENXML (#IDOC, '/ROOT/ID', 2) WITH ([ID] INT '.') AS XMLDOC
EXEC sp_xml_removedocument #IDOC
Similar to freefaller's example, but using xml type instead and inserting into a table variable #ParsedIds
DECLARE #IdXml XML = N'<root><id value="1"/><id value="2"/></root>'
DECLARE #ParsedIds TABLE (parsedId int not null)
INSERT INTO #ParsedIds (parsedId)
SELECT v.parsedId.value('#value', 'int')
FROM #IdXml.nodes('/root/id') as v(parsedId)
SELECT * FROM #ParsedIds
Interestingly I've worked on an large scale system with 1000's of users and we found that using this method out performed the table-valued parameter approach for small lists of id's (no more than say 5 id's). The table-valued parameter approach was faster for larger lists of Id's.
EDIT following edited question:
Looking at your example it looks like you want to update ATable based on the Title parameter. If you can you'd benefit from rewriting your stored procedure to instead except the title parameter.
create procedure [TargetSP](
#title varchar(50)
)
as
begin
update [ATable]
set [AColumn] =
(
select [ACalculatedValue] from [AnotherTable]
)
where [ATable].[Member_Id] in (select [M].[Id] from [Member] as [M] where [M].[Title] = #title);
end
Since you only care about all the rows with a title of 'Example', you shouldn't need to determine the list first and then tell SQL Server the list you want to update, since you can already identify those with a query. So why not do this instead (I'm guessing at some data types here):
ALTER PROCEDURE dbo.TargetSP
#title VARCHAR(255)
AS
BEGIN
SET NOCOUNT ON;
-- only do this once instead of as a subquery:
DECLARE #v VARCHAR(255) = (SELECT [ACalculatedValue] From [AnotherTable]);
UPDATE a
SET AColumn = #v
FROM dbo.ATable AS a
INNER JOIN dbo.Member AS m
ON a.Member_Id = m.Id
WHERE m.Title = #title;
END
GO
Now call it as:
EXEC dbo.TargetSP #title = 'Example';
DECLARE #VId BIGINT;
DECLARE [My_Cursor] CURSOR FAST_FORWARD READ_ONLY FOR
Select [M].[Id] From [Member] AS [M] Where [M].[Title] = 'Example'
OPEN [My_Cursor]
FETCH NEXT FROM [My_Cursor] INTO #VId
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC [TargetSp]
#Id = #VId
FETCH NEXT FROM [My_Cursor] INTO #VId
END
CLOSE [My_Cursor]
DEALLOCATE [My_Cursor];
GO
if the parameter is integer, you can only pass one value at a time.
Your options are:
call the proc several times, one for each parameter
Change the proc to accept a structure where you can pass more than
one id like a varchar where you pass a coma separated list of values
(not so good) or a table-value parameter
About the performance question, it would be faster to re-write the proc to iterate through a list of ids than call it several times, once per id, BUT unless you are dealing with a HUGE list of ids, I dont think you will see much of a difference