Create new column by each value separated by ";" SQL Server SUBSTRING - sql

I have this 'Tests' column with n Rows
1 Test0;Test1;Test2
2 Test3;Test5;Test8
...
...
I need to separate each value by semicolon and create 1 column for each index.
This should be the result:
Column New1:
Test0
Test3
Column New2:
Test1
Test5
Column New3:
Test2
Test8

I think this is probably close enough to what you want:
select t.*, s.*
from t cross apply
(select max(case when seqnum = 1 then s.value end) as test_1,
max(case when seqnum = 2 then s.value end) as test_2,
max(case when seqnum = 3 then s.value end) as test_3
from (select s.value,
row_number() over (order by charindex(';' + s.value + ';', ';' + t.tests + ';')) as seqnum
from string_split(t.tests, ';') s
) s
) s;
Here is a db<>fiddle.
Note: This approach will not work if there are duplicates in the column.

Solved here How to split one column into two columns in SQL Server
(Note: this approach is made to split one column into two columns)
declare #t table (id int, name varchar(50))
select
case when CHARINDEX(';', Column)>0
then SUBSTRING(Column, 1, CHARINDEX(';', Column)-1)
else Column end Column1,
CASE WHEN CHARINDEX(';', Column)>0
THEN SUBSTRING(Column, CHARINDEX(';',Column)+1,len(Column))
ELSE NULL END as Column2
from #t

Try this
select SUBSTRING(Tests,1,CHARINDEX(';', Tests)-1) as New1,
SUBSTRING(Tests,CHARINDEX(';', Tests)+1,CHARINDEX(';', Tests,CHARINDEX(';', Tests)-1)-1) as New2,
SUBSTRING(Tests,CHARINDEX(';', Tests,CHARINDEX(';', Tests)+1)+1,len(Tests)) as New3

Related

Concatenate rows into columns (NO FOR XML PATH('') and recursive CTEs) - SQL Server 2012

I have a very particular problem at hand.
Brief introduction: I have two columns at a database that I need to "group concatenate", in MySQL I would simply use GROUP_CONCAT to get the desired result, in SQL Server 2017 and on I would use STRING_AGG, the problem that I have is in the SQL Server 2012, which doesn't have this function.
Now, under normal circumstances I would use FOR XML PATH('') to get the solution, this is not viable since I'm running the query from the editor inside a third source application, the error that I get is
FOR XML PATH('') can't be used inside a cursor
For the sake of the argument let's assume that it's completely out of question to use this function.
I have tried using recursive CTE, however, it's not viable due to execution time, UNION ALL takes too much resources and can't execute properly (I am using the data for reporting).
I will no post the screenshots of the data due to the sensitivity of the same, imagine just having two columns, one with an id (multiple same id's), and a column with the data that needs to be concatenated (some string). The goal is to concatenate the second columns for all of the same id's in the first columns, obviously make it distinct in the process.
Example:
Input:
col1 col2
1 a
1 b
2 a
3 c
Output:
col1 col2
1 a/b
2 a
3 c
Does anyone have a creative idea on how to do this?
If you know the maximum number of values that need to be concatenated together, you can use conditional aggregation:
select col1,
stuff( concat(max(case when seqnum = 1 then '/' + col2 end),
max(case when seqnum = 2 then '/' + col2 end),
max(case when seqnum = 3 then '/' + col2 end),
max(case when seqnum = 4 then '/' + col2 end),
max(case when seqnum = 5 then '/' + col2 end)
), 1, 1, ''
) as col2s
from (select t.*,
row_number() over (partition by col1 order by col2) as seqnum
from t
) t
group by col1;
You can get the maximum number using:
select top (1) count(*)
from t
group by col1;
Your sample output seems wrong as 'a/b' should come for value 2.
try the following:
declare #t table (col1 int, col2 varchar(100))
insert into #t select 1, 'a'
insert into #t select 2, 'b'
insert into #t select 2, 'a'
insert into #t select 3, 'c'
declare #final_table table (col1 int, col2 varchar(100), col2_all varchar(1000))
insert into #final_table (col1, col2)
select * from #t
declare #col2_all varchar(1000)
declare #Name sysname
update #final_table
SET #col2_all = col2_all = COALESCE(CASE COALESCE(#Name, '')
WHEN col1 THEN #col2_all + '/' + col2
ELSE col2 END, ''),
#Name = col1;
select col1, col2_grouped = MAX(col2_all)
from #final_table
group by col1
Using CTE:
;with cte(col1,col2_grouped,rn)
as
(
select col1, col2 , rn=ROW_NUMBER() over (PARTITION by col1 order by col1)
from #t
)
,cte2(col1,final_grouped,rn)
as
(
select col1, convert(varchar(max),col2_grouped), 1 from cte where rn=1
union all
select cte2.col1, convert(varchar(max),cte2.final_grouped+'/'+cte.col2_grouped), cte2.rn+1
from cte2
inner join cte on cte.col1 = cte2.col1 and cte.rn=cte2.rn+1
)
select col1, MAX(final_grouped) col2_grouped from cte2 group by col1
Please see db<>fiddle here.

T-SQL Truncate text and add number at the end to avoid duplicates

I need to truncate data from a column to 10 characters. However, I cannot have any duplicates, so I want any duplicates to end with ~1 for the first duplicate, ~2 for the second duplicate. Here's an example of what I have:
Column
------
The ABC Company Inc.
The ABC Cooperative
XYZ Associates LLC.
I'd like the result to be:
Column
------
The ABC ~1
The ABC ~2
XYZ Associ
The end doesn't have to be ~1 or ~2, I just need something to make it unique after truncating. There may be more than 3 or 4 duplicates after truncating.
So far, I'm just truncating and editing the table manually:
update Table set Column = Left(Column, 10) where len(Column) > 10
First, you care about the first 8 characters, not the first 10, because you need to reserve slots for the additional number.
Assuming that you have fewer than 10 repeats, you can do this:
with toupdate as (
select t.*,
row_number() over (partition by left(col, 8) order by (select null)) as seqnum,
count(*) over (partition by left(col, 8) ) as cnt
from t
update toupdate
set col = (case when cnt = 1 then left(col, 10)
else left(col, 8) + '~' + cast(seqnum as char(1));
The same idea can be used for a select.
Declare #Table Table (Column1 varchar(50))
Insert into #Table values
('The ABC Company Inc.'),
('The ABC Cooperative'),
('XYZ Associates LLC.')
Select NewColumn = Concat(substring(Column1,1,10),' ~',Row_Number() over (Partition By substring(Column1,1,10) Order by Column1))
From #Table
Returns
NewColumn
The ABC Co ~1
The ABC Co ~2
XYZ Associ ~1
The numbers are noisy, so I only add them when necessary:
select case when _r > 1
then Company + '~' + cast(_r as varchar(5))
else Company end as Company
from (
select Company
, ROW_NUMBER() over (partition by Company order by Company) as _r
from(
select left(Company, 10) as Company
from MyTable
) x
) y
order by Company
Company
--------------
The ABC Co
The ABC Co~2
XYZ Associ
Assuming your table is COMPANY and the field is CompanyName.....
You'll have to tweek but hope it helps..
SELECT SUBSTRING( Q.Comp, 1, 5) + '~' + CONVERT(nvarchar(4), Row) as NewFieldValue FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY SUBSTRING( C.CompanyName, 1, 6) ORDER BY SUBSTRING( C.CompanyName, 1, 6)) AS Row,
SUBSTRING( C.CompanyName, 1, 6) as Comp
FROM COMPANY C
)Q
DECLARE #Table TABLE (Column1 varchar(50))
INSERT INTO #Table VALUES
('The ABC Company Inc.')
, ('The ABC Cooperative')
, ('XYZ Associates LLC.')
, ('Acme')
, ('Ten Char 123')
, ('Ten Char 132')
, ('Ten Char 231')
;WITH FLen
AS (
SELECT Column1, LEFT(LEFT(Column1,13) + SPACE(13),13) + CHAR(164) AS Column2
FROM #Table
)
,TenCharPD -- Includes possible duplicates
AS (
SELECT Column1, LEFT(Column2,8) +
RIGHT('0' + CAST (
(ASCII(SUBSTRING(Column2, 9,1)) +
ASCII(SUBSTRING(Column2,10,1)) +
ASCII(SUBSTRING(Column2,11,1)) +
ASCII(SUBSTRING(Column2,12,1)) +
ASCII(SUBSTRING(Column2,13,1)))%100
AS NVARCHAR(2)),2) AS Column2
FROM Flen
)
,CullPD
AS (
SELECT Column1, Column2,
ROW_NUMBER() OVER (PARTITION BY Column2 ORDER BY Column2) AS rowx
FROM TenCharPD
)
UPDATE t1
SET Column1 = LEFT(Column2,9) +
CASE rowx
WHEN 1 THEN RIGHT(Column2,1)
ELSE CHAR(rowx + CAST (RIGHT(Column2,1) AS INT) * 5 + 63)
END
FROM #Table t1
JOIN CullPD cpd
ON t1.Column1 = cpd.Column1
SELECT * FROM #Table

Two dimensional rank using T-SQL

This is the data I'm dealing with:
I would like to find a way, in sql, of adding numbers to the yellow column which will rank the Names in such a way that I get the following.
note: This is the final pivoted result - in the sql table there is no need to pivot the data.
This ranking is decided via these rules:
The most recent week (ie Wk5 column) is the most important.
The next most recent week is next most important.
...so on to the left with the oldest week column "WK1" being the least important.
A data value that is small e.g. 1, is best. A data value that is high e.g. 7, is not good. A blank space is the worst and if at all possible should be located near the bottom of the page - but rules 1/2/3 always take precedence.
This is the data with a placeholder of 0 in the column Idx:
CREATE TABLE #values
(
Name varchar(5),
Idx int,
"Week" varchar(5),
Amount int
);
INSERT INTO #values
VALUES
('A',0,'WK1',3),
('T',0,'WK1',2),
('H',0,'WK1',1),
('P',0,'WK1',4),
('V',0,'WK1',6),
('N',0,'WK1',5),
('A',0,'WK2',2),
('F',0,'WK2',1),
('K',0,'WK2',3),
('P',0,'WK2',4),
('W',0,'WK2',7),
('V',0,'WK2',5),
('B',0,'WK2',6),
('A',0,'WK3',1),
('F',0,'WK3',2),
('T',0,'WK3',3),
('K',0,'WK3',4),
('W',0,'WK3',5),
('V',0,'WK3',6),
('N',0,'WK3',7),
('A',0,'WK4',2),
('F',0,'WK4',1),
('T',0,'WK4',5),
('K',0,'WK4',4),
('B',0,'WK4',6),
('A',0,'WK5',1),
('F',0,'WK5',2),
('T',0,'WK5',3),
('H',0,'WK5',4),
('K',0,'WK5',5);
This is my current attempt:
WITH
allData AS
(
SELECT Name,
"Week",
newRank = RANK() OVER (ORDER BY "Week" DESC,Amount)
FROM #values
)
,allData2 AS
(
SELECT *,
newRank2 = 1 / CONVERT(NUMERIC(18,10),newRank)
FROM allData
)
,allData3 AS
(
SELECT Name,
smRank = SUM(newRank2)
FROM allData2
GROUP BY Name
)
SELECT Name,
smRank,
rnk = RANK() OVER (ORDER BY smRank DESC)
INTO #RankA
FROM allData3;
UPDATE X
SET X.Idx = Y.rnk
FROM #values X
INNER JOIN #RankA Y ON
X.Name = Y.Name;
Unfortunately if I pivot the results, and then order by the Idx column it is not in the order I am aiming at.
This is based on two nested ROW_NUMBERs:
select *,
row_number()
over (order by "Week" desc, amount)
from
(
select *,
row_number()
over (partition by name
order by "Week" desc, amount) as rn
from #values
) as dt
where rn = 1 -- for each name find the latest week and it's lowest number
What if two names share the same week/amount? You might consider RANK or DENSE_RANK instead.
Using your #values table, here is how to pivot it (since the data you provided was not in the same table format) and then assign a value to the index based on your requirements.
select *
, ROW_NUMBER() OVER(ORDER BY CASE WHEN wk5 IS NULL THEN 1 ELSE 0 END, wk5, CASE WHEN wk4 IS NULL THEN 1 ELSE 0 END, wk4, CASE WHEN wk3 IS NULL THEN 1 ELSE 0 END,wk3, CASE WHEN wk2 IS NULL THEN 1 ELSE 0 END,wk2, CASE WHEN wk1 IS NULL THEN 1 ELSE 0 END, wk1) AS new_index
from (
select * from #values
) p
PIVOT (
MAX(Amount)
FOR [week] IN (wk1, wk2, wk3, wk4, wk5)) AS pvt
USING DYNAMIC FOR 52 WEEKS
DECLARE #COLS AS NVARCHAR(MAX),
#QUERY AS NVARCHAR(MAX)
SELECT #COLS = STUFF(( SELECT distinct ','+QUOTENAME(C.[week])
FROM #values AS C
FOR XML PATH('')), 1, 1, '')
SET #QUERY = '
select *
, ROW_NUMBER() OVER(ORDER BY CASE WHEN wk5 IS NULL THEN 1 ELSE 0 END, wk5, CASE WHEN wk4 IS NULL THEN 1 ELSE 0 END, wk4, CASE WHEN wk3 IS NULL THEN 1 ELSE 0 END,wk3, CASE WHEN wk2 IS NULL THEN 1 ELSE 0 END,wk2, CASE WHEN wk1 IS NULL THEN 1 ELSE 0 END, wk1) AS new_index
from (
select * from #values
) p
PIVOT (
MAX(Amount)
FOR [week] IN (' + #cols+ ')) AS pvt'
EXEC(#QUERY)

SQL select statement with a quotename removing the last character on the last row

I have a select statement that uses QUOTNAME to add single quotes and a comma to each of the results.
SELECT QUOTENAME(field1,'''')+',' AS [1]
Which changes the results from this.
1
11111
22222
33333
44444
To this
1
'11111',
'22222',
'33333',
'44444',
However I would like to know if it is possible to remove the comma from the very last row? Too look like this.
1
'11111',
'22222',
'33333',
'44444'
edit: I should have mentioned this is a View
SELECT QUOTENAME(field1,'''')+
case when row_number() over(order by (select 1))=
count(*) over () then '' else ',' end AS [1]
FROM <table>
Try something like this
DECLARE #count int
SELECT #count = COUNT(*) FROM my_table
SELECT QUOTENAME(field1, '''') + CASE WHEN ROW_NUMBER() OVER (ORDER BY field1) < #count THEN ',' ELSE '' END AS [1]
FROM my_table
I'd like to say that SQL is the wrong place to be formatting your data and that it should be done in your application or client.
But as you've asked it is possible and this is one way of doing it:
WITH
MyData AS (
SELECT
field1,
ROW_NUMBER() OVER(ORDER BY field1 DESC) AS rowNo
FROM Data
)
SELECT
CONCAT(
QUOTENAME( field1, '''' ),
(CASE WHEN rowNo <> 1 THEN ',' END)
) AS [1]
FROM MyData
ORDER BY rowNo DESC;
SQL Fiddle

Unique ID base on other column

DROP TABLE #ABC
CREATE TABLE #ABC (ID INT NOT NULL, Name VARCHAR (2) NOT NULL, name2 VARCHAR(2))
INSERT INTO #ABC (ID, NAME)
VALUES (1,'01'),(1,'F5'),(1,'05'),(1,'08'),(1,'02'), (1,'03'), (1,'04'), (1,'06'),(1,'07'),(1,'09'),(1,'10'),(1,'11'),(1,'12'),(1,'13'),(1,'14'),
(1,'15'),(1,'2D'),(1,'2E'),(1,'4B'),(1,'5F'),(1,'64'),(1,'73'),(1,'83'),(1,'88'),(1,'A9'),(1,'AC'),(1,'D0'),(1,'D7'),(1,'15'),(2,'76'),(2,'J5')
Script I am using to populate name2 (not working)
UPDATE A
SET name2 = SUBSTRING(REPLACE(CONVERT(VARCHAR(36), NEWID()) , '-', ''), 0, 3)
FROM #ABC AS A
select * from #ABC
So, I have table #ABC already populated with ID and NAME, I want to populate 'Name2' so that 'name2' should not have the same value as 'name' for the same ID. Example, for ID = 1 , all name2 values should be different than name values.
Thanks
It looks like you are using SQL Server. If so, you can make use of the row_number() function to append a unique identifier to the end of name:
with toupdate as (
select t.*, row_number() over (partition by id order by name desc) as num
from #abc t
)
update toupdate
set name2 = cast(num as varchar(2))
This will work for up to 99 duplicates for a given id.
Unfortunately, I don't have SQL Server available right now. But here is an idea. You can take the maximum value of name and then do arithmetic, base 36 (26 alpha and 10 numeric). The result is something like this:
with toupdate as (
select t.*, row_number() over (partition by id order by name desc) as seqnum,
max(name) over (partition by id) as maxname,
((case when left(name, 1) between '0' and '9'
then ascii(left(name, 1))
else ascii(upper(left(name, 1))) - ascii('A')+10
end) * 36 +
(case when right(name, 1) between '0' and '9'
then ascii(right(name, 1))
else ascii(upper(right(name, 1))) - ascii('A')+10
end)
) as namenum
from #abc t
)
update toupdate
set name2 = (case when (namenum+seqnum)/36 < 10
then char(ascii('0')+((namenum+seqnum)/36))
else char(ascii('A')+((namenum+seqnum)/36) - 10)
end)+
(case when ((namenum+seqnum)%36) < 10
then char(ascii('0')+((namenum+seqnum)%36))
else char(ascii('A')+((namenum+seqnum)%36) - 10)
end)
This finds the maximum name and then generates names bigger than that. This assumes that you are using only alphabetic characters (upper case) and numbers. It can fail if name takes on a value near the maximum possible value ('ZZ' in this case).