Cómo evitar el error "No se puede volver a abrir la tabla" de MySQL

91

Actualmente estoy ocupado implementando una especie de filtro para el que necesito generar una clausse INNER JOIN para cada "etiqueta" para filtrar.

El problema es que después de un montón de SQL, tengo una tabla que contiene toda la información que necesito para hacer mi selección, pero la necesito nuevamente para cada INNER JOIN generada

Esto básicamente se ve así:

SELECT
    *
FROM search
INNER JOIN search f1 ON f1.baseID = search.baseID AND f1.condition = condition1
INNER JOIN search f2 ON f2.baseID = search.baseID AND f2.condition = condition2
...
INNER JOIN search fN ON fN.baseID = search.baseID AND fN.condition = conditionN

Esto funciona, pero preferiría que la tabla de "búsqueda" sea temporal (puede ser varios órdenes de magnitud más pequeña si no es una tabla normal) pero eso me da un error muy molesto: Can't reopen table

Algunas investigaciones me llevan a este informe de error. pero a la gente de MySQL no parece importarle que una característica tan básica (usar una tabla más de una vez) no funcione con tablas temporales. Tengo muchos problemas de escalabilidad con este problema.

¿Existe alguna solución alternativa viable que no requiera que administre potencialmente muchas tablas temporales pero muy reales o que me haga mantener una tabla enorme con todos los datos en ella?

Saludos cordiales, Kris

[adicional]

La respuesta GROUP_CONCAT no funciona en mi situación porque mis condiciones son múltiples columnas en un orden específico, haría OR de lo que necesito para ser AND. Sin embargo, me ayudó a resolver un problema anterior, por lo que ahora la tabla, temporal o no, ya no es necesaria. Estábamos pensando en algo demasiado genérico para nuestro problema. La aplicación completa de filtros ahora se ha reducido de alrededor de un minuto a menos de un cuarto de segundo.

Kris
fuente
2
Tuve el mismo problema al usar una tabla temporal dos veces en la misma consulta usando UNION.
Sebastián Grignoli

Respuestas:

126

Una solución sencilla es duplicar la tabla temporal. Funciona bien si la tabla es relativamente pequeña, que suele ser el caso de las tablas temporales.

Pete
fuente
8
En realidad, debería ser la respuesta elegida, ya que responde al problema, sin dar vueltas.
tintes tintes
4
¿Algún consejo sobre cómo duplicarías la tabla? (Me refiero a una forma de copiar no repetir la consulta)
Hernán Eche
17
Incluso si la tabla temporal es grande, la caché de mysql debería ayudarlo. En cuanto a copiar de una tabla temporal a otra, un simple "CREAR TABLA TEMPORAL tmp2 SELECT * FROM tmp1" debería hacerlo.
AS7K
2
Si copia el contenido tentador, no olvide crear índices también, de lo contrario su consulta puede ser bastante lenta.
gaborsch
1
@NgSekLong Sí. Todo el tiempo. Obviamente, depende de su aplicación para la consulta, pero no veo problemas de rendimiento "enormes" hasta> 100.000. En un proceso ETL, utilizo este método con una tabla de 3,5 mil. Sin embargo, la velocidad de esa aplicación no es tan importante.
Tanner Clark
49

Correcto, los documentos de MySQL dicen: "No se puede hacer referencia a una TEMPORARYtabla más de una vez en la misma consulta".

Aquí hay una consulta alternativa que debería encontrar las mismas filas, aunque todas las condiciones de las filas coincidentes no estarán en columnas separadas, estarán en una lista separada por comas.

SELECT f1.baseID, GROUP_CONCAT(f1.condition)
FROM search f1
WHERE f1.condition IN (<condition1>, <condition2>, ... <conditionN>)
GROUP BY f1.baseID
HAVING COUNT(*) = <N>;
Bill Karwin
fuente
2
Esto en realidad no resolvió mi problema en cuestión, pero me permitió simplificar el problema que lo causó, negando así la necesidad de lo tentador. ¡Gracias!
Kris
6

Lo solucioné creando una tabla "temporal" permanente y agregando el sufijo SPID (lo siento, soy de SQL Server land) al nombre de la tabla, para hacer un nombre de tabla único. Luego, creando sentencias SQL dinámicas para crear las consultas. Si sucede algo malo, la tabla se eliminará y se volverá a crear.

Espero una mejor opción. Vamos, desarrolladores de MySQL. ¡El 'error' / 'solicitud de función' ha estado abierto desde 2008! Parece que todos los "errores" que he encontrado están en el mismo barco.

select concat('ReviewLatency', CONNECTION_ID()) into @tablename;

#Drop "temporary" table if it exists
set @dsql=concat('drop table if exists ', @tablename, ';');
PREPARE QUERY1 FROM @dsql;
EXECUTE QUERY1;
DEALLOCATE PREPARE QUERY1;

#Due to MySQL bug not allowing multiple queries in DSQL, we have to break it up...
#Also due to MySQL bug, you cannot join a temporary table to itself,
#so we create a real table, but append the SPID to it for uniqueness.
set @dsql=concat('
create table ', @tablename, ' (
    `EventUID` int(11) not null,
    `EventTimestamp` datetime not null,
    `HasAudit` bit not null,
    `GroupName` varchar(255) not null,
    `UserID` int(11) not null,
    `EventAuditUID` int(11) null,
    `ReviewerName` varchar(255) null,
    index `tmp_', @tablename, '_EventUID` (`EventUID` asc),
    index `tmp_', @tablename, '_EventAuditUID` (`EventAuditUID` asc),
    index `tmp_', @tablename, '_EventUID_EventTimestamp` (`EventUID`, `EventTimestamp`)
) ENGINE=MEMORY;');
PREPARE QUERY2 FROM @dsql;
EXECUTE QUERY2;
DEALLOCATE PREPARE QUERY2;

#Insert into the "temporary" table
set @dsql=concat('
insert into ', @tablename, ' 
select e.EventUID, e.EventTimestamp, e.HasAudit, gn.GroupName, epi.UserID, eai.EventUID as `EventAuditUID`
    , concat(concat(concat(max(concat('' '', ui.UserPropertyValue)), '' (''), ut.UserName), '')'') as `ReviewerName`
from EventCore e
    inner join EventParticipantInformation epi on e.EventUID = epi.EventUID and epi.TypeClass=''FROM''
    inner join UserGroupRelation ugr on epi.UserID = ugr.UserID and e.EventTimestamp between ugr.EffectiveStartDate and ugr.EffectiveEndDate 
    inner join GroupNames gn on ugr.GroupID = gn.GroupID
    left outer join EventAuditInformation eai on e.EventUID = eai.EventUID
    left outer join UserTable ut on eai.UserID = ut.UserID
    left outer join UserInformation ui on eai.UserID = ui.UserID and ui.UserProperty=-10
    where e.EventTimestamp between @StartDate and @EndDate
        and e.SenderSID = @FirmID
    group by e.EventUID;');
PREPARE QUERY3 FROM @dsql;
EXECUTE QUERY3;
DEALLOCATE PREPARE QUERY3;

#Generate the actual query to return results. 
set @dsql=concat('
select rl1.GroupName as `Group`, coalesce(max(rl1.ReviewerName), '''') as `Reviewer(s)`, count(distinct rl1.EventUID) as `Total Events`
    , (count(distinct rl1.EventUID) - count(distinct rl1.EventAuditUID)) as `Unreviewed Events`
    , round(((count(distinct rl1.EventUID) - count(distinct rl1.EventAuditUID)) / count(distinct rl1.EventUID)) * 100, 1) as `% Unreviewed`
    , date_format(min(rl2.EventTimestamp), ''%W, %b %c %Y %r'') as `Oldest Unreviewed`
    , count(distinct rl3.EventUID) as `<=7 Days Unreviewed`
    , count(distinct rl4.EventUID) as `8-14 Days Unreviewed`
    , count(distinct rl5.EventUID) as `>14 Days Unreviewed`
from ', @tablename, ' rl1
left outer join ', @tablename, ' rl2 on rl1.EventUID = rl2.EventUID and rl2.EventAuditUID is null
left outer join ', @tablename, ' rl3 on rl1.EventUID = rl3.EventUID and rl3.EventAuditUID is null and rl1.EventTimestamp > DATE_SUB(NOW(), INTERVAL 7 DAY) 
left outer join ', @tablename, ' rl4 on rl1.EventUID = rl4.EventUID and rl4.EventAuditUID is null and rl1.EventTimestamp between DATE_SUB(NOW(), INTERVAL 7 DAY) and DATE_SUB(NOW(), INTERVAL 14 DAY)
left outer join ', @tablename, ' rl5 on rl1.EventUID = rl5.EventUID and rl5.EventAuditUID is null and rl1.EventTimestamp < DATE_SUB(NOW(), INTERVAL 14 DAY)
group by rl1.GroupName
order by ((count(distinct rl1.EventUID) - count(distinct rl1.EventAuditUID)) / count(distinct rl1.EventUID)) * 100 desc
;');
PREPARE QUERY4 FROM @dsql;
EXECUTE QUERY4;
DEALLOCATE PREPARE QUERY4;

#Drop "temporary" table
set @dsql = concat('drop table if exists ', @tablename, ';');
PREPARE QUERY5 FROM @dsql;
EXECUTE QUERY5;
DEALLOCATE PREPARE QUERY5;
beeks
fuente
Con suerte, ahora que Oracle se hace cargo de las riendas, puede darle un buen impulso a MySQL.
Pacerier
2
suspiro lo dudo :(
beeks
3
Un gran suspiro . Julio de 2016, y este error de la tabla temporal aún no se ha solucionado. Probablemente se me ocurrirá algún tipo de número de secuencia concatenado con un nombre de tabla permanente (soy de Oracle Land) para evitar este problema.
TheWalkingData
Hattrick suspiro ... Puede que nunca se arregle, ya que es 2019.
Zimano
3

Personalmente, lo convertiría en una mesa permanente. Es posible que desee crear una base de datos separada para estas tablas (presumiblemente necesitarán nombres únicos, ya que muchas de estas consultas podrían realizarse a la vez), también para permitir que los permisos se establezcan de manera sensata (puede establecer permisos en bases de datos; puede ' t establecer permisos en comodines de tabla).

Luego, también necesitaría un trabajo de limpieza para eliminar los antiguos de vez en cuando (MySQL recuerda convenientemente la hora en que se creó una tabla, por lo que podría usar eso para calcular cuándo se requirió una limpieza)

MarkR
fuente
9
Las tablas temporales tienen la gran ventaja de que puede tener varias consultas ejecutándose simultáneamente. Esto no es posible con tablas permanentes.
Pacerier
Creo que la "solución" de la mesa permanente no es una solución. Resuelve el problema con seguridad, pero no es práctico. Surgen tantas preguntas: ¿Cómo puedo crear más de uno al mismo tiempo? ¿Cómo lidiaría con la convención de nomenclatura y la sobrescritura de tablas con el mismo nombre? ¿Cuál es el proceso para eliminar la mesa permanente? Si pudiera elaborar una solución factible utilizando tablas permanentes mientras responde estas preguntas, ¡soy todo oídos!
Tanner Clark
0

Pude cambiar la consulta a una tabla permanente y esto me solucionó. (cambió la configuración de VLDB en MicroStrategy, tipo de tabla temporal).


fuente
0

Puede evitarlo haciendo una tabla permanente, que eliminará después, o simplemente haciendo 2 tablas temporales separadas con los mismos datos

Inc33
fuente