May 13, 2009 at 11:14 am
So I have a product table that contains products. I then have a product detail table that stores details as individual line items in a child table.
The client has requested a report that lists all products in one column with all product details in a coma separated list in a second column.
I.E.
Column 1:
Blender
Column 2:
White, 110 Volt, Dual Speed, Pulse Feature, Low Noise, 1 Year Warranty
Right now I am using a function with a cursor to go through each row in the detail table and build the result string. But I don't like the idea of using a cursor. Is there a way to do this with a set based query. I explored a pivot query but this isn't an aggregate query with a known number of columns and rows. Each product can of 0 to many rows in the detail table.
Suggestions??
May 13, 2009 at 11:43 am
Hey russ,
this is a pretty common question, with several different solutions. Why exactly do you need to concatenate all the rows on sql server? Typically this is something done on the application side depending on the scenario. Nevertheless,
DECLARE @stmts TABLE (
id INT IDENTITY,
stmt NVARCHAR(MAX),
PRIMARY KEY (id)) -- your table
insert into @stmts (stmt)
select 'White' UNION
select '110 volt'
select stmt + ', ' from @stmts for xml path('') -- Using for xml path concatentates all the rows, and the + ', ' ensures there is no row name.
Result:
White, 110 volt,
Read up more where I got this from: http://www.sqlservercentral.com/Forums/Topic683335-338-1.aspx
---
Dlongnecker
May 13, 2009 at 11:48 am
It always help if you provide sample code for people to work with here is a link to an article to help in the future: How to post data/code on a forum to get the best help
But since I just went through something similar, here is a solution that should work for you:
[font="Courier New"]CREATE TABLE #Product
(
ProductID INT NOT NULL,
ProductName VARCHAR(40) NOT NULL
);
--Add some sample data
INSERT INTO #Product
SELECT 1, 'Blender';
CREATE TABLE #ProductFeature
(
ProductID INT NOT NULL,
Feature VARCHAR(40) NOT NULL
);
--Add some sample data
INSERT INTO #ProductFeature
SELECT 1, 'White' UNION ALL
SELECT 1, '110 Volt' UNION ALL
SELECT 1, 'Dual Speed' UNION ALL
SELECT 1, 'Pulse Feature' UNION ALL
SELECT 1, 'Low Noise' UNION ALL
SELECT 1, '1 Year Warranty';
--Display the sample data
SELECT *
FROM #Product;
SELECT *
FROM #ProductFeature;
--Display the results
SELECT
t1.ProductID,
t1.ProductName,
STUFF((SELECT ', ' + CAST(t2.Feature AS VARCHAR(MAX))
FROM #ProductFeature t2
WHERE t2.ProductID = t1.ProductID
FOR XML PATH(''), TYPE).value('.[1]','varchar(max)')
,1,2,'') AS ProductFeatures --Use STUFF to remove the initial ", " from the returned list
FROM #Product t1;[/font]
I see Dlongnecker beat me to it, but I added the .VALUE portion to deal with characters in the features text that could cause a problem like >, <, and &. (Non printable characters, other than CR, LF, and TAB, will cause this to go boom, so hopefully that isn't an issue for you.)
edit: removed unnecessary group by.
May 13, 2009 at 11:53 am
Thanks I'll look into the xml path. The reason this isn't being handled at the application level is because they want this for a Crystal Report that can be ran outside of the application.
May 13, 2009 at 12:04 pm
Very sneaky UMG Developer. That's a very hot solution. Right when I thought I knew a cool trick someone one-ups me.
ewwww... Crystal Reports. Fair Enough, Russ.
---
Dlongnecker
May 13, 2009 at 12:18 pm
Thanks again guys, the combination of responses gave me exactly what I needed. That stuff post that accounts for '&' and '' characters is pretty trick. I can guarantee that sooner or later the client will attempt to drop those characters into the details table.
The server is breathing a sigh of relief now that I am one step closer to getting rid of all cursors.
May 13, 2009 at 5:27 pm
I'll add one more comment to this post for future forum users. I discovered that I had to have the database option "arithabort" set to 'ON' when using this method.
When I would encounter a product with no details in the child table Crystal would report an error. Crystal of course just said 'Error' but using visual studio's debugger I was able to discover the procedure was tossing a divide by zero error.
With "arithabort" set to ON, products with no child details simply come back as a null field, which is correct in this scenario.
May 13, 2009 at 5:34 pm
russ.thomas (5/13/2009)
I'll add one more comment to this post for future forum users. I discovered that I had to have the database option "arithabort" set to 'ON' when using this method.When I would encounter a product with no details in the child table Crystal would report an error. Crystal of course just said 'Error' but using visual studio's debugger I was able to discover the procedure was tossing a divide by zero error.
With "arithabort" set to ON, products with no child details simply come back as a null field, which is correct in this scenario.
Interesting, I added a second product to my sample with no matching records in the ProductFeature table and it returned NULL as I expected, and I don't have "arithabort" turned on.
Can you post the query/view that you were seeing this issue on? (I'm thinking that it is something else in your query causing the divide by zero error.)
May 13, 2009 at 5:43 pm
The interesting thing is that with it 'ON' or 'OFF' SQL behaves the same. It returns a null if no child records are found.
Crystal however was only happy with arithabort set 'ON'. I'm doing some more testing to see if I can get any more information but that was my experience.
I'm on SQL2008 but I doubt they changed the behavior since 2005.
May 13, 2009 at 5:52 pm
I created a view and connected using Crystal Reports 10, and it didn't give me an errors. (I am using SQL Server 2005.)
I could try Crystal Reports XI if that would help.
I would still like to see the view you have a problem with.
May 13, 2009 at 6:26 pm
I'm using Crystal 2008, SQL Server 2008 and Visual Studio 2008.
Here is the exact error message I get when I run the crystal report while debugging within VS. This is with arithabort set 'OFF'.
https://www.sqlservercentral.com/Forums/Uploads/image-unavailable.png
This is my UDF which get's called from the view below. Notice that if I rem out the XML Path block Crystal is happy, but If I include the XML Path block Crystal will only run the report based on the view that uses this UDF if arithabort is set to 'ON'
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ProductDetailString]
(
@pProductKey int
)
RETURNS varchar(8000)
AS
BEGIN
DECLARE @pResult varchar(8000)
DECLARE @pDetail varchar(100)
SELECT @pResult = Ltrim(Rtrim(IsNull(Number,'')+': '+IsNull(Model,''))) FROM Product WHERE ProductKey = @pProductKey
/*** when this block is taken out crystal runs fine with or without arithabort
when this block is in, crystal will only run with arithabort set to ON **/
SELECT @pResult = @pResult + ' ' +
IsNull((STUFF((SELECT ', ' + CAST(Detail AS VARCHAR(MAX))
FROM Detail WHERE ProductKey = @pProductKey
FOR XML PATH(''), TYPE).value('.[1]','varchar(max)'),1,2,'')),'')
/*** xml path block ends here **/
IF SubString(@pResult,1,1) = ':'
BEGIN
SET @pResult = SubString(@pResult,2,Len(@pResult)-1)
END
RETURN Ltrim(Rtrim(@pResult))
END
This is the view that calls the UDF. Notice the view does call other UDF's but the error goes away if I take out the ProductDetailString UDF or modify the UDF as described above.
SELECT dbo.Product.ProductKey, dbo.Product.CategoryKey, dbo.Category.Title AS CategoryTitle, dbo.Product.Model,
dbo.ProductPriceString(dbo.Product.ProductKey) AS Price, dbo.Product.BidPrice, dbo.Product.Discount, dbo.Product.PriceDetail,
dbo.Product.ProductURL, dbo.Product.Description, dbo.Product.Sort, dbo.Catalog.CatalogKey, dbo.Catalog.Title AS CatalogTitle,
dbo.ProductDetailString(dbo.Product.ProductKey) AS Details, dbo.Category.Make, dbo.Product.LastUpdate, dbo.Product.Number,
dbo.PromoProduct(dbo.Product.ProductKey, GETDATE()) AS Promo, dbo.Product.PromoEnd, dbo.Product.PromoDetail, dbo.Contract.ContractNum,
dbo.Supplier.SupplierKey, dbo.Supplier.Supplier, dbo.Supplier.SupplierURL
FROM dbo.Product INNER JOIN
dbo.Category ON dbo.Product.CategoryKey = dbo.Category.CategoryKey INNER JOIN
dbo.Catalog ON dbo.Category.CatalogKey = dbo.Catalog.CatalogKey LEFT OUTER JOIN
dbo.Contract ON dbo.Category.ContractKey = dbo.Contract.ContractKey LEFT OUTER JOIN
dbo.Supplier ON dbo.Contract.SupplierKey = dbo.Supplier.SupplierKey
WHERE (dbo.Product.Status = 'Active')
May 14, 2009 at 6:31 am
russ.thomas (5/13/2009)
I'm using Crystal 2008, SQL Server 2008 and Visual Studio 2008.Here is the exact error message I get when I run the crystal report while debugging within VS. This is with arithabort set 'OFF'.
https://www.sqlservercentral.com/Forums/Uploads/image-unavailable.png
The image is too small to read anything, can you post a larger version
May 14, 2009 at 9:02 am
I thought the link I provided took you to a larger version. Apparantly not, sorry. I've attached the image here:
May 15, 2009 at 1:49 am
Hmm, no clue from the error message then
but if its the XML PATH part thats causing the problem you could replace it with the old simple SQL 2000 way of doing things - i.e.
DECLARE @pResult varchar(100);
SET @pResult = [.. snip ..];
SELECT @pResult = @pResult + IsNull(Detail,'') + ', '
FROM Detail
WHERE ProductKey = @pProductKey
-- ORDER BY ... if required
RETURN @pResult
Such functions worked fine for beating the socks off cursors before XML PATH appeared.
http://www.sqlservercentral.com/articles/Advanced+Querying/replacingcursorsandwhileloops/1956/
May 15, 2009 at 9:14 am
Though the technical trickery of the XML path is cool, I am really disappointed in myself for not coming up with the simplicity of the "select =" statement. Thanks everyone for the input. The issue has been solved for my solution everything else has been extremely educational.
In parting...
I'm more and more convinced that the error from above is purely a Crystal problem. Calling those functions from within query analyzer cause no problems with or without arithabort, it's only when calling it from within crystal that I get an error. I think they are pre-emptively requiring the setting whether it actually needs it or not. Especially since the error it's self says, incorrect setting.
Viewing 15 posts - 1 through 14 (of 14 total)
You must be logged in to reply to this topic. Login to reply