开发者

Multi-dimensional search on a table

I have the following table that stores the preferences of the users in the system

UserId | Product   | Brand     | City      |
-------------------------------------------
A      | Soap      | Tide      | NYC       |
A      | Cereal    | Dont-care | NYC       |
B      | Dont-Care | Tide      | Dont-care |
C      | Shampoo   | Dont-care | Dont-Care |

I would like to search this based on user provided search values. So if one searches for

City: NYC, Brand: Tide

the output should be:

    A    | Soap      | Tide      | NYC       |
    B    | Dont-Care | Tide      | Dont-care |

where as if they search for

Brand: Tide, Product: Soap

the result should be:

    A    | Soap      | Tide      | NYC       |

The current solution I have, is the following query (where null represents 'don't care') going against a MySQL table:

select *
from user_preferences
where (product is null or product = <user provided value>开发者_运维技巧)
and (brand is null or brand = <user provided value>)
and (city is null or city = <user provided value>)

Though it works as expected, the [and + (or)] combination makes me think this is not the right way to do this. I am also fairly certain that once the dataset increases, the query will not perform well.

What would be most efficient way of storing and retrieving such data? Are there any no-sql type approaches that can be used to make this efficient?

Update

After some googling around I figured the approach I have may be the safest bet. One factor I still am ambivalent about with this approach is that adding another 'searchable' attribute would mean adding a new column.

This blog about the EAV anti-pattern provides some good reading material on such a scheme. Also see how friend-feed uses MySQL for another take on storing variable attributes in a table.


You could use the IFNULL() function.

It would be something like this:

select *
from user_preferences
where ifnull(product, <user provided value>) = <user provided value>
and ifnull(brand, <user provided value>) = <user provided value>
and ifnull(city, <user provided value>) = <user provided value>


based on @Pompom6784's answer -

but logically it has very different conditions-

select * from user_preferences 
where product = nvl(<user provided value> , product)
and brand = nvl(<user provided value> ,brand)
and city = nvl(<user provided value> ,city)

I am a oracle pl sql developer so please check above query having syntex of oracle sql.. if in your database ifnull is the function to check for null values please replace nvl() with ifnull()

@Pompom6784 you had to apply ifnull on < user provided value > not on column values ..


Since you ask how to store such data, here's what I'd probably do:

            USERS
            userid  PK
            name
            etc


            PREFTYPES
            preftypeid  PK
            prefname



           PREFTYPEVALUES
           preftypeid  foreign key references PREFTYPES(preftypeid)
           value

           composite primary key (preftypeid, value)

PREFTYPEVALUES table above lets you constrain the possible value-entries in the USERPREFS table:

            USERPREFS
            userid      foreign key references USERS(userid)
            preftypeid  
            value

           => composite  foreign key(preftypeid, value) references PREFTYPEVALUES(preftypeid, value)

To combine the two sets of users, the first set being those who prefer Tide for their soap and the second set being those who prefer NYC as their city, you could UNION the two sets:

             select U.userid, U.name, UP.value
             from USERS U 
             inner join USERPREFS UP on U.userid = UP.userid
             where 
             (UP.preftypeid = 'soap' and UP.value = 'Tide' )


              UNION

             select U.userid, U.name, UP.value
             from USERS U 
             inner join USERPREFS UP on U.userid = UP.userid
             where                  
             (UP.preftypeid = 'city' and UP.value = 'NYC')

but this structure would also permit queries that do not use UNION, of course.

             select U.userid, U.name, UP.value
             from USERS U 
             inner join USERPREFS UP on U.userid = UP.userid
             where 
             (UP.preftypeid = 'soap' and UP.value = 'Tide' )
              OR
             (UP.preftypeid = 'city' and UP.value = 'NYC')

I bet your next question would be: how to get a single row for each user, with the user's preferences side-by-side?

              user-A, Tide, NYC

That denormalization is better left to the presentation layer, although it can be done in SQL using a variety of inelegant approaches. The SQL key here is composite (user,preftype).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜