Contornar o erro “Não é possível reabrir tabela” do MySQL

88

No momento, estou ocupado implementando um tipo de filtro para o qual preciso gerar uma cláusula INNER JOIN para cada "tag" a ser filtrada.

O problema é que, depois de muito SQL, tenho uma tabela que contém todas as informações de que preciso para fazer minha seleção, mas preciso dela novamente para cada INNER JOIN gerado

Isso basicamente se parece com:

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

Isso funciona, mas eu preferiria que a tabela de "pesquisa" fosse temporária (pode ser várias ordens de magnitude menor se não for uma tabela normal), mas isso me dá um erro muito irritante: Can't reopen table

Algumas pesquisas me levam a este relatório de bug, mas o pessoal do MySQL não parece se importar que esse recurso básico (usar uma tabela mais de uma vez) não funcione com tabelas temporárias. Estou tendo muitos problemas de escalabilidade com esse problema.

Existe alguma solução alternativa viável que não exija que eu gerencie muitas tabelas temporárias, mas muito reais, ou me obrigue a manter uma tabela enorme com todos os dados nela?

Atenciosamente, Kris

[adicional]

A resposta GROUP_CONCAT não funciona na minha situação porque minhas condições são várias colunas em uma ordem específica, faria ORs do que eu preciso ser ANDs. No entanto, isso me ajudou a resolver um problema anterior, então agora a mesa, temporária ou não, não é mais necessária. Estávamos pensando genérico demais para o nosso problema. A aplicação inteira de filtros agora foi trazida de cerca de um minuto para bem menos de um quarto de segundo.

Kris
fonte
2
Eu tive o mesmo problema usando uma tabela temporária duas vezes na mesma consulta usando UNION.
Sebastián Grignoli

Respostas:

122

Uma solução simples é duplicar a tabela temporária. Funciona bem se a tabela for relativamente pequena, o que geralmente é o caso com tabelas temporárias.

Pete
fonte
8
Na verdade, deve ser a resposta escolhida, pois responde ao problema, sem rodeios.
dyesdyes
4
algum conselho sobre como você duplicaria a tabela? (Quero dizer uma forma de copiar não repetir a consulta)
Hernán Eche
16
Mesmo se a tabela temporária for grande, o cache do mysql deve ajudá-lo. Quanto a copiar de uma tabela temporária para outra, um simples "CREATE TEMPORARY TABLE tmp2 SELECT * FROM tmp1" deve fazer isso.
AS7K de
2
Se você copiar o conteúdo tentável, não se esqueça de criar índices também, caso contrário, sua consulta pode ser bem lenta.
gaborsch de
1
@NgSekLong Sim. O tempo todo. Obviamente, depende do seu aplicativo para a consulta, mas não vejo problemas de desempenho "enormes" até> 100.000. Em um processo ETL, eu uso esse método com uma tabela de 3,5mil. A velocidade desse aplicativo não é tão importante.
Tanner Clark
49

Certo, os documentos do MySQL dizem: "Você não pode se referir a uma TEMPORARYtabela mais de uma vez na mesma consulta."

Aqui está uma consulta alternativa que deve encontrar as mesmas linhas, embora todas as condições de linhas correspondentes não estejam em colunas separadas, elas estarão em uma lista separada por vírgulas.

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
fonte
2
Isso não resolveu realmente o meu problema, mas me permitiu simplificar o problema que o causou, negando assim a necessidade da tentação. Obrigado!
Kris
6

Eu contornei isso criando uma tabela "temporária" permanente e sufixando o SPID (desculpe, eu sou do SQL Server Land) para o nome da tabela, para fazer um nome de tabela exclusivo. Em seguida, criar instruções SQL dinâmicas para criar as consultas. Se algo de ruim acontecer, a tabela será descartada e recriada.

Espero uma opção melhor. Vamos, MySQL Devs. O 'bug' / 'solicitação de recurso' está aberto desde 2008! Parece que todos os 'bugs' que encontrei estão no mesmo 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;
abelhas
fonte
Esperançosamente, agora que temos a Oracle assumindo as rédeas, ela pode dar um bom empurrão ao MySQL.
Pacerier
2
suspiro , duvido :(
será
3
Um grande suspiro . Julho de 2016, e este bug de tabela temporária ainda não foi corrigido. Provavelmente, apresentarei algum tipo de número de sequência concatenado com um nome de tabela permanente (sou da terra da Oracle) para contornar esse problema.
TheWalkingData
Hattrick suspira ... Pode nunca ser consertado, visto que já é 2019.
Zimano
3

Pessoalmente, eu apenas faria dela uma mesa permanente. Você pode querer criar um banco de dados separado para essas tabelas (presumivelmente, eles precisarão de nomes exclusivos, pois muitas dessas consultas podem ser feitas de uma vez), também para permitir que as permissões sejam definidas de maneira sensata (você pode definir permissões nos bancos de dados; você pode ' t definir permissões em curingas de tabela).

Então você também precisaria de um trabalho de limpeza para remover os antigos ocasionalmente (o MySQL lembra convenientemente a hora em que uma tabela foi criada, então você poderia apenas usar isso para resolver quando uma limpeza foi necessária)

MarkR
fonte
9
As tabelas temporárias têm a vantagem extrema de permitir que várias consultas sejam executadas simultaneamente. Isso não é possível com tabelas permanentes.
Pacerier
Acho que a "solução" da mesa permanente não é uma solução. Resolve o problema com certeza, mas não é prático. Muitas perguntas surgem: Como faço para criar mais de um ao mesmo tempo? Como você lidaria com a convenção de nomenclatura e sobrescrevendo as mesmas tabelas nomeadas? Qual é o processo de exclusão da mesa permanente? Se você pudesse elaborar uma solução viável usando tabelas permanentes enquanto responde a essas perguntas, estou todo ouvidos!
Tanner Clark
0

Consegui alterar a consulta para uma tabela permanente e isso corrigiu para mim. (alterou as configurações de VLDB no MicroStrategy, tipo de tabela temporária).


fonte
-1

Você pode contornar isso criando uma tabela permanente, que irá remover depois, ou apenas fazendo 2 tabelas temporárias separadas com os mesmos dados

Inc33
fonte