views:

101

answers:

2

I need to run a sweepstakes script to get X amount of winners from a customers table. Each customer has N participations. The table looks like this

CUSTOMER-A 5

CUSTOMER-B 8

CUSTOMER-C 1

I can always script to have CUSTOMER-A,B and C inserted 5, 8 and 1 times respectively in a temp table and then select randomly using order by newid() but would like to know if there's a more elegant way to address this.

+1  A: 

try this:

   Select Top X CustomerId
   From (Select CustomerId,
         Rand(CustomerId) *
            Count(*) / 
              (Select Count(*) 
               From Table) Sort
         From Table
         Group By CustomerId) Z
   Order By Sort Desc

EDIT: abovbe assumed multiple rows per customer, one row per participation... Sorry, following assumes one row per customer, with column Participations holding number of participations for that customer.

Select Top 23 CustomerId
From ( Select  CustomerId,
        Participations - RAND(CustomerId) * 
         (Select SUM(Participations ) From customers) sort
       from customers) Z
Order By sort  desc
Charles Bretana
How does this give more opportunities to a customer based on the participations column? Doesn't the divide by count() negates the previous multiply by count()? I tried this but the result always comes up the same.
Joel
The divide by count(*) is inside the sub-sub query, and should be the total count of all rows, the first count is inside the sub query, which is aggregate countof just one customer's rows. But the whole thing may be using the same rand each time...
Charles Bretana
Interesting solution, but have you tried it with real data? I got CustomerIds in descending order each time I ran it.
Rob Garrison
I have not tried with real data to be honest.. Have a paying job I need to devote most of my time too [grin]. It shoiuld be sorting the final output by the defined 'Sort' column, descending, is it not ? Can you run just the first subquery (aliased as Z) and see what the Sort column looks like?
Charles Bretana
If I order by Sort, I always get back CustomerIDs in ascending order. Sort value for CustomerID 1 is 0.142718398642585. And yes, I have a job too. I just found this question intriguing.
Rob Garrison
Ok, I got it.. actually tried it I was (unfortunately assuming the table had a separate record for each customer participation... Now I understand, and have edited to fix...
Charles Bretana
On a table with a separate record for each customer participation a select top X from table order by newid() does the trick. I tried your second query but always get the same results and I need to get a different result-set each time making it strictly aleatory.
Joel
+1  A: 

(Update: Added final query.)
(Update2: Added single query to avoid temp table.)

Here's the hard part using a recursive CTE plus the final query that shows "place".

Code

DECLARE @cust TABLE (
    CustomerID      int     IDENTITY,
    ParticipationCt int
)

DECLARE @list TABLE (
    CustomerID      int,
    RowNumber       int
)

INSERT INTO @cust (ParticipationCt) VALUES (5)
INSERT INTO @cust (ParticipationCt) VALUES (8)
INSERT INTO @cust (ParticipationCt) VALUES (1)
INSERT INTO @cust (ParticipationCt) VALUES (3)
INSERT INTO @cust (ParticipationCt) VALUES (4)

SELECT * FROM @cust

;WITH t AS (
    SELECT
        lvl = 1,
        CustomerID,
        ParticipationCt
    FROM @Cust

    UNION ALL

    SELECT
        lvl = lvl + 1,
        CustomerID,
        ParticipationCt
    FROM t
    WHERE lvl < ParticipationCt
)
INSERT INTO @list (CustomerID, RowNumber)
SELECT
    CustomerID,
    ROW_NUMBER() OVER (ORDER BY NEWID())
FROM t

--<< All rows
SELECT * FROM @list ORDER BY RowNumber

--<< All customers by "place"
SELECT
    CustomerID,
    ROW_NUMBER() OVER (ORDER BY MIN(RowNumber)) AS Place
FROM @list
GROUP BY CustomerID

Results

 CustomerID ParticipationCt
----------- ---------------
          1               5
          2               8
          3               1
          4               3
          5               4

 CustomerID   RowNumber
----------- -----------
          4           1
          1           2
          1           3
          2           4
          1           5
          5           6
          2           7
          2           8
          4           9
          2          10
          2          11
          2          12
          1          13
          5          14
          5          15
          3          16
          5          17
          1          18
          2          19
          2          20
          4          21

 CustomerID Place
----------- -----
          4     1
          1     2
          2     3
          5     4
          3     5


Single Query with No Temp Table

It is possible to get the answer with a single query that does not use a temp table. This works fine, but I personally like the temp table version better so you can validate the interim results.

Code (Single Query)

;WITH List AS (
    SELECT
        lvl = 1,
        CustomerID,
        ParticipationCt
    FROM @Cust

    UNION ALL

    SELECT
        lvl = lvl + 1,
        CustomerID,
        ParticipationCt
    FROM List
    WHERE lvl < ParticipationCt
),
RandomOrder AS (
    SELECT
        CustomerID,
        ROW_NUMBER() OVER (ORDER BY NEWID()) AS RowNumber
    FROM List
)
SELECT
    CustomerID,
    ROW_NUMBER() OVER (ORDER BY MIN(RowNumber)) AS Place
FROM RandomOrder
GROUP BY CustomerID
Rob Garrison
Currently testing this with my data. Will post back.
Joel
Can you explain me the lvl usage? When applying to my table I get an error on the where clause using lvl.
Joel
"lvl" is the level of recursion. It starts with the "base" (the part before the UNION) where lvl = 1. The recursion is the part after the UNION. Recursion stops when lvl = ParticipationCt.
Rob Garrison
Be sure to check out MAXRECURSION if you use this solution.
Rob Garrison
I was incorrectly setting the union select to the same source table until you mentioned maxrecursion and I realized the union query references the with result. This worked perfectly for my purposes. Thanks a lot!
Joel