I'm trying to migrate a MySQL-based app over to Microsoft SQL Server 2005 (not by choice, but that's life).
In the original app, we used almost entirely ANSI-SQL compliant statements, with one significant exception -- we used MySQL's group_concat function fairly frequently.
group_concat, by the way, does this: given a table of, say, employee names and projects...
SELECT empName, projID FROM project_members;
returns:
ANDY | A100
ANDY | B391
ANDY | X010
TOM | A100
TOM | A510
... and here's what you get with group_concat:
SELECT
empName, group_concat(projID SEPARATOR ' / ')
FROM
project_members
GROUP BY
empName;
returns:
ANDY | A100 / B391 / X010
TOM | A100 / A510
So what I'd like to know is: Is it possible to write, say, a user-defined function in SQL Server which emulates the functionality of group_concat?
I have almost no experience using UDFs, stored procedures, or anything like that, just straight-up SQL, so please err on the side of too much explanation :)
No REAL easy way to do this. Lots of ideas out there, though.
Best one I've found:
SELECT table_name, LEFT(column_names , LEN(column_names )-1) AS column_names
FROM information_schema.columns AS extern
CROSS APPLY
(
SELECT column_name + ','
FROM information_schema.columns AS intern
WHERE extern.table_name = intern.table_name
FOR XML PATH('')
) pre_trimmed (column_names)
GROUP BY table_name, column_names;
Or a version that works correctly if the data might contain characters such as <
WITH extern
AS (SELECT DISTINCT table_name
FROM INFORMATION_SCHEMA.COLUMNS)
SELECT table_name,
LEFT(y.column_names, LEN(y.column_names) - 1) AS column_names
FROM extern
CROSS APPLY (SELECT column_name + ','
FROM INFORMATION_SCHEMA.COLUMNS AS intern
WHERE extern.table_name = intern.table_name
FOR XML PATH(''), TYPE) x (column_names)
CROSS APPLY (SELECT x.column_names.value('.', 'NVARCHAR(MAX)')) y(column_names)
I may be a bit late to the party but this method works for me and is easier than the COALESCE method.
SELECT STUFF(
(SELECT ',' + Column_Name
FROM Table_Name
FOR XML PATH (''))
, 1, 1, '')
SQL Server 2017 does introduce a new aggregate function
STRING_AGG ( expression, separator).
Concatenates the values of string expressions and places separator
values between them. The separator is not added at the end of string.
The concatenated elements can be ordered by appending WITHIN GROUP (ORDER BY some_expression)
For versions 2005-2016 I typically use the XML method in the accepted answer.
This can fail in some circumstances however. e.g. if the data to be concatenated contains CHAR(29) you see
FOR XML could not serialize the data ... because it
contains a character (0x001D) which is not allowed in XML.
A more robust method that can deal with all characters would be to use a CLR aggregate. However applying an ordering to the concatenated elements is more difficult with this approach.
The method of assigning to a variable is not guaranteed and should be avoided in production code.
Possibly too late to be of benefit now, but is this not the easiest way to do things?
SELECT empName, projIDs = replace
((SELECT Surname AS [data()]
FROM project_members
WHERE empName = a.empName
ORDER BY empName FOR xml path('')), ' ', REQUIRED SEPERATOR)
FROM project_members a
WHERE empName IS NOT NULL
GROUP BY empName
Have a look at the GROUP_CONCAT project on Github, I think I does exactly what you are searching for:
This project contains a set of SQLCLR User-defined Aggregate functions (SQLCLR UDAs) that collectively offer similar functionality to the MySQL GROUP_CONCAT function. There are multiple functions to ensure the best performance based on the functionality required...
To concatenate all the project manager names from projects that have multiple project managers write:
SELECT a.project_id,a.project_name,Stuff((SELECT N'/ ' + first_name + ', '+last_name FROM projects_v
where a.project_id=project_id
FOR
XML PATH(''),TYPE).value('text()[1]','nvarchar(max)'),1,2,N''
) mgr_names
from projects_v a
group by a.project_id,a.project_name
With the below code you have to set PermissionLevel=External on your project properties before you deploy, and change the database to trust external code (be sure to read elsewhere about security risks and alternatives [like certificates]) by running ALTER DATABASE database_name SET TRUSTWORTHY ON.
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.SqlServer.Server;
[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined,
MaxByteSize=8000,
IsInvariantToDuplicates=true,
IsInvariantToNulls=true,
IsInvariantToOrder=true,
IsNullIfEmpty=true)]
public struct CommaDelimit : IBinarySerialize
{
[Serializable]
private class StringList : List<string>
{ }
private StringList List;
public void Init()
{
this.List = new StringList();
}
public void Accumulate(SqlString value)
{
if (!value.IsNull)
this.Add(value.Value);
}
private void Add(string value)
{
if (!this.List.Contains(value))
this.List.Add(value);
}
public void Merge(CommaDelimit group)
{
foreach (string s in group.List)
{
this.Add(s);
}
}
void IBinarySerialize.Read(BinaryReader reader)
{
IFormatter formatter = new BinaryFormatter();
this.List = (StringList)formatter.Deserialize(reader.BaseStream);
}
public SqlString Terminate()
{
if (this.List.Count == 0)
return SqlString.Null;
const string Separator = ", ";
this.List.Sort();
return new SqlString(String.Join(Separator, this.List.ToArray()));
}
void IBinarySerialize.Write(BinaryWriter writer)
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(writer.BaseStream, this.List);
}
}
I've tested this using a query that looks like:
SELECT
dbo.CommaDelimit(X.value) [delimited]
FROM
(
SELECT 'D' [value]
UNION ALL SELECT 'B' [value]
UNION ALL SELECT 'B' [value] -- intentional duplicate
UNION ALL SELECT 'A' [value]
UNION ALL SELECT 'C' [value]
) X
And yields: A, B, C, D
Tried these but for my purposes in MS SQL Server 2005 the following was most useful, which I found at xaprb
declare #result varchar(8000);
set #result = '';
select #result = #result + name + ' '
from master.dbo.systypes;
select rtrim(#result);
#Mark as you mentioned it was the space character that caused issues for me.
About J Hardiman's answer, how about:
SELECT empName, projIDs=
REPLACE(
REPLACE(
(SELECT REPLACE(projID, ' ', '-somebody-puts-microsoft-out-of-his-misery-please-') AS [data()] FROM project_members WHERE empName=a.empName FOR XML PATH('')),
' ',
' / '),
'-somebody-puts-microsoft-out-of-his-misery-please-',
' ')
FROM project_members a WHERE empName IS NOT NULL GROUP BY empName
By the way, is the use of "Surname" a typo or am i not understanding a concept here?
Anyway, thanks a lot guys cuz it saved me quite some time :)
2021
#AbdusSalamAzad's answer is the correct one.
SELECT STRING_AGG(my_col, ',') AS my_result FROM my_tbl;
If the result is too big, you may get error "STRING_AGG aggregation result exceeded the limit of 8000 bytes. Use LOB types to avoid result truncation." , which can be fixed by changing the query to this:
SELECT STRING_AGG(convert(varchar(max), my_col), ',') AS my_result FROM my_tbl;
For my fellow Googlers out there, here's a very simple plug-and-play solution that worked for me after struggling with the more complex solutions for a while:
SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ CONVERT(VARCHAR(10), projID )
FROM returns
WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM
returns t
Notice that I had to convert the ID into a VARCHAR in order to concatenate it as a string. If you don't have to do that, here's an even simpler version:
SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ projID
FROM returns
WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM
returns t
All credit for this goes to here:
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/9508abc2-46e7-4186-b57f-7f368374e084/replicating-groupconcat-function-of-mysql-in-sql-server?forum=transactsql
For SQL Server 2017+, use STRING_AGG() function
SELECT STRING_AGG(Genre, ',') AS Result
FROM Genres;
Sample result:
Result
Rock,Jazz,Country,Pop,Blues,Hip Hop,Rap,Punk
Related
I need to create a report of all our items with a URL link to the webpage that is generated for each item. The website uses the .ToUrlString extension (http://www.extensionmethod.net/1935/csharp/string/tourlstring) to generate the link address. Is there a way to get the same result in SQL? The database is SqlServer 2008R2.
This is the code for the extension:
public static string ToUrlString(this string str) {
if (String.IsNullOrEmpty(str)) return "";
// Unicode Character Handling: http://blogs.msdn.com/b/michkap/archive/2007/05/14/2629747.aspx
string stFormD = str.Trim().ToLowerInvariant().Normalize(NormalizationForm.FormD);
var sb = new StringBuilder();
foreach (char t in
from t in stFormD
let uc = CharUnicodeInfo.GetUnicodeCategory(t)
where uc != UnicodeCategory.NonSpacingMark
select t) {
sb.Append(t);
}
return Regex.Replace(sb.ToString().Normalize(NormalizationForm.FormC), "[\\W\\s]{1,}", "-").Trim('-');
}
This function turns "Cups & Saucers" into "cups-saucers" and
"Spoon-Style Long Sticks-Set of 4" into "spoonstyle-long-sticksset-of-4"
EDIT:
Because it was 2am, I was tired, and was pretty sure I wasn't going to successfully teach my self anything new at that time, I created a long-azz REPLACE() statement to filter out what I needed. That's below in a separate answer for anyone that decides they want to do it the hard way...
The correct way is to create a CLR function, as suggested by #DaleBurrell in the comments. This article explains it, step by step: SQL CLR Functions:
UPDATED ON 20190424 BASED ON OP'S COMMENTS BELOW
This is easy as pie using T-SQL. Using PatReplace8K and a function I just wrote to remove repeated instances of a specific character (RemoveDupes8K) you could do this:
DECLARE #string VARCHAR(1000) = 'Spoon-Style Long Sticks-Set of 4'
SELECT newstring = LOWER(r.NewString)
FROM dbo.patreplace8k(REPLACE(#string,'-',''),'[^a-zA-Z0-9]','-') AS f
CROSS APPLY dbo.RemoveDupes8K(f.NewString,'-') AS r;
Returns: spoonstyle-long-sticksset-of-4
Here's what's going on. PatReplace8k is taking the input string (#string) and replacing all non-alphanumeric characters with a hyphen. RemoveDupes8K takes that new string and replaces duplicate hyphens with one.
Here's the functions:
CREATE FUNCTION dbo.RemoveDupes8K
(
#string VARCHAR(8000),
#char CHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT NewString =
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(#string)),
REPLICATE(#char,33),#char),
REPLICATE(#char,17),#char),
REPLICATE(#char,9), #char),
REPLICATE(#char,5), #char),
REPLICATE(#char,3), #char),
REPLICATE(#char,2), #char),
REPLICATE(#char,2), #char);
GO
CREATE FUNCTION dbo.PatReplace8K
(
#string VARCHAR(8000),
#pattern VARCHAR(50),
#replace VARCHAR(1)
)
/*****************************************************************************************
Purpose:
Given a string (#String), a pattern (#Pattern), and a replacement character (#Replace)
PatReplace8K will replace any character in #String that matches the #Pattern parameter
with the character, #Replace.
Usage:
--===== Basic Syntax Example
SELECT pr.NewString
FROM dbo.PatReplace8K(#String,#Pattern,#Replace);
--===== Replace numeric characters with a "*"
SELECT pr.NewString
FROM dbo.PatReplace8K('My phone number is 555-2211','[0-9]','*') pr;
--==== Using againsts a table
DECLARE #table TABLE(OldString varchar(40));
INSERT #table VALUES
('Call me at 555-222-6666'),
('phone number: (312)555-2323'),
('He can be reached at 444.665.4466');
SELECT t.OldString, pr.NewString
FROM #table t
CROSS APPLY dbo.PatReplace8K(t.oldstring,'[0-9]','*') pr;
Programmer Notes:
1. Required SQL Server 2008+
2. #Pattern IS case sensitive but can be easily modified to make it case insensitive
3. There is no need to include the "%" before and/or after your pattern since since we
are evaluating each character individually
4. Certain special characters, such as "$" and "%" need to be escaped with a "/"
like so: [/$/%]
Revision History:
Rev 00 - 10/27/2014 Initial Development - Alan Burstein
Rev 01 - 10/29/2014 Mar 2007 - Alan Burstein
- Redesigned based on the dbo.STRIP_NUM_EE by Eirikur Eiriksson
(see: http://www.sqlservercentral.com/Forums/Topic1585850-391-2.aspx)
- change how the cte tally table is created
- put the include/exclude logic in a CASE statement instead of a WHERE clause
- Added Latin1_General_BIN Colation
- Add code to use the pattern as a parameter.
Rev 02 - 20141106
- Added final performane enhancement (more cudo's to Eirikur Eiriksson)
- Put 0 = PATINDEX filter logic into the WHERE clause
Rev 03 - 20150516
- Updated to deal with special XML characters
Rev 04 - 20170320
- changed #replace from char(1) to varchar(1) to address how spaces are handled
*****************************************************************************************/
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
WITH
E1(N) AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS E1(N)),
iTally(N) AS
(
SELECT TOP (LEN(#String)) CHECKSUM(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))
FROM E1 a,E1 b,E1 c,E1 d
)
SELECT NewString =
((
SELECT
CASE
WHEN PATINDEX(#Pattern,SUBSTRING(#String COLLATE Latin1_General_BIN,N,1)) = 0
THEN SUBSTRING(#String,N,1)
ELSE #replace
END
FROM iTally
FOR XML PATH(''), TYPE
).value('.[1]','varchar(8000)'));
GO
As promised, for anyone that wanted to see the REPLACE() method...
SELECT 'Product' Source
,prd.[id]
,prd.[name] as Title
,CAST(prd.[description] as varchar(max)) as [Description]
,'/' + lower(replace(replace(RTrim(par.name), ' &', ''), ' ' ,'-') + '/' + REPLACE(replace(replace(RTrim(child.name),'\',''), ' &', ''), ' ', '-') + '/' + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTrim(prd.name)),'.',''),'$',''),'/',''),')',''),'(',''),'-',''),',',''), '''',''), '"', ''), ' &', ''),' ',' '), ' ' , '-') ) as Link
,prd.[upc] as GTIN
,prd.[active]
FROM [dbo].[Products] prd
INNER join [dbo].[CategoryProducts] catprd ON catprd.productId = prd.id
INNER JOIN [dbo].[Categories] child on child.Id = catprd.categoryId
INNER join [dbo].[Categories] par on par.id = child.parentCategoryId
I have a SQL statement which - trimmed down for this question - looks like this:
SELECT *
FROM Customers
LEFT JOIN SMS_Blacklist ON Customers.MobileNumber = SMS_Blacklist.Mobile_Number
WHERE SMS_Blacklist.Mobile_Number IS NULL
This works for the most part however the database has no data continuity or consistency and when the data looks like this:
Customer.MobileNumber = "07123 456789",
SMS_Blacklist.Mobile_Number = "07123456789"
it returns the record when it shouldn't (because the phone numbers don't match).
My question is, is it possible to perform a string function (i.e. string.replace(" ", "")) in the middle of the SQL statement so it might look something like this (I think probably not):
...
ON (Customers.MobileNumber).replace(" ","") = SMS_Blacklist.Mobile_Number
...
or how could I achieve something like this?
I am creating the SQL statement using new SqlCommand() in C#.
Rectifying phone numbers is very tricky. But if you just want to remove spaces and hyphens you can do replace() in the SQL:
SELECT c.
FROM Customers c LEFT JOIN
SMS_Blacklist b
ON b.Mobile_Number = REPLACE(REPLACE(c.MobileNumber, ' ', ''), '-', '')
WHERE b.Mobile_Number IS NULL;
I am using dynamic linq to parse some conditions. I wrote stored procedure and want to filter it dynamicly.
this is my procedure:
;WITH cte
AS
(
SELECT
ID
,[NO]
,Firstname
,Lastname
,PersonalNO
,ReferanceID
,CAST('' AS VARCHAR(MAX)) AS ReferanceNO
FROM dbo.Employees WHERE ReferanceID IS NULL
UNION ALL
SELECT
c.ID
,c.[NO]
,c.Firstname
,c.Lastname
,c.PersonalNO
,c.ReferanceID
,CASE
WHEN ct.ReferanceNO = ''
THEN CAST(ct.[NO] AS VARCHAR(MAX))
ELSE CAST(ct.[NO] AS VARCHAR(MAX))
END
FROM dbo.Employees c
INNER JOIN cte ct ON ct.ID = c.ReferanceID
)
SELECT * FROM cte
and in C# I am calling this procedure;
public List<Employees> GetEmployees(string searchValue, int skip, int pageSize, string sortColumn, string sortColumnDir)
{
var query = DB.sp_GetConsultants().ToList();
var totalRecords = query.Count;
query = query.Where(searchValue).ToList(); // if the searchValue is value
//"PersonalNO.Contains(\"15\")" it filters, with this kind of value
//"Lastname.Contains(\"fish\")" it dose not, but with "Fish" it does. Is the matter with uppercase?
}
and i uploaded table picture:
What is the problem?
string.Contains is case sensitive; as you noticed, searching for "fish" won't return "Fisher", even though searching for "Fish" will. There doesn't seem to be a case insensitive version in .NET (even though you can compare strings case insensitively as an option).
As a workaround, you can convert both strings to lowercase or uppercase (ToLower / ToUpper) before comparing. This might have some issues with certain non-Latin characters, however.
I think there is also a collation option in SQL Server which lets you specify the case sensitivity for strings, if you want to do the comparison at the database level instead.
I have a SQL Server 2008 R2 datatable dbo.Forum_Posts with columns Subject (nvarchar(255)) andBody (nvarchar(max)).
I would like to get all words with length >= 3 from columns Subject and Body and insert them into datatable dbo.Search_Word (column Word, nvarchar(100)) and datatable dbo.SearchItem (column Title (nvarchar(200)).
I also want to get new generated SearchWordsID (primary key, autoincrement, int) from dbo.Search_Word, and SearchItemID (primary key, autoincrement,int) from dbo.SearchItem, and insert them into datatable dbo.SearchItemWord (columns SearchWordsID (foreign key,int, not null) and SearchItemID (foreign key,int,not null).
What is a fastest way to do this in T-SQL? Or I have to use C#? Thank you in advance for any help.
As requested, this will keep the ID's. So you will get a DISTINCT list of works BY id.
Slightly different approach than the first answer, but easily achieved via the Outer Apply
**
You must edit the initial query Select KeyID=[YourKeyID],Words=[YourField1]+' '+[YourField2] from [YourTable]
**
Declare #String varchar(max) = ''
Declare #Delimeter varchar(25) = ' '
-- Generate and Strip special characters
Declare #StripChar table (Chr varchar(10));Insert Into #StripChar values ('.'),(','),('/'),('('),(')'),(':') -- Add/Remove as needed
-- Generate Base Data and Expand via Outer Apply
Declare #XML xml
Set #XML = (
Select A.KeyID
,B.Word
From ( Select KeyID=[YourKeyID],Words=[YourField1]+' '+[YourField2] from [YourTable]) A
Outer Apply (
Select Word=split.a.value('.', 'varchar(150)')
From (Select Cast ('<x>' + Replace(A.Words, #Delimeter, '</x><x>')+ '</x>' AS XML) AS Data) AS A
Cross Apply data.nodes ('/x') AS Split(a)
) B
For XML RAW)
-- Convert XML to varchar(max) for Global Search & Replace (could be promoted to Outer Appy)
Select #String = Replace(Replace(cast(#XML as varchar(max)),Chr,' '),' ',' ') From #StripChar
Select #XML = cast(#String as XML)
Select Distinct
KeyID = t.col.value('#KeyID', 'int')
,Word = t.col.value('#Word', 'varchar(150)')
From #XML.nodes('/row') AS t (col)
Where Len(t.col.value('#Word', 'varchar(150)'))>3
Order By 1
Returns
KetID Word
0 UNDEF
0 Undefined
1 HIER
1 System
2 Control
2 UNDEF
3 JOBCONTROL
3 Market
3 Performance
...
87 Analyitics
87 Market
87 UNDEF
88 Branches
88 FDIC
88 UNDEF
...
You're going to need T-SQL to do the inserting into your tables. Your biggest challenge is going to be splitting the posts into words.
My suggestion would be to read the posts into C#, split each post into words (you can use the Split method to split on spaces or punctuation), filter the collection of words, and then execute your inserts from C#.
You can avoid using T-SQL directly if you use Entity Framework or a similar ORM.
Don't try to use T-SQL to split your posts into words unless you really want a totally SQL solution and are willing to take time to perfect it. And, yes, it will be slow: T-SQL is not fast at string operations.
You can also investigate full text indexing, which I believe has support for search keywords.
Perhaps this will help
Declare #String varchar(max) = ''
Declare #Delimeter varchar(25) = ' '
Select #String = #String + ' '+Words
From (
Select Words=[YourField1]+' '+[YourField2] from [YourTable]
) A
-- Generate and Strip special characters
Declare #StripChar table (Chr varchar(10));Insert Into #StripChar values ('.'),(','),('/'),('('),(')'),(':') -- Add/Remove as needed
Select #String = Replace(Replace(#String,Chr,' '),' ',' ') From #StripChar
-- Convert String into XML and Split Delimited String
Declare #Table Table (RowNr int Identity(1,1), String varchar(100))
Declare #XML xml = Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML)
Insert Into #Table Select String.value('.', 'varchar(max)') From #XML.nodes('x') as T(String)
-- Generate Final Resuls
Select Distinct String
From #Table
Where Len(String)>3
Order By 1
Returns (sample)
String
------------------
Access
Active
Adminstrators
Alternate
Analyitics
Applications
Branches
Cappelletti
City
Class
Code
Comments
Contact
Control
Daily
Data
Date
Definition
Deleted
Down
Email
FDIC
Variables
Weekly
I have the below SQL query using the Query Builder in Visual Studio. As you can see the same user is duplicated 3 times, this is due to the user having 3 different skills. How can I merge the 3 skills together in the SQL query or in a ListView control so that it only displays one result instead of 3 and that the user has their 3 skills listed?
SELECT users.role_id, users.username, users.first_name, users.last_name, users.description, roles.role_id, roles.role, skills.skill_id, skills.user_id, skills.skill
FROM users
INNER JOIN roles ON users.role_id = roles.role_id
INNER JOIN skills ON users.user_id = skills.user_id
WHERE (users.role_id = 3)
Use For XML Path(''), Type. It is a bit of a hack, because you're really creating an XML string without a root and fashioning odd elements, but it works well. Be sure to include the Type bit, otherwise the XML trick will attempt to convert special characters, like < and & into their XML escape sequences (here is an example).
Here is a simplified version of your problem in a SQL Fiddle. Below is the relevant Select snippet.
SELECT users.user_id, users.first_name,
STUFF(
(SELECT ', ' + skill
FROM skills
WHERE users.user_id = skills.user_id
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
, 1, 2, '') AS skill_list
FROM users
Try using Stuff and For Xml
Here's the Fiddle:
http://sqlfiddle.com/#!6/fcf71/5
See if it helps, it's just a sample so you will have to change the column names.
You can use PIVOT on the Skill then group those skills into one column.
To make it simple, I test it with some sample data like the following:
CREATE SCHEMA _Test
CREATE TABLE _Test.SkillSet(SkillId INT IDENTITY(1,1) PRIMARY KEY, SkillName NVARCHAR(64))
INSERT INTO _Test.SkillSet(SkillName) VALUES('C/C++')
INSERT INTO _Test.SkillSet(SkillName) VALUES('C#')
INSERT INTO _Test.SkillSet(SkillName) VALUES('Java')
CREATE TABLE _Test.Employees(EmpId INT IDENTITY(1,1) PRIMARY KEY, FullName NVARCHAR(256))
INSERT INTO _Test.Employees(FullName) VALUES('Philip Hatt')
INSERT INTO _Test.Employees(FullName) VALUES('John Rosh')
CREATE TABLE _Test.Employee_Skill(EmpId INT FOREIGN KEY REFERENCES _Test.Employees(EmpId), SkillId INT FOREIGN KEY REFERENCES _Test.SkillSet(SkillId))
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(1, 1)
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(1, 2)
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(1, 3)
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(2, 2)
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(2, 3)
WITH tEmpSkill
AS
(SELECT A.EmpId, A.FullName, C.SkillName
FROM _Test.SkillSet C RIGHT JOIN
(
_Test.Employees A LEFT JOIN _Test.Employee_Skill B
ON A.EmpId = B.EmpId
)
ON B.SkillId = C.SkillId
)
SELECT * FROM tEmpSkill
PIVOT(COUNT(SkillName) FOR SkillName IN([C/C++], [C#], [Java])) AS Competency
The query above gives me an intermediate result
PIVOT RESULT
Now you can easily make a string containing all the skills needed for each employee. You can also search for some articles to use the PIVOT with unknown number of columns (skill sets), which may better serve your need.
Hope this can help.