tags:

views:

342

answers:

5

Say I have a query like this:

SELECT * FROM my_table WHERE name = "john doe" AND phone = "8183321234" AND email = "[email protected]" AND address = "330 some lane";

But say I only need 3 out of the 4 to match, I know I can write a very long query with several ORs but I was wondering if there was a feature for this?

Thanks.

+20  A: 
SELECT
  * 
FROM 
  my_table 
WHERE 
  CASE WHEN name = "john doe"           THEN 1 ELSE 0 END +
  CASE WHEN phone = "8183321234"        THEN 1 ELSE 0 END +
  CASE WHEN email = "[email protected]" THEN 1 ELSE 0 END +
  CASE WHEN address = "330 some lane"   THEN 1 ELSE 0 END
  >= 3;

Side note: this will very likely not be using indexes efficiently. On the other hand, there will very likely be no indexes on these kinds of columns anyway.

Tomalak
that's awesome - probably slow, but cool.
Brian
+1 Clever solution.
soulmerge
mysql does that! wow!
tharkun
I think it will suffice to just sum the comparison expressions as they themselse always return 0 or 1: `name="john doe" + phone="12345" + email="[email protected]" + address="123 some lane" >= 3`.
Gumbo
Yes, that does it on MySQL, I guess. Nevertheless I like explicitness better, and a comparison operation does not necessarily return a numerical value on other systems.
Tomalak
Hm... I wonder why this keeps catching down-votes. Is it wrong or is this just opinion voting?
Tomalak
It's correct, but chaos' solution is better/cleaner/shorter.
Paolo Bergantino
Well, yeah. You could argue that it is cleaner and shorter. I would hardly call my solution "overcomplex" though. It's correct, not performing any worse *and* it would run on almost any other SQL environment there is. Frankly, I don't see a good reason to down-vote, but there you go.
Tomalak
Well, I'd say it is at the very least flirting with over complexity, especially when there's a much shorter way of doing it. That being said, I've removed my downvote, because I guess you could also make the argument chaos' solution is not very clear to someone that doesn't understand how the comparisons get evaluated into 1s. :)
Paolo Bergantino
@Paolo Bergantino: "especially when there's a much shorter way of doing it" is a very Perlesque view on the world. ;-) I am used to XSLT and ColdFusion, so verbosity does not drive me away. I'm rather verbose than implicit any time, even in environments where I don't have to.
Tomalak
Yes, I suppose I should have said 'holy thundering sesquipedalian verbosity, Batman'. If someone doesn't understand numeric evaluation of boolean expressions, though, they should be able to infer it pretty quickly from the existence of my example. :)
chaos
@chaos: ses...what? :)
Tomalak
@Tomalak: "sesquipedialian" = "1,5 feet long", from Horace's "Ars Poetica"
Quassnoi
@Quassnoi: My Google Translate already told me. :) I just like the word a lot, never saw it before. That's one detail I love about the English language - it never stops to surprise and amuse me.
Tomalak
That's more the Latin language. :)
chaos
@chaos: I am quite aware of that. ;) Nevertheless it's the English language the word is used in.
Tomalak
@chaos how did that word end up in your vocabulary?
John Isaacks
A friend of mine used it as an element of his merciless mocking of certain turns of phrase I would use. I love it, of course. It's like I always say: never use a big word where a diminutive lexeme will suffice.
chaos
+7  A: 

Same thing using indexes:

SELECT  *
FROM    (
        SELECT  id
        FROM    (
                SELECT  id
                FROM    mytable _name
                WHERE   name = 'john doe'
                UNION ALL
                SELECT  id
                FROM    mytable _name
                WHERE   phone = '8183321234'
                UNION ALL
                SELECT  id
                FROM    mytable _name
                WHERE   email = "[email protected]"
                UNION ALL
                SELECT  id
                FROM    mytable _name
                WHERE   address = '330 some lane'
                ) q
        GROUP BY 
                id
        HAVING
                COUNT(*) >= 3
        ) di, mytable t
WHERE   t.id = di.id

See the entry in my blog for performance details.

Quassnoi
+1, I was just waiting for you ;-)
Tomalak
I upvoted you first, that's why it took so long :)
Quassnoi
Tedious task, this up-voting business, I know. :-D
Tomalak
OBTW: Can you clarify whether or not indexes are used within CASE WHEN predicates? Thank you.
Tomalak
Sure. The indexes are not used. I'm going to make today's entry in my blog about this question, see there for details.
Quassnoi
I'll keep an eye on it, thanks.
Tomalak
+14  A: 

Holy overcomplexity, Batman.

SELECT * 
FROM my_table 
WHERE (
    (name = "john doe") +
    (phone = "8183321234") +
    (email = "[email protected]") +
    (address = "330 some lane")
) >= 3;
chaos
+1 for the Batman comment. I'm not sure if that works, but it does, the +1 is well deserved anyways, as that is very neat. :)
Paolo Bergantino
Thank you kindly. And of course it works; I never post SQL without testing it if I can help it.
chaos
I am still newb to SQL but I don't understand how that query does what it does.
John Isaacks
Does (name = "john doe") return an int instead of a Boolean?
John Isaacks
MySQL has no concept of 'booleans' as distinct from ints. Even most language that do have such a concept allow evaluation of a boolean in numeric context, yielding 0 or 1 integer.
chaos
Thanks, I knew you could use 1 or 0 as a Boolean but I didn't know you could use a Boolean as a 1 or a 0 (If that makes sense).
John Isaacks
+1  A: 

I like the IF construct:

SELECT * FROM my_table
WHERE
(    IF(name    = 'john doe', 1, 0) +
     IF(phone   = '8183311234', 1, 0) +
     IF(email   = '[email protected]', 1, 0) +
     IF(address = '330 some lane', 1, 0)
) >= 3
orlandu63
A: 

Modifying Tomalak's query slightly so that it will use indexes if they are present. Although unless there is an index on each field, a full table scan will happen anyway.

SELECT
*, 
(
    IF(name="john doe", 1, 0) +
    IF(phone = "8183321234", 1, 0) +
    IF(email = "[email protected]", 1, 0) +
    IF(address = "330 some lane", 1, 0) 
) as matchCount
FROM my_table 
WHERE 
    name = "john doe" OR 
    phone = "8183321234" OR 
    email = "[email protected]" OR 
    address = "330 some lane"
HAVING matchCount >= 3
Brent Baisley
You need to indent code samples 4 spaces to make them display nicely. I made this change for you, hope you don't mind. :)
Paolo Bergantino
Mysql index merge performs worse than UNION ALL. See here: http://explainextended.com/2009/03/07/selecting-friends/
Quassnoi
@Paolo how do you indent when you are typing code samples on here?
John Isaacks
@John Isaacks: select a piece of code and press a 0100101 button above the edit field. The selected code will be indented with 4 spaces.
Quassnoi
@John Isaacks: For copy/paste code samples, it helps to replace all tabs with spaces in a text editor beforehand, since tabs and spaces combined tend to blow the layout.
Tomalak