Reputation: 9138
Say I have 2 tables:
Person
+-------------+
| Id. Name. |
+-------------+
| 1. Hello |
| 2. World |
+-------------+
PersonAttribute
+------------------------------------------------------+
| Id. PersonId. AttributeName AttributeValue |
+------------------------------------------------------+
| 1. 1. Gender M |
| 2. 1. WearsGlasses Y |
| 3. 1. LikesColorGreen N |
| 4. 2. Gender F |
| 5. 2. WearsGlasses N |
| 6. 2. LikesColorGreen Y |
+------------------------------------------------------+
What if I wanted to return results where I wanted to look for people that matched an arbitary set of attributes
So I have 2 questions:
how would I search for people with a query like: Gender = F and LikesColorGreen = Y
What if I wanted to make the query allow for an arbitrary set of attributes Eg: a Query table specifies
QueryAttributeName QueryValue
+----------------------------------+
| 1. LikesColorGreen N |
| 2. WearsGlasses Y |
| 3. Gender F |
+----------------------------------+
The one query I have for "Gender = F and LikesColorGreen = Y" is:
select * from Person p
Join PersonAttribute pa on p.id = pa.PersonId and pa.AttributeName = Gender and AttributeValue = F
interset
select * from Person p
Join PersonAttribute pa on p.id = pa.PersonId and pa.AttributeName = LikesColorGreen and AttributeValue = Y
Is there a better query that will get this?
What is such a query called?
Upvotes: 2
Views: 37
Reputation: 6713
What you are trying to implement is called a Entity-Attribute-Value Model (EAV). It can give you some flexibility in satisfying customer's unique requirements without needing to modify application code and database structure, but it also has it's drawbacks.
One thing that will enhance your performance is if you create another table for attributes so that you are using an [AttributeID]
in PersonAttribute
instead of the text. That will make your joins much faster and reduce the size of your table which could get pretty big depending on how many entities and attributes you add.
All of the attributes in your examples are binary values (y/n, m/f... etc). If a majority of your attributes are really bit flags, you might get more bang from storing some data in int, bigint, or varbinary fields and have a configuration table allocate which bits map to your attributes. Then do a check on a bit mask to find out if a record matches your query parameters. You could also do a mix, using bit fields and an attribute table.
Otherwise your fastest query to get all attribute matches with AND is probably just going to be your standard join with appropriate indexes:
select distinct Person.*
from Person
join PersonAttribute att1
on Person.PersonID=att1.PersonID
and att1.attId = 1 --maps to AGE
and att1.AttributeValue BETWEEN 30 AND 40
join PersonAttribute att2
on Person.PersonID=att2.PersonID
and att2.attId = 1 --maps to Income
and att2.AttributeValue < 200000
join ... etc.
Upvotes: 1
Reputation: 2583
You can try this :
select pid from atts where
(aname='Gender' and avalue='M')
or
(aname='WearsGlasses' and avalue='Y')
or
(aname='LikesColorGreen' and avalue='Y')
group by pid having COUNT(*)=3
It's easy to expand the query to check more attributes.
Performance can be an issue but with this kind of data structure, you can't expect too much.
If you have a table to match, you can try:
select pid from atts a join att_match m
on a.aname=m.aname and a.avalue = m.avalue
group by pid having COUNT(*)= (select count(*) from att_match);
it matches all conditions in your "match crietria" table.
Upvotes: 1