views:

63

answers:

4

Hi,

I have a SQL Select statement where I need to return certain values depending on a condition. I need to return multiple values each time, but my understanding of the Case statement is that you can only return a single value for each case.

I'm getting around this by using UNION statements at the moment, but it all looks a bit cumbersome - is there a better way to do this? Basically, I have a bunch of prompts, each with a response of either "Yes", "No" or "In Future" (I actually have more responses, but I'll just use 3 for the example to keep it short!) - I need to produce a column for each response type, with a 1 as the value for the appropriate response, and a 0 for all others. It's probably clearer to understand if you look at the SQL...

My (simplified) query looks like this:

SELECT branch,
       promptType,
       response,
       1 AS 'Yes',
       0 AS 'No',
       0 AS 'Not Discussed'
FROM prompts
WHERE response = 'Y'

UNION

SELECT branch,
       promptType,
       response,
       0 AS 'Yes',
       1 AS 'No',
       0 AS 'Not Discussed'
FROM prompts
WHERE response = 'N'

UNION

SELECT branch,
       promptType,
       response,
       0 AS 'Yes',
       0 AS 'No',
       1 AS 'Not Discussed'
FROM prompts
WHERE response = 'D'
+2  A: 

Something like...

SELECT branch,
       prompttype,
       CASE WHEN response = 'Y' THEN 'Yes'
            WHEN response = 'N' THEN 'No'
            WHEN response = 'D' THEN 'Not Discussed'
    FROM prompts;

might be what you are after.

After your comment, perhaps...

SELECT branch,
       prompttype,
       CASE WHEN response = 'Y' THEN 1 ELSE 0 AS Yes,
       CASE WHEN response = 'N' THEN 1 ELSE 0 AS No,
       CASE WHEN response = 'D' THEN 1 ELSE 0 AS Not_Discussed
    FROM prompts;

might do it.

Brian Hooper
I need to return all 3 responses each time, just with the value 0 or 1. So the prompt values ("Yes", "No", "Not Discussed") would actually be column headers and each record would have a 0 or 1 in each field.The output I get from the UNION statement in my original post is actually the correct output, I just thought there might be a better way of producing it.
TabbyCool
Ah. I see. I'll think about it a bit more then.
Brian Hooper
Thanks :-)Essentially, I need to be able to set the values for the "Yes", "No" and "Not Discussed" columns for each row from within the Case statement, but I'm not sure this is possible. Could it be done with an If statement instead?If not, the Union will just have to do, it just feels like a lot of duplication.
TabbyCool
No, I don't believe that is possible. Presumably you have something in mind other than just the choice of number; otherwise I can't see it matters whether the three columns are set together or separately.
Brian Hooper
No, it doesn't really matter whether they're set together or separately. I was really just looking for the most efficient and easily maintainable solution. My original query with the Unions works fine, I just wasn't sure if there was a better way to achieve the same thing. Matt's solution also works and is probably simpler than what I have currently as it means I don't need to use the Union statements anymore, making my query a lot smaller and therefore more readable.
TabbyCool
I always add an `ELSE 'UNKNOWN: '+COALESCE('"'+response+'"','null')` in `CASE` statements like this. This will help if someone adds a new value but doesn't update this code. However the best thing to do is add a new table: `Responses`, with a char(1) PK: `Response`, with a FK to prompts.response and a varchar(100) `ResponseDescription`, and the three bit/tiny int columns: `Yes`, `No`, and `Not_discussed`. insert your Y - N - D rows and just join to this new table and return the `Yes`, `No`, and `Not_discussed` columns. This way if any new responses are added your code will handle them.
KM
Good idea, KM. I'll remember that.
Brian Hooper
+2  A: 

Have you considered creating a decoding table for the responses, and joining to that?

For example, this would create a table for decoding the responses:

CREATE TABLE decoder (response CHAR(1), [Yes] BIT, [No] BIT, [Not Discussed] BIT)
INSERT INTO decoder VALUES ('Y', 1, 0, 0)
INSERT INTO decoder VALUES ('N', 0, 1, 0)
INSERT INTO decoder VALUES ('D', 0, 0, 1)

...and then you could join to it to get similar (the same?) results as you're getting with your UNION:

SELECT
    prompts.branch,
    prompts.prompttype,
    prompts.response,
    decoder.yes,
    decoder.no,
    decoder.[Not Discussed]
FROM 
    prompts INNER JOIN decoder ON prompts.response = decoder.response

Might be an approach worth considering; it's a more relational solution than your union, and probably easier to maintain.

Matt Gibson
Interesting, I'll give it a go! Hadn't thought of that, thanks :-)
TabbyCool
Accepted as best answer, the resulting query is much more maintainable than what I had originally.
TabbyCool
A: 
SELECT branch,
       prompttype,
    response,
       CASE WHEN response = 'Y' THEN 1 ELSE 0 END AS Yes,
       CASE WHEN response = 'N' THEN 1 ELSE 0 END AS [No],
       CASE WHEN response = 'D' THEN 1 ELSE 0 END AS  Not_Discussed
    FROM prompts;
Muhammad Kashif Nadeem
This is the same as Brian's answer - it doesn't produce the required output.
TabbyCool
A: 

If this proposed CASE statement of yours returned multiple values in a single column, what would the data type be: an array, a list, a table, an XML document, a business object, etc? For a truly relational database management system (TRDBMS), the answer would be a relation variable. But we are talking about SQL Server, here.

I think the answer you are looking for is that SQL Server's data types are scalar, not multivalued.

onedaywhen
I don't want multiple values in a single column, I want to return multiple columns, but have their values set in the case statement so that the output is the same as that produced by my original query.
TabbyCool