Split datatable into n datatables - vb.net

I need to dynamically split a datatable into multiple datatables and the number of subsequent datatables will vary. The end user will enter a value and this will determine the number of datatables that will be derived from the original. For example: the user enters 10, the original dt has 20 rows. There will be 10 dt's with 2 rows each created. However, if the original dt has 11 rows, there would be 9 dt's with 1 row and 1 dt with 2 rows created. How can I accomplish this in vb.net without hardcoding a bunch if rules? I have read through and tried the post below but it still does not get me there.
Split a collection into `n` parts with LINQ?

You can use LINQ's GroupBy:
Dim tbl1 = New DataTable
tbl1.Columns.Add("ID", GetType(Int32))
tbl1.Columns.Add("Text", GetType(String))
For rowIndex As Int32 = 1 To 11
tbl1.Rows.Add(rowIndex, "Row " & rowIndex)
Next
Dim tableCount = 10 ' what the user entered '
Dim divisor = tbl1.Rows.Count / tableCount ' needed to identify each group '
Dim tables = tbl1.AsEnumerable().
Select(Function(r, i) New With {.Row = r, .Index = i}).
GroupBy(Function(x) Math.Floor(x.Index / divisor)).
Select(Function(g) g.Select(Function(x) x.Row).CopyToDataTable())

Related

How can I build sums of values from DataTables in vb.net?

I'm using VB.Net to retrieve data from a database, do some calculations, and then show this data in a datagridview. I already imported the data from the database into a DataTable in my software. So far so good.
Now I want to add up values from columns where another column in the same row has a specific text.
Here's an example. I have data (in the DataTable) in the following form:
Date // type // value
10/09/2020 // value80 // 92
10/09/2020 // value71 // 5
10/09/2020 // value63 // 2
10/07/2020 // value80 // 85
10/07/2020 // value71 // 10
10/07/2020 // value63 // 1
I want to build the sum of the 3 type values, where "value80", "value71" and "value63" are from the same date. The new variable value_sum should be calculated such that for 10/09/2020:
value_sum = value80 + value71 + value63 = 92 + 5 + 2 = 99
and for 10/7/2020:
value_sum = value80 + value71 + value63 = 85+ 10 + 1 = 96
How can I calculate the new values in order to save them in another DataTable?
In the Linq query we Group By the date field and select the date and the sum. See https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/queries/group-by-clause There is a .CopyToDataTable method (https://learn.microsoft.com/en-us/dotnet/api/system.data.datatableextensions.copytodatatable?view=netcore-3.1) but I don't know if or how it works with anonymous types. It is easy enough to just loop through the query result and add the rows to the new DataTable.
Private Sub OPCode()
'Create original DataTable with your sample data
Dim dt As New DataTable
dt.Columns.Add("ValueDate", GetType(Date))
dt.Columns.Add("Type", GetType(String))
dt.Columns.Add("Value", GetType(Integer))
With dt.Rows
.Add(#10/09/2020#, "value80", 92)
.Add(#10/08/2020#, "value71", 5)
.Add(#10/09/2020#, "value85", 2)
.Add(#10/07/2020#, "value80", 85)
.Add(#10/07/2020#, "value71", 10)
.Add(#10/07/2020#, "value63", 1)
End With
'Select the date groups and sum by group
Dim TotalsByDate = From row In dt.AsEnumerable
Group By DateValue = row.Field(Of Date)("ValueDate")
Into Dates = Group, Sum(row.Field(Of Integer)("Value"))
Select DateValue, Sum
'Prepare a new DataTable
Dim dt2 As New DataTable
dt2.Columns.Add("DateGroup", GetType(Date))
dt2.Columns.Add("DateSum", GetType(Integer))
'Fill the new DataTable
For Each item In TotalsByDate
dt2.Rows.Add(item.DateValue, item.Sum)
Next
'Display results
DataGridView1.DataSource = dt2
End Sub

is that possible to calculate the value between 2 or more textbox that are grouped?

I have a table that consists of 8 columns (A, B, C, etc). Column D and E are grouped by each month. Is it possible to calculate D and E value and return the value in column G?
Here is needed more information about your scenario, as yes it’s possible, even before you make this DataTable. However in an unknown scenario, you can do that even after, as code below shows.
(And if I well understand what you want to do):
Dim dataTable As New DataTable
dataTable.Columns.Add("A")
dataTable.Columns.Add("B")
dataTable.Columns.Add("C")
dataTable.Columns.Add("D")
dataTable.Columns.Add("Result_OF_B_C")
For i As Integer = 0 To 10
dataTable.Rows.Add(dataTable.NewRow)
dataTable.Rows(dataTable.Rows.Count - 1).ItemArray =
{"ColumnA" + i.ToString, i, i + i / 2, "ColumnD" + i.ToString}
Next
Dim newDataTable As DataTable = (From R In dataTable.Rows).Select(Function(R)
Dim Cr As DataRow = CType(R, DataRow)
Cr.Item("Result_OF_B_C") = CDbl(Cr.Item("B")) + CDbl(Cr.Item("C"))
Return Cr
End Function).CopyToDataTable
DataGridView1.DataSource = newDataTable

Looping through only columns containing values for specific row in dataTable

I am developing an application using Visual Basic 2010 for hydraulic calculations of a pipe network.
This application uses a lot of iterations and loops, depending on the user input and size of network. Most of the results have to be saved temporarily to be used for the next step of calculations.
Firstly, I used a DataGridView to save the results but as the number of iterations increased, the application became very slow.
Now I am trying to create a DataTable, then populate it with some initial results (this part was successful). The obtained DataTable has some columns that are not populated like so:
22 24 10
3 16 22 9 15
16 12 24 13
14 21 10 23 12 1
24 18 23 2 1
Other calculations are performed and a certain value (X) is obtained.
Now I am trying to loop through the columns of a specific row to check if the calculated value (X) equals to one of the values in those columns.
My question is: How can I loop through only the columns that have values (avoiding the columns containing NULL values) for a specific row?
I am a beginner in VB.net. I hope my question is clear as I didn't provide any code.
Thanks in advance for you help.
This is the initial code I used:
Results.DGVInitial.Rows.Clear()
Results.DGVFinal.Rows.Clear()
For m As Integer = 0 To NetworkLayout.DGVNetworkLayout.Rows.Count - 1
Results.DGVInitial.Rows.Add()
Next
Dim I As Integer = NetworkLayout.DGVNetworkLayout.Rows.Count - 1
Dim Sec(I), Ini(I) As Integer
Dim Hyd(I), Dia(I), Len(I) As Single
Dim Qsec(I), Qini(I), Vsec(I) As Single
Dim U(I), Y(I) As Single
Do
I = I - 1
Sec(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(0).Value
Ini(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(1).Value
Hyd(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(6).Value
Dia(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(4).Value
Len(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(3).Value
Dim V As Integer
V = Results.DGVRandomGen.Rows(TotalNum_Runs - 1).Cells(I).Value
Qsec(I) = 0
Dim q As Single = 0
For n As Integer = 0 To Results.DGVInitial.Rows.Count - 1
If Results.DGVInitial.Rows(n).Cells(1).Value = Sec(I) Then
q = Results.DGVInitial.Rows(n).Cells(0).Value
Qsec(I) = Qsec(I) + q
Else
Qsec(I) = Qsec(I)
End If
Next
If V = 1 Then ' if the hydrant is open
Qini(I) = Hyd(I) + Qsec(I)
Else ' if the hydrant is close
Qini(I) = Qsec(I)
End If
Results.DGVInitial.Rows(I).Cells(0).Value = Qini(I)
Results.DGVInitial.Rows(I).Cells(1).Value = Ini(I)
Results.DGVSectionDischarges.Rows(TotalNum_Runs - 1).Cells(I).Value = ini(I).ToString("F2")
Now instead of using
V = Results.DGVRandomGen.Rows(TotalNum_Runs - 1).Cells(I).Value
I would like to replace the "DGVRandomGen" with a DataTable called "DT_Random"
Like I said I am a beginner so I am not sure how to code it but it will be something like this:
For DT_Random.Rows (TotalNum_Runs - 1)
For Each col As DataColumn In DT_Random.Columns
If DT_Random.Rows(TotalNum_Runs - 1).Item(col) = I Then
Qini(I) = Hyd(I) + Qsec(I)
Else
Qini(I) = Qsec(I)
End If
Next
But I want to avoid Null values as not all columns are populated
Thanks
Maybe this will help you:
Dim myXvalue = 24
Dim myDataTable As New DataTable
myDataTable.Columns.Add("Col1")
myDataTable.Columns.Add("Col2")
myDataTable.Columns.Add("Col3")
myDataTable.Columns.Add("Col4")
myDataTable.Rows.Add(22, 24, 10, DBNull.Value)
myDataTable.Rows.Add(3, 16, 22, DBNull.Value)
myDataTable.Rows.Add(24, 18, DBNull.Value, 24)
For Each column As DataColumn In myDataTable.Columns
If IsDBNull(myDataTable.Rows(0).Item(column)) Then
MsgBox("DB Null Found At: " & column.ColumnName)
Continue For
End If
If myDataTable.Rows(0).Item(column) = myXvalue Then
MsgBox("Match: " & myDataTable.Rows(0).Item(column) & " found at " & column.ColumnName)
End If
Next column
Just a quick example, you may need to restructure it a bit, but at least it shows you how to access the values in your datatable by columns. I would do a function that passes a row index as a parameter and returns a boolean. Create two booleans inside the sub, one for dbnull existing in the row, and one for finding a matching value. If dbnull bool is false, and match value is true, then return true. Just make sure you loop all the columns and dont exit early.
If you need me to elaborate let me know.

Adding two column values to listbox in vb.net

I have a table named users which has the following columns in it
User_id,user_name,user_pwd,First_Name,Middle_Name,Last_Name and user_type.
I have dataset named dst and created a table called user in the dataset. Now I want to populate listbox with user_Name, First_Name, Last_name of each and every row in the table user.
I am able to add one column value at a time but not getting how to add multiple column values of each row to listbox
Dim dt As DataTable = Dst.Tables("user")
For Each row As DataRow In dt.Rows
lstUsers.Items.Add(row("User_Name"))
Next
Above code works perfectly but I also want to add First_name as well as last_name to the list box at the same time.
Use same approach as you have, but put all values you want in one string.
Dim dt As DataTable = Dst.Tables("user")
For Each row As DataRow In dt.Rows
Dim sItemTemp as String
sItemTemp = String.Format("{0},{1},{2}", row("User_Name"), row("First_Name"), row("Last_Name"))
lstUsers.Items.Add(sItemTemp)
Next
String.Format() function will call .ToString() on all parameters.
In this case if row(ColumnName) is NULL value then .ToString() return just empty string
You have 2 choices:
Using the ListBox:
To use the ListBox, set the font to one that is fixed width like courier new (so that the columns line up), and add the items like this:
For Each row As DataRow In dt.Rows
lstUsers.Items.Add(RPAD(row("User_Name"),16) & RPAD(row("First_Name"),16) & RPAD(row("Last_Name"),16))
Next
The RPAD function is defined like this:
Function RPAD(a As Object, LENGTH As Object) As String
Dim X As Object
X = Len(a)
If (X >= LENGTH) Then
RPAD = a : Exit Function
End If
RPAD = a & Space(LENGTH - X)
End Function
Adjust the LENGTH argument as desired in your case. Add one more for at least one space. This solution is less than ideal because you have to hard-code the column widths.
Use a DataGridView control instead of a ListBox. This is really the best option, and if you need, you can even have it behave like a ListBox by setting the option to select the full row and setting CellBorderStyle to SingleHorizontal. Define the columns in the designer, but no need to set the widths - the columns can auto-size, and I set that option in the code below. if you still prefer to set the widths, comment out the AutoSizeColumnsMode line.
The code to set up the grid and add the rows goes like this:
g.Rows.Clear() ' some of the below options are also cleared, so we set them again
g.AutoSizeColumnsMode = DataGridViewAutoSizeColumnMode.AllCells
g.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal
g.SelectionMode = DataGridViewSelectionMode.FullRowSelect
g.AllowUserToAddRows = False
g.AllowUserToDeleteRows = False
g.AllowUserToOrderColumns = True
For Each row As DataRow In dt.Rows
g.Rows.Add(row("User_Name"), row("First_Name"), row("Last_Name"))
Next
You might solved your problem by now but other users like me might have issue with it.
Above answers given worked for me even but I found a same answer in a simple way according to what I want..
cmd = New SqlCommand("select User_Name, First_Name, Last_Name from User")
Dim dr As SqlDataReader = cmd.ExecuteReader(YourConnectionString)
If dr.HasRows Then
Do While dr.Read
lst.Items.Add(dr.Item(0).ToString & " " & dr.Item(1).ToString & " " & dr.Item(2).ToString)
Loop
End If
This worked for me, maybe wrong way but I found it simple :)
May I suggest you use a ListView control instead of Listbox?
If you make the switch, here's a sample subroutine you could use to fill it up with the data you said you want. Adapt it the way you like; there's much room for improvement but you get the general idea:
Public Sub FillUserListView(lstUsers As ListView, Dst As DataSet)
Dim columnsWanted As List(Of String) = New List(Of String)({"User_Name", "First_Name", "Last_Name"})
Dim dt As DataTable = Dst.Tables("user")
Dim columns As Integer = 0
Dim totalColumns = 0
Dim rows As Integer = dt.Rows.Count
'Set the column titles
For Each column As DataColumn In dt.Columns
If columnsWanted.Contains(column.ColumnName) Then
lstUsers.Columns.Add(column.ColumnName)
columns = columns + 1
End If
totalColumns = totalColumns + 1
Next
Dim rowObjects(columns - 1) As ListViewItem
Dim actualColumn As Integer = 0
'Load up the rows of actual data into the ListView
For row = 0 To rows - 1
For column = 0 To totalColumns - 1
If columnsWanted.Contains(dt.Columns(column).ColumnName) Then
If actualColumn = 0 Then
rowObjects(row) = New ListViewItem()
rowObjects(row).SubItems(actualColumn).Text = dt.Rows(row).Item(actualColumn)
Else
rowObjects(row).SubItems.Add(dt.Rows(row).Item(actualColumn))
End If
lstUsers.Columns.Item(actualColumn).Width = -2 'Set auto-width
actualColumn = actualColumn + 1
End If
Next
lstUsers.Items.Add(rowObjects(row))
Next
lstUsers.View = View.Details 'Causes each item to appear on a separate line arranged in columns
End Sub

Convert the following VB code to a LINQ statement?

I only want to return a certain number of rows into the DataTable via LINQ's Take or use it on the Rows property, but I am not sure how or where to do it: Here is the current code:
Dim dt As DataTable = GetDataTable("sp", params)
For Each dr As DataRow In dt.Rows
Dim o As New OR()
o.P = p
o.Id = dr ("A")
o.R = dr ("B")
Next
Would it be something like:
Dim dt As DataTable = GetDataTable("sp", params).AsEnumerable().Take(10)
When I run the above, I get the error:
The 'TakeIterator' start tag on line 4 position 60 does not match the end tag of 'Error'. Line 4, position 137.
Unable to cast object of type '<TakeIterator>d__3a1[System.Data.DataRow]' to type 'System.Data.DataTable'.`
If you need a DataTable:
Dim dt As DataTable = (From row in GetDataTable("sp", params) Take 10).CopyToDataTable()
If you need a List(Of [Or]) (you need the brackets since Or is a keyword)
Dim list As List(Of [Or]) = (From row In GetDataTable("sp", params)
Select New [Or]() {
o.Id = row.Field(Of Int32)("Id"),
o.R = row.Field(Of String)("R")
}).Take(10).ToList()
Edit:
is there a way I can also include a Where with the Take, for example, I have 3 statuses in my table, Open, Semi-Open, Closed, I only want to Take 10 for Open, but I want to leave Semi-Open and Closed alone. As an example Semi-Open and Closed will have 5 records combined and I will take 10 from Open, so I would get a total of 15 rows
Yes, there's a way:
Dim dt = GetDataTable("sp", params)
Dim open10 = From row In dt
Where row.Field(Of String)("Status") = "Open"
Take 10
Dim rest = From row In dt
Where row.Field(Of String)("Status") <> "Open"
Dim tblResult = open10.Concat(rest).CopyToDataTable()