SQL: Improving the string split - sql-server-2012

I have a set of code that takes a string value, split it, and pass it to a table. The code works, but it runs slow. Any suggestion to modify the code and make it run faster would be greatly appreciated.
DECLARE #StrPropertyIDs VARCHAR(1000)
SET #StrPropertyIDs = '419,429,459'
DECLARE #TblPropertyID TABLE
(
property_id varchar(100)
)
INSERT INTO #TblPropertyID(property_id)
select x.Item
from dbo.SplitString(#StrPropertyIDs, ',') x
select *
from vw_nfpa_firstArv_RPT
where property_use IN
(
SELECT property_id
FROM #TblPropertyID
)

The best long term strategy here would be to move away from CSV data in your SQL tables if at all possible. As a quick fix here, we could try creating the table variable with an index on property_id:
DECLARE #TblPropertyID TABLE (
property_id varchar(100) INDEX idx CLUSTERED
);
This would make the WHERE IN clause of your query faster, though we could try rewriting it using EXISTS:
SELECT *
FROM vw_nfpa_firstArv_RPT t1
WHERE EXISTS (SELECT 1 FROM #TblPropertyID t2
WHERE t2.property_id = t1.property_use);
Note that this would only work on SQL Server 2014 or later.

Related

Checking if field contains multiple string in sql server

I am working on a sql database which will provide with data some grid. The grid will enable filtering, sorting and paging but also there is a strict requirement that users can enter free text to a text input above the grid for example
'Engine 1001 Requi' and that the result will contain only rows which in some columns contain all the pieces of the text. So one column may contain Engine, other column may contain 1001 and some other will contain Requi.
I created a technical column (let's call it myTechnicalColumn) in the table (let's call it myTable) which will be updated each time someone inserts or updates a row and it will contain all the values of all the columns combined and separated with space.
Now to use it with entity framework I decided to use a table valued function which accepts one parameter #searchQuery and it will handle it like this:
CREATE FUNCTION myFunctionName(#searchText NVARCHAR(MAX))
RETURNS #Result TABLE
( ... here come columns )
AS
BEGIN
DECLARE #searchToken TokenType
INSERT INTO #searchToken(token) SELECT value FROM STRING_SPLIT(#searchText,' ')
DECLARE #searchTextLength INT
SET #searchTextLength = (SELECT COUNT(*) FROM #searchToken)
INSERT INTO #Result
SELECT
... here come columns
FROM myTable
WHERE (SELECT COUNT(*) FROM #searchToken WHERE CHARINDEX(token, myTechnicalColumn) > 0) = #searchTextLength
RETURN;
END
Of course the solution works fine but it's kinda slow. Any hints how to improve its efficiency?
You can use an inline Table Valued Function, which should be quite a lot faster.
This would be a direct translation of your current code
CREATE FUNCTION myFunctionName(#searchText NVARCHAR(MAX))
RETURNS TABLE
AS RETURN
(
WITH searchText AS (
SELECT value token
FROM STRING_SPLIT(#searchText,' ') s(token)
)
SELECT
... here come columns
FROM myTable t
WHERE (
SELECT COUNT(*)
FROM searchText
WHERE CHARINDEX(s.token, t.myTechnicalColumn) > 0
) = (SELECT COUNT(*) FROM searchText)
);
GO
You are using a form of query called Relational Division Without Remainder and there are other ways to cut this cake:
CREATE FUNCTION myFunctionName(#searchText NVARCHAR(MAX))
RETURNS TABLE
AS RETURN
(
WITH searchText AS (
SELECT value token
FROM STRING_SPLIT(#searchText,' ') s(token)
)
SELECT
... here come columns
FROM myTable t
WHERE NOT EXISTS (
SELECT 1
FROM searchText
WHERE CHARINDEX(s.token, t.myTechnicalColumn) = 0
)
);
GO
This may be faster or slower depending on a number of factors, you need to test.
Since there is no data to test, i am not sure if the following will solve your issue:
-- Replace the last INSERT portion
INSERT INTO #Result
SELECT
... here come columns
FROM myTable T
JOIN #searchToken S ON CHARINDEX(S.token, T.myTechnicalColumn) > 0

Best way to Iterate through temp table to build string in SQL Server 2014

I have created a temp table, the idea being that I want to loop through it, match all records with the same email address and then populate a string, which will go in to an email, then drop the table. This will be run as a stored procedure.
I've used a cursor that first grabbed all the unique email addresses and then coalesce the records but with potentially 100k-500k records performance won't be acceptable, and I know there must be a far more efficient way of doing it.
Example data (apologies, don't know how to format it properly)
#temptable
temp_email, temp_string
test#test.com string1
test#test.com string2
test2#test.com string3
test2#test.com string4
test3#test.com string5
I then want to populate another table with this data
emailto... emailbody
test#test.com 'string1<br / > string2'
test2#test.com 'string3<br / > string4'
test3#test.com 'string5'
Thank you.
The STUFF and FOR XML PATH method achieves this nicely in SQl Server 2014 and prior. Because you have the characters < and > however, they need to be "un-escaped" afterwards:
WITH VTE AS(
SELECT *
FROM (VALUES('test#test.com','string1'),
('test#test.com','string2'),
('test2#test.com','string3'),
('test2#test.com','string4'),
('test3#test.com','string5')) V(Email, String))
SELECT Email,
STUFF(REPLACE(REPLACE((SELECT '<br/>' + sq.String
FROM VTE sq
WHERE sq.Email = V.Email
FOR XML PATH('')),'<','<'),'>','>'),1,5,'')
FROM VTE V
GROUP BY Email;
you dont need to use cursor, please use string_agg function.
Create table #temptable
(temp_email varchar(50), temp_string varchar(50))
INSERT INTO #temptable
VALUES ('test#test.com', 'string1'),
('test#test.com', 'string2'),
('test2#test.com', 'string3'),
('test2#test.com', 'string4'),
('test3#test.com', 'string5')
Select temp_email, STRING_AGG(temp_string,' <br/>')
from #temptable
Group by temp_email
In SQL Server 2014 there are ways of doing this without a cursor but they are basically quite convoluted hacks and lead to pretty unreadable SQL in my opinion. See here for details:
How to concatenate text from multiple rows into a single text string in SQL server?
A cursor is arguably the best way in SQL 2014 because at least its readable.
In Sql Server 2017 there is an official aggregation function for this:
String_Agg
... but thats no use to you at the mo. Sorry.
You can do something like this :
-- Create temp table
CREATE TABLE #temptable (temp_email varchar(50), temp_string varchar(50))
-- populate table with data
INSERT INTO #temptable
VALUES ('test#test.com', 'string1'),
('test#test.com', 'string2'),
('test2#test.com', 'string3'),
('test2#test.com', 'string4'),
('test3#test.com', 'string5')
-- actual query
;WITH CTE_table AS(
SELECT C.temp_email,
REPLACE(REPLACE(STUFF(
(SELECT CAST(temp_string AS VARCHAR(20))+'<br/>' AS [text()]
FROM #temptable AS O
WHERE C.temp_email= o.temp_email
FOR XML PATH('')), 1, 0, NULL)
,'<','<') -- replace this < with html code <
,'>','>') -- replace this > with html code >
AS temp_string
,ROW_NUMBER() OVER (partition by temp_email order by temp_email) rownumber
FROM #temptable AS C
)
-- Get only unique records
SELECT temp_email,temp_string FROM CTE_table
Where rownumber=1

Scalar Variable error using var Table but not Temp Table

I am stumped with this one. I have the following code it works fine up to the point of the last #POC_XLATE in the update statement and then I get the error MUST DECLARE SCALAR VARIABLE.
If I change the table to a temp table the code works fine. I have tried moving the select statement to the end of the code, that didn't work. Hope someone has some suggestion on why it is doing this. Thanks in advance.
declare #POC_XLATE as TABLE(
POC_XLATE_ID int NULL,
TAR_ID int NULL,
POC_USERID varchar(50) NULL,
ACTION_DATE datetime NULL
)
insert into #POC_XLATE(POC_XLATE_ID, TAR_ID, POC_USERID, ACTION_DATE)
select * from POC_XLATE
where POC_XLATE.ACTION_DATE is null
select * from #POC_XLATE
update #POC_XLATE
set ACTION_DATE = TAR_DATA.OPEN_DATE
from TAR_DATA
where #POC_XLATE.TAR_ID = TAR_DATA.TAR_ID
A column alias cannot start with a #. That is the sign for a declared scalar variable. So, use table aliases:
update p
set ACTION_DATE = td.OPEN_DATE
from #POC_XLATE p JOIN
TAR_DATA td
on p.TAR_ID = td.TAR_ID ;
But why you would write the query in two steps?
insert into #POC_XLATE(POC_XLATE_ID, TAR_ID, POC_USERID, ACTION_DATE)
select p.POC_XLATE_ID, p.TAR_ID, p.POC_USERID, td.OPEN_DATE
from POC_XLATE p left join
TAR_DATA td
on p.TAR_ID = td.TAR_ID
where p.ACTION_DATE is null;
One step is much cleaner than two.

Replace Any Occurrence of "P" in String With A Value From Another Table

I have a column, sort_order in a table that contains a string of numbers, a delimiter and some P values:
1150||P||1168||1144||1149||1147||1164||1152||P||1148||1162||1163||P||1156||1157||1154||
I would like to replace any P values in this string with another value from the event_tile_id column of another table.
So far I've drafted this SQL below with no luck. What changes can I make to this Query to get the effect I need?
`SELECT sort_order,
(
REPLACE(sort_order,'P',
(SELECT TOP 1 event_tile_id
FROM daily_email_sales_today)
)
)
as sort_order
FROM daily_email_preview`
Removed "default_SaleID" from Query. Replace should now have 4 arguments.
This is how I would do it.
Since you don't have any joins, why not do a simpler update query using a static value?
DECLARE #update VARCHAR(100)
SET #update = (SELECT TOP 1 event_tile_id FROM daily_email_sales_today)
update daily_email_preview
SET sort_order = replace(sort_order,'P', #update)
Or even,
update daily_email_preview
SET sort_order = replace(sort_order,'P', '<new value>')
Assuming you are using SQL Server.
Along the same thought process as #Eric_Hauenstein if you are running this in a TSQL process:
declare #rSTR as varchar(50)
SELECT TOP 1 #rSTR = event_tile_id FROM daily_email_sales_toda
SELECT sort_order, REPLACE(sort_order,'P', #rSTR) as sort_order
FROM daily_email_preview

Find whole value without using LIKE operator

I have a large number to find in a table. But only a little part of it is given to me to find the whole number. I am looking for some new way to find these out without using the like operator. I know it is a simple thing, but I need a new approach.
Value given is: 4213076600
Value to find is: 89013106904213076600
I want to find it in the following query:
SELECT *
FROM table_name
WHERE column_name in (value,value,value)
i have searched the websites for this and came to know about the left() and right() functions but don't know how to arrange them to get the result
You can use CONTAINS more info:
SELECT *
FROM table_name
WHERE CONTAINS(column_name , '"*value*"')
For CONTAINS you need to create a FULL TEXT INDEX on the table.
DECLARE #table table (n varchar(20))
DECLARE #param1 varchar(20)
SET #param1 = '4213076600'
INSERT INTO #table (n)
SELECT '89013106904213076600'
SELECT n
FROM #table
WHERE RIGHT(n, len(#param1)) = #param1
Edit
select *
from table_name
where right(column_name, len(value)) = value
SELECT
*
FROM
table_name
WHERE
CHARINDEX(value1,column_name)>0
OR CHARINDEX(value2,column_name)>0
OR CHARINDEX(value3,column_name)>0
SELECT left(column_name,length_of_value)
FROM table_name
WHERE RIGHT(column_name,length_of_value_given) IN (value,value,value)
That's way it works for IN clause