I am trying to split a string that has 3 set Alpha Characters that can appear in any order followed by a numeric value. The issue I am having is that the order of the alpha characters isn't fixed. And neither is the number of numeric values after the alpha character it may contain any of the following examples:
X1Y45Z1
Y25Z1
X1Y9Z1
X2Z6
With a a lot of help from our local IT ( I am still learning SQL) I have managed to separate out X Y and Z into separate columns with the numbers after them, but they don't always appear in order
Col1 may contain X or Y
Col2 may contain Y or Z
Col3 may contain Z or nothing
I am trying to get a result like the following:
If X is in Col1, Show number(s) after X, in new column "X", if Y is in col1, Show number(s) after Y in new column "Y", etc.
At present we are using 2 cte's to break up the string. and I am trying to simplify it so that I can search the string, have 3 columns after created 'X','Y','Z' and put the correct number(s) after each Alpha delimiter into it. I should note I Do Not have full admin access so I cannot create new tables or update/insert data or clean it.
Also apologies if this is slightly formatted incorrectly. It is my first post on StackOverflow
declare #tbl table
(
Col1 varchar(100), <-------This Column contains the values I want
)
insert into #tbl
select Col1,
from table1,
where xyz
;with cte as
(
select
Col1,
replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(**Col1**,'P', '</x><x>P'),'C', '</x><x>C'),'I', '</x><x>I'),'M', '</x><x>M'),'S', '</x><x>S'),'Q', '</x><x>Q'),'L', '</x><x>L'),'T', '</x><x>T'),'E', '</x><x>E'),'R', '</x><x>R'),'U', '</x><x>U'),'W', '</x><x>W')
**Col1NODES**
from
#tbl
)
, cte2 (Col1, Col1Nodes) as
(
select
Col1,
convert(xml,'<z><x>' + Col1nodes + '</x></z>') **Col1NODES**
from
cte
)
select
Col1,
isnull(Col1Nodes.value('/z[1]/x[2]','varchar(100)'),'-') F1,
isnull(Col1Nodes.value('/z[1]/x[3]','varchar(100)'),'-') F2,
isnull(Col1Nodes.value('/z[1]/x[4]','varchar(100)'),'-') F3
from
cte2
Current output is below:
If you have SQL Server 2016+ you may try to use the following solution, based on JSON. The important part is to transform the input data into a valid JSON object (X1Y45Z1 is transformed into {"X":1,"Y":45,"Z":1} for example). After that you need to parse this object with OPENJSON() function using the appropriate WITH clause to define the columns in the output.
Table:
CREATE TABLE Data (
TextData nvarchar(100)
)
INSERT INTO Data
(TextData)
VALUES
('X1Y45Z1'),
('Y25Z1'),
('X1Y9Z1'),
('X2Z6'),
('Z1X6')
Statement:
SELECT d.TextData, j.*
FROM Data d
CROSS APPLY OPENJSON(
CONCAT(
N'{',
STUFF(REPLACE(REPLACE(REPLACE(d.TextData, N'X', N',"X":'), N'Y', N',"Y":'), N'Z', N',"Z":'), 1, 1, N''),
N'}'
)
) WITH (
X int '$.X',
Y int '$.Y',
Z int '$.Z'
) j
Output:
---------------------
TextData X Y Z
---------------------
X1Y45Z1 1 45 1
Y25Z1 25 1
X1Y9Z1 1 9 1
X2Z6 2 6
Z1X6 6 1
For versions before SQL Server 2016, you may use an XML based approach. You need to transform text data into an appropriate XML (X1Y45Z1 is transformed into <row><name>X</name><value>1</value></row><row><name>Y</name><value>45</value></row><row><name>Z</name><value>1</value></row> for example):
SELECT
TextData,
XmlData.value('(/row[name = "X"]/value/text())[1]', 'nvarchar(4)') AS X,
XmlData.value('(/row[name = "Y"]/value/text())[1]', 'nvarchar(4)') AS Y,
XmlData.value('(/row[name = "Z"]/value/text())[1]', 'nvarchar(4)') AS Z
FROM (
SELECT
TextData,
CONVERT(
xml,
CONCAT(
STUFF(REPLACE(REPLACE(REPLACE(d.TextData, N'X', N'</value></row><row><name>X</name><value>'), N'Y', N'</value></row><row><name>Y</name><value>'), N'Z', N'</value></row><row><name>Z</name><value>'), 1, 14, N''),
N'</value></row>'
)
) AS XmlData
FROM Data d
) x
Related
Im trying to write a select statement which returns the value if it doesnt have at least 3 of the declared characters but I cant think of how to get it working, can someone point me in the right direction?
One thing to consider, I am not allowed to create a temporary table for this exercise.
I havn't really got any SQL so far as I cant think of a way to do it without a temp table.
the declared characters are any alpha characters between a and z, so if the value in the db is '1873' then it would return the value because it doesnt have at least 3 of the declared characters, but if the value was 'abcdefg' then it would not be returned as it has at least 3 of the declared characters.
Is anyone able to point me in a starting direction for this?
This will find all sys.objects with an x or a z:
Some explanations, as this is an exercise and you want to learn something:
You can split a delimitted string by transforming it into XML. x,z comes out as <x>x</x><x>z</x>. You can use this to create a derived table.
I use a CTE to avoid a created or declared table...
You can use CROSS APPLY for row-wise actions. Here I use CHARINDEX to find the position(s) of the chars you are looking for.
If all of them are not found, there SUM is zero. I use GROUP BY and HAVING to check this.
Hope this is clear :-)
DECLARE #chars VARCHAR(100)='x,z';
WITH Splitted AS
(
SELECT A.B.value('.','char') AS TheChar
FROM
(
SELECT CAST('<x>' + REPLACE(#chars,',','</x><x>')+ '</x>' AS XML) AS AsXml
) AS tbl
CROSS APPLY AsXml.nodes('/x') AS A(B)
)
SELECT name
FROM sys.objects
CROSS APPLY (SELECT CHARINDEX(TheChar,name) AS Found FROM Splitted) AS Found
GROUP BY name,Found
HAVING SUM(Found)>0
With
SrcTab As (
Select *
From (values ('Contains x y z')
, ('Contains x and y')
, ('Contains y only')) v (SrcField)),
CharList As ( --< CTE instead of temporary table
Select *
From (values ('x')
, ('y')
, ('z')) v (c))
Select SrcField
From SrcTab, CharList
Group By SrcField
Having SUM(SIGN(CharIndex(C, SrcField))) < 3 --< Count hits
;
If Distinct is not desirable and we need to only check count for each row:
With
SrcTab As ( --< Sample Data CTE
Select *
From (values ('Contains x y z')
, ('Contains x and y')
, ('Contains y only')
, ('Contains y only')) v (SrcField))
Select SrcField
From SrcTab
Where (
Select Count(*) --< Count hits
From (Values ('x'), ('y'), ('z')) v (c)
Where CharIndex(C, SrcField) > 0
) < 3
;
Using Numbers Table and Joins..I used declared characters as only 4 for demo purposes
Input:
12345
abcdef
ab
Declared table:used only 3 for demo..
a
b
c
Output:
12345
ab
Demo:
---Table population Scripts
Create table #t
(
val varchar(20)
)
insert into #t
select '12345'
union all
select 'abcdef'
union all
select 'ab'
create table #declarecharacters
(
dc char(1)
)
insert into #declarecharacters
select 'a'
union all
select 'b'
union all
select 'c'
Query used
;with cte
as
(
select * from #t
cross apply
(
select substring(val,n,1) as strr from numbers where n<=len(val))b(outputt)
)
select val from
cte c
left join
#declarecharacters dc1
on
dc1.dc=c.outputt
group by val
having
sum(case when dc is null then 0 else 1 end ) <3
I have a complex Id in my Data Base table Like is 00-000-000.
All names store in one field
01-000-000=Warehouse
01-001-000-=Rack
01-001-001=Bin cart
into the same table. I want to segregate data in 3 different fields. Is it possible in SQL?
there is another method with parsename function
select PARSENAME(replace(left(FieldName,10),'-','.'),3) col1,
PARSENAME(replace(left(FieldName,10),'-','.'),2) col2,
PARSENAME(replace(left(FieldName,10),'-','.'),1) col3 from yourTable
As we don't have quite enough information I am assuming that you wish to split the 3 number identifier at the start of each row into the 3 separate numbers...
If the Ids are always of fixed length (i.e. 2 letters then a dash then 3 letters then a dash then 3 more letters), you can use the substring function to break them out;
WITH TestData as (
SELECT '01-000-000=Warehouse' AS Id
UNION
SELECT '01-001-000-=Rack' AS Id
UNION
SELECT '01-001-001=Bin cart' AS Id
)
SELECT
Id,
substring(Id, 0, 2) AS FirstId,
substring(Id, 4, 3) AS SecondId,
substring(Id, 8, 3) AS ThirdId,
substring(Id, 11, len(id) - 10) AS RestOfString
FROM TestData
If they are variable lengths you will have to use something like the CHARINDEX function to find the positions of the dashes, and then split on them.
If it's always going to be the same length then you can do some simple code using LEFT and RIGHT
Test Data;
IF OBJECT_ID('tempdb..#TestData') IS NOT NULL DROP TABLE #TestData
GO
CREATE TABLE #TestData (FieldName varchar(50))
INSERT INTO #TestData (FieldName)
VALUES
('01-000-000=Warehouse')
,('01-001-000-=Rack')
,('01-001-001=Bin cart')
Query;
SELECT
FieldName
,LEFT(FieldName,2) Result1
,RIGHT(LEFT(FieldName,6),3) Result2
,RIGHT(LEFT(FieldName,10),3) Result3
FROM #TestData
Result;
FieldName Result1 Result2 Result3
01-000-000=Warehouse 01 000 000
01-001-000-=Rack 01 001 000
01-001-001=Bin cart 01 001 001
Use this for dynamic value with 2 "-" sign.
SELECT SUBSTRING('001-0011-0010',1,CHARINDEX('-','001-0011-0010')-1) COLA,
SUBSTRING ('001-0011-0010',
CHARINDEX('-','001-0011-0010')+1,
CHARINDEX('-','001-0011-0010',
CHARINDEX('-','001-0011-0010')+1)-(CHARINDEX('-','001-0011-0010')+1)
) COLB,
SUBSTRING ('001-0011-0010',(CHARINDEX('-','001-0011-0010',
CHARINDEX('-','001-0011-0010')+1))+1, LEN('001-0011-0010')
) COLC
Thanks
One table X is having a column C1 and having value with comma separated, i.e., 1,2,3
Another table Y is having a column C2 with unique tinyint value and having multiple rows and the column C2 values are, i.e.,
1
2
3
4
5
Requirement: Check is all the values of X(C1) are exist in the Y(C2) table
Tried One:
((select Data from dbo.split(X.C1,',')) in ((Select C2 from Y where <some condition>)))
Where Split is a user defined function which splits based on 'Comma' and put it into individual rows of a table and returns that table, i.e.,
Split(X.C1,',') returns a table with multiple rows like
1
2
3
But, using this query is giving run time error:
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
Could any one please help to get more feasible solution
Thanks in Advance
Kiran Bussa
I think you have to join the two parts of your query.
i.e.
((SELECT Data from dbo.split(X.C1,',') WHERE data.C1 IN ((Select C2 from Y WHERE <some condition>)))
Remove the unwanted brackets and add where condition
SELECT Data
FROM dbo.Split(X.C1, ',')
WHERE data IN (SELECT C2
FROM Y
WHERE <SOME condition>)
I would recommend you to split the comma separated values and store in a table variable. This will help you to query with your records.
For your reference i have create a small sqlfiddle using CROSS Apply to split the comma separated values and check in Y table.
CLICK HERE FOR FIDDLE
You can also refer to the complete code as below:
Create table x (c1 varchar(10) )
insert into x values ('1,2,3')
insert into x values ('1,2,4')
insert into x values ('2,3,4')
insert into x values ('1,2')
create table Y (c1 tinyint)
insert into Y values (1)
insert into Y values (2)
insert into Y values (3)
insert into Y values (4)
insert into Y values (5)
SELECT DISTINCT Data.C1
FROM
(select
n.r.value('.', 'varchar(50)') AS C1
from x as T
cross apply (select cast('<r>'+replace(replace(c1,'&','&'), ',', '</r><r>')+'</r>' as xml)) as S(XMLCol)
cross apply S.XMLCol.nodes('r') as n(r)) DATA
WHERE data.C1 IN (SELECT C1
FROM Y)
EDIT: based on the comment Edited the answer with Where condition
Please refer to the new fiddle link below
EDITED FIDDLE CLICK HERE
For same table structure new select query to identify the column in which the value appeared.
SELECT DISTINCT Data.C1, DATA.FROMX
FROM
(select
T.C1 as FROMX,
n.r.value('.', 'varchar(50)') AS C1
from x as T
cross apply (select cast('<r>'+replace(replace(c1,'&','&'), ',', '</r><r>')+'</r>' as xml)) as S(XMLCol)
cross apply S.XMLCol.nodes('r') as n(r)) DATA
WHERE data.C1 IN (SELECT C1
FROM Y WHERE Y.C1 = 4)
Hurray!!!! Finally got the solution :)
declare #Int_Res varchar(max)
set #Int_Res = ''
SELECT #Int_Res = #Int_Res + ',' + Cast(C1 as varchar) FROM X where
dbo.Split((#Int_Res ),',') Int_Result
(Int_Result.Data in (Select C1 from Y where ))
What I'm looking for is a way in MSSQL to create a complex IN or LIKE clause that contains a SET of values, some of which will be ranges.
Sort of like this, there are some single numbers, but also some ranges of numbers.
EX: SELECT * FROM table WHERE field LIKE/IN '1-10, 13, 24, 51-60'
I need to find a way to do this WITHOUT having to specify every number in the ranges separately AND without having to say "field LIKE blah OR field BETWEEN blah AND blah OR field LIKE blah.
This is just a simple example but the real query will have many groups and large ranges in it so all the OR's will not work.
One fairly easy way to do this would be to load a temp table with your values/ranges:
CREATE TABLE #Ranges (ValA int, ValB int)
INSERT INTO #Ranges
VALUES
(1, 10)
,(13, NULL)
,(24, NULL)
,(51,60)
SELECT *
FROM Table t
JOIN #Ranges R
ON (t.Field = R.ValA AND R.ValB IS NULL)
OR (t.Field BETWEEN R.ValA and R.ValB AND R.ValB IS NOT NULL)
The BETWEEN won't scale that well, though, so you may want to consider expanding this to include all values and eliminating ranges.
You can do this with CTEs.
First, create a numbers/tally table if you don't already have one (it might be better to make it permanent instead of temporary if you are going to use it a lot):
;WITH Numbers AS
(
SELECT
1 as Value
UNION ALL
SELECT
Numbers.Value + 1
FROM
Numbers
)
SELECT TOP 1000
Value
INTO ##Numbers
FROM
Numbers
OPTION (MAXRECURSION 1000)
Then you can use a CTE to parse the comma delimited string and join the ranges with the numbers table to get the "NewValue" column which contains the whole list of numbers you are looking for:
DECLARE #TestData varchar(50) = '1-10,13,24,51-60'
;WITH CTE AS
(
SELECT
1 AS RowCounter,
1 AS StartPosition,
CHARINDEX(',',#TestData) AS EndPosition
UNION ALL
SELECT
CTE.RowCounter + 1,
EndPosition + 1,
CHARINDEX(',',#TestData, CTE.EndPosition+1)
FROM CTE
WHERE
CTE.EndPosition > 0
)
SELECT
u.Value,
u.StartValue,
u.EndValue,
n.Value as NewValue
FROM
(
SELECT
Value,
SUBSTRING(Value,1,CASE WHEN CHARINDEX('-',Value) > 0 THEN CHARINDEX('-',Value)-1 ELSE LEN(Value) END) AS StartValue,
SUBSTRING(Value,CASE WHEN CHARINDEX('-',Value) > 0 THEN CHARINDEX('-',Value)+1 ELSE 1 END,LEN(Value)- CHARINDEX('-',Value)) AS EndValue
FROM
(
SELECT
SUBSTRING(#TestData, StartPosition, CASE WHEN EndPosition > 0 THEN EndPosition-StartPosition ELSE LEN(#TestData)-StartPosition+1 END) AS Value
FROM
CTE
)t
)u INNER JOIN ##Numbers n ON n.Value BETWEEN u.StartValue AND u.EndValue
All you would need to do once you have that is query the results using an IN statement, so something like
SELECT * FROM MyTable WHERE Value IN (SELECT NewValue FROM (/*subquery from above*/)t)
I need to split a nvarchar(100) column into three nvarchar(28) columns without a known delimiter and without breaking mid-word. I am thinking that I would need to find the space that is near the 28th character, measure length and position of the word just before that space and decide weather the delimiter should be before or after that word. Then, again for the 3rd column.
From:
Col
-------------------------------------------------------------------------------------
Royal Mission Open Hutch with 4-Arch Doors, Plain Glass (with Glass Shelves Standard)
To:
Col1 Col2 Col3
------------------------ ------------------------- --------------------------
Royal Mission Open Hutch 4-Arch Doors, Plain Glass (with Glass Shelves Standa
Any Ideas?
Thanks nab
I am using SQL 2008
Using CROSS APPLYs, you could try something like this:
WITH data (col) AS (
SELECT CAST('Royal Mission Open Hutch with 4-Arch Doors, Plain Glass (with Glass Shelves Standard)' AS nvarchar(100))
)
SELECT
col1, col2, col3
FROM data
CROSS APPLY (
SELECT
NULLIF(30 - CHARINDEX(' ', REVERSE(LEFT(col, 29))), 0)
) AS x1 (first_space_pos)
CROSS APPLY (
SELECT
LEFT(col, ISNULL(first_space_pos, 28)),
LTRIM(NULLIF(SUBSTRING(col, ISNULL(first_space_pos, 29), 999), ''))
) AS x2 (col1, col23)
CROSS APPLY (
SELECT
NULLIF(30 - CHARINDEX(' ', REVERSE(LEFT(col23, 29))), 0)
) AS x3 (second_space_pos)
CROSS APPLY (
SELECT
LEFT(col23, ISNULL(second_space_pos, 28)),
LTRIM(NULLIF(SUBSTRING(col23, ISNULL(second_space_pos, 29), 28), ''))
) AS x4 (col2, col3)
;
The first CROSS APPLY searches for the last space in the first 29 characters of the big column, and the second one uses the found position to produce the first smaller column, col1 and return the rest of the string as col23.
The next two CROSS APPLYs do perform the same manipulations on col23, thus producing col2 and col3. The only difference is, the last CROSS APPLY puts into col3 at most 28 characters rather than all the remaining ones.
The hardcoded values like 28, 29 and 30 could be parametrised, but I'll leave that part of the job to you.
You can try this query at SQL Fiddle.