Sql Developer - Can you use a case statement in a cursor to return multiple values - sql

I've been working through a task of trying to classify several million rows of data into a variety of different topics. The data involves calls from our customer support, and we're trying to find a way to classify each call into one of 109 topics. Due to the confidentiality of the data I can't disclose any of the actual data, but will try to give a relatable subset of data that other people could compare to.
DATA:
Incident_Number | Call_Description
000123456 | Issue with oranges and apples
000987654 | oranges
004567891 | with apples and kiwis
026589741 | Issue with kiwis
SQL:
select
Incident_Number,
Call_Description,
(case
when call_description like '%oranges%' then oranges
when call_description like '%apples%' then apples
when call_descritpion like '%kiwis%' then 'kiwis'
else 'Unclassified' end) Topic
from DATA
Question
My hope would be to have Incident 000123456 classified as both oranges and apples and Incident 004567891 get classified as apples and kiwis
Desired Output
Incident_Number | Call_Description ......................| Topic
000123456 ........ | Issue with oranges and apples | oranges
000123456 ........ | Issue with oranges and apples | apples
000987654 ........ | oranges ...................................| oranges
004567891 .........| with apples and kiwis............... | apples
004567891 .........| with apples and kiwis............... | kiwis
026589741 .........| Issue with kiwis........................ | kiwis
Wrapup
From my limited knowledge and what I've garnered from research a simple case statement can't do this because it short circuits after finding the first true value. My question is whether or not it is possible to make some alterations to my code OR instead to somehow set up a cursor to run through my initial table and give me the desired output noted above.
I appreciate any help or advice and hope that I've adhered to the rules of this website (which has honestly saved my butt before!)
Regards,
Richard

I use Microsoft SQL Server instead of Oracle, so I'm not sure about the Oracle syntax, but one solution I have used in the past is to create a temporary table:
CREATE GLOBAL TEMPORARY TABLE my_temp_table (
groupName varchar(50)
) ON COMMIT DELETE ROWS;
Insert Into my_temp_table (groupName) VALUES('oranges')
Insert Into my_temp_table (groupName) VALUES('apples')
Insert Into my_temp_table (groupName) VALUES('kiwis')
then I would inner join to the table to duplicate the records:
select
Incident_Number,
Call_Description,
my_temp_table.groupName Topic
from DATA
inner join my_temp_table
on Data.call_description like '%' + my_temp_table.groupName + '%'
One problem with this method is that if a record doesn't fall into any categories, it will be excluded completely.

One option would be to create (physically or virtually) a table of keywords and use that to join to your table of data. Something like
WITH keywords AS (
SELECT 'apples' topic FROM dual UNION
SELECT 'oranges' FROM dual UNION
SELECT 'kiwis' FROM dual
)
SELECT data.incident_number,
data.incident_description,
keywords.topic
FROM data
JOIN keywords
ON( data.incident_description LIKE '%' || keywords.topic || '%' )
This will work but it's not the most efficient or flexible approach in the world. It doesn't handle different forms of the word well (if a description references the singular "apple" for example). It doesn't handle words that appear within other words (if a description talks about "crabapples" for example). And it's doing a relatively slow match based on scanning through the entire incident_description.
An alternative approach would be to use Oracle Text to index your data. That's likely to be a more complex solution but it would be much more flexible and should be more efficient.

My exaple is for MS SQL Server: http://www.sqlfiddle.com/#!3/8e904/2
You could create a table with details and a simple cross join with it
create table data(
Incident_Number varchar(9),
Call_Description varchar(50))
create table detail(detail varchar(20))
insert into data select
'000123456','Issue with oranges and apples'
union select
'000987654', 'oranges '
union select
'004567891', 'with apples and kiwis'
union select
'026589741', 'Issue with kiwis'
insert into detail select 'kiwis'
union select 'oranges'
union select 'apples'
select * from data a
cross join detail b
where a.call_description like '%'||b.detail||'%'
order by a.Incident_Number
Add a new version http://www.sqlfiddle.com/#!3/b3378/1
It manages all incidents identifying those without coincidences with null
select * from data a
left join detail b
on a.call_description like '%'||b.detail||'%'

I have gone for the CURSOR option to loop through the records in the table and update each row with the category information.
Because you mentioned your rules are quite complicated I think each one might need to be hand written in this procedure and not stored in a table.
Sorry this is MSSQL so might need converting a little for Oracle.
/*BEGIN SETUP TEMP DATA*/
CREATE TABLE #tmp1 (
idno int,
title nvarchar(100),
Categories nvarchar(100) --I have added this row to my temp data, you could store this in another table if needed.
)
INSERT INTO #tmp1 (idno, title) VALUES (1, 'problem apples and oranges')
INSERT INTO #tmp1 (idno, title) VALUES (2, 'problem with apples')
INSERT INTO #tmp1 (idno, title) VALUES (3, 'problem with oranges')
INSERT INTO #tmp1 (idno, title) VALUES (4, 'problem with kiwis')
INSERT INTO #tmp1 (idno, title) VALUES (5, 'problem with something')
/*END SETUP TEMP DATA*/
/*SETUP VARIABLES TO USE IN CURSOR*/
DECLARE #idno int,
#title nvarchar(100)
/*DECLARE CURSOR, OPEND IT AND FETCH DATA INTO INTO IT*/
DECLARE incident_cursor CURSOR FOR
SELECT idno, title
FROM #tmp1 --You could add WHERE Categories IS NULL to only update records that have not been processed
OPEN incident_cursor
FETCH NEXT FROM incident_cursor
INTO #idno, #title
/*LOOP THROUGH CURSOR*/
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #allCategories nvarchar(100)
SET #allCategories = ''
/*WRITE RULES HERE TO WORK OUT WHETHER CATEGORY NEEDS ADDING*/
IF (#title LIKE '%apples%') BEGIN SET #allCategories = #allCategories + 'Apples ' END
IF (#title LIKE '%oranges%') BEGIN SET #allCategories = #allCategories + 'Oranges ' END
IF (#title LIKE '%kiwis%') BEGIN SET #allCategories = #allCategories + 'Kiwis ' END
IF #allCategories = '' BEGIN SET #allCategories = 'Uncategorised' END
/*UPDATE ORIGINAL TABLE WITH CATEGORY INFORMATION*/
UPDATE #tmp1 SET Categories = #allCategories WHERE idno = #idno
FETCH NEXT FROM incident_cursor
INTO #idno, #title
END
CLOSE incident_cursor;
DEALLOCATE incident_cursor;
/*THIS ARE JUST TO DISPLAY OUTPUT AND CLEAR UP TEST DATA*/
SELECT * FROM #tmp1
DROP TABLE #tmp1
You could set this to run in a scheduled job to update your records as frequently as you see fit.
Might not be a perfect solution for you but hopefully a starting point

Sorry for how long it's taken me to come back to this. I got pulled into some more pressing deliverables. I ended up figuring out how to get it working by storing the queries in one table as records, then running them using an execute immediate command. I've included the code below in case someone can use it in the future.
create table text_kw_search_queries
(
incident_number varchar2(15),
description varchar2(100),
topic_level_1 varchar2(89),
topic_level_2 varchar2(89)
);
DECLARE
V_SQL VARCHAR2(1000);
CURSOR UPTO IS
SELECT TOPIC_level_1, TOPIC_level_2, description FROM tcstopic
;
BEGIN
FOR i IN UPTO LOOP
V_SQL := 'INSERT /*+APPEND PARALLEL(TEXT_KW_SEARCH_LOOP,2)*/ INTO text_kw_search_queries
SELECT
t2.Incident_Number,
t2.description,
t2.TOPIC_level_1,
t2.TOPIC_level_2
FROM (
SELECT
t1.Incident_number,
t1.description,
'''||i.TOPIC_level_1||''' AS TOPIC_level_1,
'''||i.TOPIC_level_2||''' AS TOPIC_level_2,
(CASE WHEN '|| i.description ||' THEN 1 ELSE 0 END) as FLAG
FROM cso_text_query t1
) t2
WHERE t2.FLAG = 1';
EXECUTE IMMEDIATE V_SQL;
DBMS_OUTPUT.PUT_LINE(V_SQL);
COMMIT WORK;
END LOOP;
END;

Related

SQL: Delete Rows from Dynamic list of tables where ID is null

I'm a SQL novice, and usually figure things out via Google and SO, but I can't wrap my head around the SQL required for this.
My question is similar to Delete sql rows where IDs do not have a match from another table, but in my case I have a middle table that I have to query, so here's the scenario:
We have this INSTANCES table that basically lists all the occurrences of files sent to the database, but have to join with CROSS_REF so our reporting application knows which table to query for the report, and we just have orphaned INSTANCES rows I want to clean out. Each DETAIL table contains different fields from the other ones.
I want to delete all single records from INSTANCES if there are no records for that Instance ID in any DETAIL table. The DETAIL table got regularly cleaned of old files, but the Instance record wasn't cleaned up, so we have a lot of INSTANCE records that don't have any associated DETAIL data. The thing is, I have to select the Table Name from CROSS_REF to know which DETAIL_X table to look up the Instance ID.
In the below example then, since DETAIL_1 doesn't have a record with Instance ID = 1001, I want to delete the 1001 record from INSTANCES.
INSTANCES
Instance ID
Detail ID
1000
123
1001
123
1002
234
CROSS_REF
Detail ID
Table Name
123
DETAIL_1
124
DETAIL_2
125
DETAIL_3
DETAIL_1
Instance ID
1000
1000
2999
Storing table names or column names in a database is almost always a sign for a bad database design. You may want to change this and thus get rid of this problem.
However, when knowing the possible table names, the task is not too difficult.
delete from instances i
where not exists
(
select null
from cross_ref cr
left join detail_1 d1 on d1.instance_id = i.instance_id and cr.table_name = 'DETAIL_1'
left join detail_2 d2 on d2.instance_id = i.instance_id and cr.table_name = 'DETAIL_2'
left join detail_3 d3 on d3.instance_id = i.instance_id and cr.table_name = 'DETAIL_3'
where cr.detail_id = i.detail_id
and
(
d1.instance_id is not null or
d2.instance_id is not null or
d3.instance_id is not null
)
);
(You can replace is not null by = i.instance_id, if you find that more readable. In that case you could even remove these criteria from the ON clauses.)
Much thanks to #DougCoats, this is what I ended up with.
So here's what I ended up with (#Doug, if you want to update your answer, I'll mark yours correct).
DECLARE #Count INT, #Sql VARCHAR(MAX), #Max INT;
SET #Count = (SELECT MIN(DetailID) FROM CROSS_REF)
SET #Max = (SELECT MAX(DetailID) FROM CROSS_REF)
WHILE #Count <= #Max
BEGIN
IF (select count(*) from CROSS_REF where file_id = #count) <> 0
BEGIN
SET #sql ='DELETE i
FROM Instances i
WHERE NOT EXISTS
(
SELECT InstanceID
FROM '+(SELECT TableName FROM Cross_Ref WHERE DetailID=#Count)+' d
WHERE d.InstanceId=i.InstanceID
AND i.detailID ='+ cast(#Count as varchar) +'
)
AND i.detailID ='+ cast(#Count as varchar)
EXEC(#sql);
SET #Count=#Count+1
END
END
this answer assumes you have sequential data in the CROSS_REF table. If you do not, you'll need to alter this to account it (as it will bomb due to missing object reference).
However, this should give you an idea. It also could probably be written to do a more set based approach, but my answer is to demonstrate dynamic sql use. Be careful when using dynamic SQL though.
DECLARE #Count INT, #Sql VARCHAR(MAX), #Max INT;
SET #Count = (SELECT MIN(DetailID) FROM CROSS_REF)
SET #Max = (SELECT MAX(DetailID) FROM CROSS_REF)
WHILE #Count <= #Max
BEGIN
IF (select count(*) from CROSS_REF where file_id = #count) <> 0
BEGIN
SET #sql ='DELETE i
FROM Instances i
WHERE NOT EXISTS
(
SELECT InstanceID
FROM '+(SELECT TableName FROM Cross_Ref WHERE DetailID=#Count)+' d
WHERE d.InstanceId=i.InstanceID
AND i.detailID ='+ cast(#Count as varchar) +'
)
AND i.detailID ='+ cast(#Count as varchar)
EXEC(#sql);
SET #Count=#Count+1
END
END

stuck in trying to use uiee in mssql

I'm not new to programming but new to mssql. I have searched online for help with this but am getting nowhere.
I have a book inventory file in UIEE format.
UR|2706
AA|Parker, William Harwar
TI|RECOLLECTIONS OF A NAVAL OFFICER, 1841-1865:
XD|S
UR|15184
AA|Goodrich, Norma Lorre
TI|King Arthur
and so on. As you see each line is tagged and each record starts with a 'UR' tag and ends with a 'XD' tag. I've uploaded this to a mssql table called testuiee with columns tag and data. I'm trying to use sql to identify the fields and put them into another table called btdata. For example, put UR data into Book id, AA into author, etc.
Book_id Author Title
2706 Parker, William Harwar RECOLLECTIONS OF A NAVAL OFFICER, 1841-1865:
15184 Goodrich, Norma Lorre King Arthur
The script I wrote works but only puts the last record into the btdata table. I've tried conditional processing, (while, if, case) and also a cursor but the result is always one record, the last one. King Arthur in this example. I'm sure its because I am thinking of things in a row by row way and not in blocks of data but I can't get my head around this.
Here's my code so far.
DECLARE #bookid nvarchar(max),
#author nvarchar(max),
#title nvarchar(max)
SELECT tag, data from testuiee
SELECT #bookid = data from testuiee where tag = 'UR'
SELECT #author = data from testuiee where tag = 'AA'
SELECT #title = data from testuiee where tag = 'TI'
IF #bookid IS NOT NULL
INSERT INTO btdata (book_id, author, title)
VALUES (#bookid, #author, #title)
GO
I did try to use the 'XD' tag conditionally to execute the insert but the same result.
Any help would be greatly appreciated.
You will need an Auto ID field/Rownumber field in your table to be able to keep the Rows together.
With you script you will get "random" values fitting your condition.
Using ROW_NUMBER() OVER (Partition by tag Order By ID) you are able to join your CTE with 3 aliases for the desired result.
Using SQL Server 2008 + you might use a CTE
declare #testui Table (ID integer Identity(1,1),tag varchar(10),data varchar(50))
declare #dest Table (book_id varchar(50),author varchar(50),title varchar(50))
Insert into #Testui
Select 'UR','2706'
Insert into #Testui
Select 'AA','Parker, William Harwar'
Insert into #Testui
Select 'TI','RECOLLECTIONS OF A NAVAL OFFICER, 1841-1865:'
Insert into #Testui
Select 'XD','S'
Insert into #Testui
Select 'UR','15184'
Insert into #Testui
Select 'AA','Goodrich, Norma Lorre'
Insert into #Testui
Select 'TI','King Arthur'
;With CTE as (
Select *,ROW_NUMBER() OVER (Partition by tag Order By ID) as RN
from #Testui)
Insert into #dest
Select a1.Data as book_id,a2.data author,a3.data title
from CTE a1
JOIN CTE a2 ON a1.RN=A2.RN
JOIN CTE a3 ON a1.RN=A3.RN
Where a1.tag='UR' and a2.tag='AA' and a3.tag='TI'
Select * from #dest

Remove a sentence from a paragraph that has a specific pattern with T-SQL

I have a large number of descriptions that can be anywhere from 5 to 20 sentences each. I am trying to put a script together that will locate and remove a sentence that contains a word with numbers before or after it.
before example: Hello world. Todays department has 345 employees. Have a good day.
after example: Hello world. Have a good day.
My main problem right now is identifying the violation.
Here "345 employees" is what causes the sentence to be removed. However, each description will have a different number and possibly a different variation of the word employee.
I would like to avoid having to create a table of all the different variations of employee.
JTB
This would make a good SQL Puzzle.
Disclaimer: there are probably TONS of edge cases that would blow this up
This would take a string, split it out into a table with a row for each sentence, then remove the rows that matched a condition, and then finally join them all back into a string.
CREATE FUNCTION dbo.fn_SplitRemoveJoin(#Val VARCHAR(2000), #FilterCond VARCHAR(100))
RETURNS VARCHAR(2000)
AS
BEGIN
DECLARE #tbl TABLE (rid INT IDENTITY(1,1), val VARCHAR(2000))
DECLARE #t VARCHAR(2000)
-- Split into table #tbl
WHILE CHARINDEX('.',#Val) > 0
BEGIN
SET #t = LEFT(#Val, CHARINDEX('.', #Val))
INSERT #tbl (val) VALUES (#t)
SET #Val = RIGHT(#Val, LEN(#Val) - LEN(#t))
END
IF (LEN(#Val) > 0)
INSERT #tbl VALUES (#Val)
-- Filter out condition
DELETE FROM #tbl WHERE val LIKE #FilterCond
-- Join back into 1 string
DECLARE #i INT, #rv VARCHAR(2000)
SET #i = 1
WHILE #i <= (SELECT MAX(rid) FROM #tbl)
BEGIN
SELECT #rv = IsNull(#rv,'') + IsNull(val,'') FROM #tbl WHERE rid = #i
SET #i = #i + 1
END
RETURN #rv
END
go
CREATE TABLE #TMP (rid INT IDENTITY(1,1), sentence VARCHAR(2000))
INSERT #tmp (sentence) VALUES ('Hello world. Todays department has 345 employees. Have a good day.')
INSERT #tmp (sentence) VALUES ('Hello world. Todays department has 15 emps. Have a good day. Oh and by the way there are 12 employees somewhere else')
SELECT
rid, sentence, dbo.fn_SplitRemoveJoin(sentence, '%[0-9] Emp%')
FROM #tmp t
returns
rid | sentence | |
1 | Hello world. Todays department has 345 employees. Have a good day. | Hello world. Have a good day.|
2 | Hello world. Todays department has 15 emps. Have a good day. Oh and by the way there are 12 employees somewhere else | Hello world. Have a good day. |
I've used the split/remove/join technique as well.
The main points are:
This uses a pair of recursive CTEs, rather than a UDF.
This will work with all English sentence endings: . or ! or ?
This removes whitespace to make the comparison for "digit then employee" so you don't have to worry about multiple spaces and such.
Here's the SqlFiddle demo, and the code:
-- Split descriptions into sentences (could use period, exclamation point, or question mark)
-- Delete any sentences that, without whitespace, are like '%[0-9]employ%'
-- Join sentences back into descriptions
;with Splitter as (
select ID
, ltrim(rtrim(Data)) as Data
, cast(null as varchar(max)) as Sentence
, 0 as SentenceNumber
from Descriptions -- Your table here
union all
select ID
, case when Data like '%[.!?]%' then right(Data, len(Data) - patindex('%[.!?]%', Data)) else null end
, case when Data like '%[.!?]%' then left(Data, patindex('%[.!?]%', Data)) else Data end
, SentenceNumber + 1
from Splitter
where Data is not null
), Joiner as (
select ID
, cast('' as varchar(max)) as Data
, 0 as SentenceNumber
from Splitter
group by ID
union all
select j.ID
, j.Data +
-- Don't want "digit+employ" sentences, remove whitespace to search
case when replace(replace(replace(replace(s.Sentence, char(9), ''), char(10), ''), char(13), ''), char(32), '') like '%[0-9]employ%' then '' else s.Sentence end
, s.SentenceNumber
from Joiner j
join Splitter s on j.ID = s.ID and s.SentenceNumber = j.SentenceNumber + 1
)
-- Final Select
select a.ID, a.Data
from Joiner a
join (
-- Only get max SentenceNumber
select ID, max(SentenceNumber) as SentenceNumber
from Joiner
group by ID
) b on a.ID = b.ID and a.SentenceNumber = b.SentenceNumber
order by a.ID, a.SentenceNumber
One way to do this. Please note that it only works if you have one number in all sentences.
declare #d VARCHAR(1000) = 'Hello world. Todays department has 345 employees. Have a good day.'
declare #dr VARCHAR(1000)
set #dr = REVERSE(#d)
SELECT REVERSE(RIGHT(#dr,LEN(#dr) - CHARINDEX('.',#dr,PATINDEX('%[0-9]%',#dr))))
+ RIGHT(#d,LEN(#d) - CHARINDEX('.',#d,PATINDEX('%[0-9]%',#d)) + 1)

SQL SELECT statement, column names as values from another table

I'm working on a database which has the following table:
id location
1 Singapore
2 Vancouver
3 Egypt
4 Tibet
5 Crete
6 Monaco
My question is, how can I produce a query from this which would result in column names like the following without writing them into the query:
Query result:
Singapore , Vancouver, Egypt, Tibet, ...
< values >
how can I produce a query which would result in column names like the
following without writing them into the query:
Even with crosstab() (from the tablefunc extension), you have to spell out the column names.
Except, if you create a dedicated C function for your query. The tablefunc extension provides a framework for this, output columns (the list of countries) have to be stable, though. I wrote up a "tutorial" for a similar case a few days ago:
PostgreSQL row to columns
The alternative is to use CASE statements like this:
SELECT sum(CASE WHEN t.id = 1 THEN o.ct END) AS "Singapore"
, sum(CASE WHEN t.id = 2 THEN o.ct END) AS "Vancouver"
, sum(CASE WHEN t.id = 3 THEN o.ct END) AS "Egypt"
-- more?
FROM tbl t
JOIN (
SELECT id, count(*) AS ct
FROM other_tbl
GROUP BY id
) o USING (id);
ELSE NULL is optional in a CASE expression. The manual:
If the ELSE clause is omitted and no condition is true, the result is null.
Basics for both techniques:
PostgreSQL Crosstab Query
You could do this with some really messing dynamic sql but I wouldn't recommend it.
However you could produce something like below, let me know if that stucture is acceptable and I will post some sql.
Location | Count
---------+------
Singapore| 1
Vancouver| 0
Egypt | 2
Tibet | 1
Crete | 3
Monaco | 0
Script for SelectTopNRows command from SSMS
drop table #yourtable;
create table #yourtable(id int, location varchar(25));
insert into #yourtable values
('1','Singapore'),
('2','Vancouver'),
('3','Egypt'),
('4','Tibet'),
('5','Crete'),
('6','Monaco');
drop table #temp;
create table #temp( col1 int );
Declare #Script as Varchar(8000);
Declare #Script_prepare as Varchar(8000);
Set #Script_prepare = 'Alter table #temp Add [?] varchar(100);'
Set #Script = ''
Select
#Script = #Script + Replace(#Script_prepare, '?', [location])
From
#yourtable
Where
[id] is not null
Exec (#Script);
ALTER TABLE #temp DROP COLUMN col1 ;
select * from #temp;

Is there a way to return multiple results with a subquery?

I have need to return multiple results from a subquery and have been unable to figure it out. The end result will produce the persons name across the vertical axis, various actions based on an action category across the horizontal axis. So the end result looking like:
----------
**NAME CATEGORY 1 CATEGORY 2**
Smith, John Action 1, Action 2 Action 1, Action 2, Action 3
----------
Is there a way to do this in a single query?
select
name,
(select action from actionitemtable where actioncategory = category1 and contact = contactid)
from
contact c
inner join actionitemtable a
on c.contactid = a.contactid
If more than one result is returned in that subquery I would like to be able to display it as a single comma separated string, or list of actions, etc.
Thank you.
Microsoft Sql Server 2005 is being used.
I use a User Defined Function for this task. The udf creates a delimited string with all elements matching the parameters, then you call the udf from your select statement such that you pull a delimited list for each record in the recordset.
CREATE FUNCTION dbo.ud_Concat(#actioncategory int, #contactid int)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #sOutput VARCHAR(8000)
SET #sOutput = ''
SELECT #sOutput = COALESCE(#sOutput, '') + action + ', '
FROM dbo.actionitemtable
WHERE actioncategory=#actioncategory AND contact=#contact
ORDER BY action
RETURN #sOutput
END
SELECT
name,
dbo.ud_Concat(category1, contactid) as contactList
FROM contact c
INNER JOIN actionitemtable a ON c.contactid = a.contactid
you need to give more info about your table structure and how they join to each other.
here is a generic example about combining multiple rows into a single column:
declare #table table (name varchar(30)
,ID int
,TaskID char(3)
,HoursAssigned int
)
insert into #table values ('John Smith' ,4592 ,'A01' ,40)
insert into #table values ('Matthew Jones',2863 ,'A01' ,20)
insert into #table values ('Jake Adams' ,1182 ,'A01' ,100)
insert into #table values ('Matthew Jones',2863 ,'A02' ,50)
insert into #table values ('Jake Adams' ,2863 ,'A02' ,10)
SELECT DISTINCT
t1.TaskID
,SUBSTRING(
replace(
replace(
(SELECT
t2.Name
FROM #Table AS t2
WHERE t1.TaskID=t2.TaskID
ORDER BY t2.Name
FOR XML PATH(''))
,'</NAME>','')
,'<NAME>',', ')
,3,2000) AS PeopleAssigned
FROM #table AS t1
OUTPUT:
TaskID PeopleAssigned
------ --------------------------------------
A01 Jake Adams, John Smith, Matthew Jones
A02 Jake Adams, Matthew Jones
(2 row(s) affected)
This is pretty abstract and complex. My initial reaction was "pivot query", but the more I looked at it (and at the earlier responses) the more I thought: Can you pass this one off to the application team? You return the "base", and they write and apply the procedural code that makes this kind of problem a snap. Sure, you can squeeze it in to SQL, but that doesn't make it the right place to do the work.
According to your query try this:
SELECT [Name],
STUFF(
(
SELECT ' ,' + [Action]
FROM [AactionItemTable]
WHERE [ActionCategory] = category1
AND [Contact] = contactid
FOR XML PATH('')
), 1, 2, ''
) AS [AdditionalData]
FROM [Contact] C
INNER JOIN [ActionItemTable] A
ON C.[ContactId] = A.[ContactId]
Guess this is the simplest way to do what you want.
EDIT: if there is no action in the subquery found, the [AdditionalData] result will be NULL.
You will probably have to create a custom aggregate function. Microsoft has a knowledge base article with sample code here.
If you don't mind using cursors, you can write your own function to do this. Here's an example that will work on the Adventureworks sample DB:
CREATE FUNCTION CommaFunctionSample
(
#SalesOrderID int
)
RETURNS varchar(max)
AS
BEGIN
DECLARE OrderDetailCursor CURSOR LOCAL FAST_FORWARD
FOR
SELECT SalesOrderDetailID
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = #SalesOrderID
DECLARE #SalesOrderDetailID INT
OPEN OrderDetailCursor
FETCH NEXT FROM OrderDetailCursor INTO #SalesOrderDetailID
DECLARE #Buffer varchar(max)
WHILE ##FETCH_STATUS = 0
BEGIN
IF #Buffer IS NOT NULL SET #Buffer = #Buffer + ','
ELSE SET #Buffer = ''
SET #Buffer = #Buffer + CAST(#SalesOrderDetailID AS varchar(12))
FETCH NEXT FROM OrderDetailCursor INTO #SalesOrderDetailID
END
CLOSE OrderDetailCursor
DEALLOCATE OrderDetailCursor
RETURN #Buffer
END
This is how such a function would appear in a select query:
SELECT AccountNumber, dbo.CommaFunctionSample(SalesOrderID)
FROM Sales.SalesOrderHeader