How to use wildcards in SQL SELECT Replace - sql

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

Related

MS SQL Combining 5 columns to 3 columns

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;

Convert Data in a Column to a row in SQL Server

Fairly new to SQL, so I do apologise!
Currently I have the following SQL Query:
select [data]
from Database1.dbo.tbl_Data d
join Database1.tbl_outbound o on d.session_id = o.session_id
where o.campaign_id = 1047
and d.session_id = 12
This returns ONE column which looks like this (and it can return different number of rows, depending on campaign_id and session_id!):
[data]
[1] Entry 1
[2] Entry 2
[3] Entry 3
[4] Entry 4
[5] Entry 5
.....
[98] Entry 98
[99] Entry 99
I would like to convert the data so they are displayed in 1 row and not 1 column, for example:
[data1] [data2] [data3] [data4] [data5] .... [data98] [data99]
[1] Entry 1 Entry 2 Entry 3 Entry 4 Entry 5 .... Entry 98 Entry 99
I hope I have explained that well enough! Thanks! :)
I have seen some information floating around about Pivot and Unpivot, but couldn't get it to play ball!
Try This Dynamic sql which helps your requirement
IF OBJECT_ID('tempdb..#Temp')IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp (data VARCHAR(100))
GO
IF OBJECT_ID('tempdb..#FormatedTable')IS NOT NULL
DROP TABLE #FormatedTable
Go
INSERT INTO #Temp(data)
SELECT 'Entry1' UNION ALL
SELECT 'Entry2' UNION ALL
SELECT 'Entry3' UNION ALL
SELECT 'Entry4' UNION ALL
SELECT 'Entry5'
SELECT ROW_NUMBER()OVER(ORDER BY Data) AS SeqId,
Data,
'Data'+CAST(ROW_NUMBER()OVER(ORDER BY Data) AS VARCHAR(100)) AS ReqColumn
INTO #FormatedTable
FROM #Temp
DECLARE #Sql nvarchar(max),
#DynamicColumn nvarchar(max),
#MaxDynamicColumn nvarchar(max)
SELECT #DynamicColumn = STUFF((SELECT ', '+QUOTENAME(ReqColumn)
FROM #FormatedTable FOR XML PATH ('')),1,1,'')
SELECT #MaxDynamicColumn = STUFF((SELECT ', '+'MAX('+(ReqColumn)+') AS '+QUOTENAME(CAST(ReqColumn AS VARCHAR(100)))
FROM #FormatedTable FOR XML PATH ('')),1,1,'')
SET #Sql=' SELECT ROW_NUMBER()OVER(ORDER BY (SELECT 1)) AS SeqId, '+ #MaxDynamicColumn+'
FROM
(
SELECT * FROM #FormatedTable
) AS src
PIVOT
(
MAX(Data) FOR [ReqColumn] IN ('+#DynamicColumn+')
) AS Pvt
'
EXEC (#Sql)
PRINT #Sql
Result
SeqId Data1 Data2 Data3 Data4 Data5
----------------------------------------------
1 Entry1 Entry2 Entry3 Entry4 Entry5
There is no really simple way. You can use pivot or conditional aggregation. I prefer the latter:
select max(case when left(data, 3) = '[1]' then data end) as data_001,
max(case when left(data, 3) = '[2]' then data end) as data_002,
max(case when left(data, 5) = '[100]' then data end) as data_100
from Database1.dbo.tbl_Data d join
Database1.tbl_outbound o
on d.session_id = o.session_id
where o.campaign_id = 1047 and d.session_id = 12;
Note that the columns are fixed, so you will always have 100 columns, regardless of the number of actual values in the data.
If you need a flexible number of columns, then you need dynamic pivoting, which requires constructing the query as a string and then executing the string.
The easiest way to do that is to utilize SQLCLR.
Check out the solution and explanation on An Easier Way of Transposing Query Result in SQL Server

SQL Server Loop thru rows to form Groups

I using SQL Server 2008 R2 / 2014. I wish to find a SQL query that can do the following:
Rules:
Each [Group] must have [Number] 1 to 6 to be complete group.
[Name] in each [Group] must be unique.
Each row only can use 1 time.
Table before sorting is...
Name Number Group
---- ------ -----
A 1
B 6
A 123
C 3
B 4
C 23
D 45
D 4
C 56
A 12
D 56
After sorting, result I want is below or similar....
Name Number Group
---- ------ -----
A 1 1
C 23 1
D 45 1
B 6 1
A 123 2
D 4 2
C 56 2
A 12 3
C 3 3
B 4 3
D 56 3
What I tried before is to find a subgroup that have [Number] consist of 1-6 with below concatenate method...
SELECT *
FROM [Table1] ST2
WHERE
SUBSTRING((SELECT ST1.[Number] AS [text()]
FROM [Table1] ST1
-- WHERE ST1.[Group] = ST2.[Group]
ORDER BY LEFT(ST1.[Number],1)
FOR XML PATH ('')), 1, 1000) = '123456'
Maybe you should check ROW_NUMBER function.
select Name
, Number
, ROW_NUMBER () OVER(PARTITION BY Name ORDER BY Number) as Group
from [Table1]
If you have more than 6 rows with same NAME value then it will return more groups. You can filter additional groups out since you are interested in only 6 groups with unique values of NAME column.
I'm not sure if this can be done more simply or not, but here's my go at it...
Advanced warning, this requires some means of splitting strings. Since you're not on 2016, I've included a function at the beginning of the script.
The bulk of the work is a recursive CTE that builds the Name and Number columns into comma delimited groups. We then reduce our working set to only the groups where the numbers would create 123456, split the groups and use ROW_NUMBER() OVER... to identify them, and then select based on the new data.
Demo: http://rextester.com/NEXG53500
CREATE FUNCTION [dbo].[SplitStrings]
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
CREATE TABLE #temp
(
name VARCHAR(MAX),
number INT
)
INSERT INTO #temp
VALUES
('a',1),
('b',6),
('a',123),
('c',3),
('b',4),
('c',23),
('d',45),
('d',4),
('c',56),
('a',12),
('d',56);
/*** Recursively build groups based on information from #temp ***/
WITH groupFinder AS
(
SELECT CAST(name AS VARCHAR(MAX)) AS [groupNames], CAST(number AS VARCHAR(max)) AS [groupNumbers] FROM #temp
UNION ALL
SELECT
cast(CONCAT(t.[Name],',',g.[groupNames]) as VARCHAR(MAX)),
CAST(CONCAT(CAST(t.[Number] AS VARCHAR(max)),',',CAST(g.[groupNumbers] AS VARCHAR(max))) AS VARCHAR(max))
FROM #temp t
JOIN groupFinder g
ON
g.groupNames NOT LIKE '%' + t.name+'%'
AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number/100 AS VARCHAR(10)) +'%'
AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number/10 AS VARCHAR(10)) +'%'
AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number%10 AS VARCHAR(10)) +'%'
)
/*** only get groups where the numbers form 123456 ***/
, groupPruner AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY [groupNames]) AS [rn] FROM groupFinder WHERE REPLACE([groupNumbers],',','') = '123456'
)
/*** split the name group and give it identifiers ***/
, nameIdentifier AS
(
SELECT g.*, c1.[item] AS [Name], ROW_NUMBER() OVER (PARTITION BY [rn] ORDER BY (SELECT NULL)) AS [rn1]
FROM groupPruner g
CROSS APPLY splitstrings(g.groupnames,',') c1
)
/*** split the number group and give it identifiers ***/
, numberIdentifier AS
(
SELECT g.*, c1.[item] AS [Number], ROW_NUMBER() OVER (PARTITION BY [rn], [rn1] ORDER BY (SELECT NULL)) AS [rn2]
FROM nameIdentifier g
CROSS APPLY splitstrings(g.groupNumbers,',') c1
)
SELECT [Name], [Number], [rn] AS [Group]
--,groupnames, groupNumbers /*uncomment this line to see the groups that were built*/
FROM numberIdentifier
WHERE rn1 = rn2
ORDER BY rn, rn1
DROP TABLE #temp

Trying to Sum up Cross-Tab Data in SQL

I have a table where every ID has one or more places, and each place comes with a count. Places can be repeated within IDs. It is stored in rows like so:
ID ColumnName DataValue
1 place1 ABC
1 count1 5
2 place1 BEC
2 count1 12
2 place2 CDE
2 count2 6
2 place3 BEC
2 count3 9
3 place1 BBC
3 count1 5
3 place2 BBC
3 count2 4
Ultimately, I want a table where every possible place name is its own column, and the count per place per ID is summed up, like so:
ID ABC BEC CDE BBC
1 5 0 0 0
2 0 21 6 0
3 0 0 0 9
I don't know the best way to go about this. There are around 50 different possible place names, so specifically listing them out in a query isn't ideal. I know I ultimately have to pivot the data, but I don't know if I should do it before or after I sum up the counts. And whether it's before or after, I haven't been able to figure out how to go about summing it up.
Any ideas/help would be greatly appreciated. At this point, I'm having a hard time finding where to even start. I've seen a few posts with similar problems, but nothing quite as convoluted as this.
EDIT:
Right now I'm working with this to pivot the table, but this leaves me with columns named place1, place2, .... count1, count2,...
and I don't know how to appropriately sum up the counts and make new columns with the place names.
DECLARE #cols NVARCHAR(MAX), #query NVARCHAR(MAX);
SET #cols = STUFF(
(
SELECT DISTINCT
','+QUOTENAME(c.[ColumnName])
FROM #temp c FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SET #query = 'SELECT [ID], '+#cols+'from (SELECT [ID],
[DataValue] AS [amount],
[ColumnName] AS [category]
FROM #temp
)x pivot (max(amount) for category in ('+#cols+')) p';
EXECUTE (#query);
Your table structure is pretty bad. You'll need to normalize your data before you can attempt to pivot it. Try this:
;WITH IDs AS
(
SELECT DISTINCT
id
,ColId = RIGHT(ColumnName, LEN(ColumnName) - 5)
,Place = datavalue
FROM #temp
WHERE ISNUMERIC(datavalue) = 0
)
,Counts AS
(
SELECT DISTINCT
id
,ColId = RIGHT(ColumnName, LEN(ColumnName) - 5)
,Cnt = CAST(datavalue AS INT)
FROM #temp
WHERE ISNUMERIC(datavalue) = 1
)
SELECT
piv.id
,ABC = ISNULL(piv.ABC, 0)
,BEC = ISNULL(piv.BEC, 0)
,CDE = ISNULL(piv.CDE, 0)
,BBC = ISNULL(piv.BBC, 0)
FROM (SELECT i.id, i.Place, c.Cnt FROM IDs i JOIN Counts c ON c.id = i.id AND c.ColId = i.ColId) src
PIVOT ( SUM(Cnt)
FOR Place IN ([ABC], [BEC], [CDE], [BBC])
) piv;
Doing it with dynamic SQL would yield the following:
SET #query =
';WITH IDs AS
(
SELECT DISTINCT
id
,ColId = RIGHT(ColumnName, LEN(ColumnName) - 5)
,Place = datavalue
FROM #temp
WHERE ISNUMERIC(datavalue) = 0
)
,Counts AS
(
SELECT DISTINCT
id
,ColId = RIGHT(ColumnName, LEN(ColumnName) - 5)
,Cnt = CAST(datavalue AS INT)
FROM #temp
WHERE ISNUMERIC(datavalue) = 1
)
SELECT [ID], '+#cols+'
FROM
(
SELECT i.id, i.Place, c.Cnt
FROM IDs i
JOIN Counts c ON c.id = i.id AND c.ColId = i.ColId
) src
PIVOT
(SUM(Cnt) FOR Place IN ('+#cols+')) piv;';
EXECUTE (#query);
Try this out:
SELECT id,
COALESCE(ABC, 0) AS ABC,
COALESCE(BBC, 0) AS BBC,
COALESCE(BEC, 0) AS BEC,
COALESCE(CDE, 0) AS CDE
FROM
(SELECT id,
MIN(CASE WHEN columnname LIKE 'place%' THEN datavalue END) AS col,
CAST(MIN(CASE WHEN columnname LIKE 'count%' THEN datavalue END) AS INT) AS val
FROM t
GROUP BY id, RIGHT(columnname, 1)
) src
PIVOT
(SUM(val)
FOR col in ([ABC], [BBC], [BEC], [CDE])) pvt
Tested here: http://rextester.com/XUTJ68690
In the src query, you need to re-format your data, so that you have a unique id and place in each row. From there a pivot will work.
If the count is always immediately after the place, the following query will generate a data set for pivoting.
The result data set before pivoting has the following columns:
id, placename, count
select placeTable.id, placeTable.datavalue, countTable.datavalue
from
(select *, row_number() over (order by id, %%physloc%%) as rownum
from test
where isnumeric(datavalue) = 1
) as countTable
join
(select *, row_number() over (order by id, %%physloc%%) as rownum
from test
where isnumeric(datavalue) <> 1
) as placeTable
on countTable.id = placeTable.id and
countTable.rownum = placeTable.rownum
Tested on sqlfidde mssqlserver: http://sqlfiddle.com/#!6/701c91/18
Here is one other approach using PIVOT operator with dynamic style
declare #Col varchar(2000) = '',
#Query varchar(2000) = ''
set #Col = stuff(
(select ','+QUOTENAME(DataValue)
from table where isnumeric(DataValue) = 0
group by DataValue for xml path('')),1,1,'')
set #Query = 'select id, '+#Col+' from
(
select id, DataValue,
cast((case when isnumeric(DataValue) = 1 then DataValue else lead(DataValue) over (order by id) end) as int) Value
from table
) as a
PIVOT
(
sum(Value) for DataValue in ('+#Col+')
)pvt'
EXECUTE (#Query)
Note : I have used lead() function to access next rows data if i found character string values and replace with numeric data values
Result :
id ABC BBC BEC CDE
1 5 NULL NULL NULL
2 NULL NULL 21 6
3 NULL 9 NULL NULL

Comma Separated SQL Server Result Set 'JOINED' with other Columns

I have a table say ProjectMaster:
Id ProjectName
1 A
2 B
3 C
another table ProjectMeter
Id ProjectId MeterNumber
1 1 #0001
2 1 #0002
3 1 #0003
4 2 #0004
5 2 #0005
6 3 #0006
I wish to have following output
ProjectName MeterNumbers
A #0001, #0002, #0003
B #0004, #0005
C #0006
I tried this and this, but unable to solve my problem.
I cannot use a table variable.
I have a already written Stored Procedure and it brings data from many joined tables. ProjectMaster also happens to be joined in one of these tables. Now am required to fetch data from ProjectMeter, such that, each row has concatenated ProjectMeter.MeterNumber corresponding to the ProjectId in that column.
right now, I get concatenated list of all meternumbers in all the rows.
I cannot use CURSOR, TABLE variable , Temp TABLE
( I hope still something can be done to my cause)
please help.....
Try this:
SELECT projectname, STUFF((SELECT distinct ', ' + meternumber
from projectmeter m
where p.id = m.projectid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'') MeterNumbers
from projectmaster p
See SQL Fiddle with Demo
DECLARE #ProjectMaster AS TABLE
(
ID INT IDENTITY(1, 1) ,
ProjectName VARCHAR(2)
)
DECLARE #ProjectMeter AS TABLE
(
ID INT IDENTITY(1, 1) ,
ProjectID INT ,
MeterNumber VARCHAR(50)
)
INSERT INTO #ProjectMaster
( ProjectName )
VALUES ( 'A' )
INSERT INTO #ProjectMeter
( ProjectID, MeterNumber )
VALUES ( 1, '#0001' )
INSERT INTO #ProjectMeter
( ProjectID, MeterNumber )
VALUES ( 1, '#0002' )
SELECT pMaster.ID, STUFF(( SELECT ',' + MeterNumber
FROM #ProjectMeter
FOR
XML PATH('')
), 1, 1, '') AS 'Concat Result'
FROM #ProjectMeter pMeter
INNER JOIN #ProjectMaster pMaster ON pMaster.ID = pMeter.ProjectID
GROUP BY pMaster.ID
I have used table variables here but surely you just need to drop the #'s as I have used the same table names as you have specified? Not sure if this is okay? :)
Also in MS SQL you can do it using recursive query with CTE.
Here is a SQLFiddle demo
;with t1 as (
select t.*,
cast(meternumber as varchar(max)) as m2,
0 as level
from ProjectMeter t
where not exists
(select id
from ProjectMeter l
where l.id<t.id and l.ProjectId=t.ProjectID
)
union all
select b.*,
cast(c.m2+','+b.MeterNumber as varchar(max)) as m2,
c.level+1 as level
from ProjectMeter b
inner join t1 c
on (c.id < b.id) and (b.ProjectID=c.ProjectId)
)
select pm.ProjectName as ProjectName,
t1.m2 as MeterNumbers
from t1
inner join
(select ProjectId,max(level) ml
from t1
group by ProjectId
) t2
on (t1.ProjectId=t2.ProjectID) and (t1.level=t2.ml)
left join ProjectMaster pm
on (t1.ProjectId=pm.Id)
order by t1.ProjectID