PDA

Vollständige Version anzeigen : Datensätze aus 2. Tabelle übernehmen


kalliseppel
20.01.2008, 11:59
Hallo,

ich hoffe, ich bin hier in der richtigen Rubrik mit meinem Problemchen.

Ich habe 2 Tabellen, tblZiel und tblQuelle, mit identischem Aufbau.
PrimaryKey ist jeweils zusammengesetzt aus den Feldern fldPK1 und fldPK2.
In den Tabellen befindet sich jeweils ein AutoIncrement-Feld fldAI.

Nun möchte ich sämtliche Datensätze von tblQuelle nach tblZiel übernehmen, die noch nicht in tblZiel enthalten sind.

Mit einem platten INSERT INTO tblZiel SELECT * FROM tblQuelle bekomme ich zwei Probleme.

Zum einen wird es nicht funktionieren wegen PrimaryKey-Duplikaten, da in tblQuelle Datensätze enthalten sind, die sich bereits in tblZiel befinden und somit aus der Abfrage auszuschließen sind.
Wie müsste ich hierfür das INSERT-Statement erweitern?

Zum zweiten werde ich das AutoIncrement-Feld fldAI nicht übertragen dürfen, da es in tblZiel mit vorhandenen Werten kollidieren würde.
Meine Idee hierzu ist, an das INSERT-Statement ein ORDER BY tblQuelle.fldAI anzufügen. So bliebe die ursprüngliche Reihenfolge der Werte aus tblQuelle erhalten, was für meine Zwecke völlig ausreicht.
Allerdings müsste ich jetzt sämtliche Tabellenfelder im INSERT-Statement einzeln aufführen ohne das AutoIncrement-Feld. Und damit komme ich zum nächsten Problem.

Ich möchte die SQL-Abfrage dynamisch erstellen. So dass ich sie für mehrere Tabellenpaare verwenden kann.
Alle Tabellenpaare haben wie bereits geschrieben den gleichen Aufbau. Einzige Gemeinsamkeit aller dieser Tabellen ist, dass deren PrimaryKey intern jeweils PrimaryKey heißt. Je nach Tabelle kann der PrimaryKey aus einem einzelnen Feld bestehen oder aus mehreren zusammengesetzt sein.
Kennt jemand dazu einen Ansatz?
Ich verwende übrigens VB6 und ADO/ADOX.

hcscherzer
20.01.2008, 13:39
INSERT INTO tblZiel
(SELECT * FROM tblQuelle As Q
where Q.fldPK1+Q.fldPK2 Not In
(SELECT Z.fldPK1+Z.fldPK2 FROM tblZiel As Z));

Wenn nicht alle Felder importiert werden sollen, dann muss das '*' in Zeile 2 durch die Auflicstung der Felder ersetzt werden.

Für die 'Dynamik' fällt mir spontan nur ein, dass der SQL String in Abhängigkeit der verschiedenen Vorgaben zusammengesetzt werden muss. So a ladim sqls as string = "insert into "
select case quelle
case "tblQuelle"
sqls &= ziel & "(select * from " & quelle & " as Q " ... usw.

end select

ich hoffe, ich bin hier in der richtigen Rubrik
Mit Access hat Deine Frage ja nicht so viel zu tun. Es gibt auch eine VB und eine .NET Rubrik hier im Forum.

achtelpetit
20.01.2008, 13:49
Zu welchem Zweck möchtest Du Daten auf diese Art duplizieren? Das könnte auf einen Fehler im Datenmodell hindeuten.
PrimaryKey intern jeweils PrimaryKey heißt. Je nach Tabelle kann der PrimaryKey aus einem einzelnen Feld bestehen oder aus mehreren zusammengesetzt sein
Wenn der Schlüssel aus mehreren Feldern besteht kann er doch nicht nur eien Feldnamen tragen?
Datensätze sortieren bringt überhaupt nix, wenn's um Anfügen geht. Außerdem haben Datensätze prinzipiell keine Reihenfolge in Bezug auf die Normalisierung.
Kurz und gut: mir ist schleierhaft, wohin das führen soll.

hcscherzer
20.01.2008, 13:54
Wenn der Schlüssel aus mehreren Feldern besteht kann er doch nicht nur eien Feldnamen tragen?Der Schlüssel hat ja auch keinen Feld- sondern einen Schlüssel-Namen. :p

kalliseppel
20.01.2008, 13:55
Danke dir für die Antwort.
Mit Access hat die Frage nur insofern zu tun, dass die Abfrage auf eine mdb zielt. Die Frage würde natürlich auch in eine SQL-Rubrik passen oder auch in eine VB-Rubrik. Nicht einfach zu entscheiden.

Zu deinem Vorschlag: Ich habe zwischenzeitlich ein wenig experimentiert und bin auf eine andere Lösung gekommen, mit der ich zumindest meine erste Frage selbst beantwortet habe. Anders als dein Vorschlag, aber ich denke performanter.


INSERT INTO tblZiel
SELECT tblQuelle.*
FROM tblQuelle
LEFT JOIN tblZiel ON tblQuelle.fldPK1 = tblZiel.fldPK1 AND tblQuelle.fldPK2 = tblZiel.fldPK2
WHERE tblZiel.fldPK1 Is Null;


Funktioniert für ein Tabellenpaar ohne Autoincrement-Feld sehr gut.
Ich denke, den Rest meines Problems, die dynamische Generierung des SQL-Statements, muss ich programmtechnisch lösen, indem ich die Felder des PrimaryKeys, die Felder der Tabelle sowie AutoFeld (ja/nein) mit Hilfe von ADO ermittle.
Das gehört dann wirklich nicht mehr hierher.

hcscherzer
20.01.2008, 13:59
Das gehört dann wirklich nicht mehr hierher.Na ja, vielleicht doch, wenn das BackEnd eine Access MDB ist. ;)
Allerdings gibt es ja doch einige Unterschiede zwischen VBA und VB(.NET).

kalliseppel
20.01.2008, 14:05
Zu welchem Zweck möchtest Du Daten auf diese Art duplizieren? Das könnte auf einen Fehler im Datenmodell hindeuten.
Diese Schlussfolgerung zu ziehen, halte ich für ein wenig zu voreilig von dir.;)


Wenn der Schlüssel aus mehreren Feldern besteht kann er doch nicht nur eien Feldnamen tragen?

Ich meinte den Schlüsselnamen. Der heißt immer "PrimaryKey" und ist in verschiedenen Tabellen unterschiedlich aufgebaut. Könnte ich den Schlüsselname im SQL-Statement verwenden, dann wäre mein Problem keines.


Datensätze sortieren bringt überhaupt nix, wenn's um Anfügen geht. Außerdem haben Datensätze prinzipiell keine Reihenfolge in Bezug auf die Normalisierung.
Doch, das bring im Falle von AutoIncrement-Felder sehr viel. Denn Übertrage ich diese Werte mit dem INSERT-Statement nicht von der Quell- in die Zieltabelle, wird der Wert in der Zieltabelle automatisch erstellt. Er entspricht dort zwar nicht dem ursprünglichen Wert, aber die Reihenfolge der ursprünglichen Werte bleibt erhalten.


Kurz und gut: mir ist schleierhaft, wohin das führen soll.
Ich habe etwa 20 Tabellen in einer Ziel-mdb. Und aus einer anderen (identisch aufgebauten) Quell-mdb möchte ich alle Datensätze übertragen, die noch nicht in der Ziel-mdb existieren.
Und das nicht per Hand sondern per Programmcode.

kalliseppel
20.01.2008, 14:11
Na ja, vielleicht doch, wenn das BackEnd eine Access MDB ist. ;)
Allerdings gibt es ja doch einige Unterschiede zwischen VBA und VB(.NET).

Im Hintergrund liegt eine Access MDB. Darauf greife ich per ADO zu. Die sql-Staements erstelle ich mit VB6.

Danke für dein Interesse. Ich denke, in Sachen VB6 und ADO/ADOX bin ich fit genug, um nach ein wenig Herumprobieren eine Lösung zu haben. Eine Idee, wie das gehen müsste, habe ich schon. Und oben ja auch bereits angedeutet.
Vermutlich haben das schon andere "erfunden". Ist ja eigentlich etwas, das viele mal brauchen. Daher dachte ich, hier könnte ich ja mal fragen bevor ich alles nochmal erfinden muss.

Josef P.
20.01.2008, 15:10
Ich denke, in Sachen VB6 und ADO/ADOX bin ich fit genug, um nach ein wenig Herumprobieren eine Lösung zu haben. Eine Idee, wie das gehen müsste, habe ich schon. Und oben ja auch bereits angedeutet.
...
Ist ja eigentlich etwas, das viele mal brauchen. Daher dachte ich, hier könnte ich ja mal fragen bevor ich alles nochmal erfinden muss.
Und um diesen Gedanken fortzusetzen: wenn du eine Lösung hast, könntest du sie später hier anhängen.


Warum verwendest du eigentlich ADODB und nicht DAO für den Jet-Zugriff?
Ich erstellte mir einmal Code um Tabellenfelder, Indizes und Beziehungen auszulesen um daraus SQL-Code zum Erstellen dieser Tabellen in anderen DBMS zu erzeugen.
per DAO las ich z.B. die Tabellen-Indizes so aus:
' Indexfelder auslesen
For Each idx In tdf.Indexes
Set tsIndex = New clsTableStructIndex
tsIndex.ReadDaoIndex idx
m_colIndexes.Add tsIndex, idx.Name
If idx.Primary Then
m_PrimaryKey = idx.Name
End If
Next idx
Set idx = Nothing
Anm.: clsTableStructIndex ist eine VBA-Klasse, in der ich die Auswertung zwischenspeichere und SQL-Anweisungen daraus generiere.

interessant könnte für dich vermutlich die ADODB-Variante zum Erkennen eines Autowertfeldes sein:
fld.Properties("ISAUTOINCREMENT")

achtelpetit
20.01.2008, 15:24
Offenbar hab' ich das Ganze gründlich mißverstanden.

kalliseppel
20.01.2008, 15:35
Und um diesen Gedanken fortzusetzen: wenn du eine Lösung hast, könntest du sie später hier anhängen.
Das werde ich gern tun.


Warum verwendest du eigentlich ADODB und nicht DAO für den Jet-Zugriff?
Philosophische Frage?;)
Als ich vor langer Zeit mit Access-Zugriffen begann, habe ich mich für das neuere Konzept von M$ entschieden. ADO wird heute noch supportet, DAO ist m.W. bei M$ megaout.


interessant könnte für dich vermutlich die ADODB-Variante zum Erkennen eines Autowertfeldes sein:
fld.Properties("ISAUTOINCREMENT")
Nehmen wir fld.Properties("AUTOINCREMENT") und die Sache stimmt.;)

Josef P.
20.01.2008, 15:41
zu DAO/ADODB ... oftmals irrt auch MS bei seinen Standard-Vorschlägen für die "Zukunft". :D ... siehe Ac07, odert ist nun eine erneuerte Version von DAO wieder Standard. ;)

Nehmen wir
fld.Properties("AUTOINCREMENT")
und die Sache stimmt.
bist du dir sicher? ;)

VBA-Code:
Dim rst As ADODB.Recordset
Dim fld As ADODB.Field

Set rst = New ADODB.Recordset
rst.Open "SELECT * from Tabelle", CurrentConnection
Set fld = rst.Fields(0)
MsgBox fld.Properties("ISAUTOINCREMENT")
MsgBox fld.Properties("AUTOINCREMENT")
...

/edit
Eine kurze Anmerkung, falls die Ironie nicht bemerkt wurde:
kalliseppel dachte vermutlich an die ADOX-Variante ADOX.Column.Properties("AUTOINCREMENT")

... ein nettes Beispiel, wie "einheitlich" die Eigenschaftskennungen sind. :D

lalo
20.01.2008, 16:10
Hallo,

Zitat: von achtelpetit
Datensätze sortieren bringt überhaupt nix, wenn's um Anfügen geht. Außerdem haben Datensätze prinzipiell keine Reihenfolge in Bezug auf die Normalisierung.
Doch, das bring im Falle von AutoIncrement-Felder sehr viel. Denn Übertrage ich diese Werte mit dem INSERT-Statement nicht von der Quell- in die Zieltabelle, wird der Wert in der Zieltabelle automatisch erstellt. Er entspricht dort zwar nicht dem ursprünglichen Wert, aber die Reihenfolge der ursprünglichen Werte bleibt erhalten.

Die Reihenfolge wie die Daten abgespeichert werden kann man mit SQL nicht beeinflussen, auch nicht mit "Trick 17". Die Aussage des letzten Satzes ist daher nicht richtig. Grundsätzlich soll man auf Tabellen mit Abfragen zugreifen und wenn man eine bestimmte Sortierfolge benötigt dann mit einer "order by" Klausel. Wenn man sich auf eine empirsch ermittelte Sortierfolge verlässt dann kann man ganz schnell auf die Nase fallen.
Gruß Bernd

kalliseppel
20.01.2008, 17:20
Eine kurze Anmerkung, falls die Ironie nicht bemerkt wurde:
kalliseppel dachte vermutlich an die ADOX-Variante ADOX.Column.Properties("AUTOINCREMENT")

Das ist richtig. Hier verwende ich ADOX. Ist mir bisher noch nicht aufgefallen, dass die Property in ADO anders heißt.


Die Reihenfolge wie die Daten abgespeichert werden kann man mit SQL nicht beeinflussen, auch nicht mit "Trick 17". Die Aussage des letzten Satzes ist daher nicht richtig. Grundsätzlich soll man auf Tabellen mit Abfragen zugreifen und wenn man eine bestimmte Sortierfolge benötigt dann mit einer "order by" Klausel.
"order by" Klausel! Nichts anderes habe ich bereits in meinem ersten Beitrag geschrieben. Darauf bezog sich meine Antwort zum Beitrag von achtelpetit.


:) :) :) Die VB6-Lösung habe ich mittlerweile. Und funktioniert! Die vielen Sportübertragungen heute haben das Ganze ein wenig verzögert. Ich poste die Lösung in wenigen Minuten. Sobald ich sie ein wenig verallgemeinert habe.

lalo
20.01.2008, 17:52
Vielleicht wurde ich nicht richtig verstanden:

Das Einfügen in eine Tabelle in einer bestimmten Reihenfolge hat keinen Einfluss auf die Reihenfolge wie die Daten ausgegeben werden wenn man die Daten selektiert ohne "order by" anzugeben.
Meine Aussage bezüglich "order by" bezieht sich nur auf das Selektieren, nicht auf das Einfügen. Deshalb ist die Idee (#1), beim Insert ... select .. order by... anzugeben nicht erfolgversprechend.
Gruß Bernd

kalliseppel
20.01.2008, 18:05
@lalo: Ich glaube, das hatte bei mir in anderen Fällen immer funktioniert, aber ich prüfe das nochmal.

Josef P.
20.01.2008, 18:16
Eine Ausgabe ohne Order By ist von der SQL-Anweisung und deren Where-Ausdruck abhängig, da davon die Nutzung eines passenden Index abhängt.

Beispiel:
Tabelle
- ID (Autowert + PK)
- Z (int , Index)

SELECT * FROM Tabelle ... DS werde mit großer Wahrscheinlichkeit nach dem PK sortiert ausgegeben.

SELECT * FROM Tabelle where Z < 5 ... nun wird der Index über Z genutzt und eine Ausgabe erfolgt nach dessen Sortierung.

=> Wenn eine bestimmte Sortierung erwünscht ist, ist dies unbedingt mittels Order By auszudrücken.

kalliseppel
20.01.2008, 18:18
So, und hier der VB6-Code. Ist nicht sehr lang, daher nicht als Anhang.

Ich weiß, die Routine müsste noch ein wenig erweitert werden, wenn sie allgemeingültig sein soll. Beispielsweise nicht sauber, wenn eine Tabelle keinen Schlüssel hat.
Funktioniert aber für meine Zwecke. Und als Ansatz für jene, die sowas brauchen, sicher geeignet.

Anregungen zur Verbesserung? Gerne!;)

Ein Beispiel für den Aufruf:
MergeData cn:=MyDBConnection, tblName:="Mitarbeiter", SourceDBPath:="C:\Test\Test.mdb"


Public Sub MergeData(ByRef cn As ADODB.Connection, _
ByVal tblName As String, _
ByVal SourceDBPath As String)

Dim cat As ADOX.Catalog
Dim tbl As ADOX.Table
Dim sqlInsert As String
Dim sqlSelect As String
Dim sqlJoin As String
Dim sqlWhere As String
Dim sqlOrderBy As String
Dim i As Long
Dim ii As Long

Set cat = New ADOX.Catalog
cat.ActiveConnection = cn

'----------------------------------------
' Verknüpfung zur SourceTabelle erstellen
'----------------------------------------

DeleteDBTable cn:=cn, tblName:="lnk" ' Nur sicherheitshalber für den Fall, dass die Verknüpfung bereits existiert.

Set tbl = New ADOX.Table
With tbl
.Name = "lnk"
Set .ParentCatalog = cat
.Properties("Jet OLEDB:Remote Table Name") = tblName
.Properties("Jet OLEDB:Link Datasource") = SourceDBPath
.Properties("Jet OLEDB:Create Link") = True
End With
cat.Tables.Append tbl
cat.Tables.Refresh

'----------------------------------------
' SQL-Statement generieren
'----------------------------------------

sqlInsert = vbNullString
sqlSelect = vbNullString
sqlJoin = vbNullString
sqlWhere = vbNullString
sqlOrderBy = vbNullString

With cat.Tables(tblName)

For i = 0 To .Columns.Count - 1

If .Columns(i).Properties("AutoIncrement") Then

sqlOrderBy = IIf(sqlOrderBy = vbNullString, _
" ORDER BY ", _
", ") & _
"lnk." & .Columns(i).Name

Else

sqlInsert = sqlInsert & _
IIf(sqlInsert = vbNullString, _
" INSERT INTO " & tblName & " (", _
", ") & _
.Columns(i).Name

sqlSelect = sqlSelect & _
IIf(sqlSelect = vbNullString, _
" SELECT ", _
", ") & _
"lnk." & .Columns(i).Name

End If

Next

sqlInsert = sqlInsert & ")"
sqlSelect = sqlSelect & " FROM lnk"

For i = 0 To .Keys.Count - 1
If .Keys(i).Type = adKeyPrimary Then

For ii = 0 To .Keys(i).Columns.Count - 1
If Not .Columns(.Keys(i).Columns(ii).Name).Properties("AutoIncrement") Then

If sqlWhere = vbNullString Then
sqlWhere = " WHERE " & tblName & "." & .Keys(i).Columns(ii).Name & " IS NULL"
End If

sqlJoin = sqlJoin & _
IIf(sqlJoin = vbNullString, _
" LEFT JOIN " & tblName & " ON ", _
" AND ") & _
"lnk." & .Keys(i).Columns(ii).Name & " = " & tblName & "." & .Keys(i).Columns(ii).Name

End If
Next

Exit For
End If
Next

End With

'----------------------------------------
' Daten der Tabelle übertragen
'----------------------------------------
cn.Execute sqlInsert & _
sqlSelect & _
sqlJoin & _
sqlWhere & _
sqlOrderBy

' Verknüpfung wieder löschen
DeleteDBTable cn:=cn, _
tblName:="lnk"

End Sub

Public Sub DeleteDBTable(ByRef cn As ADODB.Connection, _
ByVal tblName As String)

Dim cat As New ADOX.Catalog

cat.ActiveConnection = cn

' Tabelle löschen, falls bereits in DB vorhanden.
On Error Resume Next
cat.Tables.Delete tblName
cat.Tables.Refresh
On Error GoTo 0

End Sub