A: 
SELECT UserLogin, FirstName, LastName 
FROM Users 
where UserID in ( 
    select UserID
    from UserPropertyValues upv1
    inner join UserPropertyValues upv2 on upv1.UserID = upv2.UserID 
        and upv1.PropertyValueID = 1 and upv2.PropertyValueID = 5
) a
RedFilter
Clarified the post so that the query can be based on parameters passed to it.
Steve Wright
A: 

You need to join to the key_value store individually for each property you want to get (2 properties, 2 joins, 100 properties, 100 joins), This is why it is a poor design choice for performance.

HLGEM
How else would I be able to design it so users can add their own properties?
Steve Wright
First do a good job of defining what people will need so they don't need to add many properties. It is better to have fields with null values becasue someone doesn't need them than a key-value store. You can also give each client a set number of custom fields they can define themselves in a related table. So customfield1 means districts to client1 and regions to client2.There is no way to allow clients to add as many fields as they want and have decent performance, flexibilty is the opposite of performance you can have one or the other, not both. Most clients strongly prefer performance.
HLGEM
Ran out of room. In my experience most clients prefer performance over flexibility. Flexibility almost always has a huge performance cost and frankly customers tend to hate customizing and then trying to make things work for things the orginal designers should have put there anyway as most people would want them.
HLGEM
unfortunately, the design is already in place and I am building a report to fit what it already does - I would probably have done this, but this is a multi-tenant app and it used my multiple customers who utilize these fields in different ways. I see your point on performance.
Steve Wright
A: 

Expanding on OrbMan's answer. This is going to give you one row per user/property.

SELECT U.UserLogin, U.FirstName, U.LastName, P.Name AS PropertyName, PV.Name AS PropertyValue
FROM Users U
INNER JOIN UserPropertyValues UPV
ON U.ID = UPV.UserID
INNER JOIN Properties P
ON UPV.PropertyID = P.ID
INNER JOIN PropertyValues PV
ON UPV.PropertyID = PV.ID
where UserID in ( 
    select UserID
    from UserPropertyValues upv1
    inner join UserPropertyValues upv2 on upv1.UserID = upv2.UserID 
        and upv1.PropertyValueID IN (@PropertyIDs))
gbogumil
+1  A: 

I figured out how to get this working in a completely hacky fashion:

SELECT UserLogin, FirstName, LastName
FROM Users
LEFT OUTER JOIN 
(
    SELECT UserID
    FROM UserPropertyValues 
    WHERE PropertyValueID IN (@PropertyIDs)
    GROUP BY UserID
    HAVING COUNT(UserID) = 
        (
            SELECT COUNT(*) FROM 
            (
            SELECT PropertyID FROM PropertyValues 
            WHERE ID IN (@PropertyIDs) GROUP BY PropertyID
            ) p
        )   
) filtered on Users.UserID = filtered.UserID

Because we can determine the number of properties used in the parameter @PropertyIDs (returned by the select count(*)), we can make sure that the users returned from the query has the same number of properties as what was passed in @PropertyIDs.

Steve Wright
That's a good solution. If @PropertyIDs is a table variable you may need something like "IN (SELECT p.ID FROM @PropertyIDs p)". And you may want to change that LEFT OUTER JOIN into an INNER JOIN.
Anthony Faull
In this case, @PropertyIDs corresponds to a multi-value parameter in SSRS 2005. The LEFT JOIN was left in on accident because this filter could be null in my full query.
Steve Wright