By:Sergey Gigoyan | Last Updated: 2018-12-03 || Related Tips:More > T-SQL
ProblemIn theprevious article, we have discussed how the new features of SQL Server allows us to rewrite the old code in a more compact way. In this article, we will continue to demonstrate some other new features of SQL Server 2016/2017, allowing us to write shorter code for the same tasks.
SolutionIn this article, we will demonstrate the usage of two string functions STRING_SPLIT and CONCAT_WS introduced in SQL Server 2016 and 2017 correspondingly and will compare the old code written without using these functions with the new code.
Creating the test environmentIn theprevious article, we created the TestDB database with three tables and data: Student, Course, and StudentCourse table that represents the mapping between Student and Course tables:
USE masterGO
--Database
CREATE DATABASE TestDB
GO
USE TestDB
GO
--Tables
CREATE TABLE Student
(
StudentID INT NOT NULL,
LoginName NVARCHAR(50),
PRIMARY KEY (StudentID)
)
GO
CREATE TABLE Course
(
CourseID INT NOT NULL,
CourseName NVARCHAR(50) UNIQUE,
PRIMARY KEY (CourseID)
)
GO
CREATE TABLE StudentCourse
(
StudentCourseID INT NOT NULL IDENTITY(1,1),
StudentID INT,
CourseID INT,
PRIMARY KEY (StudentCourseID),
CONSTRAINT UC_StudentID_CourseID UNIQUE(StudentID,CourseID),
)
GO
--Filling data
INSERT INTO Student(StudentID, LoginName)
VALUES (1, ' <a href="/cdn-cgi/l/email-protection" data-cfemail="99edf6f4d9edfceaedecf7f0effcebeaf0ede0b7faf6f4">[email protected]</a> '),
(2, '<a href="/cdn-cgi/l/email-protection" data-cfemail="6a190b072a1e0f191e1f04031c0f1819031e1344090507">[email protected]</a> '),
(3, ' <a href="/cdn-cgi/l/email-protection" data-cfemail="5238333c371226372126273c3b243720213b262b7c313d3f">[email protected]</a>'),
(4, '<a href="/cdn-cgi/l/email-protection" data-cfemail="f1909f9fb185948285849f9887948382988588df929e9c">[email protected]</a>')
INSERT INTO Course(CourseID, CourseName)
VALUES (1, 'psychology'),
(2, 'physics'),
(3, 'chemistry'),
(4, 'astrology'),
(5, 'mathematics'),
(6, 'biology'),
(7, 'geography')
INSERT INTO StudentCourse(StudentID, CourseID)
VALUES (1, 1),
(1, 3),
(1, 5),
(2, 5),
(3, 1),
(3, 2),
(3, 3),
(3, 4),
(3, 5),
(3, 6),
(3, 7),
(4, 2),
(4, 4),
(4, 5)
In this article, we will also use this environment for testing the remainder of the examples.
Build a Single-Column SQL Server Table from Separated Values in a StringMany developers have faced a situation where they needed to separate values in a string into several rows. Let's imagine, that we regularly receive updated, comma-separated list of subjects and we need to update the data in the 'Course' table.
Let's assume that we have been provided with the following string as a list of subjects to compare them with the subjects in the 'Course' table and add the new subjects to the table:
'psychology,physics,chemistry,astrology,mathematics,biology,geography,computer science,history'Having a single-column table, like in the picture below, instead of the comma-separated list will allow us to compare and merge data into the existing table. So, we need to convert the comma-separated string to a single-column table.
Image may be NSFW.
Clik here to view.

Earlier SQL Server Versions - Separate Delimited String into Rows Using a Loop
Before SQL Server 2016, we can solve this task in the following way:
USE TestDBGO
--Comma-separated string into table in older versions of SQL Server
DECLARE @Subject TABLE
(
SubjectName NVARCHAR(50)
)
DECLARE @CurrentValue NVARCHAR(50)
DECLARE @str NVARCHAR(MAX)='psychology,physics,chemistry,astrology,mathematics,biology,geography,computer science,history'
WHILE CHARINDEX(',',@str)>0
BEGIN
SET @CurrentValue = (SELECT SUBSTRING(@str,1,CHARINDEX(',',@str)-1) )
INSERT INTO @Subject(SubjectName)
VALUES(@CurrentValue)
SET @str=(SELECT RIGHT(@str, LEN(@str)-LEN(@CurrentValue )-1 ))
END
INSERT INTO @Subject(SubjectName)
VALUES(@str)
SELECT * FROM @Subject
We declare a table variable and inside the WHILE loop, we separate each subject from the string by using several string functions and then insert these values into the table variable.
SQL Server 2016 and Beyond - Separate Delimited String into Rows Using STRING_SPLITSQL Server 2016 introduced the function STRING_SPLIT. This allows us to solve the same task by writing a single line of code. This function accepts two parameters: string and separator and returns a table with one column called VALUE, where values in the VALUE column are fragments of the string defined by the separator. Using this function, we can rewrite the code above in the following way:
USE TestDBGO
--Comma-separated string into table in SQL Server 2016 and higher versions
SELECT VALUE AS SubjectName
FROM STRING_SPLIT('psychology,physics,chemistry,astrology,mathematics,biology,geography,computer science,history', ',')
As we can see the code is much shorter, but the result is the same:
Image may be NSFW.
Clik here to view.

We can then use a MERGE statement to insert the new subjects into the Course table as follows:
USE TestDBGO
DECLARE @MaxCourseID INT
SET @MaxCourseID = (SELECT MAX(CourseID) FROM Course)
MERGE
Course AS target
USING
(SELECT ROW_NUMBER() OVER (ORDER BY VALUE) + @MaxCourseID AS SubjectID,
VALUE AS SubjectName FROM STRING_SPLIT('psychology,physics,chemistry,astrology,mathematics,biology,geography,computer science,history', ',') ) AS source
ON target.CourseName=source.SubjectName
WHEN NOT MATCHED BY target
THEN INSERT (CourseID, CourseName) VALUES(source.SubjectID, source.SubjectName);
SELECT *
FROM Course
ORDER BY CourseID
We can see that two new courses, (history and computer science) have been added to the table:
Image may be NSFW.
Clik here to view.

Concatenating Several Columns into one Column in SQL Server
For each row, we need to group values from the given columns into one string.
We have a table called StudentAddress for storing students addresses:
USE TestDBGO
CREATE TABLE StudentAddress
(
StudentAddressID INT NOT NULL IDENTITY(1,1),
StudentID INT REFERENCES Student(StudentID),
AppartmentNum NVARCHAR(25),
Street NVARCHAR(50),
City NVARCHAR(50),
[State] NVARCHAR(50),
PostalCode NVARCHAR(25),
Country NVARCHAR(50),
PRIMARY KEY (StudentAddressID)
)
GO
INSERT INTO StudentAddress(StudentID,AppartmentNum,Street,City,[State], PostalCode, Country)
VALUES (1, 202, 'Queen Street', 'Albany','NH','03818', 'USA'),
(2, 54, 'Teryan Street', 'Yerevan','Yerevan','0204', 'Armenia'),
(3, 97, 'King Str', 'Albuquerque','NM','87101', 'USA'),
(4, 299, 'Woodbin ave', 'Toronto','ON','M4C5K7', 'Canada')
GO
--Students' addresses
SELECT s.LoginName, AppartmentNum,Street,City,[State], PostalCode, Country
FROM Student s
INNER JOIN StudentAddress a ON s.StudentID=a.StudentID
In this table, we have a separate column for each attribute of the address:
Image may be NSFW.
Clik here to view.

However, we have to write a report that will show the students addresses in t