I'm trying to LINQ two tables based on a dynamic key. User can change key via a combo box. Key may be money, string, double, int, etc. Currently I'm getting the data just fine, but without filtering out the doubles. I can filter the double in VB, but it's slooooow. I'd like to do it in the LINQ query right out of the gate.
Here's the data:
First Table:
-------------------------------------------------------------
| AppleIndex | AppleCost | AppleColor | AppleDescription |
------------------------------------------------------------
| 1 | 3 | Red | This is an apple |
| 2 | 5 | Green | This is an apple |
| 3 | 4 | Pink | This is an apple |
| 4 | 2 | Yellow | This is an apple |
| 5 | 2 | Orange | This is an apple |
| 1 | 3 | Red | This is a duplicate|
| 2 | 5 | Green | This is a duplicate|
| 3 | 4 | Pink | This is a duplicate|
| 4 | 2 | Yellow | This is a duplicate|
| 5 | 2 | Orange | This is a duplicate|
-------------------------------------------------------------
Second Table:
------------------------------------------------------------
| OrangeIndex | OrangeCost | OrangeColor | OrangeDescription |
------------------------------------------------------------
| 1 | 1 | Orange | This is an Orange |
| 2 | 3 | Orange | |
| 3 | 2 | Orange | This is an Orange |
| 4 | 3 | Orange | |
| 5 | 2 | Orange | This is an Orange |
------------------------------------------------------------
Currently, I'm using the following code to get too much data:
Dim Matches = From mRows In LinqMasterTable Join sRows In LinqSecondTable _
On mRows(ThePrimaryKey) Equals sRows(TheForignKey) _
Order By mRows(ThePrimaryKey) _
Select mRows, sRows Distinct
Outcome:
-------------------------------------------------------------------------
| 1 | 3 | Red | This is an apple | 1 | Orange | This is an Orange |
| 1 | 3 | Red | This is an duplicate | 1 | Orange | This is an Orange |
| 2 | 5 | Green | This is an apple | 3 | Orange | |
| 2 | 5 | Green | This is an duplicate | 3 | Orange | |
| 3 | 4 | Pink | This is an apple | 2 | Orange | This is an Orange |
| 3 | 4 | Pink | This is an duplicate | 2 | Orange | This is an Orange |
| 4 | 2 | Yellow | This is an apple | 3 | Orange | |
| 4 | 2 | Yellow | This is an duplicate | 3 | Orange | |
| 5 | 2 | Orange | This is an apple | 2 | Orange | This is an Orange |
| 5 | 2 | Orange | This is an duplicate | 2 | Orange | This is an Orange |
-------------------------------------------------------------------------
Desired Outcome:
------------------------------------------------------------------------
| 1 | 3 | Red | This is an apple | 1 | 1 | Orange | This is an Orange |
| 2 | 5 | Green | This is an apple | 2 | 3 | Orange | |
| 3 | 4 | Pink | This is an apple | 3 | 2 | Orange | This is an Orange |
| 4 | 2 | Yellow | This is an apple | 4 | 3 | Orange | |
| 5 | 2 | Orange | This is an apple | 5 | 2 | Orange | This is an Orange |
------------------------------------------------------------------------
I have tried the following:
'Get the original Column Names into an Array List
'MasterTableColumns = GetColumns(qMasterDS, TheMasterTable) '(external code)
'Plug the Existing DataSet into a DataView:
Dim View As DataView = New DataView(qMasterTable)
'Sort by the Primary Key:
View.Sort = ThePrimaryKey
'Build a new table listing only one column:
Dim newListTable As DataTable = _
View.ToTable("UniqueData", True, ThePrimaryKey)
This returns a unique list, but no associated data:
-------------
| AppleIndex |
-------------
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
-------------
So I tried this instead:
'Build a new table with ALL the columns:
Dim newFullTable As DataTable = _
View.ToTable("UniqueData", True, _
MasterTableColumns(0), _
MasterTableColumns(1), _
MasterTableColumns(2), _
MasterTableColumns(3))
Unfortunately, it yields the following... with duplicates:
-------------------------------------------------------------
| AppleIndex | AppleCost | AppleColor | AppleDescription |
------------------------------------------------------------
| 1 | 3 | Red | This is an apple |
| 2 | 5 | Green | This is an apple |
| 3 | 4 | Pink | This is an apple |
| 4 | 2 | Yellow | This is an apple |
| 5 | 2 | Orange | This is an apple |
| 1 | 3 | Red | This is a duplicate|
| 2 | 5 | Green | This is a duplicate|
| 3 | 4 | Pink | This is a duplicate|
| 4 | 2 | Yellow | This is a duplicate|
| 5 | 2 | Orange | This is a duplicate|
-------------------------------------------------------------
Any ideas?
~~~~~~~~~~~~ Update: ~~~~~~~~~~~~
Jeff M suggested the following code. (Thanks Jeff) However, it gives me a error. Does anyone know the syntax for making this work in VB? I've monkeyed with it a bit and can't seem to get it right.
Dim matches = _
From mRows In (From row In LinqMasterTable _
Group row By row(ThePrimaryKey) Into g() _
Select g.First()) _
Join sRows In LinqSecondTable _
On mRows(ThePrimaryKey) Equals sRows(TheForignKey) _
Order By mRows(ThePrimaryKey) _
Select mRows, sRows
Error in Third row at "row(ThePrimaryKey)":
"Range variable name can be inferred only from a simple or qualified name with no arguments."
Well, the basic problem isn't the LINQ. It's the fact the your First Table contains "duplicates", which aren't really duplicates, since in your example, every row is distinctive.
So, our question to you is "How do we identify the duplicates in the original table?". Once that is answered, the rest should be trivial.
For example (In C# since I'm not sure of the VB syntax)
var Matches = from mRows in LinqMasterTable
.Where(r=>r.AppleDescription=="This is an Apple")
join sRows in LinqSecondTable
on mRows(ThePrimaryKey) equals sRows(TheForignKey)
orderby mRows(ThePrimaryKey)
select new { mRows, sRows};
Edit:
Here's how I would write the C# LINQ query. Here's an alternate version rather than using Distinct(), uses a nested query with grouping which should have similar semantics. It should be easily convertible to VB.
var matches = from mRows in (from row in LinqMasterTable
group row by row[ThePrimaryKey] into g
select g.First())
join sRows in LinqSecondTable
on mRows[ThePrimaryKey] Equals sRows[TheForignKey]
orderby mRows[ThePrimaryKey]
select new { mRows, sRows }
and my attempt at a VB version of the above:
Edit:
As for the most recent error, I know exactly how to deal with it. When I was playing with VB LINQ, I found that the compiler doesn't like complex grouping expressions. To get around that, assign row(ThePrimaryKey) to a temporary variable and group by that variable. It should work then.
Dim matches = From mRows In (From row In LinqMasterTable _
Let grouping = row(ThePrimaryKey)
Group row By grouping Into g() _
Select g.First()) _
Join sRows In LinqSecondTable _
On mRows(ThePrimaryKey) Equals sRows(TheForignKey) _
Order By mRows(ThePrimaryKey) _
Select mRows, sRows
Actually upon second inspection, it turns out that what is being grouped by needs a name. The following will work.
Dim matches = From mRows In (From row In LinqMasterTable _
Group row By Grouping = row(ThePrimaryKey) Into g() _
Select g.First()) _
Join sRows In LinqSecondTable _
On mRows(ThePrimaryKey) Equals sRows(TheForignKey) _
Order By mRows(ThePrimaryKey) _
Select mRows, sRows
Declarations and Such:
Private Sub LinqTwoTableInnerJoin(ByRef qMasterDS As DataSet, _
ByRef qMasterTable As DataTable, _
ByRef qSecondDS As DataSet, _
ByRef qSecondTable As DataTable, _
ByRef qPrimaryKey As String, _
ByRef qForignKey As String, _
ByVal qResultsName As String)
Dim TheMasterTable As String = qMasterTable.TableName
Dim TheSecondTable As String = qSecondTable.TableName
Dim ThePrimaryKey As String = qPrimaryKey
Dim TheForignKey As String = qForignKey
Dim TheNewForignKey As String = ""
MasterTableColumns = GetColumns(qMasterDS, TheMasterTable)
SecondTableColumns = GetColumns(qSecondDS, TheSecondTable)
Dim mColumnCount As Integer = MasterTableColumns.Count
Dim sColumnCount As Integer = SecondTableColumns.Count
Dim ColumnCount As Integer = mColumnCount + sColumnCount
Dim LinqMasterTable = qMasterDS.Tables(TheMasterTable).AsEnumerable
Dim LinqSecondTable = qSecondDS.Tables(TheSecondTable).AsEnumerable
Get the Data and order it by the Selected Key:
Dim Matches = From mRows In LinqMasterTable Join sRows In LinqSecondTable _
On mRows(ThePrimaryKey) Equals sRows(TheForignKey) _
Order By mRows(ThePrimaryKey) _
Select mRows, sRows
Put the Results into a Dataset Table:
' Make sure the dataset is available and/or cleared:
If dsResults.Tables(qResultsName) Is Nothing Then dsResults.Tables.Add(qResultsName)
dsResults.Tables(qResultsName).Clear() : dsResults.Tables(qResultsName).Columns.Clear()
'Adds Master Table Column Names
For x = 0 To MasterTableColumns.Count - 1
dsResults.Tables(qResultsName).Columns.Add(MasterTableColumns(x))
Next
'Rename Second Table Names if Needed:
For x = 0 To SecondTableColumns.Count - 1
With dsResults.Tables(qResultsName)
For y = 0 To .Columns.Count - 1
If SecondTableColumns(x) = .Columns(y).ColumnName Then
SecondTableColumns(x) = SecondTableColumns(x) & "_2"
End If
Next
End With
Next
'Make sure that the Forign Key is a Unique Value
If ForignKey1 = PrimaryKey Then
TheNewForignKey = ForignKey1 & "_2"
Else
TheNewForignKey = ForignKey1
End If
'Adds Second Table Column Names
For x = 0 To SecondTableColumns.Count - 1
dsResults.Tables(qResultsName).Columns.Add(SecondTableColumns(x))
Next
'Copy Results into the Dataset:
For Each Match In Matches
'Build an array for each row:
Dim NewRow(ColumnCount - 1) As Object
'Add the mRow Items:
For x = 0 To MasterTableColumns.Count - 1
NewRow(x) = Match.mRows.Item(x)
Next
'Add the srow Items:
For x = 0 To SecondTableColumns.Count - 1
Dim y As Integer = x + (MasterTableColumns.Count)
NewRow(y) = Match.sRows.Item(x)
Next
'Add the array to dsResults as a Row:
dsResults.Tables(qResultsName).Rows.Add(NewRow)
Next
Give the user an option to clean doubles or not:
If chkUnique.Checked = True Then
ReMoveDuplicates(dsResults.Tables(qResultsName), ThePrimaryKey)
End If
Remove the Duplicates if they so desire:
Private Sub ReMoveDuplicates(ByRef SkipTable As DataTable, _
ByRef TableKey As String)
'Make sure that there's data to work with:
If SkipTable Is Nothing Then Exit Sub
If TableKey Is Nothing Then Exit Sub
'Create an ArrayList of rows to delete:
Dim DeleteRows As New ArrayList()
'Fill the Array with Row Number of the items equal
'to the item above them:
For x = 1 To SkipTable.Rows.Count - 1
Dim RowOne As DataRow = SkipTable.Rows(x - 1)
Dim RowTwo As DataRow = SkipTable.Rows(x)
If RowTwo.Item(TableKey) = RowOne.Item(TableKey) Then
DeleteRows.Add(x)
End If
Next
'If there are no hits, exit this sub:
If DeleteRows.Count < 1 Or DeleteRows Is Nothing Then
Exit Sub
End If
'Otherwise, remove the rows based on the row count value:
For x = 0 To DeleteRows.Count - 1
'Start at the END and count backwards so the duplicate
'item's row count value doesn't change with each deleted row
Dim KillRow As Integer = DeleteRows((DeleteRows.Count - 1) - x)
'Delete the row:
SkipTable.Rows(KillRow).Delete()
Next
End Sub
Then clean up any leftovers:
If Not chkRetainKeys.Checked = True Then 'Removes Forign Key
dsResults.Tables(qResultsName).Columns.Remove(TheNewForignKey)
End If
'Clear Arrays
MasterTableColumns.Clear()
SecondTableColumns.Clear()
Final Analysis:
Ran this against 2 Files with 4 columns, 65,535 rows, and with some doubles. Process time, roughly 1 second. In fact it took longer to load the fields into memory than it did to parse the data.
Related
I have example table like below
| ID | Qty |
| -- | ----|
| 1 | 5 |
| 2 | 7 |
| 3 | 8 |
| 4 | 9 |
| 5 | 12 |
How can I pass result of below query into VBA variable to use in next queries (update and insert)?
SELECT example_tab.Qty
FROM example_tab
WHERE ID = 4
In example that test_variable = 9
To get a single value into a variable, DLookup can be used:
Dim test_variable As Variant
test_variable = DLookup("[Qty]", "example_tab", "[ID] = 4")
Is this what your looking for ?
sSQL = "SELECT example_tab.Qty FROM example_tab WHERE ID = 4"
Dim rs As DAO.Recordset
Set rs = CurrentDB.OpenRecordset(sSQL)
I'm trying to split multi-line text box and paste the output on the next sheet in MS Excel using VBA, and I found a fragment of code below and it works:
Dim Str As String, a
Dim cnt As Integer
Dim w()
Str = xmlRequestTextBox.Value
a = Chr(10)
cnt = UBound(Split(Str, a))
MsgBox (Str)
MsgBox (a)
MsgBox (cnt)
ReDim w(1 To cnt + 1, 1 To 1)
For i = 0 To cnt
w(i + 1, 1) = Split(Str, Chr(10))(i)
Next i
Sheet2.range("A1").Resize(i, 1) = w
Sheet2.Cells.Replace Chr(13), " "
Now my problem is when I tried to modify it and change it to a single dimensional array, it only outputs the value of the first index of the array. Why does the array have to be multi-dimensional? Thank you in advanced.
For multi-line text you would need to use 2d-array returning by Split() function, where the first dimension typically refers to the rows (lines) and second index is referring to the individual words in these lines. Hope this may help.
an example of using split
Option Explicit
Private Sub CommandButton1_Click()
Dim Str As String
Dim cnt As Integer
Dim w As Variant
Str = Me.xmlRequestTextBox.Value
w = Split(Str, Chr(10))
cnt = UBound(w)
MsgBox (Str)
MsgBox (Chr(10))
MsgBox (cnt)
' suppose the multi-line textbox value is:
' first line : 1 2 3
' second line: 4 5 6
' third line: 7 8 9
With ThisWorkbook.Sheets("split")
.Range("A1").Resize(cnt + 1) = w(1) ' writes "4 5 6" in A1:A3 (i.e. the 2nd element of w)
.Range("B1").Resize(cnt + 1) = Split(w(0), " ")(2) ' writes "3" in B1:B3 (i.e. the third element of the 1st element of w)
.Cells.Replace Chr(13), " "
End With
End Sub
where I assumed that there's some UserForm with a multi-line textbox named "xmlRequestTextBox" and a button named "CommandButton1", whose click event starts that piece of code of yours
To answer your question, the array does not have to be multi-dimensional. However, Excel assumes a 1-dimensional array will be horizontal, that is, fill the columns in a single row. So if you're writing to a vertical range (multiple rows in a single column) in a spreadsheet, it will take the value of the first element (column) and write it to each cell in the column. For example, let's assume you have an array like this:
MyArray = Array(1, 2, 3, 4, 5)
This code will write the first element only of MyArray to the vertical range in column A:
Set MyRange = Range("A1").Resize(5, 1) ' Cells A1:A5
MyRange.Value = MyArray
Result:
A | B | C | D | E
+---+---+---+---+---+
1 | 1 | | | | |
2 | 1 | | | | |
3 | 1 | | | | |
4 | 1 | | | | |
5 | 1 | | | | |
If you write the array to a horizontal range in a single row then it will display the whole array:
Set MyRange = Range("A1").Resize(1, 5) ' Cells A1:E1
MyRange.Value = MyArray
Result:
A | B | C | D | E
+---+---+---+---+---+
1 | 1 | 2 | 3 | 4 | 5 |
2 | | | | | |
3 | | | | | |
4 | | | | | |
5 | | | | | |
However, I'm assuming you actually want to write the array to a vertical range in a single column. You can accomplish this by using the Application.WorksheetFunction.Transpose method on the array. This will write each element of the array into rows in column A:
Set MyRange = Range("A1").Resize(5, 1) ' Cells A1:A5
MyRange.Value = Application.Transpose(MyArray)
Result:
A | B | C | D | E
+---+---+---+---+---+
1 | 1 | | | | |
2 | 2 | | | | |
3 | 3 | | | | |
4 | 4 | | | | |
5 | 5 | | | | |
For more information on this, see Chip Pearson's page on VBA Arrays And Worksheet Ranges
I have two excel sheets, A which contains products and B, which is the products we will discontinue when stock runs out.
I would like a macro so that we can make a list in B, hit the run function, and it will go and find where it is in sheet A, go to column E of that row and enter in today's date.
The hitch I have so far, is to not make it overwrite previous entries in the column if it wasn't found.
The basic formula I have right now is this
Sub Deletions()
Dim LastRow As Long
With Sheets("A") '<-set this worksheet reference properly
LastRow = .Range("A" & Cells.Rows.Count).End(xlUp).Row
With .Range("E2:E" & LastRow)
.Formula = "=IF(A1='B'!A1,TODAY(),)"
.Cells = .Value2
End With
End With
End Sub
The reason I need to use VBA, is that we have over 100k items, and not everyone using this will know excel very well. So we want to be able to make a list, put it in excel, and click the macro button and voila.
Also, the list of removed items gets deleted afterwards, as the information is kept in sheet A. We also need to keep the dates of when products got discontinued, so it is very crucial that this macro not erase previous entries.
Heres my answer:
Please follow the comments inside the code.
Sub discontinue_Prods()
'the button need to be on sheet B
'In sheet B need to have a header
Dim r
Dim c
Dim disRange As Range
Dim i
Dim shtA As Worksheet
Dim shtB As Worksheet
Dim dLine
Dim E 'to store the column number of column E
Dim A 'to store the column number of column A
Set shtA = Sheets("A") 'storing the sheets...
Set shtB = Sheets("B")
shtB.Activate 'no matter you are in the workbook, always run from the sheet B,
'this code will do that for you.
r = Range("A2").End(xlDown).Row 'the last row of the list
'with the discounted prods
'If you do not want headers,
'use A1 here
c = 1 'column A... changed if you need
Set disRange = Range(Cells(2, c), Cells(r, c)) 'here need to change the 2 for
'1 if you do not want headers
E = 5 'column E and A, just the numbers
A = 1
shtA.Activate 'go to sheet A
For Each i In disRange 'for each item inside the list of prod going to discount
dLine = Empty
On Error Resume Next
dLine = Application.WorksheetFunction.Match(i.Value, shtA.Columns(A), False)
'here we find the row where the prod is,
'searching for the item on the list (Sheet B).
If Not dLine = Empty Then
shtA.Cells(dLine, E).Value = Date 'heres we add the today date (system date)
'to column E, just as text
'IMPORTANT!
'if you want the formula uncomment and use this:
'Cells(dLine, E).FormulaR1C1 = "=TODAY()"
End If
On Error GoTo 0
Next i
End Sub
Just go over the cells in the list of Sheet B, and go to Sheet A and find the products, and if the code find any Match product, set the column E as a Todays date, using the system date. Note, if you want to user formulas see the comments.
With a list like this:
Sheet A
+----------+-----+
| Products | Qty |
+----------+-----+
| Prod001 | 44 |
| Prod002 | 27 |
| Prod003 | 65 |
| Prod004 | 135 |
| Prod005 | 95 |
| Prod006 | 36 |
| Prod007 | 114 |
| Prod008 | 20 |
| Prod009 | 107 |
| Prod010 | 7 |
| Prod011 | 22 |
| Prod012 | 142 |
| Prod013 | 99 |
| Prod014 | 144 |
| Prod015 | 150 |
| Prod016 | 44 |
| Prod017 | 57 |
| Prod018 | 64 |
| Prod019 | 17 |
| Prod020 | 88 |
+----------+-----+
Sheet B
+----------+
| Products |
+----------+
| Prod017 |
| Prod011 |
| Prod005 |
| Prod018 |
| Prod006 |
| Prod009 |
| Prod006 |
| Prod001 |
| Prod017 |
+----------+
Result in Sheet A
+----------+-----+--+--+-----------+
| Products | Qty | | | |
+----------+-----+--+--+-----------+
| Prod001 | 44 | | | 2/23/2016 |
| Prod002 | 27 | | | |
| Prod003 | 65 | | | |
| Prod004 | 135 | | | |
| Prod005 | 95 | | | 2/23/2016 |
| Prod006 | 36 | | | 2/23/2016 |
| Prod007 | 114 | | | |
| Prod008 | 20 | | | |
| Prod009 | 107 | | | 2/23/2016 |
| Prod010 | 7 | | | |
| Prod011 | 22 | | | 2/23/2016 |
| Prod012 | 142 | | | |
| Prod013 | 99 | | | |
| Prod014 | 144 | | | |
| Prod015 | 150 | | | |
| Prod016 | 44 | | | |
| Prod017 | 57 | | | 2/23/2016 |
| Prod018 | 64 | | | 2/23/2016 |
| Prod019 | 17 | | | |
| Prod020 | 88 | | | |
+----------+-----+--+--+-----------+
I think you are overcomplicating this by using VBA.
Instead, you can do this with a simple Excel formula:
Assume 'Sheet B', column A holds the list of discontinued items. 'Sheet A' column A holds the name of each item, and you want today's date in column E, wherever there is a match of an item in Sheet B. Put this in 'Sheet A' E1 and copy it down to the end of the sheet.
=IF(ISERROR(MATCH(A1,'Sheet B'!A:A, 0)), "", TODAY())
This will put today's date, as long as the row in sheet A matches any of the rows in sheet B. It tries to find a match anywhere on Sheet B, and if it doesn't, it will produce an error, meaning ISERROR will be TRUE, and the IF statement will produce "". If it does match, there will be no error, and it will produce TODAY().
This is what I would do:
Dim b as Variant
For j=1 to Range("A1").End(xlDown).Row 'Assuming the button is on the "B" Sheet
b=Cells(j,1).Value 'This is your product in Sheet "B", assuming it is in the first column
For i=1 to Sheets("A").Range("A1").End(xlDown).Row
If Sheets("A").Cells(i,1).Value=b Then 'This would mean the product was found in the i Row
Sheets("A").Cells(i,5)=Format(Now(), "MMM-DD-YYYY") 'Write today's date
Exit For 'No need to keep looping
End if
Next i
Next j
It's very basic, but I'm sure it works.
These two datatables were queried from DIFFERENT database servers. SQL Join is not an option.
They are filled separately and I need them merged. They're actually quite large, column-wise, but for ease of my question, I simplified them.
Table A (dtA): Definitely some duplicate order numbers...
+-------+-----+
| Order | Lne |
+-------+-----+
| 101 | 1 |
| 123 | 1 |
| 123 | 2 |
| 215 | 1 |
| 333 | 1 |
| 333 | 2 |
| 333 | 3 |
| 333 | 4 |
| 405 | 1 |
| 405 | 2 |
| 405 | 3 |
| 500 | 1 |
+-------+-----+
Table B (dtB): Unique order numbers, but extra data I need
+-------+------------+------------+
| Order | ReqDate | DelivDate |
+-------+------------+------------+
| 215 | 03/15/2014 | 03/15/2014 |
| 333 | 04/02/2014 | 04/15/2014 |
| 405 | | 02/01/2014 |
+-------+------------+------------+
Combined Table (dtCombined): Hopeful goal
+-------+-----+------------+------------+
| Order | Typ | ReqDate | DelivDate |
+-------+-----+------------+------------+
| 101 | 1 | | |
| 101 | 2 | | |
| 101 | 3 | | |
| 215 | 1 | 03/15/2014 | 03/15/2014 |
| 333 | 1 | 04/02/2014 | 04/15/2014 |
| 333 | 2 | 04/02/2014 | 04/15/2014 |
| 333 | 3 | 04/02/2014 | 04/15/2014 |
| 333 | 4 | 04/02/2014 | 04/15/2014 |
| 405 | 1 | | 02/01/2014 |
| 405 | 2 | | 02/01/2014 |
| 405 | 3 | | 02/01/2014 |
| 500 | 1 | | |
+-------+-----+------------+------------+
After combining, I will be displaying in a DataGrid.
Code attempt...
Current attempt at merging the tables:
Dim colsA() As String = {"HeadOrderNo", "Line"}
Dim colsB() As String = {"OrderNo", "RequestDate", "DeliveryDate"}
'Add the columns from the first datatable (dtA)
For nCount1 As Integer = 0 To dtA.Columns.Count - 1
dtCombined.Columns.Add(New DataColumn(dtA.Columns(nCount1).ColumnName, dtA.Columns(nCount1).DataType))
Next
'Add the columns from the second datatable (dtB)
For nCount2 As Integer = 0 To dtB.Columns.Count - 1
dtCombined.Columns.Add(New DataColumn(dtB.Columns(nCount2).ColumnName, dtB.Columns(nCount2).DataType))
Next
'Dim bFirstRunDone As Boolean = False
Dim sColumnName As String
Dim drNewRow As DataRow
Dim nOuter As Integer = 0
'Go through each row in dtA and copy it to dtCombined
For Each drRowETI As DataRow In dtA.Rows
drNewRow = dtCombined.NewRow()
'Copy all column data from to drRow
For Each sColumnName In colsA
drNewRow(sColumnName) = drRowETI(sColumnName)
Next
'Go through each row in dtB and copy it to drNewRow
For Each drRowLion As DataRow In dtB.Rows
'Copy all column data from to drRow -- not working right
For Each sColumnName In colsB
'If dtA.Columns(nOuter).ColumnName = dtB.Columns(sColumnName).ColumnName Then
drNewRow(sColumnName) = drRowLion(sColumnName)
'End If
Next
Next
'If dtA.Columns.Item(sColumnName).ToString() = dtB.Columns.Item(nCurrentRowNumETI).ToString() Then
'End If
dtCombined.Rows.Add(drNewRow)
nOuter = nOuter + 1
Next
It's showing the first two columns correctly, but just repeating either the first or last row from the second table in their respective columns. I'm used to 2D arrays and iterating through them, but these DataRows are confusing for me. I'm not sure how to properly iterate through them to perform this type of task. It really shouldn't be hard I don't think.
I've tried countless ways like this: Joining two DataTables in C# with non matching data but to no avail. I might be implementing it incorrectly, but even the syntax is messing with me (e.g.: "Show New With { }).
Thank you
You don't appear to be filtering the dtB rows to find the ones matching the current dtA row. You could add your two source DataTables to a DataSet and define a DataRelation for the join. This will allow you to easily find the matching rows in dtB to combine the data.
Here is an example command line program, based on your pictures (not the code). Hopefully you can adapt this to your actual code!
Module Module1
Sub Main()
Dim row As DataRow = Nothing
Dim dtA = New DataTable()
dtA.Columns.Add("Order", GetType(Integer))
dtA.Columns.Add("Lne", GetType(Integer))
For i = 1 To 10
row = dtA.NewRow()
row("Order") = i
row("Lne") = 1
dtA.Rows.Add(row)
row = dtA.NewRow()
row("Order") = i
row("Lne") = 2
dtA.Rows.Add(row)
Next
Dim dtB = New DataTable()
dtB.Columns.Add("Order", GetType(Integer))
dtB.Columns.Add("ReqDate", GetType(Date))
dtB.Columns.Add("DelivDate", GetType(Date))
For i = 1 To 10
row = dtB.NewRow()
row("Order") = i
row("ReqDate") = New Date(2014, 1, i)
row("DelivDate") = New Date(2014, 2, i)
dtB.Rows.Add(row)
Next
Dim dtCombined = New DataTable()
dtCombined.Columns.Add("Order", GetType(Integer))
dtCombined.Columns.Add("Typ", GetType(Integer))
dtCombined.Columns.Add("ReqDate", GetType(Date))
dtCombined.Columns.Add("DelivDate", GetType(Date))
' Add source tables to a dataset
Dim ds = New DataSet()
ds.Tables.Add(dtA)
ds.Tables.Add(dtB)
' Define the relationship between the two tables
ds.Relations.Add("order", dtA.Columns("Order"), dtB.Columns("Order"), False)
For Each r1 As DataRow In dtA.Rows
' For each row in dtA, get the related rows in dtB
For Each r2 As DataRow In r1.GetChildRows("order")
row = dtCombined.NewRow()
row("Order") = r1("Order")
row("Typ") = r1("Lne")
row("ReqDate") = r2("ReqDate")
row("DelivDate") = r2("DelivDate")
dtCombined.Rows.Add(row)
Next
Next
For Each r As DataRow In dtCombined.Rows
Console.WriteLine("{0,2} {1,2} {2:yyyy-MM-dd} {3:yyyy-MM-dd}", r("Order"), r("Typ"), r("ReqDate"), r("DelivDate"))
Next
Console.ReadLine()
End Sub
End Module
And here is the output:
1 1 2014-01-01 2014-02-01
1 2 2014-01-01 2014-02-01
2 1 2014-01-02 2014-02-02
2 2 2014-01-02 2014-02-02
3 1 2014-01-03 2014-02-03
3 2 2014-01-03 2014-02-03
4 1 2014-01-04 2014-02-04
4 2 2014-01-04 2014-02-04
5 1 2014-01-05 2014-02-05
5 2 2014-01-05 2014-02-05
6 1 2014-01-06 2014-02-06
6 2 2014-01-06 2014-02-06
7 1 2014-01-07 2014-02-07
7 2 2014-01-07 2014-02-07
8 1 2014-01-08 2014-02-08
8 2 2014-01-08 2014-02-08
9 1 2014-01-09 2014-02-09
9 2 2014-01-09 2014-02-09
10 1 2014-01-10 2014-02-10
10 2 2014-01-10 2014-02-10
I'm new to reporting and the jargon that goes with so I will try to draw it insted of write it.
| | A | B | C | D | E |
-------------------------------------------------
| Apples | 1 | 3 | 6 | 2 | 12 |
-------------------------------------------------
| Oranges | 3 | 2 | 4 | 1 | 10 |
-------------------------------------------------
| Bananas | 5 | 3 | | 1 | 9 |
-------------------------------------------------
| | | | | | 31 |
I need to sum up the last column E where I indicated 31. The cells with values 12,10,9 are obtained by =Sum(Fields!A.Value + Fields!B.Value + Fields!C.Value + Fields!D.Value).
I can't change the sql query and/or the dataset that is used. Does any one have a suggestion? Thanks!
EDIT:
I've added a function to the code
Public Total_lookup_Sum As Integer = 0
Public Function Lookup_Sum(ByVal value As Integer) As Integer
Total_lookup_Sum = Total_lookup_Sum + value
Return Total_lookup_Sum
End Function
and calling like this Code.Equals(ReportItems!txtFruitTotal.Value) but I get FALSE.
With a help of a colleague the answer was reached:
Sum(Fields!A.Value) + Sum(Fields!B.Value) + Sum(Fields!C.Value) + Sum(Fields!D.Value)
In the Cell Where 31 is simply, make this Expression
=Sum(Fields!A.Value) + Sum(Fields!B.Value) + Sum(Fields!C.Value) + Sum(Fields!D.Value)
OR
=Sum(Fields!E.Value)