Contournement de l'erreur MySQL "Impossible de rouvrir la table"

88

Je suis actuellement occupé à implémenter un type de filtre pour lequel je dois générer un clausse INNER JOIN pour chaque "tag" sur lequel filtrer.

Le problème est qu'après tout un tas de SQL, j'ai une table qui contient toutes les informations dont j'ai besoin pour faire ma sélection, mais j'en ai encore besoin pour chaque INNER JOIN généré

Cela ressemble essentiellement à:

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

Cela fonctionne mais je préférerais de loin que la table de «recherche» soit temporaire (elle peut être plusieurs ordres de grandeur plus petite si ce n'est pas une table normale) mais cela me donne une erreur très ennuyeuse: Can't reopen table

Certaines recherches m'amènent à ce rapport de bogue, mais les gens de MySQL ne semblent pas se soucier du fait qu'une telle fonctionnalité de base (en utilisant une table plus d'une fois) ne fonctionne pas avec des tables temporaires. Je rencontre beaucoup de problèmes d'évolutivité avec ce problème.

Existe-t-il une solution de contournement viable qui ne m'oblige pas à gérer potentiellement beaucoup de tables temporaires mais très réelles ou à me faire maintenir une énorme table avec toutes les données?

Cordialement, Kris

[Additionnel]

La réponse GROUP_CONCAT ne fonctionne pas dans ma situation car mes conditions sont plusieurs colonnes dans un ordre spécifique, cela ferait des OU à partir de ce dont j'ai besoin pour être des ET. Cependant, cela m'a aidé à résoudre un problème antérieur, donc maintenant la table, temporaire ou non, n'est plus nécessaire. Nous pensions juste trop générique pour notre problème. L'ensemble de l'application des filtres est désormais ramené d'environ une minute à bien moins d'un quart de seconde.

Kris
la source
2
J'ai eu le même problème en utilisant une table temporaire deux fois dans la même requête en utilisant UNION.
Sebastián Grignoli

Réponses:

122

Une solution simple consiste à dupliquer la table temporaire. Fonctionne bien si la table est relativement petite, ce qui est souvent le cas des tables temporaires.

Pete
la source
8
Devrait en fait être la réponse choisie car elle répond au problème, sans faire le tour.
dyesdyes
4
des conseils sur la manière de reproduire le tableau? (Je veux dire une façon de copier sans répéter la requête)
Hernán Eche
16
Même si la table temporaire est volumineuse, le cache de mysql devrait vous aider. En ce qui concerne la copie d'une table temporaire à une autre, un simple "CREATE TEMPORARY TABLE tmp2 SELECT * FROM tmp1" devrait le faire.
AS7K
2
Si vous copiez le contenu tentant, n'oubliez pas de créer également des index, sinon votre requête peut être assez lente.
gaborsch
1
@NgSekLong Oui. Tout le temps. Cela dépend évidemment de votre application pour la requête, mais je ne vois pas de problèmes de performances «énormes» avant> 100 000. Dans un processus ETL, j'utilise cette méthode avec une table de 3,5 mil. La vitesse de cette application n'est cependant pas aussi importante.
Tanner Clark le
49

À droite, la documentation MySQL dit: "Vous ne pouvez pas faire référence à une TEMPORARYtable plus d'une fois dans la même requête."

Voici une requête alternative qui devrait trouver les mêmes lignes, bien que toutes les conditions des lignes correspondantes ne soient pas dans des colonnes séparées, elles seront dans une liste séparée par des virgules.

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
la source
2
Cela n'a pas vraiment résolu mon problème actuel, mais cela m'a permis de simplifier le problème qui l'a causé, éliminant ainsi le besoin du tentable. Merci!
Kris
6

J'ai contourné cela en créant une table "temporaire" permanente et en suffixant le SPID (désolé, je suis du pays SQL Server) au nom de la table, pour créer un nom de table unique. Créez ensuite des instructions SQL dynamiques pour créer les requêtes. Si quelque chose de mauvais se produit, la table sera supprimée et recréée.

J'espère une meilleure option. Allez, MySQL Devs. Le 'bug' / 'feature request' est ouvert depuis 2008! On dirait que tous les «bugs» rencontrés sont dans le même bateau.

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;
abeilles
la source
Espérons que maintenant qu'Oracle prend les rênes, elle peut donner un bon coup de pouce à MySQL.
Pacerier
2
soupir j'en doute :(
beeks
3
Un grand soupir . Juillet 2016, et ce bogue de la table temporaire n'est toujours pas corrigé. Je vais probablement proposer une sorte de numéro de séquence concaténé avec un nom de table permanent (je viens d'Oracle land) pour contourner ce problème.
TheWalkingData du
Hattrick soupire ... Cela ne sera peut-être jamais réparé, vu que c'est déjà 2019.
Zimano
3

Personnellement, je n'en ferais qu'une table permanente. Vous voudrez peut-être créer une base de données distincte pour ces tables (elles auront probablement besoin de noms uniques car beaucoup de ces requêtes peuvent être effectuées à la fois), également pour permettre la définition judicieuse des autorisations (vous pouvez définir des autorisations sur les bases de données; vous pouvez ' t définir les autorisations sur les caractères génériques de table).

Ensuite, vous auriez également besoin d'un travail de nettoyage pour supprimer les anciens de temps en temps (MySQL se souvient facilement de l'heure à laquelle une table a été créée, vous pouvez donc simplement l'utiliser pour déterminer lorsqu'un nettoyage était nécessaire)

MarkR
la source
9
Les tables temporaires présentent l'avantage extrême de pouvoir exécuter simultanément plusieurs requêtes. Cela n'est pas possible avec les tables permanentes.
Pacerier
Je pense que la table permanente "solution" n'est pas une solution. Cela résout le problème à coup sûr, mais ce n'est pas pratique. Tant de questions se posent: comment en créer plusieurs à la fois? Comment géreriez-vous la convention de dénomination et l'écrasement des mêmes tables nommées? Quel est le processus de suppression de la table permanente? Si vous pouviez élaborer une solution réalisable en utilisant des tables permanentes tout en répondant à ces questions, je suis toute oreille!
Tanner Clark le
0

J'ai pu changer la requête en table permanente et cela l'a corrigé pour moi. (modification des paramètres VLDB dans MicroStrategy, type de table temporaire).


la source
-1

Vous pouvez le contourner en créant une table permanente, que vous supprimerez par la suite, ou en créant simplement 2 tables temporaires séparées avec les mêmes données

Inc33
la source