dub stylee
dub stylee

Reputation: 3342

alternative to CROSS APPLY

I have a stored procedure that I use to squeeze multiple rows of data into one variable, like so:

DECLARE @Description VARCHAR(200);

SELECT @Description = COALESCE(@Description) + ', ', '') + [Description]
FROM dbo.DrugDescriptionAndImprints
WHERE NDC = @NDC;

My problem is, I recently had a need to accomplish this for multiple NDCs simultaneously for generating report data.

My current solution is to use CROSS APPLY with a table-valued function, but that is less than optimal due to the performance hit. I would like to be able to "flatten" this data in a view or something, but am unable to use variables in a view. Is there a way to accomplish this without using CROSS APPLY and table-valued functions?

Here is the table-valued function for reference:

CREATE FUNCTION [dbo].[fnFlatDescriptionByNdc]
(
    @NDC VARCHAR(11)
)
RETURNS 
@ret TABLE 
(
    [Description] VARCHAR(200)
)
AS
BEGIN
    DECLARE @Description VARCHAR(200)
    DECLARE @Imprint1 VARCHAR(40)
    DECLARE @Imprint2 VARCHAR(40)

    SELECT @Description = Coalesce(@Description + ', ', '') + [Description]
    FROM dbo.DrugDescriptionAndImprints WHERE NDC = @NDC

    SELECT @Imprint1 = COALESCE(Imprint1, '')
    FROM dbo.DrugDescriptionAndImprints WHERE NDC = @NDC 
    ORDER BY Imprint1 DESC

    SELECT @Imprint2 = COALESCE(Imprint2, '')
    FROM dbo.DrugDescriptionAndImprints WHERE NDC = @NDC 
    ORDER BY Imprint2 DESC

    INSERT INTO @ret ([Description])
    SELECT @Description + CASE WHEN @Imprint1 <> '' OR @Imprint2 <> '' THEN ' (' +
        CASE WHEN @Imprint1 <> '' THEN @Imprint1 + 
            CASE WHEN @Imprint2 <> '' THEN ' / ' + @Imprint2
            ELSE ''
            END
        ELSE @Imprint2 END + ')' ELSE '' END

    RETURN 
END

I am working with SQL Server 2008 R2.

EDIT Here is some sample data to show what I am working with:

NDC              ProdDescAbbr           Description        Imprint1      Imprint2
00005250033      FIBERCON   TAB 625MG   film-coated        LL            F 1
00005250033      FIBERCON   TAB 625MG   tan                LL            F 1
00005250033      FIBERCON   TAB 625MG   scored             LL            F 1
00005250033      FIBERCON   TAB 625MG   oblong             LL            F 1

Upvotes: 0

Views: 6316

Answers (1)

btberry
btberry

Reputation: 375

Your explanation of what you're doing now and what you want to do are a bit confusing but I will address what I see.

I think you are saying you do not want to use a user defined function, which is good. There are no performance gains from UDF's and, in fact, you will see performance losses if you use anything but an inline table function, which you do not have. The only real advantage to UDF's is code-reuse, which can be great for inline table functions that have been optimized and are used all over the place. However, let's assume this will not fall into that category. If you do need to implement a UDF, please invest the time to understand how to perform operations in a set-based manner so you can use an inline table function. The code I provide below could be easily modified to work in an inline table function.

You made reference to CROSS APPLY, which you're not using in any of the code you supplied. I am not sure if you are meaning that you are using a CROSS APPLY to join to the function. In any case, my understanding is that you want to move away from the function to be able to get multiple records at the same time. I do not know your data, but I am guessing that is a good call and you can get better performance by doing this work on multiple NDC's at the same time.

I created some test data (that is not just one NDC) and changed things a bit to be able to verify how everything works:

DECLARE @myTable TABLE
    ( tableId INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , NDC VARCHAR(11) NOT NULL 
    , ProdDescAbbr VARCHAR(30) NOT NULL 
    , [Description] VARCHAR(30) NOT NULL
    , Imprint1 VARCHAR(5) NOT NULL
    , Imprint2 VARCHAR(5) NOT NULL );

INSERT INTO @myTable VALUES
    ('00005250033', 'FIBERCON   TAB 625MG', 'film-coated', 'LL', 'F 1' ),
    ('00005250033', 'FIBERCON   TAB 625MG', 'tan', 'LL', 'F 1' ),
    ('00005250033', 'FIBERCON   TAB 625MG', 'scored', 'LQ', 'F 1' ),
    ('00005250033', 'FIBERCON   TAB 625MG', 'oblong', 'LL', 'F 2' ),
    ('00005250034', 'FIBERCON   TAB 625MG', 'short', '', '' ),
    ('00005250034', 'FIBERCON   TAB 625MG', 'green', '', '' ),
    ('00005250035', 'FIBERCON   TAB 625MG', 'open', '', '' ),
    ('00005250035', 'FIBERCON   TAB 625MG', 'yes', '', '' );

Hopefully you have some primary key on the table ... I took the liberty of creating one. Also, it may be a bit odd to take the imprints by the max value instead of having some kind of lookup table with an adjustable precedent value but we will not spend time evaluating the structure since that was not what the question was really about.

In this example (since I do not have an example query where you are consuming the results of your function) I am using the cte 'cteTemp' to represent whatever it would be in the parent query that defines which NDC's you want to know about and joining that to the source data ... @myTable in my example and dbo.DrugDescriptionAndImprints in your system:

WITH cteTemp AS
    (
    SELECT *
    FROM @myTable
    WHERE NDC IN ('00005250033', '00005250035')
    )
, cteImprints AS
    (
    SELECT  cte.NDC
        ,   Imprint1 = MAX(cte.Imprint1)
        ,   Imprint2 = MAX(cte.Imprint2)
    FROM cteTemp AS cte
    GROUP BY cte.NDC
    )
, cteFinal AS
    (
    SELECT  cte.NDC
        ,   Descriptions = STUFF(CONVERT(VARCHAR(1000), 
            (SELECT ',' + sq.[Description] 
            FROM cteTemp AS sq 
            WHERE sq.NDC = cte.NDC 
            ORDER BY sq.[Description] 
            FOR XML PATH (''))), 1, 1, '')
        ,   cte.Imprint1
        ,   cte.Imprint2
    FROM cteImprints AS cte
    )
SELECT  cte.NDC
    ,   concatenatedString = 
        cte.Descriptions + CASE WHEN cte.Imprint1 <> '' OR cte.Imprint2 <> '' THEN ' (' +
        CASE WHEN cte.Imprint1 <> '' THEN cte.Imprint1 + 
            CASE WHEN cte.Imprint2 <> '' THEN ' / ' + cte.Imprint2
            ELSE ''
            END
        ELSE cte.Imprint2 END + ')' ELSE '' END
FROM cteFinal AS cte;

There are some potential performance issues. Ideally we would only read from the source table (@myTable / dbo.DrugDescriptionAndImprints) once and do everything from there. The sticking point here is that we can get the max imprints in one query but I don't think we can avoid needing a separate pass to get the description concatenation for each NDC that we want. You could use a table variable or temp table to avoid going back to the source, but normally that is worse than accepting the return trips. But you would have to test this on your system and evaluate your execution plans to determine whether or not it meets your performance criteria.

Upvotes: 1

Related Questions