Estoy tratando de entender cómo almacenar correctamente la información ordenada en una base de datos relacional.
Un ejemplo:
Digamos que tengo una lista de reproducción, que consta de canciones. Dentro de mi base de datos relacional, tengo una tabla que Playlists
contiene algunos metadatos (nombre, creador, etc.). También tengo una tabla llamada Songs
, que contiene un playlist_id
, así como información específica de la canción (nombre, artista, duración, etc.).
Por defecto, cuando se agrega una nueva canción a una lista de reproducción, se agrega al final. Al realizar el pedido en Song-ID (ascendente), el orden será el orden de adición. Pero, ¿qué pasa si un usuario debería poder reordenar canciones en la lista de reproducción?
Se me ocurrieron algunas ideas, cada una con sus ventajas y desventajas:
- Una columna llamada
order
, que es un número entero . Cuando se mueve una canción, se cambia el orden de todas las canciones entre su posición anterior y la nueva, para reflejar el cambio. El inconveniente de esto es que se deben hacer muchas consultas cada vez que se mueve una canción, y el algoritmo de movimiento no es tan trivial como con las otras opciones. - Una columna llamada
order
, que es un decimal (NUMERIC
). Cuando se mueve una canción, se le asigna el valor de coma flotante entre los dos números adyacentes. Inconveniente: los campos decimales ocupan más espacio y es posible que se quede sin precisión, a menos que se tenga cuidado de redistribuir el rango después de cada pocos cambios. - Otra forma sería tener
previous
unnext
campo que haga referencia a otras canciones. (o son NULL en el caso de la primera y la última canción de la lista de reproducción en este momento; básicamente se crea una lista vinculada ). Inconveniente: las consultas como 'encontrar la X canción en la lista' ya no son de tiempo constante, sino lineal.
¿Cuál de estos procedimientos se usa con más frecuencia en la práctica? ¿Cuál de estos procedimientos es más rápido en bases de datos medianas y grandes? ¿Hay alguna otra forma de archivar esto?
EDITAR: En aras de la simplicidad, en el ejemplo, una canción solo pertenece a una lista de reproducción (una relación de muchos a uno). Por supuesto, también se podría usar una tabla de unión, por lo que la lista de reproducción de canciones es una relación de muchos a muchos (y aplicar una de las estrategias anteriores en esa tabla).
update songorder set order = order - 1 where order >= 12 & order <= 42; update songorder set order = 42 where id = 123;
Son dos actualizaciones, no treinta. Tres si quieres poner una restricción única en orden.Queries like 'find the Xth Song in the list' are no longer constant-time
también es cierto para la opción 2.Respuestas:
Las bases de datos están optimizadas para ciertas cosas. Actualizar muchas filas rápidamente es una de ellas. Esto se vuelve especialmente cierto cuando dejas que la base de datos haga su trabajo.
Considerar:
Y si desea avanzar
Beat It
hasta el final, tendría dos consultas:Y eso es. Esto aumenta muy bien con números muy grandes. Intente poner unos pocos miles de canciones en una hipotética lista de reproducción en su base de datos y vea cuánto tiempo lleva mover una canción de un lugar a otro. Como estos tienen formas muy estandarizadas:
Tiene dos declaraciones preparadas que puede reutilizar de manera muy eficiente.
Esto proporciona algunas ventajas significativas: el orden de la tabla es algo sobre lo que puede razonar. La tercera canción tiene un
order
de 3, siempre. La única forma de garantizar esto es usar enteros consecutivos como orden. El uso de listas seudoenlazadas o números decimales o enteros con espacios no le permitirá garantizar esta propiedad; en estos casos, la única forma de obtener la enésima canción es ordenar toda la tabla y obtener el enésimo registro.Y realmente, esto es mucho más fácil de lo que piensas. Es simple descubrir lo que quiere hacer, generar las dos declaraciones de actualización y que otras personas vean esas dos declaraciones de actualización y se den cuenta de lo que se está haciendo.
fuente
order
ya queorder by
es una palabra clave?order
valor al agregar una nueva canción a una lista de reproducción. Digamos que es la novena canción, ¿hay alguna forma mejor de insertar 9 enorder
hacer unaCOUNT
antes de agregar el registro?En primer lugar, no está claro en su descripción de lo que ha hecho, pero necesita una
PlaylistSongs
tabla que contenga aPlaylistId
y aSongId
, que describa qué canciones pertenecen a qué listas de reproducción.Es en esta tabla donde debe agregar la información de pedido.
Mi mecanismo favorito es con números reales. Lo implementé recientemente y funcionó de maravilla. Cuando desee mover una canción a una posición específica, calcule su nuevo
Ordering
valor como el promedio de losOrdering
valores de la canción anterior y la siguiente. Si usa un número real de 64 bits, se quedará sin precisión aproximadamente al mismo tiempo que el infierno se congelará, pero si realmente está escribiendo su software para la posteridad, considere reasignarOrdering
valores enteros redondeados a todas las canciones de cada canción. lista de reproducción de vez en cuando.Como una ventaja adicional, aquí está el código que he escrito que implementa esto. Por supuesto, no puede usarlo como está, y sería demasiado trabajo para mí en este momento desinfectarlo para usted, por lo que solo lo estoy publicando para que obtenga ideas de él.
La clase es
ParameterTemplate
(¡lo que sea, no pregunte!) El método obtiene la lista de plantillas de parámetros a las que pertenece esta plantilla de su padreActivityTemplate
. (¡Lo que sea, no preguntes!) El código contiene algo de protección contra la falta de precisión. El divisor se usa para las pruebas: la prueba de la unidad usa un divisor grande para quedarse sin precisión rápidamente, y así activar el código de protección de precisión. El segundo método es público y "solo para uso interno; no invocar" para que el código de prueba pueda invocarlo. (No podría ser un paquete privado porque mi código de prueba no está en el mismo paquete que el código que prueba). El campo que controla el pedido se llamaOrdering
, se accede a través degetOrdering()
ysetOrdering()
. No ve ningún SQL porque estoy usando el mapeo relacional de objetos a través de Hibernate.fuente
Lo que funcionó para mí, para una pequeña lista del orden de 100 artículos, fue adoptar un enfoque híbrido:
Entonces terminas con un orden entero sin espacios, almacenado en una columna decimal. Es bastante limpio, creo. Pero es posible que no se amplíe extremadamente bien una vez que tenga cientos de miles de filas que necesita actualizar, todo a la vez. Pero si lo hace, ¿por qué está utilizando un tipo definido por el usuario en primer lugar? (Nota: si tiene una tabla grande con millones de usuarios pero cada usuario solo tiene unos cientos de elementos para ordenar, puede usar el enfoque anterior muy bien ya que de todos modos usará una cláusula where para limitar los cambios a un solo usuario )
fuente