Saltar al contenido

En IndexedDB, ¿hay alguna forma de hacer una consulta compuesta ordenada?

Solución:

El término consulta compuesta como se usa en esta respuesta se refiere a una instrucción SQL SELECT que involucra más de una condición en su cláusula WHERE. Aunque estas consultas no se mencionan en la especificación indexedDB, puede aproximar el comportamiento de una consulta compuesta creando un índice con un ruta clave que consiste en un array de nombres de propiedad.

Esto no tiene nada que ver con el uso de la marca de entrada múltiple al crear un índice. La bandera de entrada múltiple ajusta cómo indexedDB crea un índice sobre un solo array propiedad. Estamos indexando un array de las propiedades del objeto, no los valores de un solo array propiedad de un objeto.

Creando el índice

En este ejemplo, ‘nombre’, ‘género’ y ‘edad’ corresponden a los nombres de propiedad de los objetos de los estudiantes almacenados en el almacén de objetos de los estudiantes.

// An example student object in the students store
var foo = 
  'name': 'bar',
  'age': 15,
  'gender': 'M'
;

function myOnUpgradeNeeded(event) 
  var db = event.target.result;
  var students = db.createObjectStore('students');
  var name = 'males25';
  var keyPath = ['name', 'gender', 'age'];
  students.createIndex(name, keyPath);

Abrir un cursor en el índice

Luego puede abrir un cursor en el índice:

var students = transaction.objectStore('students');
var index = students.index('males25');
var lowerBound = ['AAAAA','male',26];
var upperBound = ['ZZZZZ','male',200];
var range = IDBKeyRange.bound(lowerBound, upperBound);
var request = index.openCursor(range);

Sin embargo, por las razones que voy a explicar, esto no siempre funcionará.

Aparte: usar un parámetro de rango para abrirCursor u obtener es opcional. Si no especifica un rango, entonces IDBKeyRange.only se utiliza implícitamente para usted. En otras palabras, solo necesita usar IDBKeyRange para cursores acotados.

Conceptos fundamentales de índices

Los índices son como almacenes de objetos, pero no son directamente mutables. En su lugar, usa operaciones CRUD (crear lectura, actualización, eliminación) en el almacén de objetos al que se hace referencia, y luego indexedDB actualiza automáticamente en cascada las actualizaciones del índice.

Comprender la ordenación es fundamental para comprender los índices. Un índice es básicamente una colección de objetos especialmente ordenados. Técnicamente, también está filtrado, pero lo abordaré en un momento. Generalmente, cuando abre un cursor en un índice, está iterando de acuerdo con el orden del índice. Este orden podría ser, y probablemente sea, diferente al orden de los objetos en el almacén de objetos referenciados. El orden es importante porque esto permite que la iteración sea más eficiente y permite un límite superior e inferior personalizado que solo tiene sentido en el contexto de un orden específico de índice.

Los objetos en el índice se ordenan en el momento en que ocurren los cambios en la tienda. Cuando agrega un objeto a la tienda, se agrega a la posición adecuada en el índice. La clasificación se reduce a una función de comparación, similar a Array.prototype.sort, que compara dos elementos y devuelve si un objeto es menor que el otro, mayor que el otro o igual. Por lo tanto, podemos comprender mejor el comportamiento de clasificación al profundizar en más detalles sobre las funciones de comparación.

Las cadenas se comparan lexicográficamente

Esto significa, por ejemplo, que ‘Z’ es menor que ‘a’ y que el string ’10’ es mayor que el string ‘020’.

Los valores de diferentes tipos se comparan utilizando un orden definido por especificación.

Por ejemplo, la especificación especifica cómo string-type value viene antes o después de un valor de tipo de fecha. No importa lo que contengan los valores, solo los tipos.

IndexedDB no coacciona tipos por usted. Puedes dispararte en el pie aquí. Por lo general, nunca querrá comparar diferentes tipos.

Los objetos con propiedades indefinidas no aparecen en índices cuya ruta clave se compone de una o más de esas propiedades

Como mencioné, es posible que los índices no siempre incluyan todos los objetos del almacén de objetos al que se hace referencia. Cuando coloca un objeto en un almacén de objetos, el objeto no aparecerá en el índice si le faltan valores para las propiedades en las que se basa el índice. Por ejemplo, si tenemos un estudiante del que no sabemos la edad y lo insertamos en la tienda de estudiantes, el estudiante en particular no aparecerá en el índice de hombres25.

Recuerde esto cuando se pregunte por qué no aparece un objeto al iterar un cursor en el índice.

También tenga en cuenta la sutil diferencia entre null y un vacio string. Un vacío string es no un valor faltante. Un objeto con un vacío string porque una propiedad aún podría aparecer en un índice basado en esa propiedad, pero no aparecerá en el índice si la propiedad está presente pero no está definida o no está presente. Y si no está en el índice, no lo verá al iterar un cursor sobre el índice.

Debe especificar cada propiedad de un array ruta clave al crear un IDBKeyRange

Debe especificar un valor válido para cada propiedad en el array ruta clave al crear un límite inferior o superior para usar en un rango para cuando se abre un cursor sobre ese rango. De lo contrario, obtendrá algún tipo de error de Javascript (varía según el navegador). Por ejemplo, no puede crear un rango como IDBKeyRange.only([undefined, 'male', 25]) porque la propiedad del nombre no está definida.

Confusamente, si especifica el error escribe de valor, como IDBKeyRange.only(['male', 25]), donde el nombre no está definido, no obtendrá un error en el sentido anterior, pero obtendrá resultados sin sentido.

Hay una excepción a esta regla general: puede comparar matrices de diferentes longitudes. Por lo tanto, técnicamente puede omitir propiedades del rango, siempre que lo haga desde el fin de El array, y que truncar adecuadamente el array. Por ejemplo, podría usar IDBKeyRange.only(['josh','male']).

Cortocircuitado array clasificación

La especificación indexedDB proporciona un método explícito para ordenar matrices:

Los valores de tipo Array se comparan con otros valores de tipo Array de la siguiente manera:

  1. Sea A el primer valor de Array y B el segundo valor de Array.
  2. Sea longitud la menor de la longitud de A y la longitud de B.
  3. Sea yo 0.
  4. Si el i-ésimo valor de A es menor que el i-ésimo valor de B, entonces A es menor que B. Omita los pasos restantes.
  5. Si el i-ésimo valor de A es mayor que el i-ésimo valor de B, entonces A es mayor que B. Omita los pasos restantes.
  6. Incrementar i en 1.
  7. Si i no es igual a la longitud, vuelva al paso 4. De lo contrario, continúe con el paso siguiente.
  8. Si la longitud de A es menor que la longitud de B, entonces A es menor que B. Si la longitud de A es mayor que la longitud de B, entonces A es mayor que B. De lo contrario, A y B son iguales.

La trampa está en los pasos 4 y 5: Omita los pasos restantes. Lo que esto básicamente significa es que si estamos comparando dos matrices por orden, como [1,’Z’] y [0,’A’], el método solo considera el primer elemento porque en ese punto 1 es> 0. Nunca llega a verificar Z vs A debido a la evaluación en cortocircuito (pasos 4 y 5 en la especificación).

Entonces, el ejemplo anterior no va a funcionar. En realidad, funciona más como lo siguiente:

WHERE (students.name >= 'AAAAA' && students.name <= 'ZZZZZ') || 
(students.name >= 'AAAAA' && students.name <= 'ZZZZZ' && 
students.gender >= 'male' && students.gender <= 'male') || 
(students.name >= 'AAAAA' && students.name <= 'ZZZZZ' && 
students.gender >= 'male' && students.gender <= 'male' && 
students.age >= 26 && students.age <= 200)

Si tiene alguna experiencia con tales cláusulas booleanas en SQL o en programación general, entonces ya debería reconocer cómo el conjunto completo de condiciones no está necesariamente involucrado. Eso significa que no obtendrá la lista de objetos que desea, y es por eso que realmente no puede obtener el mismo comportamiento que las consultas compuestas de SQL.

Hacer frente a los cortocircuitos

No se puede evitar fácilmente este comportamiento de cortocircuito en la implementación actual. En el peor de los casos, debe cargar todos los objetos de la tienda / índice en la memoria y luego ordenar la colección usando su propia función de clasificación personalizada.

Hay formas de minimizar o evitar algunos de los problemas de cortocircuito:

Por ejemplo, si está utilizando index.get (array) o index.openCursor (array), entonces no hay problema de cortocircuito. Hay una coincidencia completa o no una coincidencia completa. En este caso, la función de comparación solo evalúa si dos valores son iguales, no si uno es mayor o menor que el otro.

Otras técnicas a considerar:

  • Reorganice los elementos de la ruta clave de más estrecho a más ancho. Básicamente, proporcione abrazaderas tempranas en rangos que eliminen algunos de los resultados no deseados de los cortocircuitos.
  • Almacene un objeto envuelto en una tienda que use propiedades especialmente personalizadas para que pueda ser ordenado usando unarray keypath (un índice no compuesto), o puede hacer uso de un índice compuesto que no se ve afectado por el comportamiento de cortocircuito.
  • Utilice varios índices. Esto conduce al problema del índice explosivo. Tenga en cuenta que este enlace es sobre otra base de datos sin sql, pero los mismos conceptos y explicación se aplican a indexedDB, y el enlace es una explicación razonable (y larga y complicada), por lo que no lo estoy repitiendo aquí.
  • Uno de los creadores de indexedDB (la especificación y la implementación de Chrome) sugirió recientemente usar cursor.continue: https://gist.github.com/inexorabletash/704e9688f99ac12dd336

Prueba con indexedDB.cmp

La función cmp proporciona una forma rápida y sencilla de examinar cómo funciona la clasificación. Por ejemplo:

var a = ['Hello',1];
var b = ['World',2];
alert(indexedDB.cmp(a,b));

Una buena propiedad de la función indexedDB.cmp es que su firma es la misma que el parámetro de función de Array.prototype.sort. Puede probar fácilmente los valores desde la consola sin tener que lidiar con conexiones / esquemas / índices y todo eso. Además, indexedDB.cmp es síncrono, por lo que su código de prueba no necesita involucrar devoluciones de llamada / promesas asíncronas.

Llego un par de años tarde, pero solo quiero señalar que la respuesta de Josh solo considera escenarios en los que las "columnas" de la consulta son parte del índice keyPath.

Si alguna de dichas "columnas" existe fuera del índice keyPath, tendrá que probar las condiciones que los involucran en cada entrada sobre la que itera el cursor creado en el ejemplo. Entonces, si está tratando con consultas de este tipo, o su índice no unique, ¡prepárate para escribir código de iteración!

En cualquier caso, le sugiero que consulte BakedGoods si puede representar su consulta como una expresión booleana.

Para este tipo de operaciones, siempre abrirá un cursor en el objeto focal Store a menos que esté realizando una consulta de igualdad estricta (x ===? y, dado x es un objectStore o index key), pero le evitará la molestia de escribir su propio código de iteración del cursor:

bakedGoods.getAll(
    filter: "keyObj > 5 && valueObj.someProperty !== 'someValue'",
    storageTypes: ["indexedDB"],
    complete: function(byStorageTypeResultDataObj, byStorageTypeErrorObj)
);

Solo en aras de la total transparencia, BakedGoods es mantenido por moi.

Más adelante puedes encontrar las notas de otros administradores, tú de igual manera eres capaz mostrar el tuyo si dominas el tema.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *