2016-07-01 15 views
2

Ich habe sieben große Tabellen, die zu jeder Zeit zwischen 100 bis 1 Million Zeilen speichern können. Ich rufe sie LargeTable1, LargeTable2, LargeTable3, LargeTable4 ... LargeTable7. Diese Tabellen sind größtenteils statisch: Es gibt keine Aktualisierungen oder neue Einfügungen. Sie ändern sich nur einmal alle zwei Wochen oder einmal im Monat, wenn sie abgeschnitten werden und eine neue Gruppe von Registern eingefügt wird. Alle diese Tabellen haben drei gemeinsame Felder: Headquarter, und File. Headquarter und Country sind Zahlen im Format "000", obwohl sie in zwei dieser Tabellen aufgrund anderer Systemanforderungen als int geparst werden.Leistungsprobleme mit UNION von großen Tabellen

Ich habe eine andere, viel kleinere Tabelle namens Headquarters mit den Informationen von jedem Hauptsitz. Diese Tabelle enthält nur sehr wenige Einträge. Höchstens 1000.

Nun, ich brauche eine gespeicherte Prozedur zu erstellen, die alle diese Zentrale zurückgibt, die in den großen Tabellen erscheinen, sind aber entweder nicht vorhanden in der Headquarters Tabelle oder gelöscht wurden (diese Tabelle logisch gelöscht wird: es hat eine DeletionDate Feld zu überprüfen Dies).

Dies ist die Abfrage, die ich versucht habe:

CREATE PROCEDURE deletedHeadquarters 
AS 
BEGIN 
    DECLARE @headquartersFiles TABLE 
    (
     hq int, 
     countryFile varchar(MAX) 
    ); 

    SET NOCOUNT ON 

    INSERT INTO @headquartersFiles 
    SELECT headquarter, CONCAT(country, ' (', file, ')') 
    FROM 
    (
     SELECT DISTINCT CONVERT(int, headquarter) as headquarter, 
         CONVERT(int, country) as country, 
         file 
     FROM   LargeTable1  
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable2 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable3 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable4 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable5 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable6 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable7 
    ) TC 

    SELECT RIGHT('000' + CAST(st.headquarter AS VARCHAR(3)), 3) as headquarter, 
      MAX(s.deletionDate) as deletionDate, 
      STUFF 
      (
       (SELECT DISTINCT ', ' + st2.countryFile 
       FROM @headquartersFiles st2 
       WHERE st2.headquarter = st.headquarter 
       FOR XML PATH('')), 
       1, 
       1, 
       '' 
      ) countryFile 
    FROM @headquartersFiles as st 
    LEFT JOIN headquarters s ON CONVERT(int, s.headquarter) = st.headquarter 
    WHERE s.headquarter IS NULL 
     OR s.deletionDate IS NOT NULL 
    GROUP BY st.headquarter 

END 

Die Leistung dieses sp ist für unsere Anwendung nicht gut genug. Es dauert zur Zeit etwa 50 Sekunden abgeschlossen, mit den folgenden Gesamt Zeilen für jede Tabelle (nur Sie über die Größen eine Idee geben):

  • LargeTable1: 1.516.666 Reihen
  • LargeTable2: 645.740 Reihen
  • LargeTable3 : 1950121 Reihen
  • LargeTable4: 779.336 Zeilen
  • LargeTable5: 1.100.999 Reihen
  • LargeTable6: 16499 Zeilen
  • LargeTable7: 24454 Zeilen

Was kann ich tun, um die Leistung zu verbessern? Ich habe versucht, die folgenden zu tun, ohne viel Unterschied:

  • von Chargen in der lokalen Tabelle einfügen, ohne diese Zentrale ich bereits eingefügt haben und dann für diejenigen, die Country Feld zu aktualisieren, die
  • wiederholt werden
  • Erstellen einer Ansicht für die UNION-Abfrage
  • Erstellen von Indizes für die LargeTables für den Hauptsitz Feld

Ich habe auch über das Einfügen dieser fehlenden Zentrale in eine permanente Tabelle nach der LargeTables Änderung nachgedacht, aber die Headquarters Tabelle kann öfter ändern, und ich möchte nicht sein Modul ändern müssen, um diese Dinge aufgeräumt und aktualisiert zu halten. Aber wenn es die bestmögliche Alternative ist, würde ich mich dafür entscheiden.

Dank

+2

AFAIK die Union-Klausel wird die distinct zu tun, also keine Notwendigkeit, distinct auf Select – vercelli

+0

@vercelli enthalten Es kann mehrere Zeilen mit der gleichen Hauptquartier-Land-Datei-Kombination in der gleichen Tabelle sein. Wird die UNION sie auch beseitigen, auch wenn sie von einem Tisch kommen? – Heathcliff

+2

@Heathcliff ja, das ist was UNION tut. UNION ALL wird nicht. – Hogan

Antwort

1

Die Filterung bei jedem Schritt durchführen. Aber zuerst, ändern Sie die headquarters Tabelle, so hat es den richtigen Typ für das, was Sie brauchen. . . zusammen mit einem Index:

alter table headquarters add headquarter_int as (cast(headquarter as int)); 
create index idx_headquarters_int on headquarters(headquarters_int); 

SELECT DISTINCT headquarter, country, file 
FROM LargeTable5 lt5 
WHERE NOT EXISTS (SELECT 1 
        FROM headquarters s 
        WHERE s.headquarter_int = lt5.headquarter and s.deletiondate is not null 
       ); 

Dann wollen Sie einen Index für LargeTable5(headquarter, country, file).

Dies sollte weniger als 5 Sekunden dauern. Wenn dies der Fall ist, erstellen Sie die vollständige Abfrage und stellen Sie sicher, dass die Typen in der korrelierten Unterabfrage übereinstimmen und dass Sie über den richtigen Index für die vollständige Tabelle verfügen. Verwenden Sie , um Duplikate zwischen den Tabellen zu entfernen.

+1

Dieser half am meisten die Zeit der gespeicherten Prozedur zu reduzieren, also werde ich es richtig markieren. – Heathcliff

2

Nehmen Sie diesen Filter

LEFT JOIN headquarters s ON CONVERT(int, s.headquarter) = st.headquarter 
WHERE s.headquarter IS NULL 
    OR s.deletionDate IS NOT NULL 

Und es auf jede einzelne Abfrage in der Union hinzuzufügen und fügen Sie in @headquartersFiles

Es könnte scheinen, wie dies viel mehr Filter macht, aber es wird tatsächlich beschleunigen, weil Sie filtern, bevor Sie die Verarbeitung als eine Union beginnen.

Nehmen Sie auch alle Ihre DISTINCT, es wird wahrscheinlich nicht beschleunigen, aber es scheint albern, weil Sie eine UNION und keine UNION alle tun.

+0

Dies funktioniert nicht, wenn ein Hauptquartier an einem Tisch fehlt, aber nicht an einem anderen. –

+0

@TomH Ich habe keine Ahnung, was Sie meinen, die Filterkriterien sind in der verbundenen Tabelle nicht die Quellentabelle. – Hogan

+0

Die Abfrage des OP gibt ein Hauptquartier zurück, wenn es in ALLEN Tabellen fehlt. Ihre Abfrage gibt eine Zentrale zurück, wenn sie in einer der Tabellen fehlt. –

1

Ich würde versuchen, die Filterung mit jeder einzelnen Tabelle zuerst. Sie müssen nur die Tatsache berücksichtigen, dass ein Hauptquartier in einer Tabelle erscheint, aber nicht in einer anderen. Sie können dies tun, wie so:

SELECT 
    headquarter 
FROM 
(

    SELECT DISTINCT 
     headquarter, 
     'table1' AS large_table 
    FROM 
     LargeTable1 LT 
    LEFT OUTER JOIN Headquarters HQ ON HQ.headquarter = LT.headquarter 
    WHERE 
     HQ.headquarter IS NULL OR 
     HQ.deletion_date IS NOT NULL 
    UNION ALL 
    SELECT DISTINCT 
     headquarter, 
     'table2' AS large_table 
    FROM 
     LargeTable2 LT 
    LEFT OUTER JOIN Headquarters HQ ON HQ.headquarter = LT.headquarter 
    WHERE 
     HQ.headquarter IS NULL OR 
     HQ.deletion_date IS NOT NULL 
    UNION ALL 
    ... 
) SQ 
GROUP BY headquarter 
HAVING COUNT(*) = 5 

, die dafür sorgen würde, dass es von allen fünf Tabellen fehlt.

1

Tabelle Variablen haben schreckliche Leistung, weil SQL Server keine Statistiken für sie generiert. Versuchen Sie statt einer Tabellenvariablen stattdessen eine temporäre Tabelle, und fügen Sie in der temporären Tabellendefinition eine eindeutige Integritätsregel hinzu (die einen gruppierten Index erstellt), wenn "Hauptsitz + Land + Datei" in der temporären Tabelle eindeutig ist. Sie können nach dem Erstellen Indizes für eine temporäre Tabelle festlegen. SQL Server ignoriert sie jedoch aus verschiedenen Gründen.

Edit: wie sich herausstellt, können Sie tatsächlich Indizes für Tabellenvariablen erstellen, sogar nicht eindeutig in 2014+.

Zweitens, versuchen Sie nicht, Funktionen in Ihren Joins oder Where-Klauseln zu verwenden - dies führt oft zu Leistungsproblemen.

0

Die wirkliche Antwort ist, separate INSERT Anweisungen für jede Tabelle mit dem Vorbehalt zu erstellen, dass Daten eingefügt werden nicht in der Zieltabelle vorhanden sind.