I'm trying to use the value from a variable being passed through from elsewhere in my where statement but i'm struggling on syntax.
I have a field that is being passed through where the value with in it is essentially a list
ie #Var1 where the value being passed through is AA','BB','CC (ie its missing the leading ' and trailing '
i want to use this value in my where statement as a list eg
where
field1 IN #Var1
ie - the actual result I want is
where
field1 IN ('AA','BB','CC')
I don't have any control over what is being passed through (ie AA','BB','CC will always be missing the leading and trailing ', so how do i use that info in the IN clause in the WHERE?
I seem to be coming up against issues with syntax around the use of the '
I hope that makes sense, its not the easiest thing to explain and its still relatively new to me
Here an example for my approach in the comments above:
CREATE PROCEDURE test_r(#x varchar(4000))
AS
BEGIN
DECLARE #t TABLE (keys NVARCHAR(10))
DECLARE #sql_xml XML = CAST('<root><X>'+REPLACE(#x, char(39)+','+char(39), '</X><X>') + '</X></root>' AS XML)
INSERT INTO #t(keys)
SELECT f.x.value('.', 'VARCHAR(10)') AS y
FROM #sql_xml.nodes('/root/X') f(x)
SELECT *
-- FROM [MyTable] m INNER JOIN...
FROM #t
END
GO
DECLARE #x AS nvarchar(4000) = 'AA' + char(39) + ',' + char(39) + 'BB' + char(39) + ',' + char(39) + 'CC'
EXEC test_r #x
Depending on what you have to do with the result of your query, you might want to create a function instead of a procedure.
Related
I have a procedure that selects an offset of rows from a table:
SELECT * --table contains ID and Name columns
FROM Names
ORDER BY ID
OFFSET #Start ROWS
FETCH NEXT #Length ROWS ONLY
In addition to #Start and #Length parameters, the procedure also receives #SearchValue NVARCHAR(255) parameter. #SearchValue contains a string of values delimited by a space, for example '1 ik mi' or 'Li 3'.
What I need is to query every record containing all of those values. So, if the #SearchValue is '1 ik mi', it should return any records that contain all three values: '1', 'mi', and 'ik'. Another way to understand this is by going here, searching the table (try searching 00 eer 7), and observing the filtered results.
I have the freedom to change the delimiter or run some function (in C#, in my case) that could format an array of those words.
Below are our FAILED attempts (we didn't try implementing it with OFFSET yet):
Select ID, Name
From Names
Where Cast(ID as nvarchar(255)) in (Select value from string_split(#SearchValue, ' ')) AND
Name in (Select value from string_split(#SearchValue, ' '))
SELECT ID, Name
FROM Names
WHERE #SearchValueLIKE '% ' + CAST(ID AS nvarchar(20)) + ' %' AND
#SearchValueLIKE '% ' + Name + ' %';
We used Microsoft docs on string_split for the ideas above.
Tomorrow, I will try to implement this solution, but I'm wondering if there's another way to do this in case that one doesn't work. Thank you!
Your best bet will be to use a FULL TEXT index. This is what they're built for.
Having said that you can work around it.. BUT! You're going to be building a query to do it. You can either build the query in C# and fire it at the database, or build it in the database. However, you're never going to be able to optimise the query very well because users being users could fire all sorts of garbage into your search that you'll need to watch out for, which is obviously a topic for another discussion.
The solution below makes use of sp_executesql, so you're going to have to watch out for SQL injection (before someone else picks apart this whole answer just to point out SQL injection):
DROP TABLE #Cities;
CREATE TABLE #Cities(id INTEGER IDENTITY PRIMARY KEY, [Name] VARCHAR(100));
INSERT INTO #Cities ([Name]) VALUES
('Cooktown'),
('South Suzanne'),
('Newcastle'),
('Leeds'),
('Podunk'),
('Udaipur'),
('Delhi'),
('Murmansk');
DECLARE #SearchValue VARCHAR(20) = 'ur an rm';
DECLARE #query NVARCHAR(1000);
SELECT #query = COALESCE(#query + '%'' AND [Name] LIKE ''%', '') + value
FROM (Select value from string_split(#SearchValue, ' ')) a;
SELECT #query = 'SELECT * FROM #Cities WHERE [Name] LIKE ''%' + #query + '%''';
EXEC sp_executesql #query;
I have a SQL script like below. I am using QUOTENAME to construct the name of the table to query. It is giving me an Invalid object name old.tb2018 error because QUOTENAME is generating the table name with brackets like so - [old.tb2018].
I have considered passing tablename as a parameter to sp_executesql, but I am using #old_table_name in other places in the code. I fear that will create a bunch of duplicate code because of all the other places that is being used in. How do I pass the tablename to #trunc_success without it generating an error?
DECLARE #YEAR NVARCHAR(4) = year(DATEADD(YEAR, - 1, getdate()))
DECLARE #old_table_name NVARCHAR(500)
DECLARE #new_table_name NVARCHAR(500)
DECLARE #trunc_success NVARCHAR (3000)
SET #old_table_name = QUOTENAME('old.tb' + #year);
SET #new_table_name = QUOTENAME('new.tb' + #year);
SET #trunc_success = '
SELECT
CASE
WHEN EXISTS
(
--This should no records
SELECT
top 1
*
FROM ' + #old_table_name + '
) THEN ''Failed''
ELSE ''Passed''
END result
UNION ALL
SELECT
CASE
WHEN EXISTS
(
--This should return no records
SELECT top 1 *
FROM ' + #new_table_name + '
) THEN ''Failed''
ELSE ''Passed''
END'
print #trunc_success
EXECUTE sp_executesql #trunc_success
Your table's name isn't old.tb2018 it's tb2018 and it's on the schema old. It should be:
DECLARE #old_table_name sysname; --Correct data type for object names
DECLARE #new_table_name sysname; --Correct data type for object names
...
SET #old_table_name = N'old.' + QUOTENAME(N'tb' + #year);
SET #new_table_name = N'new.' + QUOTENAME(N'tb' + #year);
...
If you want to quote the schema name too (good when not using a literal value) it would be:
QUOTENAME(N'new') + N'.' + QUOTENAME(N'tb' + #year);
Although I've used sysname here, on second though, the idea should be different. It should really be
DECLARE #old_table_name sysname; --Correct data type for object names
DECLARE #new_table_name sysname; --Correct data type for object names
...
SET #old_table_name = QUOTENAME(N'tb' + #year);
SET #new_table_name = QUOTENAME(N'tb' + #year);
...
And then in your query you'd have:
'...FROM old.' + QUOTENAME(#old_table_name) + '...'
The reason for this is that sysname is a synonym for nvarchar(128) NOT NULL, however, QUOTENAME returns an nvarchar(258); therefore quoting 2 objects, schema and table with a delimitor (.), could potentially result in an nvarchar(517) (258 + 1 + 258). It's therefore better to split the individual parts into their own parameters/variables and use QUOTENAME at the point of injection. This will avoid any unexpected truncation if you do have any overly long object names, or have (foolishly) used lots of ] characters in it's name.
Of course, the only way you could get get a 258 character value for a real object's name from QUOTENAME would be if the object's name was made up of 128 ] characters ([ for the start and ] for the end, and 128 ] characters escaped to ]]):
SELECT LEN(QUOTENAME(REPLICATE(']',128)));
If you really did have an object named that, I would honestly be questioning your sanity.
I have a stored procedure which is implemented in Dynamic SQL. I am trying to include an IN keyword and pass fixed values in the list between single quotes.
I want to write something like this:
where grp.Status in ('A,S')
but the above statement is already encapsulated in single quotes.
I tried using something like the one below:
where grp.Status in (' +QUOTENAME('A,S','''')+
but the query is only recognizing the first value in the list i.e. 'A'.
I also read about using a split function and putting the values in a temp table and using the temp table's column instead. But, I don't was to do that process for a tiny list.
Any suggestions and solutions are greatly appreciated.
You can just put the list into the SQL:
'where grp.Status in (''' + replace('A,S', ',', ''',''') + ''') . . .
If I got those single quotes right, this should produce the result as:
where grp.Status in ('A','S') . . .
If you do not want to use the split function you can do the following with your dynamic sql.
declare #xml xml
, #list Varchar(100) = 'A,B'
set #xml = N'<root><r>' + replace(#list, ',' ,'</r><r>') + '</r></root>'
Declare #Sql Nvarchar(MAX);
SET #Sql = N'Select * from TableName grp
WHERE grp.Status IN (
select r.value(''.'',''varchar(max)'') as item
from #xml.nodes(''//root/r'') as records(r)
)'
Exec sp_executesql #Sql
,N'#xml XML'
,#xml
I'm very new to SQL, so let me first apologize if my questions come off as trivial or it seems as though I haven't done my work. I am trying to learn how to grasp a lot of these concepts.
Anyway, I'm writing a complex query that will eventually take many parameters. For these parameters I'll be using a comma delimited string to allow multiple variables. (I've solved this issue previously, but not when attempting to execute an SP_ExecuteSQL.
With that being said, here is the bare bones of the query.
DECLARE #system_status varchar(30)
SELECT #system_status = '12,14'
DECLARE #sql nvarchar(4000)
SELECT #sql = 'SELECT [system_status]
FROM VW_Document_Main
WHERE 1=1 '
IF #System_Status = '-1'
Begin
SELECT #sql = #sql + 'and system_status <> 20'
End
ELSE IF #system_status IS NOT NULL AND #system_status NOT IN ('-1','0')
Begin
SELECT #sql = #sql + 'and ' + #system_Status + ' LIKE ''%,'' + system_Status + '',%'''
I'm able to populate a useable query when not building it into an sp_executesql statement, however, since I'll be building about this query it's necessary to take these steps... any thoughts as to why I'm generating the non-Boolean error?
EDIT: Not sure if it's a step in the right direction, but now after reworking the final SELECT statement to read:
SELECT #sql = #sql + 'and '',''' + #system_Status + '',''' LIKE ''%,' + 'system_Status' + ',%'''
It's giving me back a different error: A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations.
It should be worth noting that the error reads: An expression of non-boolean type specified in a context where a condition is expected, near ','.
You're plugging in your literal #system_status value '12, 14' into the final SQL so it looks like this
SELECT [system_status] FROM VW_Document_Main WHERE 1=1 and 12,14 LIKE '%,' + system_Status + ',%'
Which will fail because you don't have 12,14 in quotes. So you'll have to modify the line
SELECT #system_status = '12,14'
to be
SELECT #system_status = '''12,14'''
Alternatively, since you said you're running this through sp_executeSql you should modify your last select to
SELECT #sql = #sql + 'and #system_Status LIKE ''%,'' + system_Status + '',%'''
And change your execute SQL proc to
sp_executesql #sql, N'#system_status varchar(30)', #system_status
I have a table in SQL Server 2008 which contains custom validation criteria in the form of expressions stored as text, e.g.
StagingTableID CustomValidation
----------------------------------
1 LEN([mobile])<=30
3 [Internal/External] IN ('Internal','External')
3 ([Internal/External] <> 'Internal') OR (LEN([Contact Name])<=100)
...
I am interested in determining whether all rows in a table pass the conditional statement. For this purpose I am writing a validation stored procedure which checks whether all values in a given field in a given table meet the given condition(s). SQL is not my forte, so after reading this questions this is my first stab at the problem:
EXEC sp_executesql N'SELECT #passed = 0 WHERE EXISTS (' +
N'SELECT * FROM (' +
N'SELECT CASE WHEN ' + #CustomValidationExpr + N' THEN 1 ' +
N'ELSE 0 END AS ConditionalTest ' +
N'FROM ' + #StagingTableName +
N')t ' +
N'WHERE t.ConditionalTest = 0)'
,N'#passed BIT OUTPUT'
,#passed = #PassedCustomValidation OUTPUT
However, I'm not sure if the nested queries can be re-written as one, or if there is an entirely better way for testing for validity of all rows in this scenario?
Thanks in advance!
You should be able to reduce by at least one subquery like this:
EXEC sp_executesql N'SELECT #passed = 0 WHERE EXISTS (' +
N'SELECT 1 FROM ' + #StagingTableName +
N'WHERE NOT(' + #CustomValidationExpr + N')) ' +
,N'#passed BIT OUTPUT'
,#passed = #PassedcustomValidation OUTPUT
Before we answer the original question, have you looked into implementing constraints? This will prevent bad data from entering your database in the first place. Or is the point that these must be dynamically set in the application?
ALTER TABLE StagingTable
WITH CHECK ADD CONSTRAINT [StagingTable$MobileValidLength]
CHECK (LEN([mobile])<=30)
GO
ALTER TABLE StagingTable
WITH CHECK ADD CONSTRAINT [StagingTable$InternalExternalValid]
CHECK ([Internal/External] IN ('Internal','External'))
GO
--etc...
You need to concatenate the expressions together. I agree with #PinnyM that a where clause is easier for full table validation. However, the next question will be how to identify which rows fail which tests. I'll wait for you to ask that question before answering it (ask it as a separate question and not as an edit to this one).
To create the where clause, something like this:
declare #WhereClause nvarchar(max);
select #WhereClause = (select CustomValidation+' and '
from Validations v
for xml path ('')
) + '1=1'
select #WhereClause = replace(replace(#WhereClause, '<', '<'), '>', '>'))
This strange construct, with the for xml path('') and the double select, is the most convenient way to concatenate values in SQL Server.
Also, put together your query before doing the sp_executesql call. It gives you more flexibilty:
declare #sql nvarchar(max);
select #sql = '
select #passed = count(*)
from '+#StagingTableName+'
where '+#WhereClause
That is the number that pass all validation tests. The where clause for the fails is:
declare #WhereClause nvarchar(max);
select #WhereClause = (select 'not '+CustomValidation+' or '
from Validations v
for xml path ('')
) + '1=0'