Insert query where one field is a dynamic list of values - sql

I'm looking to solve the challenge of having an insert query where action_key is a dynamic list of strings. The cust_name and action fields will be the same for each new row, but action_key will change depending on how many action keys are in the list. Example:
INSERT INTO [table_name] (cust_name, action, action_key) VALUES (Bob, Approve, ApproveId1)
INSERT INTO [table_name] (cust_name, action, action_key) VALUES (Bob, Approve, ApproveId2) etc.
I'd appreciate some insight on this topic, thanks.

You'll have to play with it, but based upon your XML structure input that's how I handled the parsing.
Declare #cust_name Varchar(50),
#action Varchar(50),
#action_key Varchar(1000)
Select #cust_name = 'Bob',
#action = 'Approve',
#action_key = '<action_keys><action_key>ApproveId1</action_key><action_key>ApproveId2</action_key><action_key>ApproveId3</action_key><action_key>ApproveId4</action_key></action_keys>'
Select #cust_name,
#action,
n.r.value('.', 'varchar(50)')
From (Select Cast(#action_key As Xml)) As s(XMLCol)
Cross Apply s.XMLCol.nodes('action_keys/action_key') as n(r)
Reference: How Do I Split a Delimited String in SQL Server Without Creating a Function?

Well, you don't say how you get the list of strings, but generally you could create a SQL string dynamically and execute it:
EXEC ('INSERT INTO [table_name] ' +
'(cust_name, action, ' + action_key + ') ' +
'VALUES (''Bob'', ''Approve'', ''ApproveId1'')')

Related

How to add '' and , for multiple ID in SQL Server

I am writing a SELECT query that has multiple id, and I have to manually add '','' (e.g '12L','22C').
I have around 2000 id in an Excel sheet.
Is there any quicker way to add '','' to all the ID?
SELECT id, name
FROM table
WHERE id IN ('12L', '22C', 33j, 7k, 44J, 234C)
DECLARE #Ids VARCHAR(MAX) = '12L,22C,33j,7k,44J,234C'
--Your question's answer.
DECLARE #Splitted VARCHAR(MAX) = STUFF((
SELECT CONCAT(',''', value, '''')
FROM string_split(#Ids, ',')
FOR XML PATH('')), 1, 1, '')
SELECT #Splitted
--'12L','22C','33j','7k','44J','234C'
OR simplified
SELECT id, name from table where id in (SELECT value FROM string_split(#Ids, ','))
string_split: for more information docs
concat: for more information docs
Here is a conceptual example for you. It will work in SQL Server 2012 onwards.
It is a three step process:
Convert input string into XML.
Convert XML into a relational resultset inside the CTE.
Join with a DB table.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Code VARCHAR(10), City VARCHAR(50));
INSERT INTO #tbl (Code, City) VALUES
('10T', 'Miami'),
('45L', 'Orlando'),
('50Z', 'Dallas'),
('70W', 'Houston');
-- DDL and sample data population, end
DECLARE #Str VARCHAR(100) = '22C,45L,50Z,105M'
, #separator CHAR(1) = ',';
DECLARE #parameter XML = TRY_CAST('<root><r><![CDATA[' +
REPLACE(#Str, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML);
;WITH rs AS
(
SELECT c.value('.', 'VARCHAR(10)') AS Code
FROM #parameter.nodes('/root/r/text()') AS t(c)
)
SELECT t.*
FROM #tbl AS t INNER JOIN
rs ON t.Code = rs.Code;
Two alternatives if you're ok doing the transformation outside of SQL.
As one of the comments on your question suggests, you could do this in Excel using this as a formula:
="'" & A1 & "',"
Replace the "A1" with whatever cell your first ID is in. After you enter the formula, click the cell it's in, and there will be a small square on the bottom right. Double click that and it will apply the formula to every cell in the column, automatically shifting the cell reference to match the current row. You can then copy the values from that column and erase the comma at the end.
You could also use an editor that supports regular expression like SSMS, Azure Data Studio, Notepad++, etc and do a Find+Replace:
Paste your IDs in
Hit the replace hotkey (Ctrl+H in all 3 of the ones I listed). There will be an option to enable Regular Expression (SSMS/ADS have a little .* icon, Notepad++ has a labeled radio button). Click it
Find this:
(\w+)
Replace it with this
'$1',
Copy and paste the formatted IDs into your query. Same as above, you'll have to erase the final comma
This will work as long as your IDs are alphanumeric with no spaces, punctuation, etc. If the formatting is more complex, the regex (the (\w+) you search for) will need to be more complex as well. Using this strategy, you could also get rid of the linebreaks by using the regex (\w+)\r\n.
hei, you can use Function CONCATENATE in Excel before you copy those ID in sql.

SQL Merge statement with input table name

I have a scenario to copy the data from one table to another table. While copying, I need to store the Identity values of table1 and table2.
create procedure procedure1
(#sourceClientNumber int,
#destinationClientNumber int,
#statusID varchar(10))
as
declare #scriptSql nvarchar(max);
declare #Temp_ClientScriptTable table
(newScriptID int,
oldScriptID int);
begin
SET #CreatedBy = 'Initaial Creation'
SET #CreatedByName= 'Initial Creation'
SET #CreatedDateTime = GETDATE()
SET #scriptSql = 'Merge into [dbo].[Client_'+#destinationClientNumber +'_Script] using
(select ScriptID,
ScriptName,
ScriptVersion,
Description,
FacilityID,
StatusID,
Shared,
ScriptTYpeID
from [dbo].[Client_'+#sourceClientNumber +'_Script]
where statusID = ' +#statusID + '
) scripts on 1 = 0
When not matched then
insert ([ScriptName],
[ScriptVersion],
[CreatedBy],
[CreatedByName],
[CreatedDateTime],
[Description],
[FacilityID],
[StatusID],
[Shared],
[ScriptTypeID])
values (scripts.ScriptName,
scripts.ScriptVersion,'
+ #CreatedBy + ','
+ #CreatedByName + ','
+ #CreatedDateTime + ',
scripts.Description,
scripts.FacilityID,
scripts.StatusID,
scripts.Shared,
scripts.ScriptTypeID)
output Inserted.ScriptID, scripts.ScriptID
into' + #Temp_ClientScriptTable + '(newScriptID, oldScriptID)'
EXECUTE sp_executesql #scriptSql
I am getting the error at #Temp_ClientScriptTable.
Could you please help me on this..
The main problem here is faulty database design.
The fact that you have multiple tables with the same structure, only different by a number inside the name - means you are mixing data (the number) and meta data (the table name).
Instead of having a different table for each client, you should add the client number as a column to a single table.
If you can do that, it will also eliminate the need for using dynamic SQL everywhere you need to address this table.
Root cause is, #Temp_ClientScriptTable is a table variable and cannot participate in string concatenation.
Your last line of #scriptSql should be -
into #Temp_ClientScriptTable(newScriptID, oldScriptID)'
instead of
into' + #Temp_ClientScriptTable + '(newScriptID, oldScriptID)'
Also you need to add single quotes around varchar variables while using them in string concatenation. Your final #scriptSql will be -
SET #scriptSql = 'Merge into [dbo].[Client_'+#destinationClientNumber +'_Script] using
(select ScriptID,
ScriptName,
ScriptVersion,
Description,
FacilityID,
StatusID,
Shared,
ScriptTYpeID
from [dbo].[Client_'+#sourceClientNumber +'_Script]
where statusID = ''' +#statusID + '''
) scripts on 1 = 0
When not matched then
insert ([ScriptName],
[ScriptVersion],
[CreatedBy],
[CreatedByName],
[CreatedDateTime],
[Description],
[FacilityID],
[StatusID],
[Shared],
[ScriptTypeID])
values (scripts.ScriptName,
scripts.ScriptVersion,'''
+ #CreatedBy + ''','''
+ #CreatedByName + ''','
+ #CreatedDateTime + ',
scripts.Description,
scripts.FacilityID,
scripts.StatusID,
scripts.Shared,
scripts.ScriptTypeID)
output Inserted.ScriptID, scripts.ScriptID
into #Temp_ClientScriptTable(newScriptID, oldScriptID)'
To debug these kind of issue, you should always print the the concatenated query and see how your query will look like -
print #scriptSql

How to apply trim function inside this query [duplicate]

Below is simple sql query to select records using in condition.
--like this I have 6000 usernames
select * from tblUsers where Username in ('abc ','xyz ',' pqr ',' mnop ' );
I know there are LTrim & Rtrim in sql to remove the leading trailing spaces form left & right respectively.
I want to remove the spaces from left & right in all the usernames that I am supplying to the select query.
Note:-
I want to trim the values that I am passing in the in clause.(I don't want to pass LTrim & RTrim to each value passed).
There are no trailing space in the records but value that I am passing in the clause is copied from excel & then pasted in Visual Studio. Then using ALT key I put '(single quote) at the left & right sides of the string. Due to this some strings has spaces in the right side trailing.
How to use the trim function in the select query?
I am using MS SQL Server 2012
If I understand your question correctly you are pasting from Excel into an IN clause in an adhoc query as below.
The trailing spaces don't matter. It will still match the string foo without any trailing spaces.
But you need to ensure that there are no leading spaces.
As the source of the data is Excel why not just do it all there?
You can use formula
= CONCATENATE("'",TRIM(SUBSTITUTE(A1,"'","''")),"',")
Then copy the result (from column B in the screenshot above) and just need to trim off the extra comma from the final entry.
You can do like this:
select * from tblUsers where LTRIM(RTRIM(Username)) in (ltrim(rtrim('abc')),ltrim(rtrim('xyz')),ltrim(rtrim('pqr')),ltrim(rtrim('mnop')));
However, if you have permission to update the database. Please remove all the spaces in your Username field. It is really not good to do the query like this.
One way to tackle your problem and still be able to benefit from an index on username is to use a persisted computed column:
Setup
-- drop table dbo.tblUsers
create table dbo.tblUsers
(
UserId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_UserTest PRIMARY KEY,
Username NVARCHAR(64) NOT NULL,
UsernameTrimmed AS LTRIM(RTRIM(Username)) PERSISTED
)
GO
-- other columns may be included here with INCLUDE (col1, col2)
CREATE INDEX IDX_UserTest ON dbo.tblUsers (UsernameTrimmed)
GO
insert into dbo.tblUsers (Username) VALUES ('abc '),('xyz '),(' pqr '), (' mnop '), ('abc'), (' useradmin '), ('etc'), (' other user ')
GO
-- some mock data to obtain a large number of records
insert into dbo.tblUsers (Username)
select top 20000 SUBSTRING(text, 1, 64) from sys.messages
GO
Test
-- this will use the index (index seek)
select * from tblUsers where UsernameTrimmed in (LTRIM(RTRIM('abc')), LTRIM(RTRIM(' useradmin ')));
This allows for faster retrievals at the expense of extra space.
In order to get rid of query construction (and the ugliness of many LTRIMs and RTRIMs), you can push searched users in a table that looks like tblUsers.
create table dbo.searchedUsers
(
Username NVARCHAR(64) NOT NULL,
UsernameTrimmed AS LTRIM(RTRIM(Username)) PERSISTED
)
GO
Push raw values into dbo.searchedUsers.Username column and the query should look like this:
select U.*
from tblUsers AS U
join dbo.searchedUsers AS S ON S.UsernameTrimmed = U.UsernameTrimmed
The big picture
It is way better to properly trim your data in the service layer of your application (C#) so that future clients of your table may rely on decent information. So, trimming should be performed both when inserting information into tblUsers and when searching for users (IN values)
select *
from tblUsers
where RTRIM(LTRIM(Username)) in ('abc','xyz','pqr','mnop');
Answer: SELECT * FROM tblUsers WHERE LTRIM(RTRIM(Username)) in ('abc','xyz','pqr','mnop');
However, please note that if you have functions in your WHERE clause it defeats the purpose of having an indexes on that column and will use a
scan than a seek.
I would propose you clean your data before inserting into tblUsers
I think you can try this:
Just replace the table2 with you table name form where you are getting the username
select * from tblUsers where Username in ((select distinct
STUFF((SELECT distinct ', ' + RTRIM(LTRIM(t1.Username))
from table2 t1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,2,'') UserName
from table2 t) );
I'd do it in two step:
1) populate a temp table with all your strings with blanks
2) do a select with a subselect
create table a (a char(1))
insert into a values('a')
insert into a values('b')
insert into a values('c')
insert into a values('d')
create table #b (atmp char(5))
insert into #b values ('a ')
insert into #b values (' b')
insert into #b values (' c ')
select * from a where a in (select ltrim(rtrim(atmp)) from #b)

Extracting specific column values embedded within composite Strings of codes

I am trying to create a piece of code in sql server 2008 that will grab specific values from each distinct string within my dbo table. The ultimate goal is to make a drop down box within Visual Studio so that one can choose all lines from the database that contain a specific product code (see definition of product code below). Example strings:
in_0314_95pf_500_w_0315
in_0314_500_95pf_0315_w
The part of these strings I am wishing to identify is the 3 digit numeric code (in this case let us call it product code) that appears once within each string. There are roughly 300 different product codes.
The problem is that these product code values do not appear in the same position within each unique string. Hence, I am having a hard time determining the product code because I can't use substring, charindex, like, etc.
Any ideas? Any help is MUCH appreciated.
This can be done with PATINDEX:
DECLARE #s NVARCHAR(100) = 'in_0314_95pf_500_w_0315'
SELECT SUBSTRING(#s, PATINDEX('%[_][0-9][0-9][0-9][_]%', #s) + 1, 3)
Output:
500
If there are no underscores then:
SELECT SUBSTRING(#s, PATINDEX('%[^0-9][0-9][0-9][0-9][^0-9]%', #s) + 1, 3)
This means 3 digits between any symbols that are not digits.
EDIT:
Apply to table like:
SELECT SUBSTRING(ColumnName, PATINDEX('%[^0-9][0-9][0-9][0-9][^0-9]%', ColumnName) + 1, 3)
FROM TableName
One approach is to use a String splitting table function like this one which breaks the string up into its components. You can then filter the components based on your criteria:
SELECT Name
FROM dbo.splitstring('in_0314_95pf_500_w_0315', '_')
WHERE ISNUMERIC(Name) = 1 AND LEN(Name) = 3;
I've amended the function slightly to accept the delimiter as a parameter.
CREATE FUNCTION dbo.splitstring ( #stringToSplit VARCHAR(MAX), #delimiter VARCHAR(50))
RETURNS
#returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(#delimiter, #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(#delimiter, #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, len(#delimiter), #pos-len(#delimiter))
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+LEN(#delimiter),
LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
To apply this to your table, use CROSS APPLY (Single Delimiter):
SELECT mt.Name, x.Name AS ProductCode
FROM MyTable mt
CROSS APPLY dbo.splitstring(mt.Name, '_') x
WHERE ISNUMERIC(x.Name) = 1 AND LEN(x.Name) = 3
Update, Multiple Delimiters
I guess the real underlying problem is that ultimately the product codes need to be normalized out of the composite key (e.g. add a distinct ProductId or ProductCode column to the same table), derived using a query like this, and then stored back in the table via an update. Reverse engineering the product codes out of the string appears to be a trial and error process.
Nonetheless, you can continue to keep passing the split strings through further splitting functions (one per each type of delimiter), before applying your final discriminating filter:
SELECT *
FROM MyTable mt
CROSS APPLY dbo.splitstring(mt.Name, 'test') y -- First alias
CROSS APPLY dbo.splitstring(y.Name, '_') x -- Reference the preceding alias
WHERE ISNUMERIC(x.Name) = 1 AND LEN(x.Name) = 3; -- Must reference the last alias (x)
Note that the stringsplit function has again been changed to accommodate multicharacter delimiters.
If you have a table (or can generate in inline view) of the product codes, you can join the list of long strings to the product codes with a like clause.
Create Table longcodes (
longcode varchar(20)
)
Create Table products (
prodCode char(3)
)
insert products values('100')
insert products values('111')
insert products values('123')
insert longcodes values ('abc_a_100_test')
insert longcodes values ('asdf_111_bob')
insert longcodes values ('in_0314_123_95pf')
insert longcodes values ('f_100_u')
insert longcodes values ('hihi_111_bye')
insert longcodes values ('in_123_0314_95pf')
insert longcodes values ('a_b__c_d_100_efg')
select *
from products p
join longcodes l on l.longcode like '%_' + p.prodCode + '_%'
And they get aligned with the product codes like this:
prodCode longcode
100 abc_a_100_test
100 f_100_u
100 a_b__c_d_100_efg
111 asdf_111_bob
111 hihi_111_bye
123 in_0314_123_95pf
123 in_123_0314_95pf
EDIT: Seeing the developments in the other answer, you can simplify the like clause to
like p.prodCode
and just deal with the fact that you have a much greater chance of a single composite string producing multiple matches.

String manipulation SQL

I have a row of strings that are in the following format:
'Order was assigned to lastname,firsname'
I need to cut this string down into just the last and first name but it is always a different name for each record.
The 'Order was assigned to' part is always the same.......
Thanks
I am using SQL Server. It is multiple records with different names in each record.
In your specific case you can use something like:
SELECT SUBSTRING(str, 23) FROM table
However, this is not very scalable, should the format of your strings ever change.
If you are using an Oracle database, you would want to use SUBSTR instead.
Edit:
For databases where the third parameter is not optional, you could use SUBSTRING(str, 23, LEN(str))
Somebody would have to test to see if this is better or worse than subtraction, as in Martin Smith's solution but gives you the same result in the end.
In addition to the SUBSTRING methods, you could also use a REPLACE function. I don't know which would have better performance over millions of rows, although I suspect that it would be the SUBSTRING - especially if you were working with CHAR instead of VARCHAR.
SELECT REPLACE(my_column, 'Order was assigned to ', '')
For SQL Server
WITH testData AS
(
SELECT 'Order was assigned to lastname,firsname' as Col1 UNION ALL
SELECT 'Order was assigned to Bloggs, Jo' as Col1
)
SELECT SUBSTRING(Col1,23,LEN(Col1)-22) AS Name
from testData
Returns
Name
---------------------------------------
lastname,firsname
Bloggs, Jo
on MS SQL Server:
declare #str varchar(100) = 'Order was assigned to lastname,firsname'
declare #strLen1 int = DATALENGTH('Order was assigned to ')
declare #strLen2 int = len(#str)
select #strlen1, #strLen2, substring(#str,#strLen1,#strLen2),
RIGHT(#str, #strlen2-#strlen1)
I would require that a colon or some other delimiter be between the message and the name.
Then you could just search for the index of that character and know that anything after it was the data you need...
Example with format changing over time:
CREATE TABLE #Temp (OrderInfo NVARCHAR(MAX))
INSERT INTO #Temp VALUES ('Order was assigned to :Smith,Mary')
INSERT INTO #Temp VALUES ('Order was assigned to :Holmes,Larry')
INSERT INTO #Temp VALUES ('New Format over time :LootAt,Me')
SELECT SUBSTRING(OrderInfo, CHARINDEX(':',OrderInfo)+1, LEN(OrderInfo))
FROM #Temp
DROP TABLE #Temp