简体   繁体   中英

sql query to select a random value (weighted)

I have a table with three fields: ID,Value,Count

ID and Value make up the PK.

Given an ID I want to select a value weighted by Count and then decrement the count by one.

So If I have

1  A  2
1  B  3

I should have a 2/5 chance of getting an A and a 3/5 chance of getting a B. If A is selected the table should look like this after

1  A  1
1  B  3

Next time A will have a 1/4 chance of being selected and B will have a 3/4

Ideas?

Thanks!

Update:

The Weight is like the number of chips of that value that would be placed in a bag. And then one randomly chosen.

CREATE TABLE #Vals
(
ID INT,
Value CHAR(1),
[Count] INT,
PRIMARY KEY(ID,Value)
)

INSERT INTO #Vals
SELECT 1,'A',2 UNION ALL
SELECT 1,'B',3

;WITH Nums AS
(
SELECT number 
FROM master..spt_values
WHERE number>0 AND type='P'
), Row AS
(
SELECT TOP 1 v.*
FROM #Vals v
JOIN Nums n ON n.number <= v.[Count]
WHERE v.ID=1
ORDER BY CHECKSUM(NEWID())
)
UPDATE Row 
SET [Count] = [Count] -1
OUTPUT inserted.*


DROP TABLE #Vals

In MySQL (maybe someone could translate it into an MSSQL UPDATE statement?) this would give you the line that needs to be decremented:

SELECT *
FROM t
WHERE ID = 1
AND (
  SELECT COALESCE(SUM(Count), 0)
  FROM t i
  WHERE ID = 1
  AND i.Value < t.Value
) <= (
  SELECT FLOOR(SUM(Count) * RAND())
  FROM t
)
ORDER BY Value DESC
LIMIT 1

Because it's O(n²), it will be slow for large sets of data, though.

Try this (provided you don't have too many records in this table). I made a structure like so:

id          value                                              weight
----------- -------------------------------------------------- -----------
1           A                                                  2
1           B                                                  3

And then ran the below code. Keep in mind that after a certain time, all your weight will become zero, and at that point you will have no returned value. Also, I am assuming that if there is no weight match based on the random return, then return the most weight value. If that is not what you want, just take out the last null check


declare @weight_sum int
declare @rand_weight_value int
declare @selected_value varchar(10)
declare @id int
declare @weight int

select @weight_sum = SUM(weight) from table1 where id = 1
select @rand_weight_value = ROUND(((@weight_sum - 1) * RAND() + 1), 0) 

print @rand_weight_value

declare getEm cursor local  for select ID, [value], [weight] from table1 where id = 1 and [weight] > 0 order by [weight]

open getEm
        while (1=1)
        begin
                 fetch next from getEm into @id, @selected_value, @weight

                 if (@@fetch_status  0)
                    begin
                        DEALLOCATE getEm
                        break
                    end


                if(@weight >= @rand_weight_value)
                begin
                        update table1 set [weight] = [weight] - 1 where ID = @id and [value] = @selected_value

                        DEALLOCATE getEm
                        break
                end

        end

-- if no match on the weight value
if(@selected_value is null)
begin
    select @id = id, @selected_value = [value] from table1 where [weight] > 0 and id = 1 order by weight desc
         update table1 set weight = weight - 1 where id = @id
end
select @selected_value

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM