views:

155

answers:

4

Hi all,

we have a DB which stores users who may have pictures.

I am looking for an elegant way in SQL to get the following results: Select n users. Of those n users e.g. 60% should have an associated picture and 40% should not have a picture. If there are less than 60% users having a picture the result should be filled up with users wihtout an image.

Is there some elegant way in SQL without firing multiple SELECTs to the DB?

Thank you very much.

A: 
SELECT TOP(n) HasPicture --should be 0 or 1 to allow ORDER
   FROM Users
   ORDER BY 1
Dewfy
A: 

Ugly code:

SELECT TOP @n * FROM
(


      //-- We start selecting users who have a picture (ordered by HasPicture)
      //-- If there is no more users with a picture, this query will fill the 
      //-- remaining rows with users without a picture
      SELECT TOP 60 PERCENT * FROM tbUser
      ORDER BY HasPicture DESC

      UNION

      //-- This is to make sure that we select at least 40% users without a picture
      //-- AT LEAST because in the first query it is possible that users without a 
      //-- picture have been selected
      SELECT TOP 40 PERCENT * FROM tblUser
      WHERE HasPicture = 0

      //-- We need to avoid duplicates because in the first select query we haven't 
      //-- specified HasPicture = 1 (and we didn't want to).
      AND UserID not IN
      (
        SELECT TOP 60 PERCENT UserID FROM tbUser
        ORDER BY HavePicture DESC
      )
 )
Max
-1 Huh? You aren't limiting to N overall and the code for telling who has a picture and who doesn't, isn't making sense.
Shannon Severance
I added some comments so I guess it makes more sense now... I agree it's not the most elegant piece of code but I think it works fine. I personally prefer Rob's solution.
Max
Thanks Max. You can mark it as the answer if you like. I feel like I don't have many accepted answers on my profile yet. Most of my reputation comes from one answer I posted when I first joined the site.
Rob Farley
@Rob. I would love to mark it as the answer but unfortunaly I am not the "asker", even though we share the same name.-Max (in Geneva)
Max
+2  A: 

So you provide @n, being the number of users you want. You provide @x being the percentage of those users who should have pictures.

select top (@n) *
from
(
select top (@n * @x / 100) *
from users
where picture is not null 

union all

select top (@n) *
from users
where picture is null 
) u
order by case when picture is not null then 1 else 2 end;

So... you want at most @n * @x / 100 users who have pictures, and the rest have to be people who don't have pictures. So I'm doing a 'union all' between my @n*@x/100 picture-people and enough others to complete my @n. Then I'm selecting them back, ordering my TOP to make sure that I keep the people who have a picture.

Rob

Edited: Actually, this would be better:

select top (@n) *
from
(
select top (@n * @x / 100) *, 0 as NoPicture
from users
where picture is not null 

union all

select top (@n) *, 1 as NoPicture
from users
where picture is null 
) u
order by NoPicture;

...because it removes the impact of the ORDER BY.

Rob Farley
But... if you don't have enough people WITHOUT a picture, do you need to fill in the (100-x)% with picture-people?
Rob Farley
hm no. this won't be a requirement since there are far more people without a picture.
Max
Ok, cool. Well, read through all the options, make sure you understand what each scenario is doing, and pick what matches your requirements best (which I'm not sure were particularly clear).
Rob Farley
I'd make the selection of users with pictures such that is calculated how many. Then drop the outside select top (@N) ... order by case. That way you won't need any sorts.
Shannon Severance
Actually Shannon, I'm going to tweak my query a bit.
Rob Farley
A: 

Use the Select case for this type of Requirement.

KuldipMCA