SQL Dynamic Charindex - sql-server

I have a field in a sql table but I need to parse it via charindex, but the lttle caveat is, I don't know how many pieces there are.
The field data would look like the following:
(Image: "filename=a.jpg"), (Image: "filename=b.jpg")
But the question I'm not sure how many filenames there will be in this string, so i need to dynamically build this out this could be 1 or this could be 100.
Any suggestions?
Thanks

Since you cannot know in advance how many values you will extract from each value, I would suggest to represent the results as records, not columns.
If you are using SQL Server 2016 or higher, you can use function STRING_SPLIT() to turn CSV parts to records. Then, SUBSTRING() and CHARINDEX() can be used to extract the relevant information:
declare #t table ([txt] varchar(200))
insert into #t VALUES ('(Image: "filename=a.jpg"),(Image: "filename=b.jpg")')
SELECT value, SUBSTRING(
value,
CHARINDEX('=', value) + 1,
LEN(value) - CHARINDEX('=', value) - 2
)
FROM #t t
CROSS APPLY STRING_SPLIT(t.txt , ',')
Demo on DB Fiddle:
DECLARE #t table ([txt] varchar(200))
INSERT INTO #t VALUES ('(Image: "filename=a.jpg"),(Image: "filename=b.jpg")')
SELECT value, SUBSTRING(
value,
CHARINDEX('=', value) + 1,
LEN(value) - CHARINDEX('=', value) - 2
)
FROM #t t
CROSS APPLY STRING_SPLIT(t.txt , ',')
GO
value | (No column name)
:------------------------ | :---------------
(Image: "filename=a.jpg") | a.jpg
(Image: "filename=b.jpg") | b.jpg
NB : this assumes that the value to extract is always located after the first equal sign and until 2 characters before the end of string. If the pattern is different, you may need to adapt the SUBSTRING()/CHARINDEX() calls.

The real issue is: This is breaking 1.NF. You should never ever store more than one piece of data in one cell. Such CSV-formats are a pain in the neck and you really should use a related side table to store your image hints one by one.
Nevertheless, this can be handled:
--A mockup table
DECLARE #mockup TABLE(ID INT IDENTITY,YourString VARCHAR(1000));
INSERT INTO #mockup VALUES
('(Image: "filename=a.jpg"), (Image: "filename=b.jpg") ')
,('(Image: "filename=aa.jpg"), (Image: "filename=bb.jpg"), (Image: "filename=cc.jpg"), (Image: "filename=dd.jpg"), (Image: "filename=ee.jpg")');
--Pick one element by its position:
DECLARE #position INT=2;
SELECT CAST('<x>' + REPLACE(t.YourString,',','</x><x>') + '</x>' AS XML)
.value('/x[position()=sql:variable("#position")][1]','nvarchar(max)')
FROM #mockup t;
The trick is, to transform the string to XML and use XQuery to fetch the needed element by its position. The intermediate XML looks like this:
<x>(Image: "filename=a.jpg")</x>
<x> (Image: "filename=b.jpg") </x>
You can use some more replacements and L/RTRIM() to get it cleaner.
Read table data
And if you want to create a clean side table and you need all data neatly separated, you can use a bit more of the same:
SELECT CAST('<x><y><z>'
+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
t.YourString,'(','') --no opening paranthesis
,')','') --no closing paranthesis
,'"','') --no quotes
,' ','') --no blanks
,'=','</z><z>') --Split at "="
,':','</z></y><y><z>') --Split at ":"
,',','</z></y></x><x><y><z>') --Split at ","
+ '</z></y></x>' AS XML)
FROM #mockup t;
This returns
<x>
<y>
<z>Image</z>
</y>
<y>
<z>filename</z>
<z>a.jpg</z>
</y>
</x>
<x>
<y>
<z>Image</z>
</y>
<y>
<z>filename</z>
<z>b.jpg</z>
</y>
</x>
And with this you would get a clean EAV-table (
WITH Casted AS
(
SELECT ID
,CAST('<x><y><z>'
+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
t.YourString,'(','')
,')','')
,'"','')
,' ','')
,'=','</z><z>')
,':','</z></y><y><z>')
,',','</z></y></x><x><y><z>')
+ '</z></y></x>' AS XML) AS CastedToXml
FROM #mockup t
)
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS ID
,ID AS oldId
,eachElement.value('y[1]/z[1]','varchar(max)') AS DataType
,eachElement.value('y[2]/z[1]','varchar(max)') AS ContentType
,eachElement.value('y[2]/z[2]','varchar(max)') AS Content
FROM Casted
CROSS APPLY CastedToXml.nodes('/x') A(eachElement)
The result
+----+-------+----------+-------------+---------+
| ID | oldId | DataType | ContentType | Content |
+----+-------+----------+-------------+---------+
| 1 | 1 | Image | filename | a.jpg |
+----+-------+----------+-------------+---------+
| 2 | 1 | Image | filename | b.jpg |
+----+-------+----------+-------------+---------+
| 3 | 2 | Image | filename | aa.jpg |
+----+-------+----------+-------------+---------+
| 4 | 2 | Image | filename | bb.jpg |
+----+-------+----------+-------------+---------+
| 5 | 2 | Image | filename | cc.jpg |
+----+-------+----------+-------------+---------+
| 6 | 2 | Image | filename | dd.jpg |
+----+-------+----------+-------------+---------+
| 7 | 2 | Image | filename | ee.jpg |
+----+-------+----------+-------------+---------+

I used a table value function
ALTER FUNCTION [dbo].[Fn_sqllist_to_table](#list AS VARCHAR(8000),
#delim AS VARCHAR(10))
RETURNS #listTable TABLE(
Position INT,
Value VARCHAR(8000))
AS
BEGIN
DECLARE #myPos INT
SET #myPos = 1
WHILE Charindex(#delim, #list) > 0
BEGIN
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,LEFT(#list, Charindex(#delim, #list) - 1))
SET #myPos = #myPos + 1
IF Charindex(#delim, #list) = Len(#list)
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,'')
SET #list = RIGHT(#list, Len(#list) - Charindex(#delim, #list))
END
IF Len(#list) > 0
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,#list)
RETURN
END
By calling it via
select * into #test from tableX as T
cross apply [Fn_sqllist_to_table](fieldname,'(')
and then just substringed the value into the final table

Related

Create a function in t-SQL which will automatically select source and target tables and update values in specific columns

I have a table, suppose [dbo].[Anonymised_Data], which contains information about the target table in which certain values need to be modified. The column names are also specified in the table.
ยด
+--------------+--------------+----------------+---------------+
| Target_table | Column_Name | Original_Value | Masked_Value |
+--------------+--------------+----------------+---------------+
| Table 1 | 0RT01 | Dhaka | City 1 |
| Table 1 | NAME1 | P&G | Vendor 1 |
+--------------+--------------+----------------+---------------+
Now I want to create a function which will change the values in these columns of the target table from original value to masked value. I also want the function to be able to switch back the masked value to original value.
So the result would look like, for example:
Previously
Table 1
+-------------------+
| ORT01 | Client |
+-------------------+
| Dhaka | A |
+-------------------+
And after running the function it will look like:
Table 1
+-------------------+
| ORT01 | Client |
+-------------------+
| City 1 | A |
+-------------------+
That is, all the other values in target table remain the same.
I also want the option to switch back to the original values.
I understand this can be accomplised with more temporary tables, but [dbo].[Anonymised_Data] will contain reference to a large number of tables and so updating each one by one would be tedious.
If anyone could suggest a solution, would be great! Thanks.
The only way i can think to do this is by using dynamically generated SQL statements
DECLARE #rollBack BIT = 0 --SET TO 1 TO ROLLBACK TO UNMASKED VALUES
DECLARE #updateStatements TABLE
(
ID INT IDENTITY(1,1) NOT NULL
,UpdateStatement NVARCHAR(MAX)
)
;with statements
AS
(
SELECT *
,QUOTENAME(Column_Name) + ' = ''' + CASE #rollBack WHEN 1 THEN Original_Value ELSE Masked_Value END + '''' as SetStatement
,'WHERE ' + QUOTENAME(Column_Name) + ' = ''' + CASE #rollBack WHEN 1 THEN Masked_Value ELSE Original_Value END + '''' as WhereClause
,ROW_NUMBER() OVER (ORDER BY Target_Table, Column_Name) as PID
FROM Anonymised_Data
)
INSERT INTO #updateStatements
(
UpdateStatement
)
SELECT
'UPDATE ' + QUOTENAME(Target_Table) + ' SET ' + SetStatement + ' ' + WhereClause as UpdateStatement
FROM statements
DECLARE #curID INT = 1
DECLARE #maxID INT = (SELECT MAX(ID) FROM #updateStatements)
WHILE (#curID <= #maxID)
BEGIN
DECLARE #curStatement NVARCHAR(MAX) = N''
SELECT #curStatement = UpdateStatement
FROM #updateStatements
WHERE ID = #curID
PRINT #curStatement
EXEC (#curStatement)
SET #curID += 1
END

Compare XML to string?

I have a table with an XML column. The vast majority of rows contain this data:
<X C="1"></X>
I would like to find any row among the millions that does not match this. So...
select *
from DataResults
where cast(baserentamount as varchar(250)) not like '<X C="1"></X>'
This returns every row. I assume there is something very simple wrong here?
Update: perhaps I should reverse the sense of this question - what is the easiest way to do the query I want, IE, match the entry in an XML column in a WHERE?
You should look at the actual XML in your table. If you insert that xml into a table it is not the same string you think it is.
declare #Something table(baserentamount xml)
insert #Something select '<X C="1"></X>'
select *
, cast(baserentamount as varchar(250))
from #Something
Therefore your query will very likely return every row. And it will certainly return the rows you want to exclude because the xml string has changed.
Ahhh, well there it is, the devil is in the details:
XML as presented:
<X C="1" />
XML after CAST:
<X C="1"/>
Not entirely obvious, nor obvious why this should be, but there it is, it's a WS problem.
Any approach to do this on string level is wrong (due to different XML layouts, which are semantically equal and therefore possible at any stage).
Try something along this:
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<X C="1"></X>')
,(N'<X A="huh?" C="1"></X>')
,(N'<X C="2"></X>')
,(N'<X C="1"></X><X C="2"></X>')
,(N'<Y C="1"></Y>')
,(N'<root>some other</root>')
;
SELECT t.*
,CASE WHEN t.xmldata.query(N'count(/*)>1').value('.','bit')=1 THEN 'X' END MoreThanOneNodeInLevel1
,CASE WHEN t.xmldata.query(N'count(/*[1]/#*)>1').value('.','bit')=1 THEN 'X' END MoreThanOnAttributeInFirstElement
,CASE WHEN t.xmldata.query(N'/X[1]/#C != "1"').value('.','bit')=1 THEN 'X' END Attribute_X_C_isNot1
,CASE WHEN t.xmldata.query(N'local-name(/*[1])!="X"').value('.','bit')=1 THEN 'X' END FirstElementIsNotNamedX
FROM #tbl t;
The result
+----+-------------------------+-------------------------+-----------------------------------+----------------------+-------------------------+
| ID | xmldata | MoreThanOneNodeInLevel1 | MoreThanOnAttributeInFirstElement | Attribute_X_C_isNot1 | FirstElementIsNotNamedX |
+----+-------------------------+-------------------------+-----------------------------------+----------------------+-------------------------+
| 1 | <X C="1" /> | NULL | NULL | NULL | NULL |
+----+-------------------------+-------------------------+-----------------------------------+----------------------+-------------------------+
| 2 | <X A="huh?" C="1" /> | NULL | X | NULL | NULL |
+----+-------------------------+-------------------------+-----------------------------------+----------------------+-------------------------+
| 3 | <X C="2" /> | NULL | NULL | X | NULL |
+----+-------------------------+-------------------------+-----------------------------------+----------------------+-------------------------+
| 4 | <X C="1" /><X C="2" /> | X | NULL | NULL | NULL |
+----+-------------------------+-------------------------+-----------------------------------+----------------------+-------------------------+
| 5 | <Y C="1" /> | NULL | NULL | NULL | X |
+----+-------------------------+-------------------------+-----------------------------------+----------------------+-------------------------+
| 6 | <root>some other</root> | NULL | NULL | NULL | X |
+----+-------------------------+-------------------------+-----------------------------------+----------------------+-------------------------+
As you can see, just the first row is NULL in all columns.
The idea in short:
Your XML has got
- 1 single element at level 1
- This element has got 1 single attribute
- The "C"-attribute's value in the first element="1"
- The local-name of the first element is "X"
In the sample data I added various "wrong" XML instances.
You might need to add more rules.
Hint: You can place the expression in a WHERE clause or use XML.exist().
The pure XML way to handle what you need would be to create a tiny XML Schema. After that you can validate that XML column against it.
SQL
USE tempdb;
GO
-- DDL and sample data population, start
IF EXISTS (SELECT * FROM sys.xml_schema_collections
WHERE name = N'MySchema'
AND schema_id = SCHEMA_ID(N'dbo'))
DROP XML SCHEMA COLLECTION dbo.MySchema;
CREATE XML SCHEMA COLLECTION dbo.MySchema
AS N'<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="X">
<xsd:complexType>
<xsd:attribute name="C" type="xsd:int" use="required" fixed="1"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>'
GO
DECLARE #validationTbl TABLE (ID INT NOT NULL, Reason NVARCHAR(1024) NULL);
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<X C="1"></X>') -- good
,(N'<X C="1"/>') -- good
,(N'<X C="2"></X>') -- bad
,(N'<root>Miami</root>'); -- ugly
-- DDL and sample data population, end
-- Method #1
DECLARE #ID INT, #XML AS XML(dbo.MySchema)
, #RowCount INT = (SELECT COUNT(*) FROM #tbl);
WHILE #RowCount > 0
BEGIN
BEGIN TRY
SELECT #ID = ID, #XML = XMLData
FROM #tbl
ORDER BY ID DESC
OFFSET #RowCount - 1 ROWS FETCH NEXT 1 ROWS ONLY;
END TRY
BEGIN CATCH
INSERT INTO #validationTbl (ID, Reason)
VALUES (#ID, ERROR_MESSAGE());
END CATCH
SET #RowCount -= 1;
END;
-- test
SELECT * FROM #validationTbl;
-- Method #2
-- unfortunately, it stops at the very first error
-- TRY_CAST() shall swallow all XSD validation errors internally
-- and produce a NULL value for all invalid rows
/*
Validate Expressions (XQuery)
https://learn.microsoft.com/en-us/sql/xquery/validate-expressions-xquery?view=sql-server-ver15
https://learn.microsoft.com/en-us/sql/t-sql/functions/try-cast-transact-sql?view=sql-server-ver15
*/
SELECT TOP(4) *, TRY_CAST(xmldata AS XML(dbo.MySchema)) AS Result
FROM #tbl
ORDER BY ID;
Output
+----+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ID | Reason |
+----+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 2 | XML Validation: Element or attribute 'C' was defined as fixed, the element value has to be equal to value of 'fixed' attribute specified in definition. Location: /*:X[1]/#*:C |
| 3 | XML Validation: Declaration not found for element 'root'. Location: /*:root[1] |
+----+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

How to split single column into 2 columns by delimiter

I have a table with the following schema
a | b | c
qqq | www | ddd/ff
fff | ggg | xx/zz
jjj | gwq | as/we
How would I write a query so my data comes as
a | b | c_1 | c_2
qqq | www | ddd | ff
declare #t table(a varchar(20),b varchar(20),c varchar(20))
insert into #t values('qqq','www','ddd/ff')
SELECT a, b,
left(c,charindex('/',c)-1) As c_1,
right(c,charindex('/',reverse(c))-1) As c_2
FROM #t
or, if column c does not always have the format xxx/yyy, you need to validate charindex position:
declare #t table(a varchar(20),b varchar(20),c varchar(20))
insert into #t values('qqq','www','ddd/ff'), ('qqq','www','dddff')
SELECT a, b,
case when charindex('/',c) > 0 then left(c,charindex('/',c)-1) else c end As c_1,
case when charindex('/',c) > 0 then right(c,charindex('/',reverse(c))-1) else null end As c_2
FROM #t
You can use as follows :
select LEFT(name, CHARINDEX('/', name)-1) from test_table;
where it returns the left part of the string name, before slash, and the following command returns the right part, after slash.
select RIGHT(name, CHARINDEX('/', name)-1) from test_table;
I did a whole example as you can see:
create table test_table ( name varchar(50), substr1 varchar(50), substr2 varchar(50));
insert into test_table(name) values ('sub1/sub2');
update test_table set substr1 =
(select LEFT(name, CHARINDEX('/', name)-1) from test_table);
update test_table set substr2 =
(select RIGHT(name, CHARINDEX('/', name)-1) from test_table);
select * from test_table;
The result is :
name | substr1 | substr2
sub1/sub2 | sub1 | sub2
Patindex can also be used instead of Charindex
SELECT a,b,LEFT(c,PATINDEX('%/%',c)-1), RIGHT(c,PATINDEX('%/%',REVERSE(c))-1) FROM #t

How to check what column in INSERT do not have the correct data type?

Imagine I have 200 columns in one INSERT statement, and I occasionally get an "Cannot convert" error for one of columns. Things is, I do not know which column causes this error.
Is there any way in T-SQL or mybatis to check WHICH column has the incorrect format? (I have just date, char, numeric). I can use ISNUMERIC, ISDATE for every column, but this is not so elegant.
I'm using mybatis in Java, so I cannot use any PreparedStatement or so.
You could build a query that tries to convert each of the suspected columns.
And limit the query to where one of the attempts to convert fails.
Mostly the bad data will be in CHAR's or VARCHAR's when trying to cast or convert them to a datetime or number type.
So you can limit your research to those.
Also, from the error you should see which value failed to convert to which type. Which can also help to limit which fields you research.
A simplified example using table variables:
declare #T1 table (id int identity(1,1) primary key, field1 varchar(30), field2 varchar(30), field3 varchar(30));
declare #T2 table (id int identity(1,1) primary key, field1_int int, field2_date date, field3_dec decimal(10,2));
insert into #T1 (field1, field2, field3) values
('1','2018-01-01','1.23'),
('not an int','2018-01-01','1.23'),
('1','not a date','1.23'),
('1','2018-01-01','not a decimal'),
(null,'2018-01-01','1.23'),
('1',null,'1.23'),
('1','2018-01-01',null)
;
select top 1000
id,
case when try_convert(int, field1) is null then field1 end as field1,
case when try_convert(date, field2) is null then field2 end as field2,
case when try_convert(decimal(10,4), field3) is null then field3 end as field3
from #T1
where
try_convert(int, coalesce(field1, '0')) is null
or try_convert(date, coalesce(field2, '1900-01-01')) is null
or try_convert(decimal(10,4), coalesce(field3, '0.0')) is null;
Returns:
id field1 field2 field3
-- ---------- ----------- -------------
2 not an int NULL NULL
3 NULL not a date NULL
4 NULL NULL not a decimal
If the origin data doesn't have to much bad data you could try to fix the origin data first.
Or use the try_convert for the problematic columns with bad data.
For example:
insert into #T2 (field1_int, field2_date, field3_dec)
select
try_convert(int, field1),
try_convert(date, field2),
try_convert(decimal(10,4), field3)
from #T1;
With larger imports - especially when you expect issues - a two-stepped approach is highly recommended.
import the data to a very tolerant staging table (all NVARCHAR(MAX))
check, evaluate, manipulate, correct whatever is needed and do the real insert from here
Here is a generic approach you might adapt to your needs. It will check all tables values against a type-map-table and output all values, which fail in TRY_CAST (needs SQL-Server 2012+)
A table to mockup the staging table (partly borrowed from LukStorms' answer - thx!)
CREATE TABLE #T1 (id INT IDENTITY(1,1) PRIMARY KEY
,fldInt VARCHAR(30)
,fldDate VARCHAR(30)
,fldDecimal VARCHAR(30));
GO
INSERT INTO #T1 (fldInt, fldDate, fldDecimal) values
('1','2018-01-01','1.23'),
('blah','2018-01-01','1.23'),
('1','blah','1.23'),
('1','2018-01-01','blah'),
(null,'2018-01-01','1.23'),
('1',null,'1.23'),
('1','2018-01-01',null);
--a type map (might be taken from INFORMATION_SCHEMA of an existing target table automatically)
DECLARE #type_map TABLE(ColumnName VARCHAR(100),ColumnType VARCHAR(100));
INSERT INTO #type_map VALUES('fldInt','int')
,('fldDate','date')
,('fldDecimal','decimal(10,2)');
--The staging table's name
DECLARE #TableName NVARCHAR(100)='#T1';
--dynamically created statements for each column
DECLARE #columnSelect NVARCHAR(MAX)=
(SELECT
' UNION ALL SELECT id ,''' + tm.ColumnName + ''',''' + tm.ColumnType + ''',' + QUOTENAME(tm.ColumnName)
+ ',CASE WHEN TRY_CAST(' + QUOTENAME(tm.ColumnName) + ' AS ' + tm.ColumnType + ') IS NULL THEN 0 ELSE 1 END ' +
'FROM ' + QUOTENAME(#TableName)
FROM #type_map AS tm
FOR XML PATH('')
);
-The final dynamically created statement
DECLARE #cmd NVARCHAR(MAX)=
'SELECT tbl.*
FROM
(
SELECT 0 AS id,'''' AS ColumnName,'''' AS ColumnType,'''' AS ColumnValue,0 AS IsValid WHERE 1=0 '
+ #columnSelect +
') AS tbl
WHERE tbl.IsValid = 0;'
--Execution with EXEC()
EXEC(#cmd);
The result:
+----+------------+---------------+-------------+---------+
| id | ColumnName | ColumnType | ColumnValue | IsValid |
+----+------------+---------------+-------------+---------+
| 2 | fldInt | int | blah | 0 |
+----+------------+---------------+-------------+---------+
| 5 | fldInt | int | NULL | 0 |
+----+------------+---------------+-------------+---------+
| 3 | fldDate | date | blah | 0 |
+----+------------+---------------+-------------+---------+
| 6 | fldDate | date | NULL | 0 |
+----+------------+---------------+-------------+---------+
| 4 | fldDecimal | decimal(10,2) | blah | 0 |
+----+------------+---------------+-------------+---------+
| 7 | fldDecimal | decimal(10,2) | NULL | 0 |
+----+------------+---------------+-------------+---------+
The statement created is like here:
SELECT tbl.*
FROM
(
SELECT 0 AS id,'' AS ColumnName,'' AS ColumnType,'' AS ColumnValue,0 AS IsValid WHERE 1=0
UNION ALL SELECT id
,'fldInt'
,'int'
,[fldInt]
,CASE WHEN TRY_CAST([fldInt] AS int) IS NULL THEN 0 ELSE 1 END
FROM [#T1]
UNION ALL SELECT id
,'fldDate'
,'date',[fldDate]
,CASE WHEN TRY_CAST([fldDate] AS date) IS NULL THEN 0 ELSE 1 END
FROM [#T1]
UNION ALL SELECT id
,'fldDecimal'
,'decimal(10,2)'
,[fldDecimal]
,CASE WHEN TRY_CAST([fldDecimal] AS decimal(10,2)) IS NULL THEN 0 ELSE 1 END
FROM [#T1]
) AS tbl
WHERE tbl.IsValid = 0;

Find specific word in column

I am using SQL Server 2008.
My tables are :
Location
------------------------
Id | LocationName
------------------------
1 | Bodakdev
2 | Thaltej Road
3 | Andheri East
4 | Noida Sector 2
Company
--------------------------------------------------------------------------
CId | Address | LocationId
--------------------------------------------------------------------------
11 | 301, GNFC Infotower, Bodakdev, | NULL
12 | 307/308,Arundeep Complex | NULL
13 | 7 Krishana Dyeing Compund, Nagardas rd., Andheri | NULL
14 | B-23 ,Ground Floor,Sector 2 | NULL
--------------------------------------------------------------------------
Currently LocationId in the Company table are null. If Address contains any location name then update LocationId.
For example, Address of CID - 11 contains Bodakdev then update LocationId 1, second example, Address of CID - 13 contains Andheri word then update LocationId 3.
Required output :
CId | Address | LocationId
--------------------------------------------------------------------------
11 | 301, GNFC Infotower, Bodakdev, | 1
12 | 307/308,Arundeep Complex | NULL
13 | 7 Krishana Dyeing Compund, Nagardas rd., Andheri | 3
14 | B-23 ,Ground Floor,Sector 2 | 4
--------------------------------------------------------------------------
I have tried using below query
SELECT
(LEN(Address) - LEN(REPLACE(Address, LocationName, '')) ) / LEN(LocationName)
if Address contains Location Name then it will return number of occurrences otherwise it return 0.
But it will not give correct output. How can I do this? Thanks. Any suggestion would be appreciated.
Try following Query :
1.STEP1 : make one function which can split the sting by any character and return the output in table format .
CREATE FUNCTION [dbo].[fnSplit](
#sInputList VARCHAR(8000) -- List of delimited items
, #sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS #List TABLE (item VARCHAR(8000))
BEGIN
DECLARE #sItem VARCHAR(8000)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT
#sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
2.STEP2 : use following query to get your desire output .
DECLARE #LOCATION AS TABLE (ID INT ,NAME VARCHAR(MAX))
DECLARE #COMPANY AS TABLE (CID INT , ADDRESS VARCHAR(MAX) , LOCATIONID INT)
INSERT INTO #LOCATION VALUES(1,'Bodakdev')
INSERT INTO #LOCATION VALUES(2,'Thaltej Road')
INSERT INTO #LOCATION VALUES(3,'Andheri East')
INSERT INTO #LOCATION VALUES(4,'Noida Sector 2')
INSERT INTO #COMPANY VALUES(11,'301, GNFC Infotower, Bodakdev,' , NULL)
INSERT INTO #COMPANY VALUES(12,'307/308,Arundeep Complex' , NULL)
INSERT INTO #COMPANY VALUES(11,'7 Krishana Dyeing Compund, Nagardas rd., Andheri' , NULL)
INSERT INTO #COMPANY VALUES(11,'B-23 ,Ground Floor,Sector 2' , NULL)
UPDATE #Company
SET
LOCATIONID = B.ID
FROM #COMPANY AS A , #LOCATION AS B
WHERE
1 = CASE WHEN
(
SELECT COUNT(*)
FROM FNSPLIT(B.NAME , ' ')
WHERE A.ADDRESS LIKE '%' + ITEM + '%'
) > 0 THEN 1 ELSE 0 END
This is the one way to do it . we can do it using full text searching also.

Resources