Merge two DataTables using an Inner Join in vb.net - vb.net

I have two DataTables that need to be merged together. Both Tables have a field in it called "Code" Basically if one table has a certain code, it merges with the corresponding code in the other table, getting that rows values merged into it. I would say its like merging two tables together in SQL using an inner join.
Needs to be in vb.net please!

Here is a VB conversion of this answer.
Dim dt1 As New DataTable()
dt1.Columns.Add("CustID", GetType(Integer))
dt1.Columns.Add("ColX", GetType(Integer))
dt1.Columns.Add("ColY", GetType(Integer))
Dim dt2 As New DataTable()
dt2.Columns.Add("CustID", GetType(Integer))
dt2.Columns.Add("ColZ", GetType(Integer))
For i As Integer = 1 To 5
Dim row As DataRow = dt1.NewRow()
row("CustID") = i
row("ColX") = 10 + i
row("ColY") = 20 + i
dt1.Rows.Add(row)
row = dt2.NewRow()
row("CustID") = i
row("ColZ") = 30 + i
dt2.Rows.Add(row)
Next
Dim results = From table1 In dt1.AsEnumerable()Join table2 In dt2.AsEnumerable() On CInt(table1("CustID")) = CInt(table2("CustID"))New With { _
Key .CustID = CInt(table1("CustID")), _
Key .ColX = CInt(table1("ColX")), _
Key .ColY = CInt(table1("ColY")), _
Key .ColZ = CInt(table2("ColZ")) _
}
For Each item As var In results
Console.WriteLine([String].Format("ID = {0}, ColX = {1}, ColY = {2}, ColZ = {3}", item.CustID, item.ColX, item.ColY, item.ColZ))
Next
Console.ReadLine()

Related

VB.NET LINQ to DataSet (SQL 'LEFT OUTER JOIN' alternative)

I would like to join two DataTables and create the third one from the result. The result DataTable should have three columns:
ID
Name
YearOfBirth
My Compile options:
Option explicit: On
Option strict: On
Option compare: Binary
Option infer: Off
Dim dr As DataRow
REM Dt1
Dim Dt1 As New DataTable
Dt1.Columns.Add("ID", GetType(Integer))
Dt1.Columns.Add("Name", GetType(String))
dr = Dt1.NewRow
dr("ID") = 1
dr("Name") = "Peter"
Dt1.Rows.Add(dr)
dr = Dt1.NewRow
dr("ID") = 2
dr("Name") = "Anna"
Dt1.Rows.Add(dr)
dr = Dt1.NewRow
dr("ID") = 3
dr("Name") = "John"
Dt1.Rows.Add(dr)
REM End Dt1
REM Dt2
Dim Dt2 As New DataTable
Dt2.Columns.Add("ID", GetType(Integer))
Dt2.Columns.Add("YearOfBirth", GetType(Integer))
dr = Dt2.NewRow
dr("ID") = 1
dr("YearOfBirth") = 1970
Dt2.Rows.Add(dr)
dr = Dt2.NewRow
dr("ID") = 2
dr("YearOfBirth") = 1980
Dt2.Rows.Add(dr)
REM End Dt2
Dim Dt3 As New DataTable
Dim query As IEnumerable(Of DataRow) = From dr1 In Dt1.AsEnumerable()
Group Join dr2 In Dt2.AsEnumerable()
On dr1.Field(Of Integer)("ID") Equals dr2.Field(Of Integer)("ID")
Into joined = Group
From j In joined.DefaultIfEmpty()
Select New With
{
.ID = dr1.Field(Of Integer)("ID"),
.Name = dr1.Field(Of String)("Name"),
.YearOfBirth = j.Field(Of Integer)("YearOfBirth")
}
Dt3 = query.CopyToDataTable
But I get the error message in editor (VS 2017):
"Error BC36754:
'IEnumerable(Of anonymous type: ID As Integer, Name As String, YearOfBirth As Integer)' cannot be converted to 'IEnumerable(Of DataRow)' because 'anonymous type: ID As Integer, Name As String, YearOfBirth As Integer' is not derived from 'DataRow', as required for the 'Out' generic parameter 'T' in 'Interface IEnumerable(Of Out T)'."
Select New without specifying class name as query result will return anonymous type (such like IEnumerable(Of AnonymousType)), and CopyToDataTable() throwing exception because IEnumerable(Of AnonymousType) cannot be converted directly to IEnumerable(Of DataRow).
Hence, you need to convert anonymous type into DataRow using additional Select method that iterates IEnumerable(Of AnonymousType) contents and returns DataRow with DataTable.NewRow() (using prepared DataTable which includes column names as result set):
' joined DataTable columns
Dim JoinedDT As New DataTable
JoinedDT.Columns.Add("ID", GetType(Integer))
JoinedDT.Columns.Add("Name", GetType(String))
JoinedDT.Columns.Add("YearOfBirth", GetType(Integer))
' other stuff
Dim query As IEnumerable(Of DataRow) = (From dr1 In Dt1.AsEnumerable() _
Group Join dr2 In Dt2.AsEnumerable() _
On dr1.Field(Of Integer)("ID") Equals dr2.Field(Of Integer)("ID") _
Into joined = Group _
From j In joined.DefaultIfEmpty() _
Select New With
{
.ID = dr1.Field(Of Integer)("ID"),
.Name = dr1.Field(Of String)("Name"),
.YearOfBirth = If(j IsNot Nothing, j.Field(Of Integer)("YearOfBirth"), 0)
}).Select(Function(r)
' use `DataTable.NewRow` here
Dim row As DataRow = JoinedDT.NewRow()
row("ID") = r.ID
row("Name") = r.Name
row("YearOfBirth") = r.YearOfBirth
Return row
End Function)
Dt3 = query.CopyToDataTable()
Reference:
Get linq to return IEnumerable<DataRow> result (C# version)

I am having trouble with the code below. It is saying 'results' is an expression type '?'. which is not a collection type

I am having trouble with the code below. It is saying 'results' is an expression type '?'. which is not a collection type. And also my "=" sign between CInt(table1(ComboBox1.SelectedItem)) = CInt(table2(ComboBox2.SelectedItem)) is saying 'Equals' expected. Any help would be great, thanks! Not sure if table1 and table2 is the correct syntax either...
Dim tt As New DataTable()
tt = DtSet1.Tables(0)
Dim rr As New DataTable()
rr = DtSet2.Tables(0)
Dim DTA As New DataTable()
DTA.Columns.Add("Account", GetType(Integer))
DTA.Columns.Add("First", GetType(String))
DTA.Columns.Add("Last", GetType(String))
DTA.Columns.Add("Code", GetType(String))
Dim DTB As New DataTable()
DTB.Columns.Add("Code", GetType(String))
DTB.Columns.Add("Amount", GetType(Integer))
For g As Integer = 1 To 6
Dim row As DataRow = DTA.NewRow()
row("Account") = g
row("First") = 10 + g
row("Last") = 20 + g
row("Code") = 30 + g
DTA.Rows.Add(row)
row = DTB.NewRow()
row("Code") = 40 + g
row("Amount") = 50 + g
DTB.Rows.Add(row)
Next
Dim results = _
From table1 In DTA.AsEnumerable() Join table2 In DTB.AsEnumerable() _
On CInt(table1(ComboBox1.SelectedItem)) = CInt(table2(ComboBox2.SelectedItem)) _
New With { _
Key .Account = CInt(tt("Account")), _
Key .First = CInt(tt("First")), _
Key .Last = CInt(tt("Last")), _
Key .Offer = CInt(tt("Code")), _
Key .Amount = CInt(rr("Amount")) _
}
For Each item As var In results
Console.WriteLine([String].Format("Account = {0}, First = {1}, Last = {2}, Code = {3}, Amount = {4}", item.Account, item.First, item.Last, item.Code, item.Amount))
Next
Console.ReadLine()
The posted code doesn't compile - are you sure the actual version is the same?
Here it goes. The LINQ syntax is wrong. First off, in the Join clause you MUST use Equals keyword, not the = sign. Next the CInt() function is misplaced - it should be applied to the ComboBox1.SelectedItem in order to produce an integer, which will be treated as a column index. Finally you need to use the Select keyword to specify the select clause. Just before the New keyword. As a result you'll get an IEnumerable(Of <anonymous type>) in the results variable.
Later in the For ... Next loop declaration, loose the "As var". The item type will be inferred from the usage.
Plain LINQ queries are difficult. They require both practice and theoretical knowledge in order to be mastered. Fortunately there are LINQ extension methods. They achieve essentially the same, but in a different manner. If they are executed sequentially, the code becomes more readable and closes up toward the imperative approach.
E.g. the above LINQ query could be rewritten as follows:
Dim results = DTA.AsEnumerable().Join(DTB.AsEnumerable(),
Function(r1) r1(CInt(ComboBox1.SelectedItem)), ' The key selector for the LEFT row
Function(r2) r2(CInt(ComboBox2.SelectedItem)), ' The key selector for the RIGHT row
Function(tt, rr) New With {
Key .Account = CInt(tt("Account")), _
Key .First = CInt(tt("First")), _
Key .Last = CInt(tt("Last")), _
Key .Offer = CInt(tt("Code")), _
Key .Amount = CInt(rr("Amount")) _
} ' Result selector
)
Your question is rather vague with regard to where the data comes from.
I have made an example with two ComboBoxes and a Button on a form; it creates its own sample data:
Option Infer On
Option Strict On
Public Class Form1
' a method to show a datatable
Sub PrintDataTable(dt As DataTable)
Dim columnWidth = 10
Dim separator = New String("="c, columnWidth * dt.Columns.Count)
Console.WriteLine(separator)
Console.WriteLine(dt.TableName)
For Each n As DataColumn In dt.Columns
Console.Write(n.ColumnName.PadLeft(columnWidth))
Next
Console.WriteLine()
For Each r As DataRow In dt.Rows
For i = 0 To dt.Columns.Count - 1
Console.Write(r.Item(i).ToString().PadLeft(columnWidth, " "c))
Next
Console.WriteLine()
Next
Console.WriteLine(separator)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' create some test data
Dim DTA As New DataTable("DTA")
DTA.Columns.Add("Account", GetType(Integer))
DTA.Columns.Add("First", GetType(String))
DTA.Columns.Add("Last", GetType(String))
DTA.Columns.Add("Code", GetType(String))
Dim DTB As New DataTable("DTB")
DTB.Columns.Add("Code", GetType(String))
DTB.Columns.Add("Amount", GetType(Integer))
' add values of test data where some values of "Code" are equal
For g As Integer = 1 To 6
Dim row As DataRow = DTA.NewRow()
row("Account") = g
row("First") = (10 + g).ToString()
row("Last") = (20 + g).ToString()
row("Code") = (30 + g).ToString()
DTA.Rows.Add(row)
row = DTB.NewRow()
row("Code") = (30 + CInt(g Mod 4)).ToString()
row("Amount") = (50 + g).ToString()
DTB.Rows.Add(row)
Next
' show the data for visual verification
PrintDataTable(DTA)
PrintDataTable(DTB)
' N.B. for the test data it is necessary to select "Code" in each ComboBox
Dim colToMatchA = CStr(ComboBox1.SelectedItem)
Dim colToMatchB = CStr(ComboBox2.SelectedItem)
Console.WriteLine($"A: {colToMatchA} B: {colToMatchB}")
' N.B. it is required that the column data have a .ToString() method which is
' unique for each possible value. Note that the number 0 will match the string "0" etc.
Dim result = From a In DTA
Join b In DTB
On a.Item(colToMatchA).ToString() Equals b.Item(colToMatchB).ToString()
Select New With {.Account = a("Account"), .CodeA = a("Code"), .Amount = b("Amount")}
For Each r In result
Console.WriteLine(r.ToString())
Next
Console.WriteLine("Done.")
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim colsA = {"Account", "First", "Last", "Code"}
Dim colsB = {"Code", "Amount"}
ComboBox1.DataSource = colsA
ComboBox2.DataSource = colsB
End Sub
End Class
If you select "Code" in both Comboboxes, it gives this output:
========================================
DTA
Account First Last Code
1 11 21 31
2 12 22 32
3 13 23 33
4 14 24 34
5 15 25 35
6 16 26 36
========================================
====================
DTB
Code Amount
31 51
32 52
33 53
30 54
31 55
32 56
====================
A: Code B: Code
{ Account = 1, CodeA = 31, Amount = 51 }
{ Account = 1, CodeA = 31, Amount = 55 }
{ Account = 2, CodeA = 32, Amount = 52 }
{ Account = 2, CodeA = 32, Amount = 56 }
{ Account = 3, CodeA = 33, Amount = 53 }
Done.

Distinct Rows From Vb.Net DataTable

I have a scenario, in which I have to apply distinct filter onto DataTable and find the rows only which are distinct,
I am using dt.DefaultView.ToTable(True, Columns) this statement but no effect.
Here is my chunk of code..
Try
Dim dTable As New DataTable()
dTable.Columns.Add("AutoID")
dTable.Columns.Add("AnotherID")
dTable.Columns.Add("CitY")
Dim row As DataRow = Nothing
For i As Integer = 0 To 4
row = dTable.NewRow()
row("AutoID") = i + 1
row("AnotherID") = i + 10
row("City") = "Vetican"
dTable.Rows.Add(row)
Next
dTable.Rows.Add(6, "11", "Oslo")
dTable.Rows.Add(7, "12", "Toronto")
Dim TobeDistinct As String() = {"AnotherID"}
Dim dtDistinct As DataTable = GetDistinctRecords(dTable, TobeDistinct)
Catch ex As Exception
End Try
and the method ..
Public Shared Function GetDistinctRecords(ByVal dt As DataTable, ByVal Columns As String()) As DataTable
Dim dtURecords As New DataTable()
dtURecords = dt.DefaultView.ToTable(True, Columns)
Return dtURecords
End Function
Here is the screen shot , which I want..
Which rows do you want to keep and which rows should be removed? If you just want to keep one row per AnotherID it seems to be arbitrary to keep Vetican instead of Oslo. Maybe you want to concat both as in Vetican, Oslo.
I would use Linq instead:
Dim resultTable = dTable.Clone() ' empty table same columns
Dim idGroups = dTable.AsEnumerable().GroupBy(Function(r) r.Field(Of String)("AnotherID"))
For Each grp In idGroups
Dim r As DataRow = resultTable.Rows.Add()
r.SetField("AutoID", grp.First().Field(Of String)("AutoID"))
r.SetField("AnotherID", grp.Key)
Dim cities = From row In grp Select row.Field(Of String)("City")
r.SetField("City", String.Join(", ", cities))
Next

Join two queries from different data sources using VB.NET

I have two queries. One is an Oracle query, and one is a SQL Server query.
Oracle Columns: ID, Subject, Course
SQL Server Columns: ID, Recommended Subject, Recommended Course
I would like to join the two queries on ID. I need to find out which IDs have a subject that is not equal to the recommended subject or a course that is not equal to the recommended course. Then, display the results in a GridView.
Here's what I have tried to do so far. I have removed my SQL commands and connection strings.
Dim sConnectionString As String = ConfigurationManager.ConnectionStrings("sqlserver").ConnectionString
Dim sCN As New SqlConnection(sConnectionString)
Dim sCommandWrapper As SqlCommand = New SqlCommand("SQL", sCN)
Dim sDataAdapter As SqlDataAdapter = New SqlDataAdapter
sDataAdapter.SelectCommand = sCommandWrapper
Dim pConnectionString As String = ConfigurationManager.ConnectionStrings("oracle").ConnectionString
Dim pCN As New OleDbConnection(pConnectionString)
Dim pCommandWrapper As OleDbCommand = New OleDbCommand("SQL", pCN)
Dim pDataAdapter As OleDbDataAdapter = New OleDbDataAdapter
pDataAdapter.SelectCommand = pCommandWrapper
Dim stopDS As DataSet = New DataSet()
sDataAdapter.Fill(stopDS, "Recommendations")
pDataAdapter.Fill(stopDS, "Registrations")
Since you have your two results in a DataSet, you can define a relationship between the two tables using the DataSet.Relations property:
stopDS.Relations.Add(
"ID2ID",
stopDS.Tables("Recommendations").Columns("ID"),
stopDS.Tables("Registrations").Columns("ID")
)
You can then get the matching rows from either end of the relationship (apologies if I got the relationship the wrong way around!):
Dim rows() As DataRow = stopDS.Tables("Recommendations").Rows(0).GetChildRows("ID2ID")
Dim row As DataRow = stopDS.Tables("Registrations").Rows(0).GetParentRow("ID2ID")
' Can also use .GetParentRows(...) for an array.
Here is a complete example console app:
Module Module1
Sub Main()
Dim t1 = New DataTable()
t1.TableName = "Names"
t1.Columns.Add("ID", GetType(Integer))
t1.Columns.Add("Name", GetType(String))
Dim t2 = New DataTable()
t2.TableName = "Addresses"
t2.Columns.Add("ID", GetType(Integer))
t2.Columns.Add("Address", GetType(String))
Dim r As DataRow = Nothing
r = t1.NewRow()
r("ID") = 1
r("Name") = "Bob"
t1.Rows.Add(r)
r = t1.NewRow()
r("ID") = 2
r("Name") = "Joe"
t1.Rows.Add(r)
r = t1.NewRow()
r("ID") = 3
r("Name") = "Sue"
t1.Rows.Add(r)
r = t2.NewRow()
r("ID") = 1
r("Address") = "1 Main St"
t2.Rows.Add(r)
r = t2.NewRow()
r("ID") = 3
r("Address") = "2 Any St"
t2.Rows.Add(r)
Dim ds = New DataSet()
ds.Tables.Add(t1)
ds.Tables.Add(t2)
' Define relationship between the ID columns
ds.Relations.Add(
"NameToAddress",
ds.Tables("Names").Columns("ID"),
ds.Tables("Addresses").Columns("ID"))
For Each nameRow In t1.AsEnumerable()
Console.WriteLine("Name: {0}", nameRow.Field(Of String)("Name"))
For Each addrRow In nameRow.GetChildRows("NameToAddress")
Console.WriteLine("--Addr: {0}", addrRow.Field(Of String)("Address"))
Next
Next
Console.WriteLine("==========")
For Each addrRow In t2.AsEnumerable()
Console.WriteLine("Addr: {0}", addrRow.Field(Of String)("Address"))
Dim pr = addrRow.GetParentRow("NameToAddress")
If pr IsNot Nothing Then
Console.WriteLine("++Name: {0}", pr.Field(Of String)("Name"))
End If
For Each nameRow In addrRow.GetParentRows("NameToAddress")
Console.WriteLine("--Name: {0}", nameRow.Field(Of String)("Name"))
Next
Next
Console.ReadLine()
End Sub
End Module
The results are:
Name: Bob
--Addr: 1 Main St
Name: Joe
Name: Sue
--Addr: 2 Any St
==========
Addr: 1 Main St
++Name: Bob
--Name: Bob
Addr: 2 Any St
++Name: Sue
--Name: Sue
I really like Mike's approach, but let me suggest another one and you can choose whichever more suitable for you.
You can set up a linked server to your Oracle database. It is described in this answer. Alternatively you can follow this article.
Then simply join the two tables and get the results:
SELECT * FROM SqlTable s
INNER JOIN OracleServer.OracleDB..OracleTable o ON o.ID = s.ID
AND (s.Course != o.[Recommended Course] OR s.Subject != o.[Recommended Subject])
Unfortunately I don't have oracle installed to completely test this myself, but I hope you get in the direction.

convert a datatable

I have a datatable like this
X,Y,Z
0,0,A
0,2,B
0,0,C
1,0,A
1,0,C
2,2,A
2,2,B
2,0,C
3,2,B
3,1,C
4,3,A
4,0,B
4,1,C
5,3,A
5,2,B
5,0,C
and I want to convert it to something like this:
X,A,B,C
0,0,2,0
1,0, ,0
2,2,2,0
3, ,2,1
4,3,0,1
5,3,2,0
I tried with dataset and linq but not I wasn't lucky.
My code for linq:
Dim q = (From c In dt _
Select c("Z") Distinct) 'I found out which categories I have in Z column (my example :A,B,C)
Dim ldt(q.Count) As DataTable
For i = 0 To q.Count - 1
Dim sfil As String = q(i).ToString
Dim r = (From c In dt _
Select c Where c("Z") = sfil)
ldt(i) = r.CopyToDataTable
Next
So now I have 3 tables (ldt(0) with values for A, ldt(1) with values for B, ldt(2) with values for C)
and I was thinking to do something like leftJoin but anything that I tried is fail.
Any solution or even a better idea?
Thanks
So a new example it would be:
I have this table:
id,Price,Item
0,0,Laptop
0,2,Tablet
0,0,Cellphone
1,0,Laptop
1,0,Tablet
2,2,Laptop
2,2,Cellphone
2,0,Tablet
3,2,Cellphone
3,1,Tablet
4,3,Laptop
4,0,Cellphone
4,1,Tablet
5,3,Laptop
5,2,Cellphone
5,0,Tablet
and I would like to convert it to this:
X,Laptop,Tablet,Cellphone
0,0,2,0
1,0, ,0
2,2,2,0
3, ,2,1
4,3,0,1
5,3,2,0
The values for each of the columns Laptop, Tablet, Cellphone are the Y values from the first table.
I hope it make more sense now.
I believe you can create a DataTable with column names corresponding to the item names. Then you group the previous DataTable by id and use each grouping to populate a row. Forgive me if I get anything wrong. I don't work with VB or DataTables that much.
Dim itemNames = (From c In dt _
Select c("Item") Distinct)
Dim newDt as DataTable = new DataTable()
Dim idColumn As DataColumn = new DataColumn()
idColumn.DataType = System.Type.GetType("System.Int32")
idColumn.ColumnName = "id"
idColumn.ReadOnly = True
idColumn.Unique = True
newDt.Columns.Add(idColumn)
For Each itemName As String In itemNames
Dim column As DataColumn = new DataColumn()
column.DataType = GetType(Nullable(Of Integer))
column.ColumnName = itemName
column.ReadOnly = True
column.Unique = False
newDt.Columns.Add(column)
Next
Dim groupingById = From row in dt
Group By Id = row("id")
Into RowsForId = Group
For Each grouping In groupingById
Dim row as DataRow = newDt.NewRow()
row("id") = grouping.Id
For Each rowForId in grouping.RowsForId
row(rowForId("Item")) = rowForId("Price")
Next
newDt.Rows.Add(row)
Next