I have a string in SQL with the following structure:
#number <logical> #number <logical> ....
ex:
#SQL='((#1 or #2) and (#10 or #21))'
I would like to update the string to be:
#SQL='((con_1=1 or con_2=1) and (con_10=1 or con_21=1))'
meaning remove the '#' and replace it with 'con_', leave the number (1 digit or more) as is and add '=1' after the digit.
Any idea how to do it?
Please avoid composing function or procedure for that.
you may use patindex, stuff or any other built in function for that.
First split the string into records on the '#' char that starts the value needing processed.
STUFF can be used to insert the '=1' after the last digit of the value in each record. I think searching for the last number is harder than searching for the first. The reverse allows searching for the first instead of the last. Because the string is reversed, insert the reverse '1=' at the start of the reverse number. If there is no number, return the revered value as is.
Concatenate it all back with 'con_' instead of '#'.
declare #SQL varchar(200) = ' ((#1 or #2) and (#10 or #21)) #33 #55 ';
with split as (
select value, REVERSE(value) as [rev], PATINDEX('%[0-9]%', REVERSE(value)) as [pat]
FROM STRING_SPLIT(#SQL, '#')
), fixed as (
select value, rev, pat, CASE WHEN pat= 0 THEN REVERSE(rev) ELSE REVERSE(STUFF(rev, pat, 0, '1=')) END as [fix]
FROM split
) select STRING_AGG(fix, 'con_') from fixed
Result: ((con_1=1 or con_2=1) and (con_10=1 or con_21=1)) con_33=1 con_55=1
It would be nice if Regex support was included in SQL server. I imagine to do so would require a careful implementation to avoid using all the server resources. A regex search on a millions of records or varchar(max) columns....
Found this solution which uses SQL# (SQLsharp).
SQL# is a .NET / CLR library that resides in a SQL Server 2005 (or newer) database and provides a suite of User-Defined Functions, Stored Procedures, User-Defined Aggregates, and User-Defined Types.
My solution:
declare #SQL varchar(200) = '#1 OR(#2 AND #3 AND (#4 or #5 or #6) AND (#7 or #8))'
SELECT SQL#.RegEx_Replace4k(#SQL, N'(#)+(\d*)', N'CON_$2=1', -1, 1, N'')
Related
I am trying to find a way to preserve a space within SQL concatenation.
For context: A table I am selecting from a table with a single concatenated key column. Concatenated keys respect spaces.
Example: BUKRS(4) = 'XYZ ', WERKS(4) = 'ABCD' is represented as key XYZ ABCD.
I am trying to form the same value in SQL, but it seems like ABAP SQL auto-trims all trailing spaces.
Select concat( rpad( tvko~bukrs, 4, (' ') ), t001w~werks ) as key, datab, datbi
from t001w
inner join tvko on tvko~vkorg = t001w~vkorg
left join ztab on ztab~key = concat( rpad( tvko~bukrs, 4, (' ') ), t001w~werks ) "This is why I need the concat
rpad( tvko~bukrs, 4, ' ' ) in this example returns XYZ, instead of XYZ , which leads to concatenated value being XYZABCD, rather than XYZ ABCD.
lpad seems to work just fine (returning XYZ), which leads me to believe I'm doing something wrong.
SQL functions don't accept string literals or variables (which preserve spaces in the same circumstances in ABAP) as they are non-elementary types.
Is there any way to pad/preserve the spaces in ABAP SQL (without pulling data and doing it in application server)?
Update: I solved my problem by splitting key selection from data selection and building the key in ABAP AS. It's a workaround that avoids the problem instead of solving it, so I'll keep the question open in case an actual solution appears.
EDIT: this post doesn't answer the question of inserting a number of characters which vary based on values in some table columns e.g. LENGTH function is forbidden in RPAD( tvko~bukrs, LENGTH( ... ), (' ') ). It's only starting from ABAP 7.55 that you can indicate SQL expressions instead of fixed numbers. You can't do it in ABAP before that. Possible workarounds are to mix ABAP SQL and ABAP (e.g. LIKE 'part1%part2' and then filtering out using ABAP) or to use native SQL directly (ADBC, AMDP, etc.)
Concerning how the trailing spaces are managed in OpenSQL/ABAP SQL, they seem to be ignored, the same way as they are ignored with ABAP fixed-length character variables.
Demonstration: I simplified your example to extract the line Walldorf plant:
These ones don't work (no line returned):
SELECT * FROM t001w
WHERE concat( 'Walldorf ' , 'plant' ) = t001w~name1
INTO TABLE #DATA(itab_1).
SELECT * FROM t001w
WHERE concat( rpad( 'Walldorf', 1, ' ' ), 'plant' ) = t001w~name1
INTO TABLE #DATA(itab_2).
These 2 ones work, one with leading space(s), one using concat_with_space:
SELECT * FROM t001w
WHERE concat( 'Walldorf', ' plant' ) = t001w~name1
INTO TABLE #DATA(itab_3).
SELECT * FROM t001w
WHERE concat_with_space( 'Walldorf', 'plant', 1 ) = t001w~name1
INTO TABLE #DATA(itab_4).
General information: ABAP documentation - SQL string functions
EDIT: working example added, using leading space(s).
I have a table storing pathes to files on sql server. I need to replace the path before the last backslash:
C:\Users\APP\AppData\Local\Temp\test\abc deg.pdf
to for example:
\app\pp\abc deg.pdf
EDIT: The table containts many pathes - I need to run through the whole table and change all pathes.
You can use:
CHARINDEX('\', REVERSE(#str))
to get the index of the first backslash starting from the end.
Using RIGHT with this index you can extract the string after the last backslash and concatenate it to the new path:
DECLARE #str VARCHAR(50) = 'C:\Users\APP\AppData\Local\Temp\test\abc deg.pdf'
SELECT '\app\pp' + RIGHT(#str, CHARINDEX('\', REVERSE(#str)))
Reverse the input string (using REVERSE) and find the index of the first backslash (using CHARINDEX).
Take the left part up to that index (using LEFT) and concatenate with the reverse of your replacement string (using + operator).
Then reverse that to get your final result.
Try this:
declare #a varchar(max)='C:\Users\APP\AppData\Local\Temp\test\abc deg.pdf'
select REPLACE(#a,SUBSTRING(#a,1,(LEN(#a)-charindex('\',reverse(#a),1))),'\app\pp')
Update: For updatingall the table column values.
select REPLACE([column-name],SUBSTRING([column-name],1,(LEN([column-name])-charindex('\',reverse([column-name]),1))),'\app\pp')
FROM [Your-table]
This is how it can be done.
UPDATE TABLE
SET PATH = REPLACE(PATH, 'C:\Users\APP\AppData\Local\Temp\test', '\app\pp')
WHERE ...
This is going to replace 'C:\Users\APP\AppData\Local\Temp\test' with '\app\pp'. Or you can modify the path as required.
Please test before executing this UPDATE statement. I havent specified filters here
I have an interesting SQL Server search requirement.
Say I have a table with Part Numbers as follows:
PARTNO DESCRIPTION
------ -----------
ABC-123 First part
D/12a92 Second Part
How can I create a search that will return results if I search, say, for 'D12A'?
I currently have a full text search set up for the description column, but I am looking to find parts that match the part no even when users don't include the / or - etc.
I'd rather do this in a single SQL statement rather than creating functions if possible as we only have read access to the DB.
You could do something like:
SELECT * FROM PART_TABLE
WHERE REPLACE(REPLACE(PARTNO,'/', ''),'-','') LIKE '%D12A%'
This would work for the 2 characters you specified and could be extended for more character like so:
SELECT * FROM PART_TABLE
WHERE REPLACE(REPLACE(REPLACE(PARTNO,'/', ''),'-',''),*,'') LIKE '%D12A%'
Probably not the most elegant of solutions unless your special characters are limited. Otherwise I'd suggest writing a Function to strip out non-alphanumeric characters.
Here is an example of such a function:
CREATE FUNCTION dbo.udf_AlphaNumericChars
(
#String VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #RemovingCharIndex INT
SET #RemovingCharIndex = PATINDEX('%[^0-9A-Za-z]%',#String)
WHILE #RemovingCharIndex > 0
BEGIN
SET #String = STUFF(#String,#RemovingCharIndex,1,'')
#RemovingCharIndex = PATINDEX('%[^0-9A-Za-z]%',#String)
END
RETURN #String
END
------- Query Sample (untested)---------
SELECT *
FROM PART_TABLE
WHERE DBO.udf_AlphaNumericChars(PARTNO) LIKE '%D12A%'
Taken From: http://sqlserver20.blogspot.co.uk/2012/06/find-alphanumeric-characters-only-from.html
I have been seraching solution for this issue .Though this particular question has been discussed many times in this forum, i did not get any proper answer for my problem.
I will be getting data from 3rd party which can contain single quote.This data need to be inserted into data base and when it contains single quote it fails and throws following error:
Msg 105, Level 15, State 1, Line 7
Unclosed quotation mark after the character string '
---Following is c++ code to pass trandata as input along with other parameters and invoke fn_stripsingleQuote10 function from SQL server:
strSQLText = "declare #returnType as varchar(max)\n EXEC #returnType = CABINET..fn_stripsingleQuote10 ";
sqlTxtParams.Format("'%s', '%s', '%s', tranData, sing_quote, double_sing_quote);
strSQLText += sqlTxtParams;
----My sql function(fn_stripsingleQuote10) to replace single quote
USE [cabinet]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE function [dbo].[fn_stripsingleQuote10](
#strip varchar(Max),#patern varchar(5),#replace varchar(5)
)
returns varchar(Max) as begin
declare #CleanString varchar(Max)
SET #CleanString=(REPLACE(#strip,#patern, #replace))
return #CleanString
end
sample output:
ex:
declare #returnType as varchar(max) EXEC #returnType = CABINET..fn_stripsingleQuote10 'fsds'd','''',''''''
I feel the way i am invoking the function is not proper.Please provide a solution .
Strictly speaking you're looking for QUOTENAME, which does exactly what you're asking:
'quote_character' Is a one-character string to use as the delimiter.
Can be a single quotation mark ( ' ), a left or right bracket ( [ ] ),
or a double quotation mark ( " ).
However, it is very very likely that your code is exposed to SQL Injection right now and you should actually use a parameter. It is almost never required to concatenate input into the resulted executed SQL.
I want to take a poorly designed SQL statement that's embedded in C# code and rewrite it as a stored procedure (presumably), and am looking for an appropriate means to address the following pattern:
sql = "SELECT <whatever> FROM <table> WHERE 1=1";
if ( someCodition.HasValue )
{
sql += " AND <some-field> = " + someCondition.Value;
}
This is a simplification. The actual statement is quite long and contains several such conditions, some of which include INNER JOIN's to other tables if the condition is present. This last part is key, otherwise I'd probably be able to solve all of them with:
WHERE <some-condition-value> IS NULL OR <some-field> = <some-condition-value>
I can think of a few possible approaches. I'm looking for the correct approach.
Edit:
I don't want to perform concatenation in C#. I consider this a serious compromise to security.
If I understand the question properly, the idea is to replace a whole section of code in C# in charge of producing, "long hand", a specific SQL statement corresponding to a list of search criteria, by a single call to a stored-procedure which would, SQL-side, use a generic template of the query aimed at handling all allowed combinations of search criteria in a uniform fashion.
In addition to the difficulty of mapping expressions evaluated on the application-side (eg. someCondition.HasValue) to expressions evaluated on the SQL-side (eg "some-condition-value"), the solution you envision may be logically/functionally equivalent to a "hand-crafted" SQL statement, but slower and more demanding of SQL resources.
Essentially, the C# code encapsulates specific knowledge about the "physical" layout of the database and its schema. It uses this info to figure-out when a particular JOIN may be required or when a particular application-level search criteria value translate to say a SQL "LIKE" rather than an "=" predictate. It may also encaspsulate business rules such as "when the ZIP code is supplied, search by that rather than by State".
You are right to attempt and decouple the data model (the way the application sees the data) from the data schema (the way it is declared and stored in SQL), but the proper mapping needs to be done somehow, somewhere.
Doing this at the level of the application, with all the expressive power of C# as opposed to say T-SQL, is not necessarily a bad thing, provided it is done
- in a module that is independent of other features of the application
and, where practical,
- it is somewhat data/configuration-driven as so to allow small changes in the data model (say the addition of a search criteria) to be implemented by changing a configuration file, rather than plugging this in somewhere in the middle of a long series of C# conditional statements.
start with this WHERE clause:
WHERE 1=1
then append all conditions as:
AND <some-field> = " + someCondition.Value;
the optimizer will toss out the 1=1 condition and you don't have to worry about too many ANDs
EDIT based on OP's comment about not wanting to concatinate strings:
here is a very comprehensive article on how to handle this topic:
Dynamic Search Conditions in T-SQL by Erland Sommarskog
it covers all the issues and methods of trying to write queries with multiple optional search conditions
here is the table of contents:
Introduction
The Case Study: Searching Orders
The Northgale Database
Dynamic SQL
Introduction
Using sp_executesql
Using the CLR
Using EXEC()
When Caching Is Not Really What You Want
Static SQL
Introduction
x = #x OR #x IS NULL
Using IF statements
Umachandar's Bag of Tricks
Using Temp Tables
x = #x AND #x IS NOT NULL
Handling Complex Conditions
Hybrid Solutions – Using both Static and Dynamic SQL
Using Views
Using Inline Table Functions
Conclusion
Feedback and Acknowledgements
Revision History
Well you can start with
StringBuilder sb = new StringBuilder();
sb.Append("SELECT <whatever> FROM <table> WHERE 1 = 1 ");
if ( someCodition.HasValue )
{
sb.Append(" AND <some-field> = " + someCondition.Value);
}
// And so on
Will save you the trouble of putting the first WHERE - AND
[Edit]
You can also try this
Create an SP with all required parameters for the table, and write the query like this.
DECLARE #sqlStatement NVARCHAR(MAX)
#sqlStatement = " SELECT fields1, fields2 FROM TableA WHERE 1 = 1 "
if(#param1 IS NOT NULL) #sqlStatement = #sqlStatement + "AND Column1 = " + #param1
if(#param2 IS NOT NULL) #sqlStatement = #sqlStatement + "AND Column2 = " + #param2
// and so on
sp_executeSql #sqlStatement
Also you can try similar SP but with:
SELECT fields1, fields2 FROM TableA WHERE 1 = 1
AND ( ( #param1 IS NULL ) OR ( Column1 = #param1 ) )
AND ( ( #param2 IS NULL ) OR ( Column2 = #param2 ) )
this is definitely injection proof!