SQL Query on sort key values - sql

how can you sort data in sql for each column ??
for example C1 column have value in first row as 'CAB' and you want in output as 'ABC'
Input
C1
CAB
ZSA
Output
C1
ABC
ASZ

You could try this logic, maybe there is a better solution, but it does the job
DECLARE #t TABLE (Id INT, C1 VARCHAR(255))
INSERT INTO #t VALUES (1, 'CAB'),(2, 'ZSA')
;WITH mcte AS (
SELECT split.Id, split.C1, split.c, ASCII(split.c) AS asciinr
FROM (
SELECT a.Id, a.C1, SUBSTRING(a.C1, v.number+1, 1) AS c
FROM #t AS a
join master..spt_values v on v.number < LEN(a.C1)
WHERE v.type = 'P'
) AS split
)
SELECT Id, c1, REPLACE(STUFF((SELECT ' ' + mcte2.c FROM mcte AS mcte2
WHERE mcte2.C1 = mcte.C1 and mcte2.Id = mcte.Id
ORDER BY mcte2.asciinr FOR XML PATH('') ), 1, 1, ''), ' ', '') as OrderedC1
FROM mcte
GROUP BY Id, c1
ORDER BY mcte.C1
Result
Id C1 OrderderC1
-------------------
1 CAB ABC
2 ZSA ASZ

Related

How can I retrieve first second and third word of a String in SQL?

I need a query which would extract the first second and third word of a string.
I have approximately 5 words in each row and I need only the first three words out of 5 in the same row (1 row). Example "ATV BDSG 232 continue with other words".
I need only the first three words together in one row (in the same row) like "ATV BDSG 232" as a first row. The table has about 1000 rows and at the end of it I should have 1000 rows again but each row should contain only the first three words of the string.
I found a query which works fine for extracting first two like "ATV BDSG" discussed in stack overflow. The query is
"SELECT SUBSTRING(field1, 0, CHARINDEX(' ', field1, CHARINDEX(' ', field1, 0)+1))
FROM Table"
Can we derive this for extracting first three words?
Thanks in advance
If you don't want to create a dedicated function, you can use successive CROSS APPLYs:
SELECT
T.s,
FirstSpace.i,
SecondSpace.j,
ThirdSpace.k,
CASE
When ThirdSpace.k > 0 THEN LEFT(T.s, Thirdspace.k - 1)
ELSE T.S
END AS Phrase
FROM t
CROSS APPLY (SELECT CHARINDEX(' ', T.s, 1)) AS FirstSpace(i)
CROSS APPLY (SELECT CHARINDEX(' ', T.S, FirstSpace.i + 1)) AS SecondSpace(j)
CROSS APPLY (SELECT CHARINDEX(' ', T.s, SecondSpace.j + 1)) AS ThirdSpace(k)
gives you the results you need:
| s | i | j | k | phrase |
|----------------------------------------|---|---|----|------------------|
| ATV BDSG 232 Continue with other words | 4 | 9 | 13 | ATV BDSG 232 |
Things are easy, SQL Server provide STRING_SPLIT() function make that too easy
DECLARE #Var VARCHAR(100) = 'ATV BDSG 232 Continue with other words';
SELECT Word
FROM
(
SELECT Value AS Word,
ROW_NUMBER()OVER(ORDER BY (SELECT NULL)) RN
FROM STRING_SPLIT(#Var, ' ')
) T
WHERE RN <= 3;
But since you are working on 2012 version, you need to define your own function.
You can also take the hard way, first you need to get the first word, then replace it with '' and get the second word, then do the same for the 3rd word as
DECLARE #Var VARCHAR(100) = 'ATV BDSG 232 Continue with other words';
WITH FW AS
(
SELECT LEFT(#Var, CHARINDEX(' ', #Var)) FirstWord
),
SW AS
(
SELECT LEFT(REPLACE(#Var, FirstWord, ''),
CHARINDEX(' ', REPLACE(#Var, FirstWord, ''))) SecondWord
FROM FW
)
SELECT FirstWord,
SecondWord,
LEFT(REPLACE(REPLACE(V, FirstWord, ''), SecondWord, ''),
CHARINDEX(' ', REPLACE(REPLACE(V, FirstWord, ''), SecondWord, ''))
) ThirdWord
FROM
(
SELECT *, #Var V
FROM FW CROSS APPLY SW
) T
Demo
UPDATE
If you want to select the three first words then simply
SELECT SUBSTRING(Str, 0, CHARINDEX(' ', Str, CHARINDEX(' ', Str, CHARINDEX(' ', Str, 0)+1)+1)) Words
FROM Strings
Demo
--make some test data
declare #test as nvarchar(100) = 'my test string for words';
select 1 id, cast('my test string for words' as nvarchar(max)) word into #test;
insert #test (id,word) values (2,'a b c d e f g hhh yyyyyy') ;
insert #test (id,word) values (3,' a required test string d e f g hhh yyyyyy') ;
insert #test (id,word) values (4,'a quick test') ;
insert #test (id,word) values (5,'a test') ;
insert #test (id,word) values (6,'last') ;
--break up letters, count the first 3 words
;WITH CTE AS (SELECT 1 x, substring(#test,1,1) charx
UNION ALL
SELECT X + 1, substring(#test,x + 1,1) from CTE WHERE x < len(#test)
)
select * from cte c3 where (SELECT count(0) cnt FROM CTE c1 JOIN CTE c2 on c1.x <= c3.x and c1.x + 1 = c2.x and c1.charx =' ' and c2.charx != ' ') < 3
;WITH tabx as (select id, cast(ltrim(word) as nvarchar(max)) 'word' from #test), --do some ltrim
CTE AS (
SELECT id, 1 x, substring(word,1,1) charx from tabx
UNION ALL
SELECT t.id, c.X + 1, substring(t.word,x + 1,1)
from tabx t
JOIN CTE c on c.id = t.id and x < len(t.word)
),
disj as
(select * from cte c3 where
(SELECT count(0) cnt
FROM CTE c1
JOIN CTE c2 on c1.id = c3.id and c1.id = c2.id and c1.x <= c3.x and c1.x + 1 = c2.x and c1.charx =' ' and c2.charx != ' '
) < 3
),
rj as
(select disj.id,disj.x, disj.charx z
from disj
where disj.x = 1
UNION ALL
select d.id, d.x, r.z + d.charx
FROM rj r
join disj d on r.id = d.id and r.x + 1 = d.x
)
select *
from rj r1
cross apply (select max(r2.x) TheRow from rj r2 where r1.id = r2.id) dq
where r1.x = dq.TheRow
order by r1.id;
--delete test data
drop table #test
/* This is not perfect - but interesting */
declare #t table (fullname varchar(100))
insert #t values('Mr Jones'),('Mrs Amy smith'),('Jim Smith'),('Dr Harry Web '),('Paul Fred andrew jones')
select fullname,
a.value as a ,
b.Value as b,
c.Value as c,
d.Value as d,
e.Value as e,
f.value as f
from #t
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ')) a
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value )) b
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value,b.value ) ) c
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value,b.value,c.value )) d
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value,b.value,c.value,d.value) ) e
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value,b.value ,c.value,d.value,e.value) ) f
To Select First Word -
Select top 1 Ltrim(Rtrim(value)) FROM STRING_SPLIT(#input,' ')
To Select Only Second Word -
Select Ltrim(Rtrim(value)) from STRING_SPLIT(#input,' ') Order by (Select NULL) OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY

Get result from select without repeated records next to each other

i have table with records :
City Name Seq
London 1
London 2
London 3
Madrid 4
London 5
Porto 6
Problem is how to get a result in string ( merge all without repeated records ).
Result : London-Madrid-London-Porto
Another option if 2012+ ... LAG()
Example
Declare #YourTable Table ([City Name] varchar(50),[Seq] int)
Insert Into #YourTable Values
('London',1)
,('London',2)
,('London',3)
,('Madrid',4)
,('London',5)
,('Porto',6)
Select Stuff((Select '-' +Value From
(
Select top 1000 Value = case when [City Name]=lag([City Name],1) over (Order By Seq) then null else [City Name] end
From #YourTable
Order By Seq
) A
For XML Path ('')),1,1,'')
Returns
London-Madrid-London-Porto
How about this?
declare #table table (CityName varchar(64), seq int)
insert into #table
values
('London',1),
('London',2),
('London',3),
('Madrid',4),
('London',5),
('Porto',6)
--find the next row that isn't the same city name (t2seq)
;with cte as(
select distinct
t.CityName
,t.seq
,min(t2.seq) as t2seq
from #table t
left join #table t2 on
t2.seq > t.seq
and t2.CityName <> t.CityName
group by
t.CityName
,t.seq),
--limit the result set to distinct list
cte2 as(
select distinct
CityName
,seq = isnull(t2seq,9999999)
from cte)
--use stuff to concat it together
select distinct
stuff(( select '-', + t2.CityName
from cte2 t2
order by seq
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
from cte2

How to compare string type columns character by character without looping?

I have two columns: One column holds actual answers, the other one is the answer_key.
I want to compare answers to answer_key and have scores in the third column:
ID Answers Answer_key Score
1 ABCD ABCC 1110
2 ACD DCA 010
Of course, I can check the length, loop through each character to compare them individually, and get the score.
However, is there an alternative? Possibly based on XML path?
You might try binary values rather than letters.
A=0001 B=0010 C=0100 D=1000
ABCD = 0001001001001000 (0x1248)
ABCC = 0001001001000100 (0x1244)
Score = (Answers XOR Answer_key) XOR 11111111
The XOR 11111111 is optional
What you want to do is to split each char in Answers and Answers_Key into separate rows and then compare them. This can be done using a Recursive CTE. The concatenation is done using the FOR XML PATH function.
CREATE TABLE temp(
Answers VARCHAR(10),
Answer_Key VARCHAR(10)
)
INSERT INTO temp VALUES ('ABCD', 'ABCC'), ('ACD', 'DCA');
;WITH temp_numbered AS(
SELECT
ID = ROW_NUMBER() OVER(ORDER BY Answer_Key),
*
FROM temp
),
cte AS(
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, 1, 1),
Answer_Key = STUFF(Answer_Key, 1, 1, ''),
Answers_Char = SUBSTRING(Answers, 1, 1),
Answers = STUFF(Answers, 1, 1, ''),
RowID = 1
FROM temp_numbered t
UNION ALL
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, 1, 1),
Answers = STUFF(Answer_Key, 1, 1, ''),
Answers_Char = SUBSTRING(Answers, 1, 1),
Answers = STUFF(Answers, 1, 1, ''),
RowID = RowID + 1
FROM cte
WHERE LEN(Answer_Key) > 0
)
SELECT
Answers,
Answer_Key,
Score = (SELECT
CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
FROM cte
WHERE ID = t.ID
ORDER BY ID, RowID
FOR XML PATH(''))
FROM temp_numbered t
DROP TABLE temp
Here is another way using a Tally Table:
;WITH tally(N) AS(
SELECT TOP 11000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM sys.columns
)
,temp_numbered AS(
SELECT
ID = ROW_NUMBER() OVER(ORDER BY Answer_Key),
*
FROM temp
)
,cte AS(
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, N, 1),
Answers_Char = SUBSTRING(Answers, N, 1),
RowID = N
FROM temp_numbered tn
CROSS JOIN Tally t
WHERE t.N <= LEN(tn.Answer_Key)
)
SELECT
Answers,
Answer_Key,
Score = (SELECT
CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
FROM cte
WHERE ID = t.ID
ORDER BY ID, RowID
FOR XML PATH(''))
FROM temp_numbered t
I seems easiest to loop through each character for the whole set at once:
-- get max Answer length
declare #len int,#max_len int
select #max_len = max(len(Answers)),
#len = 1
from Answers
-- update scores
while #len <= #max_len
begin
update Answers
set Score = isnull(Score,'') + '1'
where substring(Answers,#len,1) = substring(Answer_Key,#len,1)
and len(Answers) >= #len
update Answers
set Score = isnull(Score,'') + '0'
where substring(Answers,#len,1) != substring(Answer_Key,#len,1)
and len(Answers) >= #len
set #len = #len + 1
end
-- return Scores
select * from Answers
SQL FIDDLE
Expanding on the answer from #weswesthemenace to get around cte limit.
DECLARE #Answers TABLE
(
Id INT IDENTITY(1, 1) not null,
Answers VARCHAR(MAX) not null,
Answer_Key VARCHAR(MAX) not null
)
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ABCD', 'ABCC')
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ACD', 'DCA')
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEGGHIJKLMNOPQRSTUVXXYZABCDEFGHIIKKMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXZZ');
WITH
E01(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E01 a CROSS JOIN E01 b),
E04(N) AS (SELECT 1 FROM E02 a CROSS JOIN E02 b),
E08(N) AS (SELECT 1 FROM E04 a CROSS JOIN E04 b),
E16(N) AS (SELECT 1 FROM E08 a CROSS JOIN E08 b),
E32(N) AS (SELECT 1 FROM E16 a CROSS JOIN E16 b),
cteTally(N) AS (SELECT row_number() OVER (ORDER BY N) FROM E32)
SELECT b.Answers, b.Answer_Key,
(
SELECT CASE when SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) then '1' else '0' end
FROM #Answers a
CROSS APPLY cteTally n
WHERE b.Id = a.Id AND n.N <= DATALENGTH(b.Answers)
ORDER BY ID, n.N
FOR XML PATH('')
) Score
FROM #Answers b
this can be simplified by a utility Number function in the database. Mine is called dbo.Number(start, end)
SELECT b.Answers, b.Answer_Key,
(
SELECT CASE WHEN SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) THEN '1' ELSE '0' END
FROM #Answers a
CROSS APPLY dbo.Number(1, DATALENGTH(b.Answers)) n
WHERE b.Id = a.Id
ORDER BY ID, n.N
FOR XML PATH('')
) Score
FROM #Answers b

SQL splitting a word in separate characters

I need to change an application and the first thing I need is to change a field in a database table.
In this table I now have 1 to 6 single characters, i.e. 'abcdef'
I need to change this to '[a][b][c][d][e][f]'
[edit] It is meant to stay in the same field. So before field = 'abcdef' and after field = '[a][b][c][d][e][f]'.
What would be a good way to do this?
rg.
Eric
You can split string to separate characters using following function:
create function ftStringCharacters
(
#str varchar(100)
)
returns table as
return
with v1(N) as (
select 1 union all select 1 union all select 1 union all select 1 union all select 1
union all
select 1 union all select 1 union all select 1 union all select 1 union all select 1
),
v2(N) as (select 1 from v1 a, v1 b),
v3(N) as (select top (isnull(datalength(#str), 0)) row_number() over (order by ##spid) from v2)
select N, substring(#str, N, 1) as C
from v3
GO
And then apply it as:
update t
set t.FieldName = p.FieldModified
from TableName t
cross apply (
select (select quotename(s.C)
from ftStringCharacters(t.FieldName) s
order by s.N
for xml path(''), type).value('text()[1]', 'varchar(20)')
) p(FieldModified)
SQLFiddle sample
DECLARE #text NVARCHAR(50)
SET #text = 'abcdef'
DECLARE #texttable TABLE (value NVARCHAR(1))
WHILE (len(#text) > 0)
BEGIN
INSERT INTO #texttable
SELECT substring(#text, 1, 1)
SET #text = stuff(#text, 1, 1, '')
END
select * from #texttable
Without using a function:
declare #t table(C varchar(18))
insert #t values('abc'), ('1234'), (' 1234a')
;with CTE as
(
select C, '[' + substring(c, a.n, 1) + ']' v, rn from
(select 1 n union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6) a
cross apply
(select c, row_number() over (order by C) rn from #t group by c) b
where a.n <= len(C)
)
update t3
set C = t4.[value]
FROM #t t3
JOIN
(
select C,
(
select v
from CTE t1
where t1.rn = t2.rn
for xml path(''), type
).value('.', 'varchar(18)') [value]
from CTE t2
group by t2.rn, C
) t4
ON t3.C = t4.C
SELECT * FROM #t

Finding unique characters from a table field for all rows

How to find the number of distinct character used in the field having multiple rows.
For example, if there are two rows having data like abcd and eaafg* then distinct character used are abcdefg*.
Try this one -
INSERT INTO #temp (txt)
VALUES ('abcd3'), ('abcdefg*')
SELECT disword = (
SELECT DISTINCT dt.ch
FROM (
SELECT ch = SUBSTRING(t.mtxt, n.number + 1, 1)
FROM [master].dbo.spt_values n
CROSS JOIN (
SELECT mtxt = (
SELECT txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)'
)
) t
WHERE [type] = N'p'
AND number <= LEN(mtxt) - 1
) dt
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)'
)
Example (edited):
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('abcd'), ('abcdefg*'), (REPLICATE('-', 8000)), (REPLICATE('+', 8000))
DECLARE #t TABLE (i BIGINT)
DECLARE
#i BIGINT = 1
, #l BIGINT = (
SELECT SUM(LEN(txt))
FROM #temp
)
WHILE (#i <= #l) BEGIN
INSERT INTO #t (i)
VALUES (#i), (#i+1), (#i+2), (#i+3), (#i+4), (#i+5), (#i+6), (#i+7), (#i+8), (#i+9)
SELECT #i += 10
END
SELECT disword = (
SELECT DISTINCT dt.ch
FROM (
SELECT ch = SUBSTRING(t.mtxt, n.i, 1)
FROM #t n
CROSS JOIN (
SELECT mtxt = (
SELECT txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)'
)
) t
) dt
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)'
)
Have a look a t this solution -
SELECT
dt.ch
, cnt = COUNT(1)
FROM (
SELECT ch = SUBSTRING(t.mtxt, n.i, 1)
FROM #t n
CROSS JOIN (
SELECT mtxt = (
SELECT txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
) dt
WHERE dt.ch != ''
GROUP BY dt.ch
ORDER BY cnt DESC
Here are questions that may refer to what you are asking:
How do I get distinct characters of string column in mssql?
and
SQL: how to get all the distinct characters in a column, across all rows
With tallys:
DECLARE #t TABLE (s NVARCHAR(MAX))
INSERT INTO #t
VALUES ('abcd'), ('abcdefg*')
;WITH tally AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) i
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t1(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t2(n))
SELECT
( SELECT DISTINCT sb
FROM tally
CROSS APPLY ( SELECT SUBSTRING(s, i, 1) sb FROM #t ) ca
WHERE sb <> ''
FOR XML PATH(N'') , TYPE , ROOT).value(N'root[1]', N'NVARCHAR(MAX)')