views:

98

answers:

3

in an ms-access database i have a table

num   weight
1       12  
4       13
2       13
6       9
7       13

how can i write a query which will sort the table according to weight in descending order . but the numbers 4, 2 and 7 have same weight (13) , so they must be sorted randomly each time query is run.

any help appreciated.

+1  A: 

Normally, your SQL would contain a random function of some sort (it looks like Access has the rnd() function for this).

So you could use:

select num, weight, rnd(num)
from tbl
order by weight desc, 3

This will let you see r for testing purposes. In a real query, you might just want to use something like:

select num, weight
from tbl
order by weight desc, rnd(num)

From this page:

When value is greater than 0, Rnd() returns the next random number.
When value is less than 0, Rnd() returns the same random number, based on value. If value occurs only once, you won’t notice this behavior. Access also resets seed, which means the sequence starts all over again.
When value is equal to 0, Rnd() returns the most recently generated random number

Update 1: I'm unsure as to whether rnd() is executed once in the following queries or once per row - the docs aren't clear. Comments seem to indicate the same results are being received for all rows which indicates it may be the latter. It may be that changing it to rnd(num) or rnd(abs(num)+1) will fix that problem. I'll have to check when I get to a box with Access installed.

Update 2: I've now tested this in Access 2007 and it does indeed give the same random value for every row when you use rnd(1). It does give a different value for rnd(num) each time you run the query and the individual rows get different values. So the query you need is:

select num, weight from tbl order by weight desc, rnd(num);

If you create a table with two Number fields and then run that query over it, you'll see that continual refreshing (with F5) will swap around the 2, 7 and 4 rows at random but leave the 1 and 6 rows in the same place since the weights of the first three are all 13 and the weights of the last two are 12 and 9 respectively.

I've updated the queries above to match this new information.

paxdiablo
The seed 1 will generate the same random nr for each record. you could use num*weight instead.
marg
@marg, the seed is not set for positive numbers, only negative ones (see the quote from the linked page). rnd(1) will generate a different number each time. Using num*weight will give you the same order every time, not what the OP wanted.
paxdiablo
marg is right about num*weight. i was getting the same random number with rnd(1)
silverkid
num multiplied by weight will return the same value for a given row every time. This will not allow a group with the same weight to sort randomly.
paxdiablo
Updated with new information. I don't know why the behavior of rnd(1) doesn't match the doco, but I can see that rnd(num) gives different results for each row and for each query, giving you the random order you desired.
paxdiablo
It's obvious you folks don't understand the Jet expression service. Any function in a row of a SQL statement will be evaluated once if its arguments are constants, as is the case with Rnd(1). But if you instead pass a value from a column it will have to be evaluated for each row. That is why Rnd(num) gives you a different result for each row, because the value of the field called "num" has to be considered different for each row so that the function has to run for each row. This is Access queries 101.
David-W-Fenton
Thanks for that education snippet, @David, though not so much for the tone it was delivered in :-) I believe that to actually be a deficiency in Access. If a function is specified as providing different results for the same input value, it should *not* be constant-folded to make it useless. Fair enough for something like abs(1) but *not* rnd(1). Forcing you to use a column means you have to do rnd(abs(col_name)+1) to ensure the other rules for rnd() don't kick in (i.e., the zero and negative argument rules). You're right that I don't know Access - my answer started as vendor-agnostic.
paxdiablo
@David W. Fenton: when I use RND(1) in Access Database SQL from outside of the Access UI I still get the same value for each row. This isn't what I was expecting after hearing your Access queries 101 missive. So can I have the next 'Jet' expression service lesson, please, the one where you explain how it works without Access being installed? Thanks in advance.
onedaywhen
A: 

I think this will do the trick...

ORDER BY weight, Rnd()
Dave Barker
A: 

When using RND() in Access Database Engine SQL from outside of the Access UI, the same sequence of random numbers will be used by each session (there is no native support for VBA's Randomize).

For example, connect to the source then execute SELECT RND(); three times in succession, you will get the following values:

0.705547511577606 
0.533424019813538 
0.579518616199493

Close the connection, connect to the source again then execute the same query again three times you will get the same three values as above in the same order.

In the knowledge that these values are predictable, we can demonstrate that a different value for RND() is used each time it is referenced. Consider this query:

SELECT RND() 
  FROM T 
 WHERE RND() > CDBL(0.6);

We know the first value in a new session will be 0.705547511577606, therefore the expression

RND() > CDBL(0.6)

will evaluate TRUE. However, the value 0.533424019813538 is repeated for every row in table T, a value which does not satisfy the WHERE clause! So it is clear that the value of RND() in the WHERE clause is different from the value in the SELECT clause. The full code is posted below.

Note I wondered if it may be possible to use the least significant portion of CURRENT_TIMESTAMP value generate a random number that could be used consistently through the scope of a single query and not be predictable for a session as RND() is. However, the Access Database Engine does not support it and its closest analogy, the NOW() function, does not have enough granularity to be useful :(

Sub jmgirpjpo()

  On Error Resume Next
  Kill Environ$("temp") & "\DropMe.mdb"
  On Error GoTo 0

  Dim cat
  Set cat = CreateObject("ADOX.Catalog")

  With cat

    .Create _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    With .ActiveConnection

      .Execute "CREATE TABLE T (T INT);"
      .Execute "INSERT INTO T VALUES (1);"
      .Execute "INSERT INTO T VALUES (2);"
      .Execute "INSERT INTO T VALUES (3);"

      Dim rs
      Set rs = .Execute("SELECT RND() FROM T WHERE RND() > CDBL(0.6);")
      MsgBox rs.GetString

    End With
  End With

End Sub
onedaywhen
Can you pass a column value from outside Jet/ACE, i.e., Rnd(num) in the original table example?
David-W-Fenton
Yes but that won't change its seed value.
onedaywhen