Hi I have following tables:
create table Features
(
FeatureId bigint,
FeatureName varchar(255),
ParentId bigint
)
insert into Features values(10, 'Feature 1', 1);
insert into Features values(11, 'Feature 2', 10);
insert into Features values(12, 'Feature 3', 11);
insert into Features values(13, 'Feature 4', 2);
insert into Features values(14, 'Feature 5', 13);
insert into Features values(15, 'Feature 6', 3);
insert into Features values(16, 'Feature 7', 15);
insert into Features values(17, 'Feature 8', 16);
insert into Features values(18, 'Feature 9', 17);
insert into Features values(19, 'Feature 10', 18);
insert into Features values(20, 'Feature 11', 19);
insert into Features values(21, 'Feature 12', 12);
create table Scenarios
(
ScenarioId bigint,
ParentId bigint,
ScenarioTitle varchar(25)
)
insert into Scenarios values(1, 0, 'Scenario 1')
insert into Scenarios values(2, 0, 'Scenario 2')
insert into Scenarios values(3, 0, 'Scenario 3')
Here, a feature can have either another feature as parent or a scenario as parent. For scenario, parent id can either be 0, or another scenario.
I would like to get path of each feature as follows:
FeatureId ParentId FeatureName PathString PathLength
10 1 Feature 1 1 0
11 10 Feature 2 1/10 1
12 11 Feature 3 1/10/11 2
13 2 Feature 4 2 0
14 13 Feature 5 2/13 1
15 3 Feature 6 3 0
16 15 Feature 7 3/15 1
17 16 Feature 8 3/15/16 2
18 17 Feature 9 3/15/16/17 3
19 18 Feature 10 3/15/16/17/18 4
20 19 Feature 11 3/15/16/17/18/19 5
21 12 Feature 12 1/10/11/12 3
Since I would like to collect this result in a temp table for further processing, I tried select into and Azure SQL DW throws Using SELECT INTO statement is not supported in Parallel Data Warehouse. Modify the statement and re-try executing it.
Here is my query (may not be in great shape as I am still figuring out recursive sql)
drop table FeaturesWithPath;
;WITH FeaturePaths (FeatureId, ParentId, FeatureName, PathString)
AS
(
-- Anchor member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, cast(CAST(g.ParentId as nvarchar(max)) as varchar(max)) as PathString
FROM dbo.Features AS g
UNION ALL
-- Recursive member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, PathString + '/' + cast(g.ParentId as varchar(max))
FROM dbo.Features AS g
INNER JOIN FeaturePaths AS gp
ON g.ParentId = gp.FeatureId
)
SELECT FeatureId, ParentId, FeatureName, PathString into FeaturesWithPath FROM FeaturePaths;
--select * from FeaturesWithPath order by FeatureId;
drop table FeaturesWithPathLength;
select *, LEN(PathString) - LEN(REPLACE(PathString, '/', '')) as PathLength into FeaturesWithPathLength from FeaturesWithPath
--select * from FeaturesWithPathLength order by FeatureId
drop table MaxFeaturePathLenghtRowTable;
select * into MaxFeaturePathLenghtRowTable
from FeaturesWithPathLength
where PathLength = (select max(PathLength) from FeaturesWithPathLength as f where f.FeatureId = FeaturesWithPathLength.FeatureId)
or PathLength = (select max(PathLength) from FeaturesWithPathLength as f where f.FeatureId = FeaturesWithPathLength.FeatureId
and PathLength > (select max(PathLength) from FeaturesWithPathLength as f2 where f2.FeatureId = FeaturesWithPathLength.FeatureId));
--select * from MaxFeaturePathLenghtRowTable order by FeatureId
drop table FeaturesPerParentTable
select FeatureId, [value] as NewParentId, FeatureName, COALESCE(NULLIF(SUBSTRING(PathString, 0, CHARINDEX('/', PathString)), ''), [value]) AS ScenarioId into FeaturesPerParentTable
from MaxFeaturePathLenghtRowTable
cross apply STRING_SPLIT (PathString, '/') cs order by FeatureId
select * from FeaturesPerParentTable order by FeatureId;
I tried to convert the CTE to use CTAS which did not work either.
This is how I tried CTAS:
;WITH FeaturePaths (FeatureId, ParentId, FeatureName, PathString)
AS
(
-- Anchor member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, cast(CAST(g.ParentId as nvarchar(max)) as varchar(max)) as PathString
FROM dbo.Features AS g
--WHERE parentId=0
UNION ALL
-- Recursive member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, PathString + '/' + cast(g.ParentId as varchar(max))
FROM dbo.Features AS g
INNER JOIN FeaturePaths AS gp
ON g.ParentId = gp.FeatureId
)
CREATE TABLE #tmp_fct
WITH
(
DISTRIBUTION = ROUND_ROBIN
)
AS
SELECT FeatureId, ParentId, FeatureName, PathString
FROM FeaturePaths;
Now I am wondering if there is a way to get path for each Feature on Azure SQL DW and collect result in to a table.
-- UPDATE --
For solution in SQL see this
Here is solution in C#
void Main()
{
var scenarios = new List<Scenario> {
new Scenario{Id = 1, Title = "Scenario 1", ParentId = 0},
new Scenario{Id = 2, Title = "Scenario 2", ParentId = 0},
new Scenario{Id = 3, Title = "Scenario 3", ParentId = 0},
};
var features = new List<Feature> {
new Feature{Id =10, Title = "Feature 1", ParentId =1},
new Feature{Id =11, Title = "Feature 2", ParentId =10},
new Feature{Id =12, Title = "Feature 3", ParentId =11},
new Feature{Id =13, Title = "Feature 4", ParentId =2},
new Feature{Id =14, Title = "Feature 5", ParentId =13},
new Feature{Id =15, Title = "Feature 6", ParentId =3},
new Feature{Id =16, Title = "Feature 7", ParentId =15},
new Feature{Id =17, Title = "Feature 8", ParentId =16},
new Feature{Id =18, Title = "Feature 9", ParentId =17},
new Feature{Id =19, Title = "Feature 10", ParentId =18},
new Feature{Id =20, Title = "Feature 11", ParentId =19},
new Feature{Id =21, Title = "Feature 12", ParentId =12}
};
var scenarioIds = new HashSet<long>(scenarios.Select(x => x.Id));
//get path
IList<Feature> withPath = features.Select(x => { x.Path = GetPath(x, features, scenarioIds); return x; }).ToList().Dump("With path");
}
private string GetPath(Feature f, IList<Feature> features, HashSet<long> scenarioIds)
{
if (scenarioIds.Contains(f.ParentId))
{
return f.ParentId.ToString();
}
else
{
var parent = features.First(d => d.Id == f.ParentId);
return GetPath(parent, features, scenarioIds) + "/" + f.ParentId;
}
}
public class Scenario
{
public long Id { get; set; }
public string Title { get; set; }
public long ParentId { get; set; }
}
public class Feature
{
public long Id { get; set; }
public string Title { get; set; }
public long ParentId { get; set; }
public string Path { get; set; } //temp
}
As Azure SQL Data Warehouse does not support recursive CTEs or cursors at this time, you could do this with a good old-fashioned loop, eg:
-- Loop thru Features
DECLARE #counter INT = 1;
-- Insert first record where no parent exists
IF OBJECT_ID('tempdb..#features') IS NOT NULL DROP TABLE #features;
CREATE TABLE #features
WITH
(
DISTRIBUTION = HASH ( FeatureId ),
LOCATION = USER_DB
)
AS
WITH cte AS
(
SELECT 1 AS xlevel, p.FeatureId, p.ParentId, p.FeatureName, CAST( p.ParentId AS VARCHAR(255) ) AS PathString, 0 AS PathLength
FROM dbo.Features p
WHERE NOT EXISTS
(
SELECT *
FROM dbo.Features c
WHERE p.ParentId = c.FeatureId
)
)
SELECT *
FROM cte;
SELECT 'before' s, * FROM #features ORDER BY FeatureId;
-- Loop recursively through the child records
WHILE EXISTS (
SELECT *
FROM #features p
INNER JOIN dbo.features c ON p.FeatureId = c.ParentId
WHERE p.xlevel = #counter
)
BEGIN
-- Insert next level
INSERT INTO #features ( xlevel, FeatureId, ParentId, FeatureName, PathString, PathLength )
SELECT #counter + 1 AS xlevel, c.FeatureId, c.ParentId, c.FeatureName, p.PathString + '/' + CAST( c.ParentId AS VARCHAR(255) ) AS PathString, #counter AS PathLength
FROM #features p
INNER JOIN dbo.features c ON p.FeatureId = c.ParentId
WHERE p.xlevel = #counter;
SET #counter += 1;
-- Loop safety
IF #counter > 99
BEGIN
RAISERROR( 'Too many loops!', 16, 1 )
BREAK
END;
END
SELECT 'after' s, * FROM #features ORDER BY FeatureId;
Full code including setup is available here.
My results:
Hope that helps.
Why not create the FeaturesWithPath table beforehand and insert into it using the following pseudocode?
CREATE TABLE FeaturesWithPath (FeatureId type, ParentId type, FeatureName type, PathString type)
;with FeaturePaths (FeatureId, ParentId, FeatureName, PathString)
AS
(
-- Anchor member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, cast(CAST(g.ParentId as nvarchar(max)) as varchar(max)) as PathString
FROM dbo.Features AS g
UNION ALL
-- Recursive member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, PathString + '/' + cast(g.ParentId as varchar(max))
FROM dbo.Features AS g
INNER JOIN FeaturePaths AS gp
ON g.ParentId = gp.FeatureId
)
insert FeaturesWithPath (FeatureId, ParentId, FeatureName, PathString)
SELECT FeatureId, ParentId, FeatureName, PathString FROM FeaturePaths;
Related
how can i replace values in string with values that are in a table?
for example
select *
into #t
from
(
select 'bla'c1,'' c2 union all
select 'table'c1,'TABLE' c2 union all
select 'value'c1,'000' c2 union all
select '...'c1,'' c2
)t1
declare #s nvarchaR(max)='this my string and i want to replace all values that are in table #t'
i have some values in my table and i want to replace C1 with C2 in my string.
the results should be
this my string and i want to replace all 000 that are in TABLE #t
UPDATE:
i solved with a CLR
using System;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Data.Linq;
namespace ReplaceValues
{
public partial class Functions
{
[SqlFunction
(
//DataAccess = DataAccessKind.Read,
SystemDataAccess = SystemDataAccessKind.Read
)
]
public static string ReplaceValues(string row, string delimitator, string values, string replace/*, bool CaseSensitive*/)
{
//return row;
string[] tmp_values = values.Split(new string[] { delimitator }, StringSplitOptions.None);
string[] tmp_replace = replace.Split(new string[] { delimitator }, StringSplitOptions.None);
row = row.ToUpper();
for (int i = 0; i < Math.Min(tmp_values.Length, tmp_replace.Length); i++)
{
row = row.Replace(tmp_values[i].ToUpper(), tmp_replace[i]);
}
return row;
}
}
}
and then
select *
into #t
from
(
select 'value1'OldValue,'one'NewValue union all
select 'value2'OldValue,'two'NewValue union all
select 'value3'OldValue,'three'NewValue union all
select 'value4'OldValue,'four'NewValue
)t1
select dbo.ReplaceValues(t1.column,'|',t2.v,t2.r)
from MyTable t1
cross apply
(
select dbo.inlineaggr(i1.OldValue,'|',1,1)v,
dbo.inlineaggr(i1.NewValue,'|',1,1)r
from #t i1
)t2
i have to improved it to manage better the case sensitive, but performance are not bad.
(also 'inlineaggr' is a CLR i wrote years ago)
You can do this via recursion. Assuming you have a table of find-replace pairs, you can number the rows and then use recursive cte:
create table #t(c1 nvarchar(100), c2 nvarchar(100));
insert into #t(c1, c2) values
('bla', ''),
('table', 'table'),
('value', '000'),
('...', '');
declare #s nvarchar(max) = 'this my string and i want to replace all values that are in table #t';
with ncte as (
select row_number() over (order by (select null)) as rn, *
from #t
), rcte as (
select rn, replace(#s, c1, c2) as newstr
from ncte
where rn = 1
union all
select ncte.rn, replace(rcte.newstr, ncte.c1, ncte.c2)
from ncte
join rcte on ncte.rn = rcte.rn + 1
)
select *
from rcte
where rn = 4
Can I translate this sql into querydsl form?
select count(ppe),v.name
from personal_progress_entity ppe left join user_detail_entity ude
on ppe.student_entity_id=ude.user_id
right join (values ('aaa'),('bbb'),('ccc'),('ddd')) as v(name)
on ude.people_category=v.name
group by v.name;
The PostgreSQL VALUES function is not supported by querydsl. However, you can get the same result using a UNION.
CREATE TABLE personal_progress_entity(student_entity_id INTEGER);
INSERT INTO personal_progress_entity VALUES (1);
CREATE TABLE user_detail_entity(user_id INTEGER, people_category VARCHAR);
INSERT INTO user_detail_entity VALUES (1, 'aaa');
INSERT INTO user_detail_entity VALUES (1, 'bbb');
SELECT COUNT(personal_progress_entity.student_entity_id),
name.value_alias
FROM personal_progress_entity personal_progress_entity
LEFT JOIN user_detail_entity user_detail_entity ON personal_progress_entity.student_entity_id = user_detail_entity.user_id
RIGHT JOIN ((SELECT 'aaa' AS value_alias)
UNION
(SELECT 'bbb' AS value_alias)
UNION
(SELECT 'ccc' AS value_alias)
UNION
(SELECT 'ddd' AS value_alias)) AS name
ON name.value_alias = user_detail_entity.people_category
GROUP BY name.value_alias;
Gives:
1 "aaa"
1 "bbb"
0 "ddd"
0 "ccc"
Here's my querydsl-sql implementation. I've added the private static <T> Union<T> union(PathBuilder<T> pathBuilder, T... values) method to reduce boilerplate.
public List<Tuple> stackoverflowAnswer() {
PathBuilder<String> valueAlias = new PathBuilder<>(String.class, "value_alias");
PathBuilder<String> name = new PathBuilder<>(String.class, "name");
return query().select(personalProgressEntity.studentEntityId.count(), name.get(valueAlias))
.from(personalProgressEntity)
.leftJoin(userDetailEntity).on(personalProgressEntity.studentEntityId.eq(userDetailEntity.userId))
.rightJoin(union(valueAlias, "aaa", "bbb", "ccc", "ddd"), name).on(name.get(valueAlias).eq(userDetailEntity.peopleCategory))
.groupBy(name.get(valueAlias))
.fetch();
}
private static <T> Union<T> union(PathBuilder<T> pathBuilder, T... values) {
return SQLExpressions.union(
Stream.of(values)
.map(p -> SQLExpressions.select(Expressions.constantAs(p, pathBuilder)))
.collect(Collectors.toList()));
}
Hi I Have Table that called Tags, in tag table I have 2 columns (QuestionID int ,Tag nvachar(100))
I want to Select Questions with all Tags in one column like the below
QuestionID Tag
---------- ----
1 Math
1 Integral
2 Physics
QuestionID QuestionText
---------- -----------
1 What is 2*2?
2 What is Quantom roles?
QuestionID QuestionText Tags
---------- ----------- -------
1 What is 2*2? Math, Integral
2 What is Quantom roles? Physics
Can any one help me with out using scalar value function
There are two ways to answer this:
can use a query like in other answer, but this is work for one table only.
create clr aggregate function for this like a below code (my code in C#).
this solution work for all tables and simple for use,
only use: select Concat(column) from Table in sql server
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToDuplicates = false, IsInvariantToNulls = true, IsInvariantToOrder = false, IsNullIfEmpty = true, MaxByteSize = -1)]
public struct Concat : IBinarySerialize
{
public void Init()
{
SB = new StringBuilder();
}
public void Accumulate(SqlString Value)
{
if (Value.IsNull)
return;
if (SB.Length > 0)
SB.Append("\n");
SB.Append(Value);
}
public void Merge(Concat Group)
{
if (SB.Length > 0 && Group.SB.Length > 0)
SB.Append("\n");
SB.Append(Group.SB.ToString());
}
public SqlString Terminate()
{
return new SqlString(SB.ToString());
}
// This is a place-holder member field
StringBuilder SB;
public void Read(System.IO.BinaryReader r)
{
SB = new StringBuilder(r.ReadString());
}
public void Write(System.IO.BinaryWriter w)
{
w.Write(SB.ToString());
}
}
CREATE TABLE #temp
(
QuestionID INT,
Tag NVARCHAR(100)
)
INSERT INTO #temp
(QuestionID,Tag)
VALUES (1,N'Math'),
(1,N'Integral'),
(2,N'Physics')
CREATE TABLE #temp1
(
QuestionID INT,
QuestionText NVARCHAR(100)
)
INSERT INTO #temp1
(QuestionID,QuestionText)
VALUES (1,N'What is 2*2?'),
(2,'What is Quantom roles?')
SELECT h.QuestionID,
h.QuestionText,
Stuff((SELECT ', ' + CONVERT(VARCHAR, b.TAG)
FROM #temp b
WHERE b.QuestionID = h.QuestionID
FOR XML PATH('')), 1, 2, '')
FROM #temp t
JOIN #temp1 h
ON t.QuestionID = h.QuestionID
GROUP BY h.QuestionID,
h.QuestionText
SELECT q.QuestionText
,STUFF((
SELECT ', ' + t2.Tag
FROM Tags t2
WHERE t1.QuestionID = t2.QuestionID
ORDER BY t2.Tag
FOR XML PATH('')
,TYPE
).value('.', 'varchar(max)'), 1, 2, '') AS Tag
FROM Questions q
INNER JOIN Tags t1
ON q.QuestionID = t1.QuestionID
GROUP BY q.QuestionText
,t1.QuestionID
Working example : http://sqlfiddle.com/#!3/e8f0f/7
Try this
create function fn_comma (#question_id int)
returns varchar(100)
as
begin
declare #value varchar(100)
set #value=(SELECT top 1 STUFF((SELECT ', ' + CAST(Value AS VARCHAR(10)) [text()]
FROM Tags
WHERE ID = t.ID
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') List_Output
FROM Tags
--where id=1
GROUP BY ID)
return #value
end
Try sub query to concat column data in comma separated values like below :
SELECT [QuestionID],
[QuestionText],
STUFF(( SELECT ',' + [Tag]
FROM [dbo].[Tags]
WHERE [QuestionID] = [Question].[QuestionID]
FOR XML PATH ('')), 1, 1, '') AS [Tags]
FROM [dbo].[Question]
SQL Fiddle Demo
Try the below idea. You just need to rewrite it as a function, then it will return all tags for the question id:
declare #function_in_questionid_para as #int
with std as
(select *,ROW_NUMBER() over(partition by QuestionID order by QuestionID,tag) as dd from #temp)
select * #temp3 into from std
declare #counter as int
set #counter = (select count(*) from #temp where QuestionID = #function_in_questionid_para as #int)
declare #c as int = 1
declare #tags as varchar(200) = ''
while (#c <= #counter)
begin
if (#c > 1) set #tags = #tags + ', '
set #tags = #tags + (select tag from #temp3 where QuestionID = #function_in_questionid_para as #int and dd = #c)
set #c = #c + 1
end
print #tags
I am having a SQL function and I want to return a table, but I'm getting an error:
A RETURN statement with a return value cannot be used in this context.
Here is my query:
ALTER FUNCTION [dbo].[Fnc_GetParentCategories]-- 21
(
#catId int
)
Returns table
As
Begin
Declare #Table table(Id int identity(1,1),Category_Id int,ParentId int);
declare #cid int;
WITH x AS (
SELECT a.Category_Id, a.ParentId
FROM t_Category a
WHERE a.Category_Id=#CatId -- enter dead node walking here
UNION ALL
SELECT b.Category_Id, b.ParentId
FROM t_Category b
JOIN x ON x.Category_Id =b.ParentId
)
insert into #Table select * from x;
return #Table
end
And the error is:
A RETURN statement with a return value cannot be used in this context
Your query is incorrect:
ALTER FUNCTION [dbo].[Fnc_GetParentCategories]-- 21
(
#catId int
)
Returns #Table table
(
Id int identity(1,1),
Category_Id int,
ParentId int
)
AS
Begin
WITH x AS
(
SELECT a.Category_Id, a.ParentId
FROM t_Category a
WHERE a.Category_Id=#CatId -- enter dead node walking here
UNION ALL
SELECT b.Category_Id, b.ParentId
FROM t_Category b
JOIN x ON x.Category_Id =b.ParentId
)
insert into #Table select * from x;
return
end
you declare your table definition just before as
--Transact-SQL Multistatement Table-valued Function Syntax
CREATE FUNCTION [ schema_name. ] function_name
( [ { #parameter_name [ AS ] [ type_schema_name. ] parameter_data_type
[ = default ] [READONLY] }
[ ,...n ]
]
)
RETURNS #return_variable TABLE <table_type_definition>
[ WITH <function_option> [ ,...n ] ]
[ AS ]
BEGIN
function_body
RETURN
END
[ ; ]
Your modified function
ALTER FUNCTION [dbo].[Fnc_GetParentCategories]-- 21
(
#catId int
)
RETURNS #Table TABLE(Id int identity(1,1),Category_Id int,ParentId int)
As
BEGIN
WITH x AS
(
SELECT a.Category_Id, a.ParentId
FROM t_Category a
WHERE a.Category_Id = #CatId -- enter dead node walking here
UNION ALL
SELECT b.Category_Id, b.ParentId
FROM t_Category b
JOIN x ON x.Category_Id = b.ParentId
)
INSERT #Table select * from x;
RETURN
END
I have a table with just product ID's and category ID's (products can be in more than one category). How can I flatten the category ID's into a product column so I end us with this:
id | name | desc | categories
1 | test1 | lorem | 1,3,4,23
2 | test2 | ipsom | 4,6,24
It is like I need to loop into a separate table for the categories column. How can I do this or is there a better way?
I created an CLR aggregate function that takes a varchar column and returns all its values separated by commas. In other words, it joins several strings into a comma-separated list. I am sure its performance is way better than any T-Sql trick.
As any aggregate function, it can be used in combination with group by. For example:
SELECT id, name, desc, JoinStrings(CONVERT(VARCHAR(20), category_id))
FROM product p
INNER JOIN category_products c ON p.category_id = c.category_id
GROUP BY id, name, desc
Here's the C# code to create the CLR assembly into Sql Server 2008:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToDuplicates=false, IsInvariantToOrder=false, IsInvariantToNulls=true, MaxByteSize=-1)]
public struct JoinStrings : IBinarySerialize
{
private char[] sb;
private int pos;
public void Init()
{
sb = new char[512000];
pos = 0;
}
public void Accumulate(SqlString Value)
{
if (Value.IsNull) return;
char[] src = Value.ToString().ToCharArray();
Array.Copy(src, 0, sb, pos, src.Length);
pos += src.Length;
sb[pos] = ',';
pos++;
}
public void Merge(JoinStrings Group)
{
Accumulate(Group.Terminate());
}
public SqlString Terminate()
{
if (pos <= 0)
return new SqlString();
else
return new SqlString(new String(sb, 0, pos-1));
}
public void Read(System.IO.BinaryReader r)
{
this.Init();
pos = r.ReadInt32();
r.Read(sb, 0, pos);
}
public void Write(System.IO.BinaryWriter w)
{
w.Write(pos);
w.Write(sb, 0, pos);
}
}
Here's the code to create the function (although deploying from Visual Studio should do it automatically):
CREATE AGGREGATE [dbo].[JoinStrings]
(#s [nvarchar](4000))
RETURNS[nvarchar](max)
EXTERNAL NAME [YouAssemblyName].[JoinStrings]
There's no in-built way to do it in MSSQL.
Simulating group_concat MySQL function in Microsoft SQL Server 2005? has a good description of how to go about implementing a workaround.
I would suggest using a Recursive CTE. I believe that it would be something like this:
select productid, categoryid,
row_number() over (partition by id order by categoryid) as rownum
into #tabletorecurse
from TABLENAME
with finaloutput as
(
select productid as id, name, desc, categoryid as categories, rownum
from #tabletorecurse
join PRODUCTTABLE
on PRODUCTTABLE.id = #tabletorecurse.productid
where rownum = 1
union all
select tr.id, tr.name, tr.desc,
finaloutput.categories + ', ' + tr.categoryid, tr.rownum
from #tabletorecurse as tr
join finaloutput
on finaloutput.rownum + 1 = tr.rownum
and finaloutput.id = tr.productid
)
select id, name, desc, categories
from finaloutput
join
(
select max(rownum) as maxrow, id
from finaloutput
group by id
) as maxvalues
on maxvalues.id = finaloutput.id
and maxvalues.maxrow = finaloutput.rownum
Use a function.
This does a lookup to text so you will need to adapt.
The COALESCE is just to put a ,.
This is from a large scale production application - it works and it fast.
Function was questioned by JustinPony as function is slow
I am hitting some tables of million of records but only returning 100 rows.
The function is only applied to the hundred rows.
usage:
select top 5 sID, ( select [dbo].[JoinMVEnum](docSVsys.sID, '140') ) as [Flag Issue]
from docSVsys
function
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[JoinMVText]
(
#sID int,
#fieldID tinyint
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #MVtextList varchar(max)
SELECT #MVtextList = COALESCE(#MVtextList + '; ', '') + docMVtext.value
FROM docMVtext with (nolock)
WHERE docMVtext.sID = #sID and fieldID = #fieldID
RETURN #MVtextList
END
GO