html5 - tutorial - En IndexedDB, ¿hay alguna manera de hacer una consulta compuesta ordenada?
query indexeddb (5)
Digamos que una tabla tiene, nombre, ID, edad, sexo, educación, etc. ID es la clave y la tabla también está indexada por nombre, edad y sexo. Necesito a todos los estudiantes varones, mayores de 25 años, ordenados por sus nombres.
Esto es fácil en mySQL:
SELECT * FROM table WHERE age > 25 AND sex = "M" ORDER BY name
IndexDB permite la creación de un índice y ordena la consulta en función de ese índice. Pero no permite consultas múltiples como la edad y el sexo. Encontré una pequeña biblioteca llamada queryIndexedDB (https://github.com/philikon/queryIndexedDB) que permite consultas compuestas pero no proporciona resultados ordenados.
Entonces, ¿hay alguna manera de hacer una consulta compuesta ordenada, mientras usa IndexedDB?
El término consulta compuesta como se utiliza en esta respuesta se refiere a una instrucción SQL SELECT que implica más de una condición en su cláusula WHERE. Aunque tales consultas no se mencionan en la especificación indexedDB, puede aproximar el comportamiento de una consulta compuesta creando un índice con una ruta clave que consiste en una matriz de nombres de propiedad.
Esto no tiene ninguna relación con el uso del indicador de entrada múltiple al crear un índice. El indicador de entrada múltiple ajusta cómo indexedDB crea un índice sobre una única propiedad de matriz. Estamos indexando una matriz de propiedades de objeto, no los valores de una sola propiedad de matriz de un objeto.
Creando el índice
En este ejemplo, ''nombre'', ''género'' y ''edad'' corresponden a los nombres de propiedad de los objetos estudiantiles almacenados dentro del 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
A continuación, 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 razones que estoy a punto de explicar, esto no siempre funcionará.
Aparte: usar un parámetro de rango para abrir Cursor u obtener es opcional. Si no especifica un rango, entonces IDBKeyRange.only
se usa implícitamente para usted. En otras palabras, solo necesita usar IDBKeyRange
para cursores delimitados.
Conceptos de índice fundamental
Los índices son como almacenes de objetos pero no son directamente mutables. En su lugar, utiliza las operaciones CRUD (crear actualizaciones de lectura de lectura) en el almacén de objetos referenciado, y luego indexedDB coloca automáticamente en cascada las actualizaciones del índice.
Comprender la clasificación es fundamental para comprender los índices. Un índice es básicamente una colección de objetos especialmente ordenada. Técnicamente, también está filtrado, pero voy a tocar eso en un momento. En general, cuando abre un cursor en un índice, está iterando según el orden del índice. Este orden podría ser, y probablemente sea, diferente del orden de los objetos en el almacén de objetos referenciado. El orden es importante porque permite que la iteración sea más eficiente y permite un límite inferior y superior personalizado que solo tiene sentido en el contexto de un orden específico del índice.
Los objetos en el índice se ordenan en el momento en que se producen los cambios en la tienda. Cuando agrega un objeto a la tienda, se agrega a la posición correcta en el índice. La ordenació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 ordenamiento sumergiéndonos 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 la cadena ''10'' es mayor que la cadena ''020''.
Los valores de diferentes tipos se comparan usando un orden definido por especificación
Por ejemplo, la especificación especifica cómo un valor de tipo de cadena viene antes o después de un valor de tipo de fecha. No importa qué contengan los valores, solo los tipos.
IndexedDB no coacciona tipos para usted. Puedes disparar en el pie aquí. Por lo general, nunca desea comparar diferentes tipos.
Los objetos con propiedades indefinidas no aparecen en los índices cuyo keypath está compuesto por una o más de esas propiedades
Como mencioné, los índices pueden no incluir siempre todos los objetos del almacén de objetos referenciado. Cuando coloca un objeto en un almacén de objetos, el objeto no aparecerá en el índice si tiene valores perdidos para las propiedades en las que se basa el índice. Por ejemplo, si tenemos un alumno en el que no conocemos la edad e insertamos esto en la tienda de estudiantes, el alumno en particular no aparecerá en el índice de hombres25.
Recuerda esto cuando te preguntes por qué un objeto no aparece cuando iteras un cursor en el índice.
También tenga en cuenta la sutil diferencia entre null y una cadena vacía. Una cadena vacía no es un valor faltante. Un objeto con una cadena vacía para 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á cuando itere un cursor sobre el índice.
Debe especificar cada propiedad de una ruta de acceso de matriz al crear un IDBKeyRange
Debe especificar un valor válido para cada propiedad en la ruta de teclado de la matriz al crear un límite inferior o superior para usar en un rango al abrir 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 tipo de valor incorrecto, como IDBKeyRange.only([''male'', 25])
, donde el nombre no está definido, no obtendrá un error en el sentido anterior, pero obtendrá resultados sin sentido.
Existe 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 final de la matriz, y que trunque apropiadamente la matriz. Por ejemplo, puede usar IDBKeyRange.only([''josh'',''male''])
.
Clasificación de matriz en cortocircuito
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:
- Deje A ser el primer valor de Array y B el segundo valor de Array.
- Deje que la longitud sea la menor de la longitud de A y la longitud de B.
- Deja que sea 0.
- 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.
- 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.
- Aumenta i por 1.
- Si no es igual a la longitud, vuelva al paso 4. De lo contrario, continúe con el siguiente paso.
- Si la longitud de A es menor que B, entonces A es menor que B. Si la longitud de A es mayor que B, entonces A es mayor que B. De lo contrario, A y B son iguales.
La captura está en los pasos 4 y 5: saltee los pasos restantes . Lo que básicamente significa que si estamos comparando dos matrices para el orden, como [1, ''Z''] y [0, ''A''], el método solo considera el primer elemento porque en ese punto 1 es> 0. Se nunca pasa a verificar Z vs A debido a la evaluación de cortocircuito (pasos 4 y 5 en la especificación).
Entonces, el ejemplo anterior no va a funcionar. De hecho, funciona más como el 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 debe reconocer cómo el conjunto completo de condiciones no está necesariamente involucrado. Eso significa que no obtendrá la lista de objetos que desea, y esta es la razón por la que realmente no puede obtener el mismo comportamiento que las consultas compuestas de SQL.
Manejo de cortocircuitos
No 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 problemas de cortocircuito. Hay un partido completo o no un partido completo. 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:
- Reorganiza los elementos de la ruta de acceso de clave de más angosto a más ancho. Básicamente, proporciona abrazaderas tempranas en rangos que cortan algunos de los resultados no deseados de cortocircuitos.
- Almacene un objeto envuelto en una tienda que utilice propiedades personalizadas especialmente para que pueda ser ordenado utilizando una ruta de teclado que no sea de matriz (un índice no compuesto), o bien, puede usar un índice compuesto que no se vea afectado por el cortocircuito. comportamiento.
- Usa índices múltiples Esto lleva al problema del índice de explosión . Tenga en cuenta que este enlace es sobre otra base de datos sin sql, pero los mismos conceptos y explicaciones se aplican a indexedDB, y el enlace es una explicación razonable (y larga y complicada), así que no lo repito 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 manera 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 para Array.prototype.filter y Array.prototype.sort . Puede probar valores fácilmente 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 incluir devoluciones de llamada / promesas asincrónicas.
Hay una biblioteca JsStore disponible para consultar datos de IndexedDB que es muy fácil de usar y ahorra mucho código y tiempo. puedes explorar más desde here
Esta es su consulta SQL equivalente utilizando JsStore .
var connection = new JsStore.Instance("DbName");
connection.select({
From: "TableName",
Where: {
age : {''>'':''25''},
sex : ''M''
},
Order: {
By: ''Name''
},
OnSuccess:function (results){
console.log(results);
},
OnError:function (error) {
console.log(error);
}
});
Solo piensa en Sql y escribe en JS . ¡Espero que esto ayude!
Intente usar Linq2indexedDB esta biblioteca le permite usar múltiples filtros, múltiples géneros e incluso seleccionar datos de sus objetos. También funciona con navegador cruzado (IE10, Firefox y Chrome)
Puede abrir solo una consulta de rango de clave en indexedDB. Entonces use el índice más eficiente, en este caso, ''edad''. Simplemente filtra el sexo en la iteración del cursor. Ordenar puede hacer más tarde utilizando métodos de iteración de matriz. IndexedDB API no tiene ningún interés en ordenar más que en las entradas de índices prearreguladas.
Tengo un par de años de retraso, pero me gustaría señalar que la respuesta de Josh solo considera escenarios en los que las "columnas" de la consulta son parte del keyPath
del índice.
Si alguna de dichas "columnas" existe fuera del keyPath
del índice, deberá probar las condiciones que las involucran en cada entrada sobre la que itera el cursor creado en el ejemplo. Entonces, si está tratando con tales consultas, o su índice no es unique
, ¡prepárese para escribir algún 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 la ObjectStore focal a menos que esté realizando una consulta estricta de igualdad ( x ===? y
, dado que x es una clave objectStore o index), pero le ahorrará el problema 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 por el bien de la transparencia completa, BakedGoods es mantenido por moi .