Transact SQL CASE over a variable length input_expression
Question
I have to produce an ad-hoc report on the number of transactions made with different credit card types. For the purposes of the report it is fine to assume that all credit cards that start with a 4 are VISA cards and that those that start with a 5 are MasterCard.
This query works well for the above distinctions:
select card_type =
case substring(pan,1,1)
when '4' then 'VISA'
when '5' then 'MasterCard'
else 'unknown'
end,count(*),
sum(amount)
from transactions
group by card_type
However in our situation (not sure how this works world wide) all cards that start with a 3 can be considered Diners Club Cards except for those that start with a 37 which are AMEX cards.
Extending the above query like this seems like a complete hack
select card_type =
case substring(pan,1,2)
when '30' then 'Diners'
...
when '37' then 'AMEX'
...
when '39' then 'Diners'
when '40' then 'VISA'
...
when '49' then 'VISA'
when '50' then 'MasterCard'
...
when '59' then 'MasterCard'
else 'unknown'
end,count(*),
sum(amount)
from transactions
group by card_type
Is there an elegant way of grouping by the first digit in all cases except where the first two digits match the special case?
I also have no idea how to Title this question if anyone wants to help out...
EDIT: I had the values for MasterCard and VISA mixed up, so just to be correct :)
Solution
You can do case statements like the following:
select case
when substring(pan,1,2) = '37' then 'AMEX'
when substring(pan,1,1) = '3' then 'Diners'
when substring(pan,1,1) = '4' then 'Mastercard'
when substring(pan,1,1) = '5' then 'VISA'
else 'unknown'
end,
count(*),
sum(amount)
from transactions
group by card_type
OTHER TIPS
Not sure about your system, but in Oracle CASE expressions are exactly that, so you can nest them:
case substring(pan,1,1)
when '3' then case substring(pan,2,1)
when '7' then 'Amex'
else 'Diners'
end
when '4' then 'VISA'
when '5' then 'MasterCard'
else 'unknown'
end
you could just store the card type column in your table and FK to a card type table, or try something like:
CASE
WHEN LEFT(pan,2)='37' then ...
WHEN LEFT(pan,1)='3' then ...
.....
EDIT
you should really consider storing a card type value in a table. Determine it one time when inserting and then then you can query your data without jumping through these hoops each time. You will also protect yourself if the algorithm changes at some point, all existing data will be correct
Personally, I think your 'longhand' way is elegant in that it is easy to read and maintain than I would find the answer @samjudson (but I do see the appeal of their approach). You could use OR
to test more than one value per case. I find LIKE
easier to read but that could just be me ;) e.g.
CASE
WHEN card_type LIKE '37%'
THEN 'AMEX'
WHEN (
card_type LIKE '30%'
OR card_type LIKE '39%'
)
THEN 'Diners'
WHEN (
card_type LIKE '40%'
OR card_type LIKE '49%'
)
THEN 'VISA'
WHEN (
card_type LIKE '50%'
OR card_type LIKE '59%'
)
THEN 'MasterCard'
ELSE
'unknown'
END