SQL INSERT und SELECT
-
Hallo,
Beide Statements sind mir bekannt, aber ich habe nun den Fall, das ich sie evtl kombinieren muss.
Ich habe als Beispiel diese 2 Tabellen:
Webseite
id autoincrement pk
seite varcharBesucher
id autoinkrement pk
id_webseiteNun möchte ich Datensätze in Besucher einfügen, habe aber nur die Webseite:
INSERT INTO Besucher(id_webseite) VALUES(SELECT id FROM Webseite WHERE seite = 'google.de');
Müsste ja grundsätzlich funktionieren, jedoch möchte ich nun auch, das wenn die Seite noch nicht in Webseite steht, diese dort eingefügt wird, und die ID in Besucher wieder landet.
Geht das? Und wenn ja, wie sieht das in SQL aus?
phlox
-
Hallo,
folgendes in Bezug auf PostgreSQL, da du dein RDBMS ja nicht genannt hast.
Ich würde es so machen:
- Sicherstellen, dass der Eintrag für die Webseite existiert (seite erhält dazu einen Unique Constraint), dann einfach den INSERT versuchen, der fehlschlägt, wenn die Zeile schon existiert.
- Danach den INSERT mit dem genesteten SELECT.
Der fehlgeschlagende INSERT ist auch nicht teurer als ein SELECT zum Herausfinden der ID.
Vorteil ist: Du kannst bei dem normalen "Read committed"-Transaktionstyp bleiben, ohne dass Probleme entstehen können. Du musst nur vor dem INSERT einen Savepoint anlegen, da dir das fehlschlagende INSERT sonst die Transaktion abbricht (Rollback).
Viele Grüße
Christian
-
RDBMS ist noch unklar, MySQL könnte es z.b. werden.
Mich würds vor allem interessieren, ob das NICHT RDBMS spezifisch geht.Interessant wäre für mich auch, ob es in SQL sowas wie Variablen gibt, weil ich nicht ein INSERT habe, sondern bis zu 10000 en block.
Deshalb frage ich mich auch, ob ich die Webseite nicht einfach in die Tabelle Besucher packe, was ja dann aber nicht so wirklich im Sinne des Erfinders wäre.phlox
-
Hallo,
die Webseiten in die Besuchertabelle zu packen, ist sicher keine gute Idee.
Variablen gibt es in SQL (portabel) nicht. Es hängt halt immer davon ab, wie viele Operationen du machen würdest. Bei 10000 würde ich mir noch keine Sorgen machen, ansonsten könntest du mal über eine Stored Procedure (bzw. das MySQL-Äquivalent dazu) nachdenken.
Du musst bedenken, dass du in einem Produktivsystem sowieso einen Query Cache hast, der dir den inneren SELECT im Besucher-INSERT direkt wegfüttert (solange sich an der Webseiten-Tabelle nichts ändert), wenn du nacheinander mehrere Besucher der selben Seiten einträgst. Das ist also kein echter Overhead.
Viele Grüße
Christian
-
Gut, das das RDBMS da noch optimiert ist richtig, und auch ist es nicht das beste Design, das ist mir schon klar.
Bleibt das Problem das ich die Webseite schlecht vorher kennen kann, ohne durch den datenbestand zu gehen, bzw. mir dann diese jeweils merken muss.
-
@ChrisM:
Wenn ich dich richtig verstehe, dann ist das grosser Unfug was du da geschrieben hast.Zeilenweise zu machen was man auch als Blockoperation machen kann, ist so ziemlich die schlimmste Performance-Sünde, wenn man mit Datenbanken arbeitet.
Das ist auch nicht einfach nur so dahergeredet, ich habe damit ausreichend Erfahrung.
Dabei ist es egal ob man irgendwas mit SQL CURSOR Schleifen macht, oder ob man es über ein Interface ala ODBC/ADO/... "von aussen" macht. Zeilenweise ist und bleibt böse.
Lade die 10000 Zeilen per BULK INSERT zum Server hoch, und mach dann etwas in der Art:
CREATE TABLE #source (seite NVARCHAR(MAX)); -- temp table -- ... BULK INSERT nach #source ... BEGIN TRAN; -- id muss ein auto-wert sein damit das geht INSERT Webseite (seite) SELECT seite FROM #source WHERE seite NOT IN (SELECT seite FROM Webseite); INSERT Besucher (id_webseite) SELECT Webseite.id FROM #source INNER JOIN Webseite ON Webseite.seite = #source.seite; COMMIT; DROP TABLE #source;
Vermutlich sind da einige Dinge nicht 100% Standard-SQL, aber es sollte funktionieren (EDIT: mit MS SQL Server /EDIT), und einigermassen schnell sein.
-
Danke, hustbaer, das sieht gut aus.
Ich hab nähmlich jetzt schon überlegt, wie ich das machen soll, das ich gerne per BULK INSERT das machen möchte war mir klar.
Deine Lösung ist aber recht elegant, kann ich doch erstmal den ganzen Kram in einen TEMPTable tun und dann in die eigentlich DB per SQL einspielen.Das SQL Skript wird generiert, und ich habe eh nicht die möglichkeit, ein 2. Insert für jeden wert zu machen, weil ich dann schon im ersten insert bin:
sql << "INSERT INTO" << tablename << "VALUES(" << list_values1 << list_values2 ");"<<
in list_values1 bzw. list_values2 prüfe ich erst, ob ich ein insert (evtl) bräuchte, auch wird das nach einer weile unwahrscheinlich, da die Daten recht gleichmäßig sind.
Noch ne frage, was bedeutet das # vor source? Hat das eine Bedeutung? Oder nur Platzhalter?
phlox
-
Das # ist T-SQL spezifisch, und kennzeichnet lokale (=Connection-spezifische) Temp-Tables.
Die legt MS-SQL dann in der "tempdb" an, und droppt sie automatisch wieder, wenn die Connection geschlossen wird.
IMO sehr praktisch, da bei einem Verbindungsabbruch/Programmabbruch nie "Müll-Tables" hängen bleiben können.Was das BULK INSERT angeht: keine Ahnung welche Datenbank du verwendest, aber mit MS-SQL ist das garnicht so einfach. Mit einigen anderen Datenbanken kann man AFAIK die Syntax
INSERT table (c1, c2) VALUES (1, 2) VALUES (2, 2) VALUES (3, 2) VALUES (4, 2);
verwenden, um *performant* mehrere Zeilen gleichzeitig einzufügen.
Bei MS-SQL gibt's, abgesehen von Methoden die Files als Quelle benötigen, nur einen einzigen mir bekannten Trick der wirklich schnell funktioniert, und der geht so:
INSERT table (c1, c2) EXEC(' SELECT 1, 2; SELECT 2, 2; SELECT 3, 2; SELECT 4, 2; SELECT 5, 2; ');
Wenn man lauter einzelne INSERTs macht, dauert das bei MS-SQL 10-100x so lange.
-
Gut zu wissen. Zur Zeit ist noch nicht ganz klar auf welcher DB ich das mal umsetzen werde.
MySQL könnte wahrscheinlich sein, aber auch sonst jede DB auf die ich mal Daten importieren muss (kontkret gings hier um beliebiges XML->DB)
-
hustbaer schrieb:
INSERT table (c1, c2) VALUES (1, 2) VALUES (2, 2) VALUES (3, 2) VALUES (4, 2);
Diese Querys würde ich für mehrere Zeilen (hier ist von 10000 die rede) nicht verwenden.
Mag sein das es funktioniert aber es könnte dann andere Probleme geben.
Der Query zur DB ist in der länge begrenzt.
-
Er kann das ja auch blockweise machen: 100 x 100 Zeilen. Aber wir wissen ja auch nicht wie oft/wann das gemacht werden soll. (Als Background-Job wenn Logdateien in die DB gepumpt werden sollen oder wenn der User davor sitzt und auf ein Ergebnis wartet?) Er kann ja auch eine Daten-Datei/pipe erstellen und dann den Mysql-Server direkt mit dem LOAD DATA INFILE - Command befüllen.
-
Ja, das ist natürlich richtig.
Ich bin da auch gerade noch am überlegen, leider scheints in XSLT keine counter oder so zu geben, weil sonst könnte man das ja jeweils für 10-100 Inserts kombinieren.