Poruszanie się po MySQL Błąd „Nie można ponownie otworzyć tabeli”

91

Obecnie jestem zajęty wdrażaniem pewnego rodzaju filtru, dla którego muszę wygenerować klauzulę INNER JOIN dla każdego „tagu” do filtrowania.

Problem w tym, że po całej masie SQL mam tabelę, która zawiera wszystkie informacje potrzebne mi do dokonania wyboru, ale potrzebuję jej ponownie dla każdego wygenerowanego INNER JOIN

Zasadniczo wygląda to tak:

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

To działa, ale wolałbym, aby tabela „wyszukiwania” była tymczasowa (może być o kilka rzędów wielkości mniejsza, jeśli nie jest to normalna tabela), ale powoduje to bardzo irytujący błąd: Can't reopen table

Niektóre badania prowadzą mnie do tego raportu o błędzie, ale ludzie z MySQL nie przejmują się tym, że taka podstawowa funkcja (używanie tabeli więcej niż raz) nie działa z tabelami tymczasowymi. W związku z tym problemem mam wiele problemów ze skalowalnością.

Czy jest jakieś możliwe obejście, które nie wymaga ode mnie zarządzania potencjalnie wieloma tymczasowymi, ale bardzo rzeczywistymi tabelami lub utrzymywania ogromnej tabeli ze wszystkimi danymi?

Z poważaniem, Kris

[dodatkowy]

Odpowiedź GROUP_CONCAT nie działa w mojej sytuacji, ponieważ moje warunki to wiele kolumn w określonej kolejności, spowodowałoby to, że OR z tego, czego potrzebuję, to AND. Jednak pomogło mi to rozwiązać wcześniejszy problem, więc teraz tabela, tymczasowa lub nie, nie jest już wymagana. Myśleliśmy po prostu zbyt ogólnikowo dla naszego problemu. Całe zastosowanie filtrów zostało teraz cofnięte z około minuty do znacznie poniżej jednej czwartej sekundy.

Kris
źródło
2
Miałem ten sam problem z użyciem tabeli tymczasowej dwukrotnie w tym samym zapytaniu przy użyciu UNION.
Sebastián Grignoli

Odpowiedzi:

126

Prostym rozwiązaniem jest zduplikowanie tabeli tymczasowej. Działa dobrze, jeśli stół jest stosunkowo mały, co często ma miejsce w przypadku tabel tymczasowych.

Pete
źródło
8
Powinna być wybraną odpowiedzią, ponieważ jest to odpowiedź na problem, bez chodzenia po okolicy.
dyesdyes
4
jakaś rada, jak powielić tabelę? (Mam na myśli sposób kopiowania bez powtarzania zapytania)
Hernán Eche
17
Nawet jeśli tabela tymczasowa jest duża, pamięć podręczna mysql powinna ci pomóc. Jeśli chodzi o kopiowanie z jednej tabeli tymczasowej do drugiej, wystarczy proste polecenie „CREATE TEMPORARY TABLE tmp2 SELECT * FROM tmp1”.
AS7K
2
Jeśli skopiujesz kuszącą zawartość, nie zapomnij również o utworzeniu indeksów, w razie potrzeby twoje zapytanie może być dość powolne.
gaborsch
1
@NgSekLong Yes. Cały czas. Oczywiście zależy to od aplikacji obsługującej zapytanie, ale nie widzę „ogromnych” problemów z wydajnością aż do> 100 000. W jednym procesie ETL używam tej metody z tabelą 3,5 miliona. Szybkość tej aplikacji nie jest jednak tak ważna.
Tanner Clark
49

Tak, dokumentacja MySQL mówi: „Nie możesz odwołać się do TEMPORARYtabeli więcej niż raz w tym samym zapytaniu”.

Oto alternatywne zapytanie, które powinno znaleźć te same wiersze, chociaż wszystkie warunki pasujących wierszy nie będą znajdować się w osobnych kolumnach, będą znajdować się na liście oddzielonej przecinkami.

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
źródło
2
To właściwie nie rozwiązało mojego problemu, ale pozwoliło mi uprościć problem, który go spowodował, tym samym negując potrzebę pokusy. Dzięki!
Kris
6

Poradziłem sobie z tym, tworząc stałą „tymczasową” tabelę i dodając sufiks SPID (przepraszam, pochodzę z SQL Server) do nazwy tabeli, aby utworzyć unikalną nazwę tabeli. Następnie utwórz dynamiczne instrukcje SQL w celu utworzenia zapytań. Jeśli stanie się coś złego, tabela zostanie usunięta i odtworzona.

Mam nadzieję na lepszą opcję. No dalej, twórcy MySQL. „Błąd” / „żądanie funkcji” jest otwarte od 2008 roku! Wygląda na to, że wszystkie napotkane `` błędy '' znajdują się na tej samej łodzi.

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;
boki
źródło
Miejmy nadzieję, że teraz, gdy Oracle przejmuje władzę, może dać MySQL dobry impuls.
Pacerier
2
westchnij , wątpię :(
buki
3
Wielkie westchnienie . Lipiec 2016 r., A ten błąd tabeli tymczasowej nadal nie został naprawiony. Prawdopodobnie wymyślę jakiś numer sekwencyjny połączony ze stałą nazwą tabeli (pochodzę z kraju Oracle), aby obejść ten problem.
TheWalkingData
Hattrick wzdycha ... To może nigdy nie zostać naprawione, biorąc pod uwagę już 2019 rok.
Zimano
3

Osobiście zrobiłbym po prostu stały stół. Możesz chcieć utworzyć oddzielną bazę danych dla tych tabel (przypuszczalnie będą one potrzebowały unikalnych nazw, ponieważ wiele z tych zapytań można wykonać naraz), a także aby umożliwić rozsądne ustawianie uprawnień (możesz ustawić uprawnienia do baz danych; możesz ' t ustaw uprawnienia dla symboli wieloznacznych tabeli).

Wtedy potrzebowałbyś również zadania porządkowania, aby od czasu do czasu usunąć stare (MySQL wygodnie zapamiętuje czas utworzenia tabeli, więc możesz po prostu użyć tego, aby ustalić, kiedy wymagane jest czyszczenie)

MarkR
źródło
9
Tabele tymczasowe mają tę ogromną zaletę, że można jednocześnie uruchomić wiele zapytań. Nie jest to możliwe w przypadku stałych tabel.
Pacerier
Myślę, że stałe „rozwiązanie” stołu nie jest rozwiązaniem. Z pewnością rozwiązuje problem, ale nie jest praktyczny. Pojawia się tyle pytań: jak utworzyć więcej niż jedno w tym samym czasie? Jak poradzisz sobie z konwencją nazewnictwa i nadpisywaniem tych samych nazwanych tabel? Jak wygląda proces usuwania tabeli stałej? Gdybyś mógł opracować wykonalne rozwiązanie przy użyciu stałych tabel, odpowiadając na te pytania, wszyscy jestem uszami!
Tanner Clark
0

Udało mi się zmienić zapytanie na stałą tabelę i to naprawiło to za mnie. (zmieniono ustawienia VLDB w MicroStrategy, tymczasowy typ tabeli).


źródło
0

Możesz to obejść, tworząc stałą tabelę, którą później usuniesz, lub po prostu utwórz 2 oddzielne tabele tymczasowe z tymi samymi danymi

Inc33
źródło