2009-02-22 14 views
8

He escrito un pequeño juego estúpido y quiero tener algún tipo de sitio web de la tabla de líderes.Esquema/consulta SQL eficiente para una tabla de líderes

Por lo general, las tablas de clasificación están limitadas a 10 o 20 jugadores principales, pero pensé que sería bueno si pudiera registrar, para cada jugador, su puntaje máximo. Entonces, siempre podría mostrar su rango mundial.

Un esquema simple, como:

create table leaderboard (
    userid varchar(128) not null, 
    score real not null, 
    when datetime not null 
); 
create index on leaderboard(userid); 

almacenaría la cantidad mínima de información que necesito - 1 entrada por usuario con su mejor puntuación.

Mi pregunta gira en torno a cómo determinar de manera eficiente la posición de alguien en la tabla de líderes. La idea general es que me gustaría que su posición en la lista devuelta por:

select userid from leaderboard order by score desc 

Pero ejecución de esta consulta y luego buscar linealmente la lista parece un poco ridículo a mí desde el punto de vista de rendimiento DB. Aun así, me está costando imaginar una consulta/esquema que lo convierta en una operación rápida.

¿Alguna idea?

(yo preferiría mantener el esquema de base de datos y consulta genérica (no atado a un proveedor). Sin embargo, si un vendedor lo hace fácil, estoy feliz de usar ya sea MS SQL o MySQL.

Respuesta

13

Qué tal:

select count(*)+1 as rank from leaderboard 
where score > (select score from leaderboard where userid = ?) 

También le interesará un índice en la columna de puntuación.

Hacer count()+1 con score > (...) le dará rangos precisos, incluso cuando varios jugadores tienen el mismo puntaje; haciendo count() con score >= (...) no lo hará.

1

La obvia opción sería crear un índice en "puntuación", lo cual es perfectamente razonable. (Parece que desea conservar dos valores: puntaje acumulativo y puntaje alto de agua) o no entiendo bien?)

A menos que espere decenas de miles de usuarios, incluso los escaneos de tabla no deberían ser un gran problema en una tabla con tantos registros.

+0

Por aclaración, solo estoy almacenando su mejor puntaje. –

+0

OK, entonces un índice en el mejor puntaje sería su mejor apuesta. Si SELECCIONA COUNT (1) FROM leaderboard DONDE topscore> = (SELECCIONAR puntaje ... etc.) será eficiente, ya que se podrá resolver simplemente escaneando el índice sin referencia a la tabla en sí. – dkretz

1

Parece que desea consultar la siguiente:

select userid , max(score) 
from leaderboard 
group by userid 
order by max(score) desc 

Esto devuelve la clasificación con 1 entrada para cada usuario.

EDITAR A CONTINUACIÓN: Veo en el comentario que desea ver el rango, no el puntaje. para este no sé la respuesta ANSI SQL, pero la base de datos específica:

En MySQL:

SELECT @rownum:[email protected]+1 rank 
, t.userid 
FROM (SELECT @rownum:=0) r, 
(select userid , score 
from leaderboard 
order by score desc 
) t; 

En Oracle se puede utilizar la instrucción RANK.

+0

Esto me dará su mejor puntaje, pero realmente estoy detrás de su "rango" de distancia desde la "cima". –

+0

Excelente, muchas gracias. Me alegra ver que algunas bases de datos tienen soporte para esto. –

1

Si esto no tiene que ser en tiempo real (por ejemplo, si la actualización una vez al día es aceptable), agregue un campo de "posición" adicional y actualícelo periódicamente mediante una consulta ordenada por puntaje.

+0

Definitivamente una buena opción. Me gustaría evitar un proceso fuera de línea por ahora. –

2

En SQL Server 2005 en adelante, se puede utilizar la función RANK() para devolver el rango para cada usuario, en función de su puntuación de

SELECT 
    userid, 
    RANK() OVER 
    (ORDER BY score) AS rank 
FROM leaderboard 

Si tuviera más de un tipo de 'tipo de juego', entonces podría incluir esto en la tabla Leaderboard y usar la cláusula PARTITION BY dentro de la función RANK para determinar el ranking para cada tipo de juego.

1

En el servidor Sql 2005, Rank() prácticamente hace el trabajo por usted. Pero si tienes millones de registros, clasifícalos en tiempo real cada vez que cambien las estadísticas subyacentes.

Intenté crear una vista indizada en la parte superior de la consulta de selección ... (la respuesta elegida en este hilo) pero Sql 2005 no me permitió crearla porque no puede usar subconsultas, autorreferencia en una vista indizada .

Por lo tanto, nuestra solución consiste en actualizar la tabla de rangos cada noche con la función Fila(). Para evitar el bloqueo al hacer esta actualización, guardamos 2 copias de la tabla de clasificación que se está actualizando y otra que se está utilizando en la aplicación. Tenemos un RankingView que apunta a la tabla de clasificación activa en cualquier momento dado.

Me gustaría saber si hay una solución para la actualización de la clasificación en tiempo real para tablas realmente grandes?

0

Estaba pensando en el selecto "select count (*) + 1 como el rango de clasificación
donde puntuación> (seleccione la puntuación de clasificación, donde identificador de usuario =?)"

pienso en la siguiente situación: el jugador 1, Score 100 jugador 2, Score 100 jugador 3, Score 50

usando ese SQL el rango de jugador 3 sería 3, donde la respuesta correcta debería ser de 2, porque reproductor 1 y 2 están vinculados en la primera posición. La función agregada count() en ese caso considera 2 registros con la columna Score> 50.

Para manejar este caso, creo que la opción correcta es hacer un grupo, por lo que los valores de puntaje repetibles se manejarán como un solo ranking posición:

select score, count(*)+1 as rank from leaderboard 
group by score having (score) > (select score from leaderboard where userid = ?) 
Cuestiones relacionadas