简体   繁体   中英

Pivoting a table-valued function

I have a TVF that returns two columns: 'measure' (a name) and 'score', a numeric score for that measure:

dbo.ScoringFunction(param1, param2, ..., paramN)
Measure   Score
-------   -----
measure1  10
measure2  5
...       ...
measureN  15

I'm running this function against a large number of rows that contain its parameters:

Name   Param1   Param2   ...   ParamN
----   ------   ------         ------
Daniel 12       5              6
etc.

I am trying to find a way to display the measures and their scores next to the parameters that determine those scores:

Name   Param1   Param2   ...   ParamN   measure1   measure2   ...   measureN
----   ------   ------         ------   --------   --------         --------
Daniel 12       5              6        10         5                15
etc.

So far I have tried using a pivot table, but it's tricky since the data being pivoted is contained in a TVF rather than a static table. I've also tried using CROSS APPLY, but once I have the data (measures & scores), I'm still unable to pivot it into a nicely formatted row.

If anybody has any ideas, they would be much appreciated!

If you make a function that looks like this:

CREATE FUNCTION [dbo].[fGetSpecificMeasures]
(
    @HeightScore INT
    , @WeightScore INT
    , @TvScore INT
)
RETURNS TABLE
RETURN
(
    SELECT
        Final.Height AS tv_height_score
        , Final.[Weight] AS tv_weight_score
        , Final.TV AS tv_score
    FROM
    (
        SELECT measure, score FROM ScoringRubric WHERE measure = 'Height' AND @HeightScore BETWEEN bottom_of_range AND top_of_range
            UNION ALL
        SELECT measure, score FROM ScoringRubric WHERE measure = 'Weight' AND @WeightScore BETWEEN bottom_of_range AND top_of_range
            UNION ALL
        SELECT measure, score FROM ScoringRubric WHERE measure = 'TV' AND @TvScore BETWEEN bottom_of_range AND top_of_range
    ) Base
    PIVOT
    (
        MAX(score) 
        FOR measure
        IN
        (
            [Height]
            , [Weight]
            , [TV]
        )
    ) Final
);
GO

And one that looks like this:

CREATE FUNCTION [dbo].[fGetMeasureScore]
(
    @Measure VARCHAR(50)
    , @Value INT
)
RETURNS TABLE
RETURN
(
    SELECT
        score
    FROM ScoringRubric
    WHERE measure = @Measure
        AND @Value BETWEEN bottom_of_range AND top_of_range
);
GO

Then you can get your data with either of the following:

DECLARE @User VARCHAR(50) = 'Daniel'

SELECT
    UserProfile.*
    , HeightScore.score AS tv_height_score
    , WeightScore.score AS tv_weight_score
    , TvScore.score AS tv_score
FROM UserProfile
INNER JOIN ScoringRubric HeightScore
    ON HeightScore.measure = 'Height'
    AND UserProfile.height BETWEEN HeightScore.bottom_of_range AND HeightScore.top_of_range
INNER JOIN ScoringRubric WeightScore
    ON WeightScore.measure = 'Weight'
    AND UserProfile.[weight] BETWEEN WeightScore.bottom_of_range AND WeightScore.top_of_range
INNER JOIN ScoringRubric TvScore
    ON TvScore.measure = 'TV'
    AND UserProfile.TV BETWEEN TvScore.bottom_of_range AND TvScore.top_of_range
WHERE UserProfile.name = @User

SELECT
    *
FROM UserProfile
CROSS APPLY dbo.fGetSpecificMeasures(height, [weight], TV)
WHERE name = @User

SELECT
    UP.*
    , HeightScore.score AS tv_height_score
    , WeightScore.score AS tv_weight_score
    , TvScore.score AS tv_score
FROM UserProfile UP
CROSS APPLY fGetMeasureScore('Height', UP.height) HeightScore
CROSS APPLY fGetMeasureScore('Weight', UP.[weight]) WeightScore
CROSS APPLY fGetMeasureScore('TV', UP.TV) TvScore
WHERE UP.name = @User

I don't really know which one you'll find most appropriate for your uses. Let me know if you have questions.

As for your original question, if this were the function:

CREATE FUNCTION [dbo].[fGetMeasureScoresOriginal]
(
    @HeightScore INT
    , @WeightScore INT
    , @TvScore INT
)
RETURNS TABLE
RETURN
(
    SELECT measure, score FROM ScoringRubric WHERE measure = 'Height' AND @HeightScore BETWEEN bottom_of_range AND top_of_range
        UNION ALL
    SELECT measure, score FROM ScoringRubric WHERE measure = 'Weight' AND @WeightScore BETWEEN bottom_of_range AND top_of_range
        UNION ALL
    SELECT measure, score FROM ScoringRubric WHERE measure = 'TV' AND @TvScore BETWEEN bottom_of_range AND top_of_range
)
GO

Then you could write the query and pivot like so:

SELECT
    Final.name
    , Final.OriginalHeight AS height
    , Final.OriginalWeight AS [weight]
    , Final.OriginalTv AS TV
    , Final.Height AS tv_height_score
    , Final.[Weight] AS tv_weight_score
    , Final.TV AS tv_score
FROM
(
    SELECT
        UP.name
        , UP.height AS OriginalHeight
        , UP.[weight] AS OriginalWeight
        , UP.TV AS OriginalTv
        , Measures.measure
        , Measures.score
    FROM UserProfile UP
    CROSS APPLY dbo.fGetMeasureScoresOriginal(UP.height, UP.[weight], UP.TV) Measures
    WHERE UP.name = @User
) Base
PIVOT
(
    MAX(score)
    FOR measure
    IN
    (
        [Height]
        , [Weight]
        , [TV]
    )
) Final

EDIT : Just realized I didn't answer the original question. Adding that now.

If you want to pivot a static set of rows you can use the technique as I described here .

Reposting the example, so it will be different tables, but the technique can be applied in your case as well I think.

SELECT
  Customers.CustID,
  MAX(CASE WHEN CF.FieldID = 1 THEN CF.FieldValue ELSE NULL END) AS Field1,
  MAX(CASE WHEN CF.FieldID = 2 THEN CF.FieldValue ELSE NULL END) AS Field2,
  MAX(CASE WHEN CF.FieldID = 3 THEN CF.FieldValue ELSE NULL END) AS Field3,
  MAX(CASE WHEN CF.FieldID = 4 THEN CF.FieldValue ELSE NULL END) AS Field4
  -- Add more...

  FROM Customers 

  LEFT OUTER JOIN CustomFields CF
  ON CF.ID = Customers.CustID

  WHERE Customers.CustName like 'C%'

  GROUP BY Customers.CustID

Without changing too much (hopefully), I would do the pivoting in the function , then use the function in CROSS APPLY and pull the columns. So if your function is something like this:

CREATE FUNCTION dbo.ScoringFunction (parameters)
RETURNS TABLE
RETURN (

SELECT Measure, Score
FROM …

)

then I would rewrite it like this:

CREATE FUNCTION dbo.ScoringFunction (parameters)
RETURNS TABLE
RETURN (


  SELECT Measure, Score
  FROM …


)

and use it in the final query like this:

SELECT
  t.Name,
  t.param1,
  t.param2,
  …
  x.measure1,
  x.measure2,
  …
FROM atable t
CROSS APPLY dbo.ScoringFunction (t.param1, t.param2, …) x

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM