How to add multiple row in datatable using LinQ - vb.net

I have 2 datatable dtMaster and dtResult
dtMaster
No Name Address Status
1 Andy Sesame Street Valid
2 Justin Guava Street Invalid
3 Taylor West Street Valid
If Status = Valid, I want to add 2 different rows in another datatable with template
No Name Address
1 Ms. Julie dtMaster("Address")
2 dtMaster("Name") dtMaster("Address")
Expected Result is
dtResult
No Name Address
1 Ms. Julie Sesame Street --> from 1st row in dtMaster
2 Andy Sesame Street --> from 1st row in dtMaster
1 Ms. Julie West Street --> from 3rd row in dtMaster
2 Taylor West Street --> from 3rd row in dtMaster
I tried this code, but it gives the type of ra1 (array) instead of the value I want, how to say :D
(From row1 In dtMaster
Let ra1 = {({"1","Ms. Julie",row1("Address")}),({"2",row1("Name"),row1("Address")})}
Select If(row1("Status").tostring="Valid",dtResult.Rows.Add(ra1),dtResult.Rows.Add(row1.itemArray))
).copytodatatable

System.Data.DataTable iteration is like this
' Declare DataTable
Dim dtResult As New DataTable()
' Define columns
dtResult.Columns.Add("No", GetType(System.Int32))
dtResult.Columns.Add("Name", GetType(System.String))
dtResult.Columns.Add("Address", GetType(System.String))
Dim index As Integer = 1;
For Each row As DataRow In dtMaster.Rows
If row.Items("Status") = "Valid" Then
' Add a row of data
dtResult.Rows.Add(index, row.Items("Name"), row.Items("Address"))
index += 1
End If
Next

Related

linq filter columns rather than data

Firstly, I'm not sure if what I'm asking is possible or not so apologies if I'm asking a stupid question.
So I am able to filter a DataTable using linq to get the data I need, I'm wondering if it's possible to filter the columns using a simlar statement.
For example if I have the below datatable dtMyData
ID
Name
1
2
3
4
1
Conor
100
87
3
0
2
Frank
35
70
0
0
3
Jeff
35
13
0
57
I can filter it to the below using the following statement
dtMyData = dtMyData.AsEnumerable().Where(Function (f) f("Name").ToString().Equals("Frank")).CopyToDataTable
ID
Name
1
2
3
4
2
Frank
35
70
0
0
What I'm wanting to do (If it's possible) is filter the columns in a similar way so that I can select all of the columsn > 2 plus the first 2 columns. Giving me the following columns
ID
Name
3
4
1
Conor
3
0
2
Frank
0
0
3
Jeff
0
57
Take a look at this method:
Private Function CopyTable(source As DataTable, columnsToKeep As IEnumerable(Of String)) As DataTable
Dim copiedTable As DataTable = source.Clone()
Dim columnsToRemove() As DataColumn = copiedTable.Columns.Cast(Of DataColumn).Where(Function(column) Not columnsToKeep.Contains(column.ColumnName)).ToArray()
For i As Integer = 0 To columnsToRemove.Length - 1
copiedTable.Columns.Remove(columnsToRemove(i))
Next
For Each row As DataRow In source.Rows
Dim values As New List(Of Object)
For Each column As DataColumn In copiedTable.Columns
values.Add(row.Item(column.ColumnName))
Next
copiedTable.Rows.Add(values.ToArray())
Next
Return copiedTable
End Function
What this does is
Clone the DataTable
Loop over the copied DataTable and remove the columns that are not in the columnsToKeep
Loop over the original DataTable and add the rows to the copied DataTable without the cells that are not in the columnsToKeep
Fiddle: https://dotnetfiddle.net/2l6wk9
Edit
It would actually be easier to use DataTable.Copy over DataTable.Clone, my apologies:
Private Function CopyTable(source As DataTable, columnsToKeep As IEnumerable(Of String)) As DataTable
Dim copiedTable As DataTable = source.Copy()
Dim columnsToRemove() As DataColumn = copiedTable.Columns.Cast(Of DataColumn).Where(Function(column) Not columnsToKeep.Contains(column.ColumnName)).ToArray()
For i As Integer = 0 To columnsToRemove.Length - 1
copiedTable.Columns.Remove(columnsToRemove(i))
Next
Return copiedTable
End Function
What this updated code does is:
Copy the DataTable with its data
Loop over the copied DataTable and remove the columns that are not in the columnsToKeep
Fiddle: https://dotnetfiddle.net/NEIm2t

Excel filter and regex match numbers to another worksheet

I'm a bit of an Excel noob so bear with me here. I have the following abridged sheet:
Sheet1
H AP AO
1 Transaction Description Employee Name Type
2 ER 12345678 blank blank
3 ER 13182984 blank blank
4 ER 18213289 blank blank
5 ER 13829429 blank blank
6 ER 89234024 blank blank
And another sheet in the same file to reference the names against:
Sheet2
E I
1 Expense Report Number Employee Name
2 12345678 Chris Rock
3 13182984 Hank Hill
4 18213289 Tom Sawyer
5 13829429 Elon Musk
6 89234024 Tupac Shakur
And I was wondering how to efficiently fill in the first excel sheet's Employee Name and Type columns from the matching report number of the second sheet as such:
Sheet1
H AP AO
1 Transaction Description Employee Name Type
2 ER 12345678 Chris Rock A
3 ER 13182984 Hank Hill A
4 ER 18213289 Tom Sawyer A
5 ER 13829429 Elon Musk A
6 ER 89234024 Tupac Shakur A
My attempt so far:
Set RE = CreateObject("vbscript.regexp")
RE.pattern = "(\d{8})"
Set allMatches = RE.Execute(ActiveSheet.Region ("H:H") #extract the 8 numbers
#somehow extract the 8 numbers to reference against the second sheet
With .Columns(AP)
.Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible).Formula = "=IF(ISERROR(VLOOKUP(reference number,EEM BI + mapping!I:I,2,0)),""Check employee ID"",VLOOKUP(reference number,EEM BI + mapping!I:I,2,0))"
As you can see I'm pretty lost in this code.. Any help is much appreciated
try this test code
Sub Test()
Dim ddd As Variant ' convert sheet1.columnH into an array
ddd = Sheets("Sheet1").Range("h2:h6").Value ' 2D array 1 x N
ddd = Application.Transpose(ddd) ' 2D array N x 1
ddd = Application.Transpose(ddd) ' this changes to 1D array
Dim i As Integer
For i = 0 To UBound(ddd) ' remove the "ER" from each member of the array
ddd(i) = Split(ddd(i))(1)
Next i
Dim findMe As String
Dim rng As Range
For Each rng In Sheets("Sheet2").Range("e2:e6")
findMe = rng.Value
For i = 1 To UBound(ddd)
If StrComp(findMe, ddd(i), vbTextCompare) = 0 Then
Sheets("Sheet1").Range("ap1").Offset(i) = rng.Offset(0, 4).Value
Sheets("Sheet1").Range("ao1").Offset(i) = "A"
End If
Next i
Next rng
End Sub
In your table in Sheet1 if the 8 digit number always starts at position 4, as you show, you can use the MID function seen in the formula below. If not, we would merely have to change MID to something a bit more complex, depending on the real data. No need for REGEX unless the text analysis is complex.
Given the order of Employee Name and Expense Report Number in your lookup table, INDEX(MATCH(... would be one solution.
Although you could use LOOKUP, it may be more efficient to use INDEX(MATCH.... LOOKUP, among other things, to work properly, requires that your lookup table be sorted. That is not necessary with INDEX/MATCH.
Something like
=INDEX(EmployeeName,MATCH(--MID(H2,4,8),ExpenseReportNumber,0))
For efficiency, the references to the two columns (EmployeeName and ExpenseReportNumber) should be as short as possible. Whole column references (eg: $E:$E and $I:$I will work, but will take longer to execute.

Copy rows on criteria VBA

I need some ideas how to copy lines which matches criteria.
I have such data:
A B C D
1 Country Brand Model Year
2 France Peugeot, Citroen (France) Car 2003
3 Germany VW, Opel, BMW (Germany) Car 2003
...
The goal is that each car brand with it's parameters would be in separate lines/
What I'd like to do is to copy line #2 to the bottom of the table and copy line #3 2 times to the bottom of the table. Also create column F which shows the brand.
Result should look like this:
A B C D F
1 Country Brand Model Year
2 France Peugeot, Citroen (France) Car 2003 Peugeot
3 France Peugeot, Citroen (France) Car 2003 Citroen
4 Germany VW, Opel, BMW (Germany) Car 2003 VW
...
Also, I have Brand list in separate sheet.
Should I try search for a , or try to match those brands from list in separate sheet?
I am new with vba so any ideas, suggestions will be appreciated!
If column B always includes the country from column A in parentheses, consider removing it from the column before performing a split. Something like:
Dim originalWs as Excel.Worksheet, newWs as Excel.Worksheet
Dim countryName as String, allBrands as String, brandArray() as String
Dim rowNumber as long, nextRow as long, i as long
rowNumber = 2 : nextRow = 2
...
countryName = originalWs.Cells(rowNumber,1)
allBrands = originalWs.Cells(rowNumber,2)
brandArray = Split(Replace(allBrands,"(" & countryName & ")",""),",")
...
For i = lbound(brandArray) to ubound(brandArray)
nextRow = nextRow+1
With newWs
.Cells(nextRow,1) = countryName
.Cells(nextRow,2) = allBrands
.Cells(nextRow,3) = originalWs.Cells(rowNumber,3)
.Cells(nextRow,4) = originalWs.Cells(rowNumber,4)
.Cells(nextRow,5) = brandArray(i)
End With
Next i
...
The key is that you have to maintain separate counters for:
The row in the original worksheet
The row being inserted into the new worksheet
The brand in the array
Good luck!

SQL calculation within VB.Net not working

This is a pre-assignment for a class I'm in. Supposed to be pretty simple and act as a warm-up, but I can't get it working. Basically the code is bringing in a test database and performing a calculation. In this case I'm trying to find the highest average batting average in a set of baseball players.
So my end result should be the name of the player with the highest batting average, or a few players if they are tied for the highest average.
Here is the code:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim dt As DataTable = New DataTable()
Dim connStr As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Baseball.accdb"
Dim sqlStr As String = "SELECT * FROM Players"
Dim dataAdapter As OleDb.OleDbDataAdapter = New OleDb.OleDbDataAdapter(sqlStr, connStr)
dataAdapter.Fill(dt)
dataAdapter.Dispose()
Dim average, pastAverage, highestAverage As Double
For i As Integer = 0 To dt.Rows.Count - 1
average = CDbl(dt.Rows(i)("hits/atBats"))
If average > pastAverage Then
highestAverage = average
End If
pastAverage = average
Next
For i As Integer = 0 To dt.Rows.Count - 1
If dt.Rows(i)("hits/atBats") = highestAverage Then
lstBoxHighest.Items.Add(dt.Rows(i)("name"))
End If
Next
End Sub
End Class
The debugger won't go past the "average = Cdbl(dt.Rows(i)("hits/atBats"))" line in the first For Loop. Can I not do calculations like that in the loop? I am sure the column titles (hits and atBats are correct)
The database looks like this in case you were wondering:
name Team atBats hits
Derek Jeter New York Yankees 511 158
Joe Mauer Minnesota Twins 545 174
etc...
Thanks!
you are missing some object refs:
average = CDbl(dt.Rows(i).item("hits") / dt.Rows(i).item("atBats"))
and like that for the rest of them. VB needs an dt.Rows(i) reference because those are 2 different columns. With "hits/atBats", it cant know those are individual columns.
Shorthand, but kind of masks that they are different cols/items is like you had it:
average = CDbl(dt.Rows(i)("hits") / dt.Rows(i)("atBats"))
Disclaimer: it's not a direct answer to the question.
Instead of pulling all the data to the client and then using two loops to find a name you can do all calculations on db-side and grab only needed rows and columns (in your case just name) with a query that may look like
SELECT name
FROM Players
WHERE atBats / hits =
(
SELECT MAX(atBats / hits)
FROM Players
)
Output:
| NAME |
|-------------|
| Derek Jeter |

SELECT Unique rows from Datagridview using LINQ

I am trying to SELECT ALL rows\columns from a datagridview where the first column is unique using LINQ.
Datagridview:
1 Blue 1111
1 Blue 1111
2 Green 1234
3 Orange 3211
2 Green 1234
4 Red 2222
Trying to get this Output:
1 Blue 1111
2 Green 1234
3 Orange 3211
4 Red 2222
I was able to use the following code which pulls all unique records from the first column but I am not sure how to get the remaining columns:
Dim unique() As String = (From row As DataGridViewRow In dgvMaestro.Rows.Cast(Of DataGridViewRow)() _
Where Not row.IsNewRow _
Select CStr(row.Cells(0).Value)).Distinct.ToArray
For Each a As String In unique
Debug.Print(a)
Next
Output:
1
2
3
4
Thanks
First you need import 2 namespaces for distinct with linq to use AsEnumerable() method in DataTables and Field() method in DataRows
Imports System.Data.DataTableExtensions
Imports System.Data.DataRowExtensions
Declare new datatable
Dim NewTbl As New System.Data.DataTable
Add columns in you scenario
NewTbl.Columns.Add("ID", GetType(Integer))
NewTbl.Columns.Add("Color", GetType(String))
NewTbl.Columns.Add("Value", GetType(Integer))
Linq use Table 'TblValues' as you datasource
Dim results = (From row in TblValues.AsEnumerable() select col1 = row(Of Integer)("ID"), col2 = row(Of String)("Color"), col3 = row(Of Integer)("Value")).Distinct().ToList()
Iterate elements in results with for each, the reason is because results is an object datarow collection and isn't convert auto to table
For each r in results
Dim Row as System.Data.DataRow = NewTbl.NewRow
Row("ID") = r.col1
Row("Color") = r.col2
Row("Value") = r.col3
NewTbl.Rows.Add(Row)
next
Now you have a DataTable 'NewTbl' with distinct values inside
The best way to solve this is to write a comparer to compare the entire row.
Dim noduplicates = dgvMaestro.Rows.Cast(Of DataGridViewRow).Distinct(New RowComparer())
There are some examples of comparers on msdn