Benutzer mit den meisten Antworten
Query wird extrem langsam, sobald TOP N im Spiel ist

Frage
-
Hallo zusammen,
Ich habe hier eine Anforderung eine Query zu bauen um herauszufinden welche als Pflicht deklarierte Software auf welchen Server NICHT installiert ist.
Als Datenbasis habe ich 3 Tabellen.
1. Die Liste aller vorhandenen Server
2. Die Liste der Software Installationen. Hier ist für jede gefundene Software eine Zeile pro Server enthalten.
3. Eine manuell gepflegte Liste an SW-Paketen nach denen gesucht werden soll. Die Einträge können auch Wildcards enthalten.Die überschneidende Spalte zwischen 1. und 2. Tabelle ist der Servername.
Wir reden hier von Datenmengen von
1. Ca. 2.000 Zeilen
2. Ca. 110.000 Zeilen
3. Derzeit nur 1 ZeileFühre ich diese Query aus oder mach ein Select * von der neuen View ist die in unter einer Sekunde durch und liefert ca. 360 Rows.
Leider arbeitet unser Webfrontend aber gerne mit TOP N für die Pagination und das bricht mir gerade das Genick.Sobald Top N im Spiel ist scheint das überhaupt nicht mehr fertig zu werden. Meistens brech ich so nach 3-4minuten ab. Das Webfrontend würde wohl nach 20 Sekunden aufgeben.
Hättet ihr Ideen wie ich das besser machen kann?
Plan B wäre es die View asynchron in eine Table zu materialisieren. Aber mehr Schahm hätte es schon, wenn das alles mit Live-Daten funktionieren würde.ALTER view View_Device_Server_Win_Missing_Required_SW AS -- 1. CTE erstellt eine Namensliste aus manueller Searchliste und realer SW-Installations-Liste WITH cte_Required_SWPackages AS ( SELECT DISTINCT Installed.Displayname ,Installed.[Version] FROM p_Device_Software_Installations Installed INNER JOIN CFGRequiredSoftware Req ON ( Installed.Displayname LIKE Req.Displayname AND Installed.[Version] LIKE ( CASE WHEN Req.[Version] IS NULL THEN '%' WHEN Req.[Version] = '' THEN '%' ELSE Req.[Version] END ) ) ) -- 2. CTE filtert nur die "Aktive" Server raus ,cte_Active_Windows_Servers AS ( SELECT [Name] ,Verw ,DeviceType ,id FROM View_Device_Server_Win WHERE InstallStatus = 'active' ) -- 3. CTE filtert nur gesuchte Software/Server-Zeilen aus der Gesamt Tabelle ,cte_Currently_Installed AS ( SELECT p_Device_Software_Installations.Computername ,p_Device_Software_Installations.DisplayName ,p_Device_Software_Installations.[Version] FROM p_Device_Software_Installations WHERE p_Device_Software_Installations.DisplayName IN ( SELECT Displayname FROM cte_Required_SWPackages ) AND p_Device_Software_Installations.[Version] IN ( SELECT [Version] FROM cte_Required_SWPackages ) ) -- 4. CTE baut ein Kartesisches Produkt aus Active-Serverlist und gesuchter Softwareprodukte ,cte_workinglist AS ( SELECT * FROM cte_Required_SWPackages FULL JOIN cte_Active_Windows_Servers ON (0 = 0) ) -- Finaler Query stellt das Kartesische Produkt gegenüber der reduzierten SW-Installliste cte_Currently_Installed um raus zufinden was nur im K-Produkt enthalten ist SELECT cte_workinglist.[Name] ,cte_workinglist.[Version] ,cte_workinglist.DisplayName ,cte_workinglist.Verw ,cte_workinglist.DeviceType ,cte_workinglist.id FROM cte_workinglist LEFT JOIN cte_Currently_Installed ON (cte_workinglist.[Name] = cte_Currently_Installed.Computername AND cte_workinglist.DisplayName = cte_Currently_Installed.DisplayName AND cte_workinglist.[Version] = cte_Currently_Installed.[Version]) WHERE Computername IS NULL
Antworten
-
Nun ja, ein SQL ist dann am schnellsten, wenn erst mal alles abgerufen und dann sortiert wird.
Wobei der Sort im Programm dann zu empfehlen ist (die meisten Programmiersprachen können das) damit SQL nicht prüfen muss ob es einen passenden Index gibt.
Bei TOP N muss erst mal alles im Speicher geladen und sortiert werden um dann den Rest wieder zu verwerfen.
Da kann es schon mal andere Optimierungswege geben.Was den Inner Join angeht, so gilt hier einfach:
select * from a inner join b on a.key = b.key
ist identisch zu
select * from b inner join a on a.key = b.key
Aus eine 1:N-Beziehung (N=1 ist auch möglich) wird eine N:1-Beziehung.
Ich kenne zumindest eine Datenbank, die im Zuge der SQL-Optimierung einen SQL diesbezüglich auch anpasst.Wichtig ist für deinen "where Exists" ob die Trefferqoute in Tabelle A oder in Tabelle B besser einzuschränken ist.
Da hier durch Exists auf den Inner Join auf beide Tabellen Bezug genommen wird, sind durch Umdrehen des Joins u.U. weniger Zugriffe zu erwarten.Eine Optimierung von Minuten zu Sekunden finde ich aber schon gut, wenn man bedenkt mit welchen simplen Maßnahmen dies erreicht werden kann.
Statt View kann man ggf. auch eine Table-Function verwenden:
https://www.sqlservertutorial.net/sql-server-user-defined-functions/sql-server-table-valued-functions/Hier kannst du tatsächlich z.B. mittels TEMPORARY TABLE die With-Zwischentabellen erstellen und den finalen Select aus den nun tatsächlichen Tabellen zurück geben.
http://www.sqlines.com/articles/sql-server/local_and_global_temporary_tablesSolange eine Connection/Sitzung stabil bleibt, kann man der Prozedur auch z.B. die Pagingwerte als Parameter mitgeben und braucht nur die fertige Tabelle, die für die Sitzung bestehen bleibt, immer wieder auszulesen.
- Als Antwort markiert Manuel Aigner Freitag, 27. November 2020 09:55
-
Statt "select distinct" solltest du einen "select a, b .... group by a, b" verwenden, da hier ein Index (falls vorhanden) verwendet werden kann.
Warum verwendest du "Installed.Displayname LIKE Req.Displayname" statt "Installed.Displayname = Req.Displayname?
Dies lässt ebenso keinen Index zu. Like lohnt sich nur, wenn du auch tatsächlich "%" im Suchbegriff hast.Dies gilt auch für die 2. Bedingung:
and (Installed.[Version] = Req.[Version] or Req.[Version] = '' or Req.[Version] is null)
Wozu machst dsu einen FULL JOIN?
Dieser liefert ja alle Daten der rechten Seite unabhängig vom Ergebnis der linken Seite.
Ist da ggf. ein CROSS JOIN nicht der richtigere Weg?Dann noch zum Verständnis für dich:
Eine CTE ist keine temporäre Tabelle sondern jeder Zugriff auf eine CTE-Tabelle führt zur Neuberechnung des Ergebnisses. Wenn also keine Indizes verwendet werden können ergeben sich schnell Millionen von Zugriffen.
Dein "... in ... cte_Required_SWPackages" führt den Distinct je Zeile erneut aus.Eine INNER JOIN lässt sich auch umdrehen da ja nur Kombinationen aus beiden benötigt werden.
Statt eines "in (select ...)" ist ein "exists (Select ... where )" erheblich schneller, ins besonders wenn ein Index möglich ist. Somit kannst du die 2 " in ..." zu einem "exists" vereinen.Die Anzahl der Zugriffe wird sich erheblich reduzieren.
- Bearbeitet Der Suchende Donnerstag, 26. November 2020 09:02
- Als Antwort markiert Manuel Aigner Donnerstag, 26. November 2020 10:27
Alle Antworten
-
Statt "select distinct" solltest du einen "select a, b .... group by a, b" verwenden, da hier ein Index (falls vorhanden) verwendet werden kann.
Warum verwendest du "Installed.Displayname LIKE Req.Displayname" statt "Installed.Displayname = Req.Displayname?
Dies lässt ebenso keinen Index zu. Like lohnt sich nur, wenn du auch tatsächlich "%" im Suchbegriff hast.Dies gilt auch für die 2. Bedingung:
and (Installed.[Version] = Req.[Version] or Req.[Version] = '' or Req.[Version] is null)
Wozu machst dsu einen FULL JOIN?
Dieser liefert ja alle Daten der rechten Seite unabhängig vom Ergebnis der linken Seite.
Ist da ggf. ein CROSS JOIN nicht der richtigere Weg?Dann noch zum Verständnis für dich:
Eine CTE ist keine temporäre Tabelle sondern jeder Zugriff auf eine CTE-Tabelle führt zur Neuberechnung des Ergebnisses. Wenn also keine Indizes verwendet werden können ergeben sich schnell Millionen von Zugriffen.
Dein "... in ... cte_Required_SWPackages" führt den Distinct je Zeile erneut aus.Eine INNER JOIN lässt sich auch umdrehen da ja nur Kombinationen aus beiden benötigt werden.
Statt eines "in (select ...)" ist ein "exists (Select ... where )" erheblich schneller, ins besonders wenn ein Index möglich ist. Somit kannst du die 2 " in ..." zu einem "exists" vereinen.Die Anzahl der Zugriffe wird sich erheblich reduzieren.
- Bearbeitet Der Suchende Donnerstag, 26. November 2020 09:02
- Als Antwort markiert Manuel Aigner Donnerstag, 26. November 2020 10:27
-
Vielen Dank schon mal für deine Einwürfe.
"Warum verwendest du "Installed.Displayname LIKE Req.Displayname" statt "Installed.Displayname = Req.Displayname?
Dies lässt ebenso keinen Index zu. Like lohnt sich nur, wenn du auch tatsächlich "%" im Suchbegriff hast."
Das mache ich weil in der Search-Tabelle auch Wildcards drinnen sein können. Sowohl beim DisplayName als auch der Version. Leider ist auf DisplayName eh kein Index möglich. Die wurde damals schlauerweise mit nvarchar(Max) deklariert.
Die groub by Geschichte anstatt von Distinct hab ich umgesetzt.
Wozu machst dsu einen FULL JOIN?
Dieser liefert ja alle Daten der rechten Seite unabhängig vom Ergebnis der linken Seite.
Ist da ggf. ein CROSS JOIN nicht der richtigere Weg?Vollkommen richtig. Hab ich auch umgesetzt. Ich war gestern nach nem Tag gebastle an der Query schon etwas Banane
Dann noch zum Verständnis für dich:
Eine CTE ist keine temporäre Tabelle sondern jeder Zugriff auf eine CTE-Tabelle führt zur Neuberechnung des Ergebnisses. Wenn also keine Indizes verwendet werden können ergeben sich schnell Millionen von Zugriffen.Das wusste ich in der Tat nicht. Dachte eigentlich, dass die CTE-Ergebnisse im RAM geladen werden und dann immer wieder verwendet werden können ohne extra Rechenaufwand. In dem Fall wäre wohl ne Temptable sinnvoller. Das geht aber leider in ner View nicht, welche ich benötige um damit im Web arbeiten zu können (Laravel (PHP) Framework / Datatables Modul).
Gibt es da noch irgend einen Trick den ich noch nicht kenne, wie man das gescheiter umsetzen kann und was View-Kompatibel ist?Eine INNER JOIN lässt sich auch umdrehen da ja nur Kombinationen aus beiden benötigt werden.
Das versteh ich jetzt leider nicht
Statt eines "in (select ...)" ist ein "exists (Select ... where )" erheblich schneller, ins besonders wenn ein Index möglich ist. Somit kannst du die 2 " in ..." zu einem "exists" vereinen.
gute Idee, hab ich jetzt auch so umgesetzt.
Nach den ganzen Maßnahmen ist die Query (View) noch immer nicht schnell, sobald man TOP N verwendet, aber es wird zumindest schon etwas erträglicher. Für TOP 200 aus jetzt 11000 Rows dauerts jetzt ca. 7 Sekunden. vorher lief es mehrere Minuten. Ich versteh auch noch immer nicht, warum mir der TOP N die Performance so dermaßen killt. Die 11000 Zeilen kommen ohne TOP N binnen < 1Sekunde zurück.
-
Hallo Manuel,
ich würde mal ROW_NUMBER() anstelle von TOP probieren.
DECLARE @Source TABLE ( ID int, Name varchar( 255 ) ); INSERT INTO @Source ( ID, Name ) VALUES ( 1, 'Name 1' ), ( 2, 'Name 2' ), ( 3, 'Name 3' ), ( 4, 'Name 5' ); WITH Query AS ( SELECT ID, Name, ROW_NUMBER() OVER( ORDER BY ID ) AS RowNumber FROM @Source ) SELECT ID, Name FROM Query WHERE RowNumber <= 2
D.h. in deiner letzten Abfrage deiner View nimmst Du die ROW_NUMBER() mit und kannst dann von außen danach filtern.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET (2001-2018)
https://www.asp-solutions.de/ - IT Beratung, Softwareentwicklung, Remotesupport
- Bearbeitet Stefan FalzModerator Donnerstag, 26. November 2020 11:24
-
Hi Stefan,
Jap, genau so macht das Laravel auch ab Seite 2 der pagination. Die erste Seite macht es aber immer mit TOP N. Da das im Framework so gegeben ist, ist es auch gar nicht so einfach zu umgehen.
Man gibt einfach an wel
Hi Stefan,
Jap, genadddu so macht das Laravel auch ab Seite 2 der pagination. Die erste Seite macht es aber immer mit TOP N. Da das im Framework so gegeben ist, ist es auch gar nicht so einfach zu umgehen.
Man gibt einfach an welche Table oder View man abfragen will. Kann noch die Sort Columns + Richtung und generell welche Columns selektiert werden sollen definieren und den Query dahinter macht es dann von selbst.
Wenn man dann so eine Table im Web ansehen will passieren im Wesentlichen folgende 2 Queries:
1. Select count(*) from <mytable2query>
Das braucht es oben für den RowCounter um auszugeben wieviele Rows von wie vielen Rows gesamt angezeigt werden.2. Anzeige der 1. Page: Select TOP <Seitengröße dynamisch nach responsive Design> <myColumns,...> from <mytable2query>
2a. Jede weitere Page läuft nach dem Konzept wie Stefan es schrieb.An dieser Methodik kommt man nicht rum, sofern man nicht am Framework vorbei alles neu erfinden will.
che Table oder View man abfragen will. Kann noch die Sort Columns + Richtung und generell welche Columns selektiert werden sollen definieren und den Query dahinter macht es dann von selbst.
Wenn man dann so eine Table im Web ansehen will passieren im Wesentlichen folgende 2 Queries:
1. Select count(*) from <mytable2query>
Das braucht es oben für den RowCounter um auszugeben wieviele Rows von wie vielen Rows gesamt angezeigt werden.2. Anzeige der 1. Page: Select TOP <Seitengröße dynamisch nach responsive Design> <myColumns,...> from <mytable2query>
2a. Jede weitere Page läuft nach dem Konzept wie Stefan es schrieb.An dieser Methodik kommt man nicht rum, sofern man nicht am Framework vorbei alles neu erfinden will.
EDIT:
Also mir bricht definitiv die erste cte_Required_SWPackages das Genick. Die läuft mit TOP N gewaltige 111.000 mal durch. Das ergibt dann auf die beiden Joined-Tables dann nen Zugriffscount im 3-Stelligen Mio. Bereich. Der Querie läuft so mit TOP 100 über die View gut 16 Min.
Ohne TOP N macht sie das nicht und ist in 500ms durch. Auch wenn ich Filter darauf anwende ohne Top ist das Pfeil schnell.
Ich verstehs nicht warum da der Queryoptimizer so komisch arbeitet. Kann der nicht einfach das Gesamtergebnis berechnen lassen und dann im RAM durch TOP N nach N Rows nen Truncate machen?!
- Bearbeitet Manuel Aigner Donnerstag, 26. November 2020 13:36 Erweiterung
-
Nun ja, ein SQL ist dann am schnellsten, wenn erst mal alles abgerufen und dann sortiert wird.
Wobei der Sort im Programm dann zu empfehlen ist (die meisten Programmiersprachen können das) damit SQL nicht prüfen muss ob es einen passenden Index gibt.
Bei TOP N muss erst mal alles im Speicher geladen und sortiert werden um dann den Rest wieder zu verwerfen.
Da kann es schon mal andere Optimierungswege geben.Was den Inner Join angeht, so gilt hier einfach:
select * from a inner join b on a.key = b.key
ist identisch zu
select * from b inner join a on a.key = b.key
Aus eine 1:N-Beziehung (N=1 ist auch möglich) wird eine N:1-Beziehung.
Ich kenne zumindest eine Datenbank, die im Zuge der SQL-Optimierung einen SQL diesbezüglich auch anpasst.Wichtig ist für deinen "where Exists" ob die Trefferqoute in Tabelle A oder in Tabelle B besser einzuschränken ist.
Da hier durch Exists auf den Inner Join auf beide Tabellen Bezug genommen wird, sind durch Umdrehen des Joins u.U. weniger Zugriffe zu erwarten.Eine Optimierung von Minuten zu Sekunden finde ich aber schon gut, wenn man bedenkt mit welchen simplen Maßnahmen dies erreicht werden kann.
Statt View kann man ggf. auch eine Table-Function verwenden:
https://www.sqlservertutorial.net/sql-server-user-defined-functions/sql-server-table-valued-functions/Hier kannst du tatsächlich z.B. mittels TEMPORARY TABLE die With-Zwischentabellen erstellen und den finalen Select aus den nun tatsächlichen Tabellen zurück geben.
http://www.sqlines.com/articles/sql-server/local_and_global_temporary_tablesSolange eine Connection/Sitzung stabil bleibt, kann man der Prozedur auch z.B. die Pagingwerte als Parameter mitgeben und braucht nur die fertige Tabelle, die für die Sitzung bestehen bleibt, immer wieder auszulesen.
- Als Antwort markiert Manuel Aigner Freitag, 27. November 2020 09:55
-
Hallo Manuel,
Jap, genau so macht das Laravel auch ab Seite 2 der pagination. Die erste Seite macht es aber immer mit TOP N. Da das im Framework so gegeben ist, ist es auch gar nicht so einfach zu umgehen.
dann würde ich sagen, dass das Framework was verkehrt macht und man sollte dem Hersteller sagen, dass er das entweder bitte korrigiert oder bspw. über eine Konfigurationseinstellung so anpassen kann, dass man entweder immer mit ROW_NUMBER(), OFFSET ... FETCH oder eben mit TOP arbeitet (wobei ich TOP ehrlich gesagt für veraltet und daher nicht mehr sinnvoll einsetzbar halte, zumindest in SQL Server Versionen, die ROW_NUMBER() und/oder OFFSET ... FETCH unterstützen.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET (2001-2018)
https://www.asp-solutions.de/ - IT Beratung, Softwareentwicklung, Remotesupport
- Bearbeitet Stefan FalzModerator Donnerstag, 26. November 2020 16:14
-
Von der Performance sollte "where RowNumber ..." und Skip/Top (bzw. offset/fetch) annähernd gleichwertig sein, da auch bei Row_Number() eine Sortierung, i.d.R. nach Name statt Id, über das Gesamtergebnis sowieso erforderlich ist.
Ich sortiere ja nicht nach internen unbekannten ID's sondern nach den Feldinhalten.Außerdem müsste ja die RowNumber mit "between N and M" je Seite gefiltert werden.
- Bearbeitet Der Suchende Donnerstag, 26. November 2020 15:31
-
Ihr seid die Besten :)
Der Hint mit der Table-Values UDF war Goldes Wert. Hab ich gleich mal so umgesetzt. Funktioniert auch schon mal ganz gut, wenns auch sicher noch dort und da Optimierungspotenzial gibt.
Jetzt gibts aber noch ne Zusatzaufgabe. Man soll sehen können ob eine bestimmte Software überhaupt nicht vorhanden ist, in egal welcher Version. Das hätte ich jetzt mit nem Left Join ON LIKE (ich weiß, Performance lässt grüßen) versucht. Aber das funktioniert nicht wie erwartet. Egal wie ich den Wildcard % definiere. Der wird einfach gefressen. Es wird nur gefunden was ganz genau so in meiner Steuertabelle Required-SWPackages steht.
ALTER FUNCTION udf_Device_Server_Win_Missing_Required_SW (@Mode NVARCHAR(32)) RETURNS @MissingSW TABLE ( [Name] NVARCHAR(64) ,DisplayName NVARCHAR(256) ,[Version] NVARCHAR(64) ,Verw NVARCHAR(16) ,PatchAm NVarchar(255) ,DeviceType NVARCHAR(128) ,id INT ,Computername nvarchar(64) ,DisplayName2 nvarchar(256) ) AS BEGIN -- Find Softwarepackages DECLARE @Required_SWPackages TABLE ( Displayname NVARCHAR(256) ,[Version] NVARCHAR(64) ) IF @mode = 'allversions' INSERT INTO @Required_SWPackages SELECT Installed.Displayname ,Installed.[Version] FROM p_Device_Software_Installations Installed INNER JOIN CFGRequiredSoftware Req ON ( Installed.Displayname LIKE Req.Displayname AND Installed.[Version] LIKE ( CASE WHEN Req.[Version] IS NULL THEN '%' WHEN Req.[Version] = '' THEN '%' ELSE Req.[Version] END ) ) GROUP BY Installed.Displayname ,Installed.[Version] ELSE INSERT INTO @Required_SWPackages SELECT Displayname ,[Version] FROM CFGRequiredSoftware -- Filter Active Servers DECLARE @Active_Windows_Servers TABLE ( [Name] NVARCHAR(64) ,Verw NVARCHAR(16) ,PatchAm NVARCHAR(255) ,DeviceType NVARCHAR(128) ,id INT ) INSERT INTO @Active_Windows_Servers SELECT [Name] ,Verw ,PatchAm ,DeviceType ,id FROM View_Device_Server_Win WHERE InstallStatus = 'active' -- Get currently installed (filtered by Required_SWPackages) DECLARE @Currently_Installed TABLE ( Computername NVARCHAR(64) ,DisplayName NVARCHAR(256) ,[Version] NVARCHAR(64) ) INSERT INTO @Currently_Installed SELECT p_Device_Software_Installations.Computername ,p_Device_Software_Installations.DisplayName ,p_Device_Software_Installations.[Version] FROM p_Device_Software_Installations WHERE EXISTS ( SELECT Displayname FROM @Required_SWPackages WHERE DisplayName = p_Device_Software_Installations.DisplayName AND [Version] = p_Device_Software_Installations.[Version] ) --- Creating cardesian product of active servers and required SW DECLARE @Workinglist TABLE ( DisplayName NVARCHAR(256) ,[Version] NVARCHAR(64) ,[Name] NVARCHAR(64) ,Verw NVARCHAR(16) ,PatchAm NVARCHAR(255) ,DeviceType NVARCHAR(128) ,id INT ) INSERT INTO @Workinglist SELECT * FROM @Required_SWPackages SW CROSS JOIN @Active_Windows_Servers MyServers -- Executing final Return Query IF @mode = 'allversions' INSERT INTO @MissingSW SELECT workinglist.[Name] ,workinglist.DisplayName ,workinglist.[Version] ,workinglist.Verw ,workinglist.PatchAm ,workinglist.DeviceType ,workinglist.id ,Currently_Installed.Computername ,Currently_Installed.DisplayName FROM @Workinglist workinglist LEFT JOIN @Currently_Installed Currently_Installed ON ( workinglist.[Name] = Currently_Installed.Computername AND workinglist.DisplayName = Currently_Installed.DisplayName AND workinglist.[Version] = Currently_Installed.[Version] ) WHERE Computername IS NULL ORDER BY Name ELSE INSERT INTO @MissingSW SELECT workinglist.[Name] ,workinglist.DisplayName ,workinglist.[Version] ,workinglist.Verw ,workinglist.PatchAm ,workinglist.DeviceType ,workinglist.id ,Currently_Installed.Computername ,Currently_Installed.DisplayName FROM @Workinglist workinglist LEFT JOIN @Currently_Installed Currently_Installed ON (workinglist.[Name] = Currently_Installed.Computername and (Currently_Installed.DisplayName Like Convert(nvarchar(64),workinglist.DisplayName)+'%')) --WHERE Computername IS NULL ORDER BY Name RETURN; END;
-
Hallo Manuel,
ich würde die Statements mal auf einzelne Abfragen runterbrechen und dann gezielt schauen, wo es hakt. Bei einer komplexen Abfrage über zig Ebenen/Unterabfragen wird es schwer, das zu debuggen.
Dazu kommt, dass wir weder deine Tabellenstruktur noch deine Daten kennen. Poste daher bitte CREATE TABLE Statements für alle beteiligten Tabellen, INSERT INTO Statements für Beispieldaten und dazu das gewünschte Ergebnis, basierend auf exakt diesen Beispieldaten.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET (2001-2018)
https://www.asp-solutions.de/ - IT Beratung, Softwareentwicklung, Remotesupport -
Der Leftjoin kann seinen Status LEFT verlieren, wenn du in einer Where-Klausel ein Feld auf explizit <> NULL abfragst. Dadurch werden die NULL-Werte wieder ausgeschlossen, die du durch LEFT ja eigentlich haben willst.
Aber ich frage mich was die Abfrage
WHERE Computername IS NULL
bringen soll.
In diesem SQL könntest du das gleichsetzen mit "where not exists ..."Tipp: In einem Exists-Select interessiert die Select-Liste nicht, da nur die Where-Klausel geprüft und ein BOOl-Wert geliefert wird.
Es reicht also ein "where [not] exists (select * from mytable <Bedingung>)".Wenn dir mein Tipp mit den temporären Tabellen geholfen hat, würde ich mich über eine Bewertung freuen.
-
Ich konnte schon einen Fehler entdecken. Hab an der völlig falschen Stelle gesucht. Die @Required_SWPackages war schon nicht vollständig. Dann kann natürlich die Endausgabe auch nicht passen.
Das hab ich jetzt gefixt.
Anhand des
WHERE Computername IS NULL
stelle ich fest, wo ich auf der Rechten-Tabelle eben NICHTS gefunden habe. Sprich ich vergleiche ja links mein kardesisches Produkt aus kompletter Serverliste und zu suchender Software-Pakete, gegen die Liste rechts bestehend aus reell installierter Software. Diese rechte Liste hat eine Zeile pro (Server, Software, Version). Also kann jeder Server mal installierter Softwarepakete in dieser Liste vorkommen.
Finde ich die Suchkombination aus Servername und Suchsoftware in diesem Left-Join auf der rechte Seite nicht, so ist der Computername NULL und ich weiß, dass diese Software fehlt.
Funktionieren tut das jetzt schon. Schön ists hald noch nicht, da die Performance auf Grund der vielen notwendigen Likes leider etwas leidet. Da arbeite ich noch dran.
Ich honoriere euch natürlich gerne. Ist das der Abstimmungspfeil nach oben auf den Post? Sorry für die doofe Frage. War auf diesem Forum hier noch nicht so aktiv.
Testdaten werde ich auch schauen, wie ich die geschickterweise zur Verfügung stelle. Muss die Exportscripts etwas überarbeiten. Das sprengt hier sonst den Rahmen. -
Für die Likes in den übergroßen Feldern gibts noch die Volltextsuche.
Dies ist ein Zusatzmodul, dass du aber bereits haben könntest. Hier sind dann erweiterte SQL-Befehle möglich.
https://docs.microsoft.com/de-de/sql/relational-databases/search/full-text-search?view=sql-server-ver15
Ist ein Volltextindex mal erstellt, gibts die Funktion "CONTAINS" statt LIKE.
https://docs.microsoft.com/de-de/sql/t-sql/queries/contains-transact-sql?view=sql-server-ver15Bei den temporären Tabellen ist noch zu überlegen, SQL mit einem Index zu unterstützen:
https://www.sqlshack.com/indexing-sql-server-temporary-tables/Zu bedenken ist auch, dass du ggf. per Select auf die Temptables prüfen kannst, ob die Daten u.U. schon mal ermittelt wurden. Somit kannst du dann bzgl. Paging optimieren.
Beim nächsten Connect mit der DB sind die Tabellen erst mal leer.Für die Bewertungen gibts die Abstimmung (5-Punkte) oder "Antwort vorschlagen" und/oder bestätigen (20 Punkte), nicht dass ich punktegeil wäre;-).
-
So, ich habs jetzt abermals optimieren können. Die "teuren" Likes konnte ich jetzt eliminieren und gegen = tauschen. Alle großen Joins Fields sind nun indiziert. Konnte so die effektive Web-Anzeigezeit von 10-20 Sekunden auf vielleicht 200-300ms senken. Auch wenn ich auf das Endergebnis über das Webfrontend filtere geht das gleich schnell wie ohne filter.
Ich danke euch nochmal vielmals
ALTER FUNCTION udf_Device_Server_Win_Missing_Required_SW (@Mode NVARCHAR(32)) RETURNS @MissingSW TABLE ( [Name] NVARCHAR(64) ,DisplayName NVARCHAR(256) ,[Version] NVARCHAR(64) ,Verw NVARCHAR(16) ,PatchAm NVARCHAR(64) ,DeviceType NVARCHAR(128) ,id INT -- ,Computername NVARCHAR(64) -- ,DisplayName2 NVARCHAR(256) INDEX MyCIDX CLUSTERED (DisplayName,Name,Patcham,Verw) ) AS BEGIN -- Find Softwarepackages DECLARE @Required_SWPackages TABLE ( Displayname NVARCHAR(256) ,[Version] NVARCHAR(64) ,Verw NVARCHAR(16) ) IF @mode = 'allversions' INSERT INTO @Required_SWPackages SELECT Installed.Displayname ,Installed.[Version] ,Req.Verw FROM p_Device_Software_Installations Installed INNER JOIN CFGRequiredSoftware Req ON ( Installed.Displayname LIKE Req.Displayname AND Installed.[Version] LIKE ( CASE WHEN Req.[Version] IS NULL THEN '%' WHEN Req.[Version] = '' THEN '%' ELSE Req.[Version] END ) ) GROUP BY Installed.Displayname ,Installed.[Version] ,Verw ELSE INSERT INTO @Required_SWPackages SELECT Displayname ,[Version] ,Verw FROM CFGRequiredSoftware -- Filter Active Servers DECLARE @Active_Windows_Servers TABLE ( [Name] NVARCHAR(64) ,Verw NVARCHAR(16) ,PatchAm NVARCHAR(255) ,DeviceType NVARCHAR(128) ,id INT ) INSERT INTO @Active_Windows_Servers SELECT [Name] ,Verw ,PatchAm ,DeviceType ,id FROM View_Device_Server_Win WHERE InstallStatus = 'active' -- Get currently installed (filtered by Required_SWPackages) DECLARE @Currently_Installed TABLE ( Computername NVARCHAR(64) ,DisplayName NVARCHAR(256) ,[Version] NVARCHAR(64) ,Verw NVARCHAR(16) ,DisplayNameSS NVARCHAR(256) ,VersionSS Nvarchar(64) ,VerwSS NVARCHAR(16) Index myIndex CLUSTERED (Computername,DisplayNameSS,VersionSS,VerwSS) ) INSERT INTO @Currently_Installed SELECT View_Software_Installations.Computername ,View_Software_Installations.DisplayName ,View_Software_Installations.[Version] ,View_Software_Installations.Verw ,Required_SWPackages.Displayname ,Required_SWPackages.[Version] ,Required_SWPackages.Verw FROM View_Software_Installations Right Join @Required_SWPackages Required_SWPackages ON ( View_Software_Installations.DisplayName Like Required_SWPackages.DisplayName AND View_Software_Installations.[Version] Like Required_SWPackages.[Version] AND View_Software_Installations.Verw Like Required_SWPackages.Verw) --- Creating cardesian product of active servers and required SW DECLARE @Workinglist TABLE ( DisplayName NVARCHAR(256) ,[Version] NVARCHAR(64) ,[Name] NVARCHAR(64) ,Verw NVARCHAR(16) ,PatchAm NVARCHAR(255) ,DeviceType NVARCHAR(128) ,id INT INDEX MyIndex CLUSTERED ([Name],Displayname) ) INSERT INTO @Workinglist SELECT Displayname ,[Version] ,MyServers.* FROM @Required_SWPackages SW --CROSS JOIN @Active_Windows_Servers MyServers Full Outer Join @Active_Windows_Servers MyServers ON (MyServers.Verw Like SW.Verw) -- Executing final Return Query IF @mode = 'allversions' INSERT INTO @MissingSW SELECT workinglist.[Name] ,workinglist.DisplayName ,workinglist.[Version] ,workinglist.Verw ,workinglist.PatchAm ,workinglist.DeviceType ,workinglist.id -- ,Currently_Installed.Computername -- ,Currently_Installed.DisplayName FROM @Workinglist workinglist LEFT JOIN @Currently_Installed Currently_Installed ON ( workinglist.[Name] = Currently_Installed.Computername AND workinglist.DisplayName = Currently_Installed.DisplayName AND workinglist.[Version] = Currently_Installed.[Version] ) WHERE Computername IS NULL ORDER BY Name ELSE INSERT INTO @MissingSW SELECT workinglist.[Name] ,workinglist.DisplayName ,workinglist.[Version] ,workinglist.Verw ,workinglist.PatchAm ,workinglist.DeviceType ,workinglist.id -- ,Currently_Installed.Computername -- ,Currently_Installed.DisplayName FROM @Workinglist workinglist LEFT JOIN @Currently_Installed Currently_Installed ON ( workinglist.[Name] = Currently_Installed.Computername AND (Currently_Installed.DisplayNameSS = workinglist.DisplayName) ) WHERE Computername IS NULL ORDER BY Name RETURN; END;
-
Also für mich sieht dies klar und übersichtlich und für jeden SQL-Spezi auch verständlich aus.
Freut mich, dass ich dir helfen konnte.Da kann man mal sehen, dass man auch bei SQL selber optimieren muss. Die Datenbank kann einem da leider nicht alles abnehmen.
Es gibt übrigens eine Datenbank, die über Zugriffswege Statistiken führt aus denen man sich direkt Indizes erstellen lassen kann. Eine Vollautomatik lohnt da allerdings nicht, da sonst zu viele Indizes erstellt werden, das ist dann bei Updates kontraproduktiv.
- Bearbeitet Der Suchende Freitag, 27. November 2020 17:43