Need help for query:
Id Name Balance
1 A 10
2 A -10
3 A 10
4 A 15
5 B 10.5
6 B -10.5
7 B 25
Requirement: I want to remove the balance with +ve and –ve values for each name. I am expecting the below output
Expected results: --
Id Name Balance
1 A 10
2 A 15
3 B 25
Can you please share me SQL query.
Here is one way to do it.
Create dummy data for testing
CREATE TABLE foo (Id INT, Name VARCHAR(50), Balance money);
INSERT INTO foo VALUES (1, 'A', 10);
INSERT INTO foo VALUES (2, 'A', -10);
INSERT INTO foo VALUES (3, 'A', 10);
INSERT INTO foo VALUES (4, 'A', 15);
INSERT INTO foo VALUES (5, 'B', 10.5);
INSERT INTO foo VALUES (6, 'B', -10.5);
INSERT INTO foo VALUES (7, 'B', 25);
1) Create a temp table that has one extra MatchFound column to record whether a match is found.
CREATE TABLE #TempTable(Id int, Name varchar(50), Balance decimal(10,2), MatchFound bit);
INSERT INTO #TempTable (Id, Name, Balance, MatchFound)
SELECT Id, Name, Balance, 0
FROM foo;
2) Loop through the data to find a match for each row. Update MatchFound column if a match is found.
DECLARE #Id INT, #Name VARCHAR(50), #Balance decimal(10,2), #MatchId INT;
DECLARE MyCursor CURSOR FOR SELECT Id, Name, Balance FROM #TempTable ORDER BY Id;
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #Id, #Name, #Balance
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS (SELECT 1 FROM #TempTable WHERE Id = #Id AND MatchFound = 0)
BEGIN
SET #MatchId = NULL
SELECT TOP 1 #MatchId = Id FROM #TempTable
WHERE Id <> #Id AND Name = #Name AND Balance = #Balance * -1 AND MatchFound = 0
IF #MatchId IS NOT NULL
UPDATE #TempTable SET MatchFound = 1 WHERE Id IN (#Id, #MatchId)
END
FETCH NEXT FROM MyCursor INTO #Id, #Name, #Balance
END
CLOSE MyCursor;
DEALLOCATE MyCursor;
3) Select and return rows that did not have a match
Select Id, Name, Balance From #TempTable Where MatchFound = 0
4) Clean up
drop table #TempTable
Related
Apologies if my question seems to be naive:
I cannot get my head around the 2 statements below, can someone please explain the difference:
OUTPUT $ACTION, INSERTED.BuildRequestID, ..... and
PRINT ##ROWCOUNT
Apparently, they both can be used to print something on the window, with output in the example above, the records that have been inserted will be displayed. And, PRINT ##ROWCOUNT returns the number of rows affected by the last executed statement in the batch, so, if the function was insert, then it will show the inserted records?
Thank you,
In its simplest terms, OUTPUT will give you the actual records affected by a DML statement (INSERT, UPDATE, DELETE, MERGE), ##ROWCOUNT will just tell you how many rows were affected by the previous Statement (not limited to DML).
This is probably easiest understood with a working example that you can run yourself and see both in action:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromDropTable INT = ##ROWCOUNT;
-- CREATE A TABLE
CREATE TABLE #T (ID INT NOT NULL PRIMARY KEY, Col CHAR(1) NOT NULL);
-- INSERT SOME VALUES AND CHECK THE OUTPUT
INSERT #T (ID, Col)
OUTPUT inserted.*
VALUES (1, 'A'), (2, 'B'), (3, 'C');
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromInsert INT = ##ROWCOUNT;
-- DELETE A VALUE AND INSPECT THE DELETED RECORD WITH OUTPUT
DELETE #T
OUTPUT deleted.*
WHERE ID = 3;
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromDelete INT = ##ROWCOUNT;
-- UPDATE A RECORD AND VIEW BEFORE AND AFTER VALUES
UPDATE #T
SET Col = 'X'
OUTPUT inserted.ID AS ID,
inserted.Col AS UpdatedTo,
deleted.Col AS UpdatedFrom
WHERE ID = 2;
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromUpdate INT = ##ROWCOUNT;
-- USE MERGE, AND CAPTURE ACTION:
MERGE #T AS t
USING (VALUES (2, 'B'), (3, 'C')) AS s (ID, Col)
ON s.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, Col) VALUES (s.ID, s.Col)
WHEN MATCHED THEN UPDATE SET Col = s.Col
WHEN NOT MATCHED BY SOURCE THEN DELETE
OUTPUT $Action AS DMLAction,
inserted.ID AS InsertedID,
inserted.Col AS InsertedCol,
deleted.ID AS DeletedID,
deleted.Col AS DeletedCol;
-- CHECK ##ROWCOUNT
DECLARE #RowCountFromMerge INT = ##ROWCOUNT;
SELECT RowCountFromDropTable = #RowCountFromDropTable,
RowCountFromInsert = #RowCountFromInsert,
RowCountFromDelete = #RowCountFromDelete,
RowCountFromUpdate = #RowCountFromUpdate,
RowCountFromMerge = #RowCountFromMerge;
The recordsets output from each of the DML are:
INSERT
ID Col
-------
1 A
2 B
3 C
DELETE
ID Col
-------
3 C
UPDATE
ID UpdatedTo UpdatedFrom
---------------------------
2 X B
MERGE
DMLAction InsertedID InsertedCol DeletedID DeletedCol
------------------------------------------------------------
INSERT 3 C NULL NULL
DELETE NULL NULL 1 A
UPDATE 2 B 2 X
INSPECT ##ROWCOUNTS
RowCountFromDropTable RowCountFromInsert RowCountFromUpdate RowCountFromMerge
--------------------------------------------------------------------------------
0 3 1 3
A quick point on some wording in the qeustion too: You cannot use OUTPUT directly to print something to the window, it returns records much like a SELECT statement. ##ROWCOUNT can be used like any scalar function, so you could use this in consecutive statements. So you could do something like this:
SELECT TOP (1) *
FROM (VALUES (1), (2), (3)) AS t (ID);
SELECT TOP (##ROWCOUNT + 1) *
FROM (VALUES (1), (2), (3)) AS t (ID);
SELECT TOP (##ROWCOUNT + 1) *
FROM (VALUES (1), (2), (3)) AS t (ID);
Which returns 1, 1,2 and 1,2,3 respectively. I have no idea why you would want to do this, but it demonstrates the scope of ##ROWCOUNT a bit better than the above, and how it can be used elsewhere.
I want to write trigger for insert update and Delete. O have one table named (tbl_rank) which have primary key (ID).
ID Name Rank
1 A 1
2 B 2
3 C 3
4 D 4
5 E 5
Now I want to insert new rank but conditions are
1) if I enter 6 it will be 6
2) if I enter 7 it also should be 6 (I mean in sequence)
3) if I enter 2 than than entered rank will be 2 and 2 will be 3 and so on
For delete trigger
1) if I delete 5 the rank should be 1 to 4
2) if I delete 2 the rank would be rearranged and 3 should be 2 and 4 would be 3 and so on
for update trigger
1) if I update 3 to 5 than 4 would be 3 and 5 would be 4
2) if I update 5 to 3 than 3 would be 4 and 4 would be 5
I wrote insert and delete trigger its working fine but in update I am getting uneven result.
Can you not just have tbl_rank as a view then you don't need any triggers? To rank them in the view you can use a windowed function row_number() over (order by Id)
How is the initial update performed? If you know it is an update then you need to do a delete and insert just for the effected range. Eg changing 3 to 5. You delete records for 3 to 5 then insert those 3 records again with the different ids. An update statement essentially does this anyway
There is an assumption that id is not an auto-identity column.
CREATE TRIGGER trg_tbl_rank
ON tbl_rank
INSTEAD OF INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #v_deleted_rank INT;
DECLARE #v_inserted_rank INT;
DECLARE #v_max_rank INT;
SELECT #v_deleted_rank = COALESCE(rank, 0) FROM deleted;
SELECT #v_inserted_rank = COALESCE(rank, 0) FROM inserted;
SELECT #v_max_rank = COALESCE(MAX(rank), 0) FROM tbl_rank;
IF #v_deleted_rank > 0
BEGIN
DELETE FROM tbl_rank
WHERE id = (SELECT id FROM deleted);
UPDATE tbl_rank
SET rank = rank - 1
WHERE rank > #v_deleted_rank;
END
IF #v_inserted_rank > 0
BEGIN
IF #v_inserted_rank <= #v_max_rank
BEGIN
UPDATE tbl_rank
SET rank = rank + 1
WHERE rank >= #v_inserted_rank;
INSERT INTO tbl_rank (id, name, rank)
SELECT id, name, #v_inserted_rank FROM inserted;
END
ELSE
INSERT INTO tbl_rank (id, name, rank)
SELECT id, name, #v_max_rank + 1 FROM inserted;
END
END
GO
Here are queries to test:
INSERT INTO tbl_rank (id, name, rank) VALUES (1, 'A', 1);
INSERT INTO tbl_rank (id, name, rank) VALUES (2, 'B', 2);
INSERT INTO tbl_rank (id, name, rank) VALUES (3, 'C', 3);
INSERT INTO tbl_rank (id, name, rank) VALUES (4, 'D', 4);
INSERT INTO tbl_rank (id, name, rank) VALUES (5, 'E', 5);
SELECT * FROM tbl_rank;
INSERT INTO tbl_rank (id, name, rank) VALUES (6, 'F', 7);
SELECT * FROM tbl_rank;
INSERT INTO tbl_rank (id, name, rank) VALUES (7, 'G', 2);
SELECT * FROM tbl_rank;
DELETE FROM tbl_rank WHERE rank = 7;
SELECT * FROM tbl_rank;
DELETE FROM tbl_rank WHERE rank = 2;
SELECT * FROM tbl_rank;
UPDATE tbl_rank SET rank = 5 WHERE rank = 3;
SELECT * FROM tbl_rank;
UPDATE tbl_rank SET rank = 3 WHERE rank = 5;
SELECT * FROM tbl_rank;
TRUNCATE TABLE tbl_rank;
I want to write trigger for insert update and Delete I have one table named(tbl_rank)which have primary key(ID)
Please post DDL, so that people do not have to guess what the keys, constraints, Declarative Referential Integrity, data types, etc. in your schema are. Learn how to follow ISO-11179 data element naming conventions and formatting rules. Temporal data should use ISO-8601 formats. Code should be in Standard SQL as much as possible and not local dialect.
This is minimal behavior on SQL forums. Putting “tbl_” on table name is a classic design flaw called “tbling” and the column names are violations of ISO-11179 rules, too. Now we have to guess at keys, data types, etc. Here is my guess and clean up.
CREATE TABLE Prizes
(prize_id INTEGER NOT NULL PRIMARY KEY,
prize_name CHAR(1) NOT NULL,
prize_rank INTEGER NOT NULL);
INSERT INTO Prizes
VALUES
(1, 'A', 1),
(2, 'B', 2),
(3, 'C', 3),
(4, 'D', 4),
(5, 'E', 5);
Why triggers? RDBMS has virtual tables and columns. This not a deck of punch cards or a magnetic tape file. A VIEW is always current and correct.
CREATE VIEW Prize_List
AS
SELECT prize_id, prize_name,
ROW_NUMBER() OVER (ORDER BY prize_id)
AS prize_rank
FROM Prizes;
But it might be better to drop the prize_id column completely and re-arrange the display order based on the prize_rank column:
CREATE TABLE Prizes
(prize_name CHAR(1) NOT NULL,
prize_rank INTEGER NOT NULL PRIMARY KEY);
Now use procedures to manipulate the table as needed.
CREATE PROCEDURE Swap_Prize_Ranks (#old_prize_rank INTEGER, #new_prize_rank INTEGER)
AS
UPDATE Prizes
SET prize_rank
= CASE prize_rank
WHEN #old_prize_rank
THEN #new_prize_rank
ELSE prize_rank + SIGN(#old_prize_rank - #new_prize_rank)
END
WHERE prize_rank BETWEEN #old_prize_rank AND #new_prize_rank
OR prize_rank BETWEEN #new_prize_rank AND #old_prize_rank;
When you want to drop a few rows, remember to close the gaps with this:
CREATE PROCEDURE Close_Prize_Gaps()
AS
UPDATE Prizes
SET prize_rank
= (SELECT COUNT (P1.prize_rank)
FROM Prizes AS P1
WHERE P1.prize_rank <= Prizes.prize_rank);
Have a very large table (over 200 million rows)
sID int, wordID int (PK sID, wordID)
Want to find the sID's that have the exact same wordID's (and no extras)
For a sID with over 100 wordID the chance of an exact match goes down so willing to limit it to 100
(but would like to go to 1000)
If this was school and sID were classes and wordID were students.
Then I want to find classes that have the exact same students.
sID, wordID
1, 1
1, 2
1, 3
2, 2
2, 3
3, 1
3, 4
5, 1
5, 2
6, 2
6, 3
7, 1
7, 2
8, 1
8, 1
sID 6 and 2 have the exact same wordID's
sID 5, 7, and 8 have the exact same wordID's
This is what I have so far
I would like to eliminate the two delete #temp3_sID1_sID2 and take care of that in the insert above
But I will try any ideas
It is not like you can easily create a table with 200 million rows to test with
drop table #temp_sID_wordCount
drop table #temp_count_wordID_sID
drop table #temp3_wordID_sID_forThatCount
drop table #temp3_sID1_sID2
drop table #temp3_sID1_sID2_keep
create table #temp_sID_wordCount (sID int primary key, ccount int not null)
create table #temp_count_wordID_sID (ccount int not null, wordID int not null, sID int not null, primary key (ccount, wordID, sID))
create table #temp3_wordID_sID_forThatCount (wordID int not null, sID int not null, primary key(wordID, sID))
create table #temp3_sID1_sID2_keep (sID1 int not null, sID2 int not null, primary key(sID1, sID2))
create table #temp3_sID1_sID2 (sID1 int not null, sID2 int not null, primary key(sID1, sID2))
insert into #temp_sID_wordCount
select sID, count(*) as ccount
FROM [FTSindexWordOnce] with (nolock)
group by sID
order by sID;
select count(*) from #temp_sID_wordCount where ccount <= 100; -- 701,966
truncate table #temp_count_wordID_sID
insert into #temp_count_wordID_sID
select #temp_sID_wordCount.ccount, [FTSindexWordOnce].wordID, [FTSindexWordOnce].sID
from #temp_sID_wordCount
join [FTSindexWordOnce] with (nolock)
on [FTSindexWordOnce].sID = #temp_sID_wordCount.sID
and ccount >= 1 and ccount <= 10
order by #temp_sID_wordCount.ccount, [FTSindexWordOnce].wordID, [FTSindexWordOnce].sID;
select count(*) from #temp_sID_wordCount; -- 34,860,090
truncate table #temp3_sID1_sID2_keep
declare cur cursor for
select top 10 ccount from #temp_count_wordID_sID group by ccount order by ccount
open cur
declare #count int, #sIDcur int
fetch next from cur into #count
while (##FETCH_STATUS = 0)
begin
--print (#count)
--select count(*), #count from #temp_sID_wordCount where #temp_sID_wordCount.ccount = #count
truncate table #temp3_wordID_sID_forThatCount
truncate table #temp3_sID1_sID2
-- wordID and sID for that unique word count
-- they can only be exact if they have the same word count
insert into #temp3_wordID_sID_forThatCount
select #temp_count_wordID_sID.wordID
, #temp_count_wordID_sID.sID
from #temp_count_wordID_sID
where #temp_count_wordID_sID.ccount = #count
order by #temp_count_wordID_sID.wordID, #temp_count_wordID_sID.sID
-- select count(*) from #temp3_wordID_sID_forThatCount
-- this has some duplicates
-- sID1 is the group
insert into #temp3_sID1_sID2
select w1.sID, w2.sID
from #temp3_wordID_sID_forThatCount as w1 with (nolock)
join #temp3_wordID_sID_forThatCount as w2 with (nolock)
on w1.wordID = w2.wordID
and w1.sID <= w2.sID
group by w1.sID, w2.sID
having count(*) = #count
order by w1.sID, w2.sID
-- get rid of the goups of 1
delete #temp3_sID1_sID2
where sID1 in (select sID1 from #temp3_sID1_sID2 group by sID1 having count(*) = 1)
-- get rid of the double dips
delete #temp3_sID1_sID2
where #temp3_sID1_sID2.sID1 in
(select distinct s1del.sID1 -- these are the double dips
from #temp3_sID1_sID2 as s1base with (nolock)
join #temp3_sID1_sID2 as s1del with (nolock)
on s1del.sID1 > s1base.sID1
and s1Del.sID1 = s1base.sID2)
insert into #temp3_sID1_sID2_keep
select #temp3_sID1_sID2.sID1
, #temp3_sID1_sID2.sID2
from #temp3_sID1_sID2 with (nolock)
order by #temp3_sID1_sID2.sID1, #temp3_sID1_sID2.sID2
fetch next from cur into #count
end
close cur
deallocate cur
select *
FROM #temp3_sID1_sID2_keep with (nolock)
order by 1,2
So, as I see, the task is to find equal subsets.
First we can find pairs of equal subsets:
;with tmp1 as (select sID, cnt = count(wordID) from [Table] group by sID)
select s1.sID, s2.sID
from tmp1 s1
cross join tmp1 s2
cross apply (
select count(1)
from [Table] d1
join [Table] d2 on d2.wordID = d1.wordID
where d1.sID = s1.sID and d2.sID = s2.sID
) c(cnt)
where s1.cnt = s2.cnt
and s1.sID > s2.sID
and s1.cnt = c.cnt
Output is:
sID sID
----------- -----------
6 2
7 5
8 5
8 7
And then pairs can be combined into groups, if necessary:
sID gNum
----------- -----------
2 1
6 1
5 2
7 2
8 2
See details in SqlFiddle sample below.
SqlFiddle Sample
The other approach is to calculate hash function for every subset data:
;with a as (
select distinct sID from [Table]
)
select sID,
hashbytes('sha1', (
select cast(wordID as varchar(10)) + '|'
from [Table]
where sID = a.sID
order by wordID
for xml path('')))
from a
Then subsets can be grouped based on hash value.
SqlFiddle Sample
The last one took less than a minute on my machine for a test data of about 10 million rows (20k sID values up to 1k wordID each). Also you can optimize it by excluding sIDs having no wordID count matches to any other.
I'm trying to write a sproc that will take a table of users and assign them to each other randomly with no duplicates and no one assigned to themselves.
The user table has an UserId column and a Username column. I have a separate table for storing the IDs. That table has a UserId column and an AssignedUserId column.
I used this snippet for randomly sorting rows:
SELECT ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS [RandomNumber]
When the code above is part of a select, it works fine. I'm just having a hard time thinking as a data set. I can work the problem row-by-row or in C#, but I'm hoping there's a SQL guru that can show me how to do this in a nice, efficient query.
Thanks!
It can be done, but not good practice on large tables, but with your table being so small this will work great.
DECLARE #A TABLE (A1 INT, A2 INT)
INSERT INTO #A VALUES(1, 1)
INSERT INTO #A VALUES(2, 2)
INSERT INTO #A VALUES(3, 3)
INSERT INTO #A VALUES(4, 4)
INSERT INTO #A VALUES(5, 5)
SELECT * FROM #A ORDER BY NEWID()
Result1:
3 3
1 1
4 4
5 5
2 2
Result2:
2 2
3 3
4 4
5 5
1 1
Result3:
4 4
3 3
1 1
5 5
2 2
Result4:
3 3
5 5
1 1
4 4
2 2
Result5:
5 5
4 4
1 1
3 3
2 2
Ok, here's an alternate solution I came up with in the mean time. I decided that the users are already in the system in random order, so maybe I should try a decoder-ring style approach. So, this code generates a shift value and then shifts the user IDs around. The select statement does the "wrap-around" with a formula. It's not completely random and it could use some polish, but this is what I have. The users probably won't perceive the lack of randomness. Thanks for the help!
DECLARE #people TABLE
(
id INT,
name VARCHAR(50)
)
INSERT INTO #people VALUES (1,'Matthew')
INSERT INTO #people VALUES (2,'Mark')
INSERT INTO #people VALUES (3,'Luke')
INSERT INTO #people VALUES (4,'John')
INSERT INTO #people VALUES (5,'Doug')
INSERT INTO #people VALUES (6,'Jamie')
INSERT INTO #people VALUES (7,'John')
INSERT INTO #people VALUES (8,'Cameron')
INSERT INTO #people VALUES (9,'Emily')
INSERT INTO #people VALUES (10,'Tyler')
INSERT INTO #people VALUES (11,'Todd')
INSERT INTO #people VALUES (12,'Kathryn')
DECLARE #Random INT;
DECLARE #Upper INT;
DECLARE #Lower INT;
DECLARE #MaxId INT;
SET #Lower = 1 -- The lowest record ID
SELECT #Upper = (MAX(Id) / 2) FROM #people -- select random int that is somewhere between 1 and half of max ID for shift
SELECT #MaxId = MAX(Id) FROM #people -- select the largest ID in the table
SELECT #Random = ROUND(((#Upper - #Lower -1) * RAND() + #Lower), 0) -- calculate a random number to shift IDs off by
SELECT id, ((id + #Random) % #MaxId) + 1, #Random
from #people
Table name: Table1
id name
1 1-aaa-14 milan road
2 23-abcde-lsd road
3 2-mnbvcx-welcoome street
I want the result like this:
Id name name1 name2
1 1 aaa 14 milan road
2 23 abcde lsd road
3 2 mnbvcx welcoome street
This function ought to give you what you need.
--Drop Function Dbo.Part
Create Function Dbo.Part
(#Value Varchar(8000)
,#Part Int
,#Sep Char(1)='-'
)Returns Varchar(8000)
As Begin
Declare #Start Int
Declare #Finish Int
Set #Start=1
Set #Finish=CharIndex(#Sep,#Value,#Start)
While (#Part>1 And #Finish>0)Begin
Set #Start=#Finish+1
Set #Finish=CharIndex(#Sep,#Value,#Start)
Set #Part=#Part-1
End
If #Part>1 Set #Start=Len(#Value)+1 -- Not found
If #Finish=0 Set #Finish=Len(#Value)+1 -- Last token on line
Return SubString(#Value,#Start,#Finish-#Start)
End
Usage:
Select ID
,Dbo.Part(Name,1,Default)As Name
,Dbo.Part(Name,2,Default)As Name1
,Dbo.Part(Name,3,Default)As Name2
From Dbo.Table1
It's rather compute-intensive, so if Table1 is very long you ought to write the results to another table, which you could refresh from time to time (perhaps once a day, at night).
Better yet, you could create a trigger, which automatically updates Table2 whenever a change is made to Table1. Assuming that column ID is primary key:
Create Table Dbo.Table2(
ID Int Constraint PK_Table2 Primary Key,
Name Varchar(8000),
Name1 Varchar(8000),
Name2 Varchar(8000))
Create Trigger Trigger_Table1 on Dbo.Table1 After Insert,Update,Delete
As Begin
If (Select Count(*)From Deleted)>0
Delete From Dbo.Table2 Where ID=(Select ID From Deleted)
If (Select Count(*)From Inserted)>0
Insert Dbo.Table2(ID, Name, Name1, Name2)
Select ID
,Dbo.Part(Name,1,Default)
,Dbo.Part(Name,2,Default)
,Dbo.Part(Name,3,Default)
From Inserted
End
Now, do your data manipulation (Insert, Update, Delete) on Table1, but do your Select statements on Table2 instead.
The below solution uses a recursive CTE for splitting the strings, and PIVOT for displaying the parts in their own columns.
WITH Table1 (id, name) AS (
SELECT 1, '1-aaa-14 milan road' UNION ALL
SELECT 2, '23-abcde-lsd road' UNION ALL
SELECT 3, '2-mnbvcx-welcoome street'
),
cutpositions AS (
SELECT
id, name,
rownum = 1,
startpos = 1,
nextdash = CHARINDEX('-', name + '-')
FROM Table1
UNION ALL
SELECT
id, name,
rownum + 1,
nextdash + 1,
CHARINDEX('-', name + '-', nextdash + 1)
FROM cutpositions c
WHERE nextdash < LEN(name)
)
SELECT
id,
[1] AS name,
[2] AS name1,
[3] AS name2
/* add more columns here */
FROM (
SELECT
id, rownum,
part = SUBSTRING(name, startpos, nextdash - startpos)
FROM cutpositions
) s
PIVOT ( MAX(part) FOR rownum IN ([1], [2], [3] /* extend the list here */) ) x
Without additional modifications this query can split names consisting of up to 100 parts (that's the default maximum recursion depth, which can be changed), but can only display no more than 3 of them. You can easily extend it to however many parts you want it to display, just follow the instructions in the comments.
select T.id,
substring(T.Name, 1, D1.Pos-1) as Name,
substring(T.Name, D1.Pos+1, D2.Pos-D1.Pos-1) as Name1,
substring(T.Name, D2.Pos+1, len(T.name)) as Name2
from Table1 as T
cross apply (select charindex('-', T.Name, 1)) as D1(Pos)
cross apply (select charindex('-', T.Name, D1.Pos+1)) as D2(Pos)
Testing performance of suggested solutions
Setup:
create table Table1
(
id int identity primary key,
Name varchar(50)
)
go
insert into Table1
select '1-aaa-14 milan road' union all
select '23-abcde-lsd road' union all
select '2-mnbvcx-welcoome street'
go 10000
Result:
if you always will have 2 dashes, you can do the following by using PARSENAME
--testing table
CREATE TABLE #test(id INT, NAME VARCHAR(1000))
INSERT #test VALUES(1, '1-aaa-14 milan road')
INSERT #test VALUES(2, '23-abcde-lsd road')
INSERT #test VALUES(3, '2-mnbvcx-welcoome street')
SELECT id,PARSENAME(name,3) AS name,
PARSENAME(name,2) AS name1,
PARSENAME(name,1)AS name2
FROM (
SELECT id,REPLACE(NAME,'-','.') NAME
FROM #test)x
if you have dots in the name column you have to first replace them and then replace them back to dots in the end
example, by using a tilde to substitute the dot
INSERT #test VALUES(3, '5-mnbvcx-welcoome street.')
SELECT id,REPLACE(PARSENAME(name,3),'~','.') AS name,
REPLACE(PARSENAME(name,2),'~','.') AS name1,
REPLACE(PARSENAME(name,1),'~','.') AS name2
FROM (
SELECT id,REPLACE(REPLACE(NAME,'.','~'),'-','.') NAME
FROM #test)x