2012-01-03 10 views
7

Tengo dos tablas:MySQL y conjunto anidado: lenta UNEN (no mediante el índice)

localidades:

CREATE TABLE `localities` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `name` varchar(100) NOT NULL, 
    `type` varchar(30) NOT NULL, 
    `parent_id` int(11) DEFAULT NULL, 
    `lft` int(11) DEFAULT NULL, 
    `rgt` int(11) DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `index_localities_on_parent_id_and_type` (`parent_id`,`type`), 
    KEY `index_localities_on_name` (`name`), 
    KEY `index_localities_on_lft_and_rgt` (`lft`,`rgt`) 
) ENGINE=InnoDB; 

locatings:

CREATE TABLE `locatings` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `localizable_id` int(11) DEFAULT NULL, 
    `localizable_type` varchar(255) DEFAULT NULL, 
    `locality_id` int(11) NOT NULL, 
    `category` varchar(50) DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `index_locatings_on_locality_id` (`locality_id`), 
    KEY `localizable_and_category_index` (`localizable_type`,`localizable_id`,`category`), 
    KEY `index_locatings_on_category` (`category`) 
) ENGINE=InnoDB; 

mesa de localidades se implementa como un conjunto anidado .

Ahora, cuando el usuario pertenece a alguna localidad (a través de alguna localización) también pertenece a todos sus antepasados ​​(localidades de más alto nivel). Necesito una consulta que seleccionará todas las localidades a las que pertenecen todos los usuarios en una vista.

Aquí es mi intento:

select distinct lca.*, lt.localizable_type, lt.localizable_id 
from locatings lt 
join localities lc on lc.id = lt.locality_id 
left join localities lca on (lca.lft <= lc.lft and lca.rgt >= lc.rgt) 

El problema aquí es que toma demasiado tiempo para ejecutarse.

Consulté explico:

+----+-------------+-------+--------+---------------------------------+---------+---------+----------------------------------+-------+----------+-----------------+ 
| id | select_type | table | type | possible_keys     | key  | key_len | ref        | rows | filtered | Extra   | 
+----+-------------+-------+--------+---------------------------------+---------+---------+----------------------------------+-------+----------+-----------------+ 
| 1 | SIMPLE  | lt | ALL | index_locatings_on_locality_id | NULL | NULL | NULL        | 4926 | 100.00 | Using temporary | 
| 1 | SIMPLE  | lc | eq_ref | PRIMARY       | PRIMARY | 4  | bzzik_development.lt.locality_id |  1 | 100.00 |     | 
| 1 | SIMPLE  | lca | ALL | index_localities_on_lft_and_rgt | NULL | NULL | NULL        | 11439 | 100.00 |     | 
+----+-------------+-------+--------+---------------------------------+---------+---------+----------------------------------+-------+----------+-----------------+ 
3 rows in set, 1 warning (0.00 sec) 

El último se unen obviamente no utiliza LFT, índice rgt como espero que lo haga. Estoy desesperado.

ACTUALIZACIÓN: Después de agregar una condición como @cairnz sugerida, la consulta tarda demasiado tiempo en procesarse.

Actualización 2: Los nombres de columna en lugar del asterisco

consulta Actualizado:

SELECT DISTINCT lca.id, lt.`localizable_id`, lt.`localizable_type` 
FROM locatings lt FORCE INDEX(index_locatings_on_category) 
JOIN localities lc 
    ON lc.id = lt.locality_id 
INNER JOIN localities lca 
    ON lca.lft <= lc.lft AND lca.rgt >= lc.rgt 
WHERE lt.`category` != "Unknown"; 

EXAPLAIN Actualizado:

+----+-------------+-------+--------+-----------------------------------------+-----------------------------+---------+---------------------------------+-------+----------+-------------------------------------------------+ 
| id | select_type | table | type | possible_keys       | key       | key_len | ref        | rows | filtered | Extra           | 
+----+-------------+-------+--------+-----------------------------------------+-----------------------------+---------+---------------------------------+-------+----------+-------------------------------------------------+ 
| 1 | SIMPLE  | lt | range | index_locatings_on_category    | index_locatings_on_category | 153  | NULL       | 2545 | 100.00 | Using where; Using temporary     | 
| 1 | SIMPLE  | lc | eq_ref | PRIMARY,index_localities_on_lft_and_rgt | PRIMARY      | 4  | bzzik_production.lt.locality_id |  1 | 100.00 |             | 
| 1 | SIMPLE  | lca | ALL | index_localities_on_lft_and_rgt   | NULL      | NULL | NULL       | 11570 | 100.00 | Range checked for each record (index map: 0x10) | 
+----+-------------+-------+--------+-----------------------------------------+-----------------------------+---------+---------------------------------+-------+----------+-------------------------------------------------+ 

Cualquier ayuda apreciada.

+0

¿No han intentado tener LFT y RFT en el mismo índice? (Uno para la LFT, uno para RFT) – cairnz

+0

@cairnz Sí, sin éxito actualizada por su actualización –

+0

respuesta. – cairnz

Respuesta

2

Ah, se me acaba de ocurrir.

Puesto que usted está pidiendo para todo en la tabla, MySQL decide utilizar una tabla de análisis completo del lugar, ya que considera que es más eficiente.

Con el fin de conseguir un poco de uso de claves, añadir en algunos filtros para restringir buscar todas las filas de todas las tablas de todos modos.

Actualizando Respuesta:

Su segunda consulta no tiene sentido. Te queda unirme a lca pero tienes un filtro en él, esto niega la combinación izquierda por sí misma. También está buscando datos en el último paso de la consulta, lo que significa que tendrá que buscar en todo lt, lc e lca para encontrar sus datos. Además, no tiene ningún índice con la columna 'escribir' de la columna de la izquierda en las ubicaciones, por lo que aún necesita una exploración de tabla completa para encontrar sus datos.

Si tuviera algunos datos de ejemplo y el ejemplo de lo que está tratando de lograr que tal vez sería más fácil de ayudar.

+0

Gracias, la consulta es mucho más rápida, pero aún requiere demasiado. Actualicé mi pregunta con una nueva consulta y la explico. –

+0

Disculpa, esta es quizás una pregunta tonta, pero ¿a qué te refieres con agregar filtros entonces? –

+1

su consulta tiene que procesar la tabla lt, uniéndose en lc, uniéndose en lca. el filtro que tienes está en lca, el último "paso" de la consulta. A continuación, puede escanear la tabla lca para las filas que coinciden con el tipo! = "Desconocido", pero con el fin de llegar a ese punto ya se tiene que leer LT y LC, si eso tiene sentido. También tiene una izquierda unirse a la mesa, lo que significa que puede tener registros NULL allí, sin embargo, que está filtrando en una cláusula WHERE, la eliminación de todos los registros NULL (igual a una unión interna). Quizás quisiste decir que tu filtro está en lc, o en lt. Si filtró en la tabla lt, tiene menos filas para escanear en lc y lca. – cairnz

2

tratar de experimentar con índice de forzar - http://dev.mysql.com/doc/refman/5.1/en/index-hints.html, tal vez es sólo cuestión optimizador.

+0

sustituya también '' DISTINCT' con un GRUPO BY' –

+0

Tratamos de forzar a índice, pero en realidad no ayudan. –

+0

@FrancisAvila reemplazar DISTINCT con un GROUP BY no hace ninguna diferencia. –

0

Parece que estás queriendo a los padres del resultado individual. De acuerdo con la persona acreditada para definir Conjuntos anidados en SQL, Joe Celko en http://www.ibase.ru/devinfo/DBMSTrees/sqltrees.html "Este modelo es una forma natural de mostrar una explosión de piezas, porque un ensamblaje final está compuesto de ensamblajes físicamente anidados que se dividen en partes separadas. "

En otras palabras, conjuntos anidados se utilizan para filtrar los niños de manera eficiente para un número arbitrario de niveles independientes dentro de una sola colección. Tienes dos tablas, pero no veo dónde las propiedades del conjunto "ubicaciones" no pueden ser des-normalizadas en "localidades".

Si la tabla localidades tenía una columna de geometría, podría no encontrar la localidad uno de una "localización" y luego seleccionar en la tabla usando un único filtro: parent.lft < = row.left y los padres. rgt> = row.rgt?

ACTUALIZADO

En esta respuesta https://stackoverflow.com/a/1743952/3018894, no es un ejemplo de http://explainextended.com/2009/09/29/adjacency-list-vs-nested-sets-mysql/ donde el ejemplo siguiente obtiene todos los antepasados ​​a una profundidad arbitraria de 100000:

SELECT hp.id, hp.parent, hp.lft, hp.rgt, hp.data 
FROM (
    SELECT @r AS _id, 
      @level := @level + 1 AS level, 
      (
      SELECT @r := NULLIF(parent, 0) 
      FROM t_hierarchy hn 
      WHERE id = _id 
      ) 
    FROM (
      SELECT @r := 1000000, 
        @level := 0 
      ) vars, 
      t_hierarchy hc 
    WHERE @r IS NOT NULL 
    ) hc 
JOIN t_hierarchy hp 
ON  hp.id = hc._id 
ORDER BY 
    level DESC 
Cuestiones relacionadas