views:

53

answers:

3

I have the following script:

SELECT left(SHI.FSOKEY, 6) AS [SoNo]
,      substring(SHI.FSOKEY, 7, 3) AS [So Item]
,      right(SHI.FSOKEY, 3) AS [So Rels]
,      QAL.FCLOT AS [LotSerial]

FROM shmast SHM
     INNER JOIN shitem SHI
     ON SHM.FSHIPNO = SHI.FSHIPNO
     INNER JOIN qalotc QAL
     ON SHM.FSHIPNO = Left(QAL.FCUSEINDOC, 6)
        AND substring(QAL.FCUSEINDOC, 7, 6) = SHI.FITEMNO

This produces output that looks like this:

SoNo      So Item  SoRels  LotSerial
123456      1      001     ABCD
123456      1      001     AMOH
123456      1      001     POWK
123456      1      001     IUIL
123456      1      002     ABCE

I want to group by SoNo, SoItem, SoRels and get a list of LotSerials for each. So, my output would look like this:

SoNo      So Item  SoRels  LotSerial
123456      1      001     ABCD, AMOH, POWK, IUIL
123456      1      002     ABCE

I need to do so that I can pull this information back into a main query based on the SoNo, SoItem, SoRels.

Any help would be greatly appreciated.

+5  A: 

As always, avoid cursors whenever possible. Your scenario would be a good fit for a user defined function. I simplified your schema a little for this example. Essentially we are concatenating the serials that match with commas to a variable within a user defined function, and then retuning the result. If Null values are possible, you might want to add usage of Coalesce

create table SO (SONO int)
insert into SO values (1)
insert into SO values (2)
insert into SO values (3)


create table SOCHILD 
(SONO int, SerialNo varchar(10))
insert into SOCHILD values (1, 'ABCD')
insert into SOCHILD values (1, 'EFGH')
insert into SOCHILD values (1, 'IJKL')
GO
create function fx_GetSerials(@SONO int)
returns varchar(1000) as
Begin
    Declare @ret varchar(1000)
    set @ret = ''
    Select @ret = @ret + SerialNo + ','
    from SOCHILD where SONO = @SONO
    if (len(@ret) > 0) 
     set @Ret = left(@ret, len(@ret) -1)
    return @ret 
End 
GO
select dbo.Fx_GetSerials(1)
drop function fx_GetSerials 
Drop table SO
Drop table SOCHILD

Results ABCD,EFGH,IJKL

cmsjr
+1 Exactly my thought. But you should be careful, because when the SELECT will not return anything the LEFT function will return error !!
Lukasz Lysik
Great point, length check added.
cmsjr
since this function will run for each row returned, you need to make it run as fast as possible. my version will perform much faster, it has none of the code that this version needs to trim the leading comma. The trick is in how NULLs are handled. in my code: _@cvs=ISNULL(@cvs+', ','')+LotSerial_ when @cvs is null, the initial comma is lost when concatenating null+', '. however the ISNULL() around that forces it to be empty string and concatenate with the first string properly. ON all subsequent concatenations, the @cvs+', ' results in a valid string, thus giving you commas when necessary.
KM
what if concat_null_yields_null is set to off?
cmsjr
@cmsjr said _what if concat_null_yields_null is set to off?_, then you have bigger problems! better start fixing you code now: http://msdn.microsoft.com/en-us/library/ms176056.aspx here is a quote from the link: __In a future version of SQL Server CONCAT_NULL_YIELDS_NULL will always be ON and any applications that explicitly set the option to OFF will generate an error. Avoid using this feature in new development work, and plan to modify applications that currently use this feature.__
KM
First, let me note I do prefer the elegance of your method and will probably apply it in the future. I would argue, however, that writing code that is tolerant of the execution environment makes more sense than not. Another question occurs to me, how do we quantify the performance difference between an arbitrary number of calls to IsNull vs a single string re-assignment.
cmsjr
+2  A: 

@cmsjr beat me to it with his answer which is just bout the same. I do build the string differently, and have a complete working example.

try this:

CREATE TABLE YourTable  (SoNo int, SoItem int, SoRels char(3),  LotSerial char(4))
go
INSERT INTO YourTable VALUES (123456,1,'001','ABCD')
INSERT INTO YourTable VALUES (123456,1,'001','AMOH')
INSERT INTO YourTable VALUES (123456,1,'001','POWK')
INSERT INTO YourTable VALUES (123456,1,'001','IUIL')
INSERT INTO YourTable VALUES (123456,1,'002','ABCE')
go
CREATE FUNCTION LotSerial_to_CVS(@SoNo int, @SoItem int, @SoRels char(3))
RETURNS varchar(2000) AS
BEGIN
    DECLARE @cvs varchar(2000)
    SELECT @cvs=ISNULL(@cvs+', ','')+LotSerial
        FROM YourTable
        WHERE SoNo=@SoNo AND SoItem=@SoItem AND SoRels=@SoRels
    RETURN @cvs
END
go

SELECT
    SoNo, SoItem, SoRels, dbo.LotSerial_to_CVS(SoNo, SoItem, SoRels)
    FROM YourTable
        GROUP BY SoNo, SoItem, SoRels

OUTPUT:

SoNo        SoItem      SoRels 
----------- ----------- ------ -----------------------
123456      1           001    ABCD, AMOH, POWK, IUIL
123456      1           002    ABCE

(2 row(s) affected)
KM
+1  A: 

There is no need for a cursor in almost ANY case any more.

You can do the same thing using XML PATH as well. Here is a working sample:

SET NOCOUNT ON

Declare @MyTable Table
(
    SoNo VarChar (100),
    SoItem VarChar (100),
    SoRels VarChar (100),
    LotSerial VarChar (100)
)

INSERT INTO @MyTable Values ('123456', '1', '001', 'ABCD')
INSERT INTO @MyTable Values ('123456', '1', '001', 'AMOH')
INSERT INTO @MyTable Values ('123456', '1', '001', 'POWK')
INSERT INTO @MyTable Values ('123456', '1', '001', 'IUIL')
INSERT INTO @MyTable Values ('123456', '1', '002', 'ABCE')

SELECT 
    SoNo, 
    SoItem, 
    SoRels, 
    STUFF ((
     SELECT ', ' + LotSerial 
     FROM @MyTable T1
     WHERE 1=1
      AND T1.SoNo = T2.SoNo
      AND T1.SoItem = T2.SoItem
      And T1.SoRels = T2.SoRels
     FOR XML PATH ('')
    ), 1, 2, '') AS LotSerial
FROM @MyTable T2
GROUP BY SoNo, SoItem, SoRels
Raj More
was FOR XML PATH available in SQL Server 2000?
KM
i missed that part
Raj More
No I don't think it is. Cool trick though.
HLGEM
+1, sql server 2000 does have FOR XML PATH, this is the fastest way to go
KM