MS SQL Combining 5 columns to 3 columns - sql

I am trying to figure out how to combine 5 columns to 3 columns and avoiding duplicities at the same time.
I have been thinking about it for a few days and I can't figure out any solution so far, so I have decided to ask for help here.
Basically right now we use 5 columns in our database let's say A,B,C,D,E and somebody from our company has decided that we will be moving this information to new 3 columns let's call them NEW_1,NEW_2,NEW_3, because there are no cases when we would have more than 3 values per row in the DB.
I need to somehow extract those 5 columns and get them to just 3 columns without repeating them.
So far I was thinking about and trying using CASE, but I can't figure it out how to stop it from showing the same value in all 3 NEW columns. I was thinking about assigning some variables to determine which columns to skip in select CASE, but I have found out that I can't do it like that.
If someone could at least direct me to the right way I would really appreciate it.
SELECT CASE
WHEN A is not null THEN A
WHEN B is not null THEN B
WHEN C is not null THEN C
WHEN D is not null THEN D
WHEN E is not null THEN E
ELSE NULL
END as NEW_1,
CASE
WHEN B is not null THEN B
WHEN C is not null THEN C
WHEN D is not null THEN D
WHEN E is not null THEN E
ELSE NULL
END as NEW_2,
CASE
WHEN C is not null THEN C
WHEN D is not null THEN D
WHEN E is not null THEN E
ELSE NULL
END as NEW_3

Here is a option where we unpivot the data and then apply a conditional aggregation within a CROSS APPLY
Example
Declare #YourTable Table ([A] varchar(50),[B] varchar(50),[C] varchar(50),[D] varchar(50),[E] varchar(50))
Insert Into #YourTable Values
(1,2,3,NULL,NULL)
,(NULL,4,NULL,5,NULL)
,(NULL,null,6,7,8)
,(9,NULL,NULL,NULL,NULL)
Select A.*
,B.*
From #YourTable A
Cross Apply (
Select Val1 = max(case when ColNr=1 then Value end)
,Val2 = max(case when ColNr=2 then Value end)
,Val3 = max(case when ColNr=3 then Value end)
From (
Select ColNr = row_number() over (order by Seq)
,B1.*
From (values (1,A)
,(2,B)
,(3,C)
,(4,D)
,(5,E)
) B1(Seq,Value)
Where Value is not null
) B2
) B
Returns
Just for Fun, here is an XML version
Select A.*
,Val1 = XMLData.value('/x[1]','varchar(max)')
,Val2 = XMLData.value('/x[2]','varchar(max)')
,Val3 = XMLData.value('/x[3]','varchar(max)')
From #YourTable A
Cross Apply ( values ( convert(xml,
concat('<x>'+A+'</x>'
,'<x>'+B+'</x>'
,'<x>'+C+'</x>'
,'<x>'+D+'</x>'
,'<x>'+E+'</x>'
) ) ) ) B(XMLData)

I like John's answer and probably better than mine, but here's a slightly different version if the columns are fixed to a specific number.
Build a delimited string of the column values, then use xml to extract the 1st, 2nd, 3rd values.
Declare #YourTable Table ([A] varchar(50),[B] varchar(50),[C] varchar(50),[D] varchar(50),[E] varchar(50));
Insert Into #YourTable Values
(1,2,3,NULL,NULL),(NULL,4,NULL,5,NULL),(NULL,null,6,7,8),(9,NULL,NULL,NULL,NULL);
WITH CTE AS (
SELECT CASE WHEN a IS NULL THEN '' ELSE a + '~' END +
CASE WHEN b IS NULL THEN '' ELSE b + '~' END +
CASE WHEN c IS NULL THEN '' ELSE c + '~' END +
CASE WHEN d IS NULL THEN '' ELSE d + '~' END +
CASE WHEN e IS NULL THEN '' ELSE e + '~' END AS Combined
FROM #YourTable
)
SELECT
ISNULL(CAST(N'<x>' + REPLACE(Combined, '~', N'</x><x>') + N'</x>' AS XML).value('/x[1]', 'nvarchar(max)'), '') [new_1],
ISNULL(CAST(N'<x>' + REPLACE(Combined, '~', N'</x><x>') + N'</x>' AS XML).value('/x[2]', 'nvarchar(max)'), '') [new_2],
ISNULL(CAST(N'<x>' + REPLACE(Combined, '~', N'</x><x>') + N'</x>' AS XML).value('/x[3]', 'nvarchar(max)'), '') [new_3]
FROM CTE;

Related

SQL: Perform arithmetic operations on values in a column

I have a varchar that contains a formula:
declare #formula varchar(50) = 'X + Y + Z'
and I also have a table:
+---+---+
| A | B |
+---+---+
| X | 1 |
+---+---+
| Y | 2 |
+---+---+
| Z | 3 |
+---+---+
Column A values are unique and the formula may change. For example, if the formula is set to 'X + Y + Z' then the result would be 6. And if the formula is set to 'Z - X + Y' then the result is 4. Operations include only addition and subtraction. How can I achieve this? Having a hard time looking for where to start.
SQL Server does NOT support macro substitution, nor does it have an Eval()... this leaves Dynamic SQL
Example
Declare #YourTable Table ([A] varchar(50),[B] varchar(50))
Insert Into #YourTable Values
('X',1)
,('Y',2)
,('Z',3)
Declare #formula varchar(50) = 'X + Y + Z'
Select #formula=replace(#formula,[A],[B])
From #YourTable
Exec('Select NewValue='+#formula)
Returns
NewValue
6
Just for fun, here is a modified option which will support a TABLE
Example
Declare #YourValues Table ([A] varchar(50),[B] varchar(50))
Insert Into #YourValues Values
('X',1)
,('Y',2)
,('Z',3)
Declare #YourFormula Table (ID int,Formula varchar(50))
Insert Into #YourFormula Values
(1,'X + Y + Z'),
(2,'X - Y + Z')
Declare #SQL varchar(max) = stuff((Select concat(',(',ID,',',Formula,')') From #YourFormula For XML Path ('')),1,1,'')
Select #SQL=replace(#SQL,[A],[B])
From #YourValues
Create Table #TempResults (ID int,Calc money)
Exec('Insert Into #TempResults Select * from (values '+#SQL+')A(ID,Calc)')
Select * from #TempResults
Returns
ID Calc
1 6.00
2 2.00
This is rather crude and only works for +/- operands, however, I believe it satisfies the question.
DECLARE #Formulas TABLE (Formula NVARCHAR(MAX))
INSERT INTO #Formulas SELECT 'Z-X+Y'
DECLARE #Values TABLE(Name NVARCHAR(50), Value DECIMAL(18,2))
INSERT #Values VALUES ('X',1),('Y',2),('Z',3)
;WITH MySplitFormula AS
(
SELECT Value = SUBSTRING(Formula,Number,1) FROM #Formulas
CROSS APPLY (SELECT DISTINCT number FROM master..spt_values WHERE number > 0 AND number <= LEN(Formula))V
)
,NormalizedFormula AS
(
SELECT
DerivedOperations = CASE WHEN F.Value IN('+','-') THEN F.Value ELSE NULL END,
IsOperator = CASE WHEN F.Value IN('+','-') THEN 1 ELSE 0 END,
DerivedValues = CASE WHEN F.Value IN('+','-') THEN NULL ELSE V.Value END
FROM
MySplitFormula F
LEFT OUTER JOIN #Values V ON V.Name = F.Value
WHERE
NOT F.Value IS NULL
),
ValidatedFormula AS
(
SELECT DerivedOperations,DerivedValues FROM NormalizedFormula WHERE NOT((DerivedOperations IS NULL) AND (DerivedValues IS NULL))
),
Operators AS
(
SELECT
OrderIndex=ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
Operator=DerivedOperations FROM ValidatedFormula WHERE NOT DerivedOperations IS NULL
),
Operands AS
(
SELECT
OrderIndex=ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
Operand=DerivedValues FROM ValidatedFormula WHERE NOT DerivedValues IS NULL
)
,Marked AS
(
SELECT
OP.OrderIndex,
DoOperation = CASE WHEN OP.OrderIndex % 2 = 1 THEN 1 ELSE 0 END,
Operand1 = Operand,
Operator,
Operand2 = LEAD(Operand) OVER(ORDER BY OP.OrderIndex)
FROM
Operands OP
LEFT OUTER JOIN Operators OPR ON OPR.OrderIndex = OP.OrderIndex
)
,MarkedAgain AS
(
SELECT
*,
CalculatedValue = CASE WHEN DoOperation = 1 THEN
CASE
WHEN Operator = '+' THEN Operand1 + Operand2
WHEN Operator = '-' THEN Operand1 - Operand2
WHEN Operator IS NULL THEN
CASE WHEN LAG(Operator) OVER(ORDER BY OrderIndex) ='+' THEN Operand1 ELSE -Operand1 END
ELSE NULL
END
END
FROM
Marked
)
SELECT SUM(CalculatedValue) FROM MarkedAgain

How to use wildcards in SQL SELECT Replace

I have a field called Event, it contains data like this:
View:
ID Event
1 2015-T4-C
2 2015-G4-C
3 2015-T4-C; 2015-R4-C; 2015-Z4-C
4 2018-T4-C
5 2015-T4-Z
With an SQL SELECT (MSSQL) how can I use wildcards to replace the wildcard portion of a string?
e.g. this is what I am trying to do though does not work. The first occurrence of the replace is to cater for values that have only one instance, the second replace it to cater for values that have multiple instances with a '; ' in them:
SELECT
REPLACE(REPLACE(view.Event, '_____T___', ''), '_____T___; ', '')
FROM view
The result I am looking for is:
View Result
ID Event
1
2 2015-G4-C
3 2015-R4-C; 2015-Z4-C
4
5
Thank you
Edit: there are multiple instances of 'T's e.g. 2017-T4-C, 2018-T4-C , 2019-T4-C , 2015-T4-Z so can't use replace on '2015-T4-C'
Edit 2:
p.s. it starts like this
Original Table
ID Event
1 2015-T4-C
2 2015-G4-C
3 2015-T4-C
3 2015-R4-C
3 2015-Z4-C
etc
Then I use a stuff XML query to get them on the same line e.g.
3 2015-T4-C; 2015-R4-C; 2015-Z4-C
Edit 3 - Now that I know wildcards are not possible in SELECT replace it changes the question. The result I am looking for is:
ID EventFilter1 EventFilter2
1 2015-T4-C
2 2015-G4-C
3 2015-R4-C; 2015-Z4-C 2015-T4-C
4 2018-T4-C
5 2015-T4-Z
I can go back to pulling the data from the original table in a different way:
Original Table
ID Event
1 2015-T4-C
2 2015-G4-C
3 2015-T4-C
3 2015-R4-C
3 2015-Z4-C
etc
Edit 4: Here is the new question knowing wildcards are not possible:
Hi
I have a table for Customers and I have a Table for Events. There are multiple Events per customer. I need to export into semicolon delimited single line for an external system import splitting the results into two columns depending on a filter.
Events Table:
ID Event
1 2015-T4-C
2 2015-G4-C
3 2015-T4-C
3 2015-R4-C
3 2015-Z4-C
4 2018-T4-C
4 2018-T4-W
4 2018-K4-I
4 2018-Z4-W
5 2015-T4-Z
Desired Result:
ID EventFilter1(T) EventFilter2(notT)
1 2015-T4-C
2 2015-G4-C
3 2015-T4-C 2015-R4-C; 2015-Z4-C
4 2018-T4-C; 2018-T4-W 2018-K4-I; 2018-Z4-W
5 2015-T4-Z
At the moment I use two separate joins though want to only use one to simply and speed it up. Here is what I do at the moment, how can I simplify this? Goal 1 is to make it faster, Goal 2 is to make it more simple but happy to favor speed over simplicity.
SELECT c.ID, e.EventFilter1(T), e2.EventFilter2(notT)
FROM Customers c
LEFT JOIN (SELECT
c.ID,
EventFilter1(T) = STUFF((
SELECT distinct '; ' + e.Event
FROM Events e
WHERE c.ID = e.ID AND Event like '%-T%'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM Customers c WHERE c.ID in (SELECT ID FROM Events)) as e
on c.ID = e.ID
LEFT JOIN (SELECT
c.ID,
EventFilter2(notT) = STUFF((
SELECT distinct '; ' + e2.Event
FROM Events e2
WHERE c.ID = e2.ID AND Event not like '%-T%'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM Customers c WHERE c.ID in (SELECT ID FROM Events)) as e2
on c.ID = e2.ID
So is there a way I can pull all data without filter in the join sub-query, then filter and split it in the main query or can I pull two sets of data with two filters in one sub query? e.g.:
LEFT JOIN (SELECT
c.ID,
EventFilter1(T) = STUFF((
SELECT distinct '; ' + e.Event
FROM Events e
WHERE c.ID = e.ID AND Event like '%-T%'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
EventFilter2(notT) = STUFF(( **** What do I put here? ****
FROM Customers c WHERE c.ID in (SELECT ID FROM Events)) as e
on c.ID = e.ID
With the help of a CROSS APPLY and a little XML as as splitter
Example
Declare #YourTable table (ID int, [Event] varchar(500))
Insert Into #YourTable values
(1, '2015-T4-C'),
(2, '2015-G4-C'),
(3, '2015-T4-C; 2015-R4-C; 2015-Z4-C'),
(4, '2018-T4-C'),
(5, '2015-T4-Z')
Declare #Pattern varchar(100) = '_____T___'
Select A.ID
,NewVal =IsNull(B.S,'')
From #YourTable A
Cross Apply (
Select S = Stuff((Select '; ' +RetVal
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(A.[Event],';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B1
Where PatIndex(#Pattern,RetVal)=0
Order by RetSeq
For XML Path ('')),1,2,'')
) B
Returns
ID NewVal
1
2 2015-G4-C
3 2015-R4-C; 2015-Z4-C
4
5
You have fixed length strings. This does give you some options. Here is one rather painful one:
select (case when substr(t.event, 6, 1) = 'T'
then stuff(t.event, 1, 11, '')
when substr(t.event, 17, 1) = 'T'
then stuff(t.event, 12, 11, '')
when substr(t.event, 28, 1) = 'T'
then stuff(t.event, 23, 11, '')
. . .
end)
This does have the downside of removing only one.
I think the original problem was incomplete...
the data we are seeing is a result of a stuff over xml therefore the original table should be cleaned first in order to get the right result.
please find the solution below against tbevents as your original events table.
create table #tbpattern(
myPat varchar(10)
)
insert into #tbpattern
values ('T1'),('T2'),('T3'),('T4-C'),('T4-Z')
select * from #tbpattern
create table #tbevents
(
eventid varchar(1000)
)
insert into #tbevents
values ('2015-T4-C'),('2015-G4-C'),('2015-Z4-C'),('2018-T4-C'),('2015-T4-Z')
select e.eventid
from #tbevents e
where not exists
(
select 1 from #tbpattern p where charindex(p.myPat,e.eventid) > 0
)
drop table #tbpattern
drop table #tbevents
apply STUFF from the result above
I added another answer because the EDITS a departure from the original question
Declare #YourTable table (ID int, [Event] varchar(50))
Insert Into #YourTable values
(1 ,'2015-T4-C'),
(2 ,'2015-G4-C'),
(3 ,'2015-T4-C'),
(3 ,'2015-R4-C'),
(3 ,'2015-Z4-C'),
(4 ,'2018-T4-C'),
(4 ,'2018-T4-W'),
(4 ,'2018-K4-I'),
(4 ,'2018-Z4-W'),
(5 ,'2015-T4-Z')
Declare #Pattern varchar(100) = '_____T___'
Select A.ID
,NewCol1 = IsNull(B.S,'')
,NewCol2 = IsNull(C.S,'')
From (Select Distinct ID from #YourTable) A
Cross Apply (
Select S = Stuff((Select Distinct '; ' +[Event]
From #YourTable
Where PatIndex(#Pattern,[Event])=0
and ID = A.ID
For XML Path ('')),1,2,'')
) B
Cross Apply (
Select S = Stuff((Select Distinct '; ' +[Event]
From #YourTable
Where PatIndex(#Pattern,[Event])>0
and ID = A.ID
For XML Path ('')),1,2,'')
) C
Returns
ID NewCol1 NewCol2
1 2015-T4-C
2 2015-G4-C
3 2015-R4-C; 2015-Z4-C 2015-T4-C
4 2018-K4-I; 2018-Z4-W 2018-T4-C; 2018-T4-W
5 2015-T4-Z

Updating database column with string built based on value of another column

I have a table with a column called Days. The Days column stores a comma delimited string representing days of the week. For example the value 1,2 would represent Sunday, Monday. Instead of storing this information as a comma delimited string, I want to convert it to JSON and store it in a column called Frequency in the same table. For example, a record with the Days value of 1,2 should be updated to store the following in it's Frequency column:
'{"weekly":"interval":1,"Sunday":true,"Monday":true,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}'
I found a way to do this using a case statement assuming that there is only one digit in the Days column like so:
UPDATE SCH_ITM
SET
FREQUENCY =
CASE
WHEN SCH_ITM.DAYS = 1 THEN '{"weekly":{"interval":1,"Sunday":true,"Monday":false,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 2 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":true,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 3 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":true,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 4 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":false,"Wednesday":true,"Thursday":false,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 5 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":false,"Wednesday":false,"Thursday":true,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 6 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":true,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 7 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":true}}'
END
WHERE SCH_TYPE = 'W';
However I cannot seem to figure out an effecient way to handle converting a value such as 1,5 into the correct JSON representation. Obviously I could write out every possible permutation, but surely is a better way?
Okay this will give you what you have asked for
create table test (days varchar(20), frequency varchar(500))
insert into test(days) values('1'),('2'),('3'),('4'),('5'),('6'),('7'),('1,5')
update test set frequency = '{"weekly":{"interval":1,'
+ '"Sunday": ' + case when days like '%1%' then 'true' else 'false' end + ','
+ '"Monday": ' + case when days like '%2%' then 'true' else 'false' end + ','
+ '"Tuesday": ' + case when days like '%3%' then 'true' else 'false' end + ','
+ '"Wednesday": ' + case when days like '%4%' then 'true' else 'false' end + ','
+ '"Thursday": ' + case when days like '%5%' then 'true' else 'false' end + ','
+ '"Friday": ' + case when days like '%6%' then 'true' else 'false' end + ','
+ '"Saturday": ' + case when days like '%7%' then 'true' else 'false' end + '}}'
select * from test
Though of course e.g. Days = '1234' will produce the same as '1,2,3,4' - as will 'Bl4arg3le12' for that matter. If Days is a string, you can put '8' which is meaningless?
Really it sounds like you need an extra table or two:
If "MyTable" is the table with the Days column, add a Days table with the days of the week, then a MyTableDays table to link MyTable entries to days - for the 1,5 example, there would be two rows in MyTableDays
With the help of a parse function and an cross apply
;with cteDays As (Select ID,Name From (Values(1,'Sunday'),(2,'Monday'),(3,'Tuesday'),(4,'Wednesday'),(5,'Thursday'),(6,'Friday'),(7,'Saturday')) D(ID,Name))
Update YourTable Set Frequency = '{"weekly":"interval":1,'+String+'}}'
From YourTable A
Cross Apply (
Select String = Stuff((Select ','+String
From (
Select String='"'+Name+'":'+case when RetVal is null then 'false' else 'true' end
From [dbo].[udf-Str-Parse](A.Days,',') A
Right Join cteDays B on RetVal=ID) N
For XML Path ('')),1,1,'')
) B
Select * from YourTable
Updated Table
Days Frequency
1,2 {"weekly":"interval":1,"Sunday":true,"Monday":true,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}
1,2,3 {"weekly":"interval":1,"Sunday":true,"Monday":true,"Tuesday":true,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}
The UDF if needed
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')

Concatenate first name, last name and middle name with comma

I want to concatenate 3 columns in SQL server as below:
MAX(LTRIM(RTRIM((ISNULL(LastName,'') +
', ' +
ISNULL(FirstName,'') +
', ' +
ISNULL(MiddleName,''))))) AS FullName
I have used value of this column in SELECT clause as:
MAX(FullName) AS FullName,
I would like to handle NULL values, in case all 3 last name, middle name and first name are BLANK or NULL. The query used above will show " , , " in case all 3 columns are NULL or BLANK. But I want to show "N/A" in such case.
Thanks in advance.
You could use a CASE expression:
SELECT MAX(CASE WHEN ISNULL(FirstName, '') = '' AND
ISNULL(MiddleName, '') = '' AND
ISNULL(LastName, '') = ''
THEN 'N/A'
ELSE LTRIM(RTRIM((ISNULL(LastName,'') + ', ' +
ISNULL(FirstName,'') + ', ' +
ISNULL(MiddleName,''))))
END) AS FullName
FROM yourTable
...
Check with COALESCE and then CASE Statement:
Declare #FirstName VARCHAR(50)='',#MiddleName VARCHAR(50),#LastName VARCHAR(50)
SELECT
CASE WHEN ISNULL(COALESCE(#FirstName,#MiddleName,#LastName),'')<>''
THEN ISNULL(#FirstName,'')+',' +ISNULL(#MiddleName,'')+','+ISNULL(#LastName,'')
ELSE 'N/A' END AS FullName
Use Concat like below this will do implicit conversion. So no need to use ISNULL.
select isnull(MAX(LTRIM(RTRIM((concat(LastName,
', ' ,
FirstName,
', ' ,
MiddleName,''))))) ,'n/a')AS FullName from table
The below method may seem quite complicated, but it does make adding or removing columns much simpler, and for all its perceived complexity it isn't actually doing that much under the hood, so doesn't add much overhead.
The first step is to unpivot each of your columns to rows with a common column name, so you would turn
FirstName MiddleName LastName
------------------------------------
A NULL C
Into
Name
------
A
NULL
C
Using CROSS APPLY along with the table value constructor VALUES
SELECT x.Name
FROM (VALUES ('A', NULL,'C')) AS t (FirstName, MiddleName, LastName)
CROSS APPLY (VALUES (1, t.FirstName), (2, t.MiddleName), (3, t.LastName)) x (SortOrder, Name)
ORDER BY x.SortOrder
Then you can remove NULLs and blanks with WHERE ISNULL(Name, '') <> '', then you only have valid data to concatenate together which you can do using SQL Server's XML Extensions. So you end up with a full query like:
WITH TestData AS
( SELECT *
FROM (VALUES ('A'), (NULL)) AS f (FirstName)
CROSS JOIN (VALUES ('B'), (NULL)) AS m (MiddleName)
CROSS JOIN (VALUES ('C'), (NULL)) AS l (LastName)
)
SELECT t.*,
NamesConcat = ISNULL(STUFF(NamesConcat.value('.', 'NVARCHAR(MAX)'), 1, 2, ''), 'N/A')
FROM TestData AS t
CROSS APPLY
( SELECT ', ' + x.Name
FROM (VALUES
(1, t.FirstName),
(2, t.MiddleName),
(3, t.LastName)
) x (SortOrder, Name)
WHERE ISNULL(x.Name, '') <> '' -- NOT BLANK OR NULL
ORDER BY x.SortOrder
FOR XML PATH(''), TYPE
) x (NamesConcat);
Result
FirstName MiddleName LastName NamesConcat
-------------------------------------------------
A B C A, B, C
A NULL C A, C
A B NULL A, B
A NULL NULL A
NULL B C B, C
NULL NULL C C
NULL B NULL B
NULL NULL NULL N/A
select Isnull(FirstName,' ') +' '+ ','+ Isnull(MiddleName,' ')+' '+ ' ,'+ Isnull(Lastname,' ') as Name from personaldata
select FirstName +' '+','+ MiddleName +' '+',' + Lastname as Name from personaldata
Note: The Second query will work fine if all value present and if anyone is null then it will return null for Name, to avoid such kind of concern please use the first query.

Coalesce and Pivot in TSQL

I am having trouble figuring out how to coalesce or pivot on a SQL recordset that looks like this:
ID VALUE GROUP
3 John 18
4 Smith 18
5 Microsoft 18
3 Randy 21
4 Davis 21
5 IBM 21
etc
and I want formatted like this
NEWVALUE GROUP
Smith, John (Microsft) 18
Davis, Randy (IBM) 21
thanks for any suggestions and help!
This is what i done, i hope it fits for you
DECLARE #t table (id int, value VARCHAR(20), grupo int)
INSERT #T VALUES (3, 'John', 18)
INSERT #T VALUES (4, 'Smith', 18)
INSERT #T VALUES (5, 'Microsoft', 18)
INSERT #T VALUES (3, 'Randy', 21)
INSERT #T VALUES (4, 'Davis', 21)
INSERT #T VALUES (5, 'IBM', 21)
SELECT grupo, (SELECT value FROM #t t2 WHERE t2.grupo = t.grupo AND id = 4) + ', ' +
(SELECT value FROM #t t2 WHERE t2.grupo = t.grupo AND id = 3) + ' (' +
(SELECT value FROM #t t2 WHERE t2.grupo = t.grupo AND id = 5) + ')'
FROM #t t
GROUP BY grupo
SELECT LEFT(gvalue, LEN(gvalue) - 1) AS newvalue, _group
FROM (
SELECT DISTINCT _group
FROM mytable
) qo
CROSS APPLY
(
SELECT value + ', '
FROM mytable qi
WHERE qi._group = qo._group
FOR XML PATH ('')
) gr(qvalue)
If you always have a set of three hardcoded ID's for each _group, you can just use:
SELECT m3._group, m3.value + ', ' + m4.value + '(' + m5.value + ')' AS newvalue
FROM mytable m3
LEFT JOIN
mytable m4
ON m4._group = m3.group
LEFT JOIN
mytable m5
ON m5._group = m3.group
WHERE m3.id = 3
AND m4.id = 4
AND m5.id = 5
What you need is not pivoted query but a simple select with group by and an aggregate string concatenation function. But i don't remember the exact function in tsql.
Update: there is no aggregate concatenation function in tsql but since sql2005 you can write your own extension to implement such function. There is plenty of examples on google search for: tsql 2005 concatenation aggregate example.
This is a little hokey, but I think it should work reasonably well for a small data set. If you've got a lot of data you need to create a cursor and a loop.
select max(case when ID = 4 then VALUE else null end) + ', ' +
max(case when ID = 4 then VALUE else null end) + '( ' +
max(case when ID = 5 then VALUE else null end) + ') as NEWVALUE,
[GROUP]
group by [GROUP]