Unique ID base on other column - sql

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).

Related

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

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

Splitting single row into more columns based on column value

I've a requirement to get 3 similar set of row data replacing the column value if any certain value exists in the given column('[#]' in this case). For example
---------------------
Type Value
---------------------
1 Apple[#]
2 Orange
3 Peach[#]
I need to modify the query to get value as below
----------------------
Type Value
--------------------
1 Apple1
1 Apple2
1 Apple3
2 Orange
3 Peach1
3 Peach2
3 Peach3
I could not come up with logic how to get this
You can also get the same result without recursivity :
select Type, Value from MyTable where Right(Value, 3) <> '[#]'
union
select Type, Replace(Value, '[#]', '1') from MyTable where Right(Value, 3) = '[#]'
union
select Type, Replace(Value, '[#]', '2') from MyTable where Right(Value, 3) = '[#]'
union
select Type, Replace(Value, '[#]', '3') from MyTable where Right(Value, 3) = '[#]'
order by 1, 2
Assuming there is only one digit (as in your example), then I would go for:
with cte as (
select (case when value like '%\[%%' then left(right(value, 2), 1) + 0
else 1
end) as cnt, 1 as n,
left(value, charindex('[', value + '[')) as base, type
from t
union all
select cnt, n + 1, base, type
from cte
where n + 1 <= cnt
)
select type,
(case when cnt = 1 then base else concat(base, n) end) as value
from cte;
Of course, the CTE can be easily extended to any number of digits:
(case when value like '%\[%%'
then stuff(left(value, charindex(']')), 1, charindex(value, '['), '') + 0
else 1
end)
And once you have the number, you can use another source of numbers. But the recursive CTE seems like the simplest solution for the particular problem in the question.
Try this query
DECLARE #SampleData AS TABLE
(
Type int,
Value varchar(100)
)
INSERT INTO #SampleData
VALUES (1, 'Apple[#]'), (2, 'Orange'), (3, 'Peach[#]')
SELECT sd.Type, cr.Value
FROM #SampleData sd
CROSS APPLY
(
SELECT TOP (IIF(Charindex('[#]', sd.Value) > 0, 3, 1))
x.[Value] + Cast(v.t as nvarchar(5)) as Value
FROM
(SELECT Replace(sd.Value, '[#]', '') AS Value) x
Cross JOIN (VALUES (1),(2),(3)) v(t)
Order by v.t asc
) cr
Demo link: Rextester
Using a recursive CTE
CREATE TABLE #test
(
Type int,
Value varchar(50)
)
INSERT INTO #test VALUES
(1, 'Apple[#]'),
(2, 'Orange'),
(3, 'Peach[#]');
WITH CTE AS (
SELECT
Type,
IIF(RIGHT(Value, 3) = '[#]', LEFT(Value, LEN(Value) - 3), Value) AS 'Value',
IIF(RIGHT(Value, 3) = '[#]', 1, NULL) AS 'Counter'
FROM
#test
UNION ALL
SELECT
B.Type,
LEFT(B.Value, LEN(B.Value) - 3) AS 'Value',
Counter + 1
FROM
#test AS B
JOIN CTE
ON B.Type = CTE.Type
WHERE
RIGHT(B.Value, 3) = '[#]'
AND Counter < 3
)
SELECT
Type,
CONCAT(Value, Counter) AS 'Value'
FROM
CTE
ORDER BY
Type,
Value
DROP TABLE #test

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)

Tricky SQL query requiring search for contains

I have data such as this:
Inventors column in my table
Hundley; Edward; Ana
Isler; Hunsberger
Hunsberger;Hundley
Names are separated by ;. I want to write a SQL query which sums up the count.
Eg. The result should be:
Hundley 2
Isler 1
Hunsberger 2
Edward 1
Ana 1
I could do a group by but this is not a simple group by as you can see. Any ideas/thoughts on how to get this output?
Edit: Changed results so it doesn't create any confusion that a row only contains 2 names.
You can take a look at this. I certainly do not recommend this way if you have lots of data, BUT you can do some modifications and use it and it works like a charm!
This is the new code for supporting unlimited splits:
Declare #Table Table (
Name Nvarchar(50)
);
Insert #Table (
Name
) Select 'Hundley; Edward; Anna'
Union Select 'Isler; Hunsberger'
Union Select 'Hunsberger; Hundley'
Union Select 'Anna'
;
With Result (
Part
, Remained
, [Index]
, Level
) As (
Select Case When CharIndex(';', Name, 1) = 0
Then Name
Else Left(Name, CharIndex(';', Name, 1) - 1)
End
, Right(Name, Len(Name) - CharIndex(';', Name, 1))
, CharIndex(';', Name, 1)
, 1
From #Table
Union All
Select LTrim(
Case When CharIndex(';', Remained, 1) = 0
Then Remained
Else Left(Remained, CharIndex(';', Remained, 1) - 1)
End
)
, Right(Remained, Len(Remained) - CharIndex(';', Remained, 1))
, CharIndex(';', Remained, 1)
, Level
+ 1
From Result
Where [Index] <> 0
) Select Part
, Count(*)
From Result
Group By Part
Cheers
;with cte as
(
select 1 as Item, 1 as Start, CHARINDEX(';',inventors, 1) as Split, Inventors from YourInventorsTable
union all
select cte.Item+1, cte.Split+1, nullif(CHARINDEX(';',inventors, cte.Split+1),0), inventors as Split
from cte
where cte.Split<>0
)
select rTRIM(lTRIM(SUBSTRING(inventors, start,isnull(split,len(inventors)+1)-start))), count(*)
from cte
group by rTRIM(lTRIM(SUBSTRING(inventors, start,isnull(split,len(inventors)+1)-start)))
You can create a split function to split the col values
select splittedValues.items,count(splittedValues) from table1
cross apply dbo.split(col1,';') splittedValues
group by splittedValues.items
DEMO in Sql fiddle
first make one function who take your comma or any other operator(;) separated string into one table and by using that temp table, apply GROUP function on that table.
So you will get count for separate value.
"select d.number,count(*) from (select number from dbo.CommaseparedListToTable('Hundley;Edward;Ana;Isler;Hunsberger;Hunsberger;Hundley',';'))d
group by d.number"
declare #text nvarchar(255) = 'Edward; Hundley; AnaIsler; Hunsberger; Hunsberger; Hundley ';
declare #table table(id int identity,name varchar(50));
while #text like '%;%'
Begin
insert into #table (name)
select SUBSTRING(#text,1,charindex(';',#text)-1)
set #text = SUBSTRING(#text, charindex(';',#text)+1,LEN(#text))
end
insert into #table (name)
select #text
select name , count(name ) counts from #table group by name
Output
name count
AnaIsler 1
Hundley 2
Hunsberger 2
Edward 1