Loop, aggregate and return unique DataRow sum - vb.net

I have a DataGrid which contains multiple transactions of Customer's buy ins.
What I want is to add up the transaction buy ins from each customer and add the result on the related images. What I have at the moment gives me logic errors i.e. adding the same amounts on more than one images.
Here is the structure of my DataGrid (column titles): [Name,Surname, Buyin, Type, StartTime, TransactionID, CustomerID]
and here is my code:
Dim tbActivePlayers As DataTable = Me.ActivePlayersTableAdapter.GetData()
Dim tbTemp As New DataTable
' table = DataSet.Tables("Orders")
' Declare an object variable.
Dim objTotalBuyin As Object
Dim iCount, ilbl As Integer
ilbl = 1
Dim viewUniquePlayers As New DataView(tbActivePlayers)
Dim iActivePlayers As Integer = viewUniquePlayers.ToTable(True, "CustomerID").Rows.Count
Dim dtDataTable As DataTable = viewUniquePlayers.ToTable(True, "CustomerID")
Dim myLabel As Label
For iCount = 0 To dtDataTable.Rows.Count
objTotalBuyin = tbActivePlayers.Compute("Sum(Buyin)", "CustomerID = " & tbActivePlayers.Rows(iCount).Item("CustomerID"))
'MsgBox("Name: " & tbActivePlayers.Rows(iCount + 1).Item("Name") & ", Sumbuyin:" & sumObject.ToString & " ResultCount:" & ResultCount)
'
myLabel = CType(Me.Controls.Find("lblPlayer" & ilbl, True)(0), Label)
If Not myLabel Is Nothing Then
myLabel.Text = "Empty Seat"
End If
'
myLabel.Text = tbActivePlayers.Rows(iCount).Item("Name") & Environment.NewLine & _
"€" & objTotalBuyin.ToString
myLabel.Image = Global.PokerBusiness.My.Resources.Resources.seatocc
ilbl += 1
Next

Your filter function for the Compute seems to be based off each row in tbActivePlayers, yet you're looping over each row (and thus counting the rows of) a view of unique players. My brain compiler tells me this might well be the source of your logic errors, because you're potentially computing your sum with a filter that's using a single, unique customer ID more than once. Consider changing
objTotalBuyin = tbActivePlayers.Compute("Sum(Buyin)", "CustomerID = " & tbActivePlayers.Rows(iCount).Item("CustomerID"))
To
objTotalBuyin = tbActivePlayers.Compute("Sum(Buyin)", "CustomerID = " & dtDataTable.Rows(iCount).Item("CustomerID"))

Related

Loopings in VB.net with two text boxes

I have two text boxes in a user-input form asking for a VisitID(s) and to set a price.
It then uses the following code to update each VisitID with a single price.
myCmd1.CommandText = "UPDATE tblVisit SET Price = (" & CustomerRefString & ") WHERE VisitID IN (" & VisitIDString & ")"
I understand that the above code loops for each VisitID in the VisitIDString, but how do I loop to allow updating each visitID with the corresponding line in the Price TextBox?
Private Sub btnOk_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim VisitIDString AS String
Dim VisitIDsUpdated As Object
Dim myComma() As Char = {","}
Dim connString As String
Dim DataSourceBase = Report.GetDataSource("Connection")
Dim ConnectionCollection = Report.Dictionary.Connections
connString = ConnectionCollection(0).ConnectionString
VisitIDString = VisitIDTextBox.Text
VisitIDString = VisitIDString.Replace(vbCr, "").Replace(vbLf, ",")
VisitIDString = VisitIDString.Replace(" ", "")
VisitIDString = VisitIDString.TrimEnd(MyComma)
Report.SetParameterValue("VisitIDByte",VisitIDString)
'------------------------------------------------------------------------------------------------------------
Dim CustomerRefString AS String
Dim CusterRefUpdated As Object
Dim myCommaRef() As Char = {","}
'------------------------------------------------------------------------------------------------------------
CustomerRefString = CustomerRefTextBox.Text
CustomerRefString = CustomerRefString.Replace(vbCr, "").Replace(vbLf, ",")
CustomerRefString = CustomerRefString.Replace(" ", "")
CustomerRefString = CustomerRefString.TrimEnd(MyComma)
'------------------------------------------------------------------------------------------------------------
myConn = New SqlConnection(connString) 'after changes
'Create a Command object
myCmd1 = myConn.CreateCommand
myCmd1.CommandText = "UPDATE tblVisit SET Price = (" & CustomerRefString & ") WHERE VisitID IN (" & VisitIDString & ")"
'Open the connection.
myConn.Open()
'Execute the statement and return a single value. If you wanted to return more than 1 value you need to add a loop here.
VisitIDsUpdated = myCmd1.ExecuteScalar()
'Close the database connection.
myConn.Close()
End Sub
There are better ways to create your user interface, but if you have to do it this way (with textboxes), then you can solve the problem easily as long as the user enters the data correctly.
Assuming you have "id" and "price" data that looks something like this:
---------
|ID|PRICE|
---------
|11|1.11 |
|22|2.22 |
|33|3.33 |
---------
The user would paste these values into the VisitIDTextBox textbox:
11
22
33
And the user would paste these values into the Price textbox:
1.11
2.22
3.33
Then your solution relies on the fact that the index number of the item in the VisitIDTextBox textbox would match the index number of the item in the Price textbox.
So you would convert the contents of both textboxes into an array. Below is a simple example of you can do that for the VisitIDTextBox textbox.
'Any character in this array will be used to split the string
Dim sAAA As String = ",|" & vbCrLf
Dim arSplitOnValues() As Char = sAAA.ToCharArray()
'Create an array of visit ID's
Dim arVisitIDs() As String = VisitIDTextBox.Text.Split(arSplitOnValues, System.StringSplitOptions.RemoveEmptyEntries)
After doing the same for the values in the Price textbox, you would now have two arrays; arVisitIDs and arPrices
The index of each item in the arrays would now match up, so you can select from arVisitIDs and arPrices using the same index and get the price that matches the ID.
You can see this by using a simple loop like this:
For iLpr As Integer = 0 To arVisitIDs.GetUpperBound(0)
MessageBox.Show(Me, arVisitIDs(iLpr) & "~" & arPrices(iLpr), "Test", MessageBoxButtons.OK, MessageBoxIcon.Information)
Next iLpr
When run, you'll get three messageboxes with values like this:
11~1.11
22~2.22
33~3.33

Update datatable and show new value

I am trying to update datatable and print out updated row but I am getting an old value. Here is my code:
Public Shared zadnjiBrojevi As DataTable
...
aaaa.ItemsSource = Globals.zadnjiBrojevi
Dim lastnr As Integer
Dim result() As DataRow = Globals.zadnjiBrojevi.Select("tip = " & tipoviDokumenataCbox.SelectedItem.tag & "")
For Each row As DataRow In result
lastnr = row(1)
Next
Console.WriteLine("Last number is: " & lastnr)
Dim myRow() As Data.DataRow
myRow = Globals.zadnjiBrojevi.Select("tip = '" & tipoviDokumenataCbox.SelectedItem.tag & "'")
myRow(0)("lastnr") = myRow(0)("lastnr") + 1
Dim filename As String = Globals.rootPath & "ZadnjiBrojevi.xml"
Globals.zadnjiBrojevi.WriteXml(filename, XmlWriteMode.WriteSchema)
Console.WriteLine("Dodan upis!")
In console i am always getting "Last number is: 4443". But GridControl "aaaa" shows updated values. And "writexml" writes updated values.

Bus Driver Analysis Application: speeding up queries, for loops, and proper syntax

I'm working on a project, at work, called the "Bus Driver." The bus driver is a material handler that goes to 9 different assembly lines, on the production floor, and picks up finished goods (refurbished receivers) and drops them off at a converyer belt where they are then shrinked wrapped and sent to be palletized.
The application I'm working on tracks each time he makes one complete route and then stores his stats in an SQL DB.
The application that records his route stats works fine but I have another application called, "The Bus Driver Analyzer." This application gets all the Bus driver's data and produces 4 charts.
Chart 1: Route Efficiency (52 weeks are shown, in this chart, on the X-value)
Chart 2: On time delivery Rates (52 weeks are shown, in this chart, on the X-value)
Chart 3: Histogram Chart (there is a radio button for a YTD or Weekly option)
Chart 4: Stack Ranking chart (top 10)
The two charts below are the two charts i want to focus on for this question.
My problem with this application is that I have 52 labels, on the windows form, to hold his route efficiencies for each week (seen on your left.) They are called "LblWkEff1" and goes up to "LblWkEff52" and then I have another 52 labels, on the windows form to hold his On time delivery rates (Seen on your right.) They are called "lblDeliveryStat1" and go up to "lblDeliveryStat52."
The code I have to retrieve these results is horrendeous, from what i've been told, on this site, and I don't disagree. I'm still new to programming so what I have written is by no means clean and perfect.
here is my code:
Dim RESULT1 As Decimal 'declare this as global
Dim RESULT2 As Decimal 'declare this as global
Private Sub Week(ByVal week As Integer)
Dim queryString As String = "SELECT " & _
" (SELECT CAST(SUM(TARGET_SECONDS) AS DECIMAL)/ CAST(SUM(ROUTE_SECONDS) AS DECIMAL) FROM dbo.APE_BUSDRIVER_MAIN WITH(NOLOCK) WHERE WEEK_TIME = " & week & " AND APE_AREA_OBJID = " & lblAreaOBJID.Text & " AND EMPLOYEE_NAME = '" & cbEmployeeName.Text & "' AND YEAR_TIME = '" & cbYear.Text & "' AND ACTIVE = 1) AS RESULT1," & _
" (SELECT (SELECT CAST(COUNT(APE_BUSDRIVER_STATUS_OBJID) AS DECIMAL) FROM dbo.APE_BUSDRIVER_MAIN AS RESULT2 WHERE WEEK_TIME = " & week & " AND APE_AREA_OBJID = " & lblAreaOBJID.Text & " AND EMPLOYEE_NAME = '" & cbEmployeeName.Text & "' AND YEAR_TIME = '" & cbYear.Text & "' AND ACTIVE = 1 AND APE_BUSDRIVER_STATUS_OBJID = 1)/(SELECT CAST(COUNT(APE_BUSDRIVER_STATUS_OBJID) AS DECIMAL) FROM dbo.APE_BUSDRIVER_MAIN AS RESULT2 WHERE WEEK_TIME = " & week & " AND APE_AREA_OBJID = " & lblAreaOBJID.Text & " AND EMPLOYEE_NAME = '" & cbEmployeeName.Text & "' AND YEAR_TIME = '" & cbYear.Text & "' AND ACTIVE = 1)) AS RESULT2" & _
" FROM dbo.APE_BUSDRIVER_MAIN "
Using connection As New SqlConnection(SQLConnectionStr)
Dim command As New SqlCommand(queryString, connection)
connection.Open()
Dim reader As SqlDataReader = command.ExecuteReader()
' Call Read before accessing data.
If reader.HasRows Then
While reader.Read()
RESULT1 = reader("RESULT1")
RESULT2 = reader("RESULT2")
End While
Else
RESULT1 = 0
RESULT2 = 0
End If
' Call Close when done reading.
reader.Close()
End Using
End Sub Private Sub LoadWeeklyStats()
'LOOP AND QUERY
For i As Integer = 0 To 51
Week(i + 1)
Dim LabelWkEff As String = "LblWkEff" + (i + 1).ToString
Dim myArray1 As Array = Controls.Find(LabelWkEff, False)
Dim myControl1 As Label = myArray1(0)
myControl1.Text = RESULT1
'AND
Dim LabelDeliveryStat As String = "lblDeliveryStat" + (i + 1).ToString
Dim myArray2 As Array = Controls.Find(LabelDeliveryStat, False)
Dim myControl2 As Label = myArray2(0)
myControl2.Text = RESULT2
Next
End Sub
Private Sub LoadWeeklyStats() is what im using to hold the results and place them on the "LblWkEffXX" and "lblDeliveryStatXX" labels, on the windows form.
this process takes 5 seconds each time a new user is selected for reviewal and I know it has something to do with the SQL query and the for loop but I dont know how else to write the code to get the results I want more efficiently.
Any feedback on how to rewrite the code or what other option i can perform to achieve the same results in a much quicker time would be most appreciated.
please let me know if you need more information.
From a cursory look, I would suggest that there is nothing inherently wrong with what you are trying to do. I think the biggest problem is in the opening of 52 sql connections, which is a relatively expensive process.
Therefore I suggest that the simplest efficiency savings would be to either amend your query to get all the data back at once, or to simply pass the Connection instance into the Week() method.
Charting software, such as dundas and I'm sure even the built-in charting types, usually take in a multi-dimensional array of values: one value being the X axis value and one being the Y value axis. You should not need to set each label individually.
However, if you want to check where your efficiencies are lost in the current system, I'd comment out:
Dim myArray1 As Array = Controls.Find(LabelWkEff, False)
Dim myControl1 As Label = myArray1(0)
myControl1.Text = RESULT1
and
Dim myArray2 As Array = Controls.Find(LabelDeliveryStat, False)
Dim myControl2 As Label = myArray2(0)
myControl2.Text = RESULT2
This will tell you whether or not that 5 seconds is coming from the DB queries or if it's coming from the amount of Controls.Find calls you're making. You could just profile your code as well, but this is a pretty easy test. If it gets dramatically faster, then it's your Controls.Find and not your queries. If, however, it's still slow, then time to profile your query to see if a restructuring or an index is needed.
If it gets faster, your best bet is to create an array of controls on first load then use that array in subsequent fills. No need to find your controls each time -- they're not going to change!
Something like this workflow:
Create two new List(Of Label) member variables in your class -- one to store the LabelWkEff values and one to store the LabelDeliveryStat values.
On Form load, loop: For i As Integer = 0 To 51, doing the same code as before to find the controls.
Instead of setting their label values, add them to their new respective member variables
In the code you pasted, where before you were finding the control, use the i index to access the correct control from the new member variables, e.g. Dim myControl1 as Label = LabelWkEffs(i)
That should make things significantly faster, assuming your bottleneck is the Controls.Find.
Joseph, as many have mentioned, 52 round trips to database is not a good idea.
Here is what I tried to to:
Create a stored procedure that takes parameter and gets the 52 week statistics for a given driver at once.
Create procedure GetDriverStatsBetweenWeeks(
#area_objid int,
#emp_name varchar(255),
#year_time int,
#start_Week int = 1,
#end_Week int = 52
)
as
begin
declare #driver_stats table(weektime int, result1 decimal(38, 10), result2 decimal(38, 10))
declare #currentWeek int
set #currentWeek = #start_Week
while (#currentWeek <= #end_Week)
begin
insert into #driver_stats(weektime, result1, result2)
select
week_time
,(cast(sum(TARGET_SECONDS) AS DECIMAL)
/cast(sum(ROUTE_SECONDS) AS DECIMAL)) as Result1
,(cast(sum((case when APE_BUSDRIVER_STATUS_OBJID = 1 then 1 else 0 end)) as decimal)
/cast(sum((case when APE_BUSDRIVER_STATUS_OBJID <> 1 then 1 else 0 end)))) as Result2
FROM
dbo.APE_BUSDRIVER_MAIN a
WHERE
WEEK_TIME = #currentWeek AND APE_AREA_OBJID = #area_objid
AND EMPLOYEE_NAME = #emp_name and YEAR_TIME = #year_time AND ACTIVE = 1
group by week_time
set #currentWeek = #currentWeek + 1
end
select weektime, result1, result2 from #driver_stats
end
go
Create a data layer (dummy one) that connects to the sql server executes the above proc gets the results into a model
Public Class ClassDriverStatistics
Property Week As Integer = 0
Property Result1 As Decimal = 0.0 ' rename this property to something like RouteEffeciency
Property Result2 As Decimal = 0.0 ' rename this property to the appropriate name
Public Sub New()
End Sub
End Class
Public Class MyDataLayer
Public Shared Function GetWeeklyDriverStats(StartWeek Integer, _
EndWeek Integer, YearTime Integer, _
AreaObjId Integer, EmployeeName String) _
as List(Of ClassDriverStatistics)
Dim oDriverWeeklyStatsList As New List(Of ClassDriverStatistics)
Using connection As New SqlConnection(SQLConnectionStr)
Dim command As SqlCommand = New SqlCommand("GetDriverStatsBetweenWeeks", connection)
connection.CommandType = CommandType.StoredProcedure
connection.Open()
Dim reader As SqlDataReader = command.ExecuteReader()
Do While objReader.Read()
Dim rec As ClassDriverStatistics = New ClassDriverStatistics()
rec.Week = Convert.ToInt32(theObjReader("weektime"))
rec.Result1 = Convert.ToDecimal(theObjReader("result1"))
rec.Result2 = Convert.ToDecimal(theObjReader("result2"))
oDriverWeeklyStatsList.Add(rec)
Loop
objReader.Close()
End Using
Return oDriverWeeklyStatsList
End Function
End Class
Obviously, not much changes to the LoadWeeklyStats() except to replace the loop of queries with a loop through the list of data model objects
Private Sub LoadWeeklyStats()
Dim weeklyStats As List(Of ClassDriverStatistics) = _
MyDataLayer.GetWeeklyDriverStats(1, 52, cbYear.Text, _
lblAreaOBJID.Text, cbEmployeeName.Text)
For Each weekStat As String In weeklyStats
Dim LabelWkEff As String = "LblWkEff" + weekStat.Week.ToString
Dim myArray1 As Array = Controls.Find(LabelWkEff, False)
Dim myControl1 As Label = myArray1(0)
myControl1.Text = weekStat.Result1
Dim LabelDeliveryStat As String = "lblDeliveryStat" + weekStat.Week.ToString
Dim myArray2 As Array = Controls.Find(LabelDeliveryStat, False)
Dim myControl2 As Label = myArray2(0)
myControl2.Text = weekStat.Result2
Next
End Sub
Please bear in mind
This code is not tested because I couldn't
Some how, I couldn't get this code in stack overflow to be properly formatted
I'm good at C# but not at VB.NET, this is some thing I tried to provide an answer because this question is already posted earlier as performance-speeding-up-application
Please see if this improves any performance. Also, apply the other aspects that others here at SO gave above.

Filter Data from dataset that is passed to textbox

I am iterating through columns in a datagridview in vb net and passing the
values to a textbox. I need to be able to filter out the emails which are in Cell(4), so that there are no duplicate emails for any single customer.
I have no idea of how to do this using a dataset.
EmailTableAdapter.Fill(Me.EmailDataset.Email)
Dim r As String = String.Empty
For i As Integer = 0 To Me.EmailDataGridView.RowCount - 1
r = r & EmailDataGridView.Rows(i).Cells(7).Value.ToString & " - " & EmailDataGridView.Rows(i).Cells(4).Value.ToString & vbNewLine
Next
TextBox2.Text = (r)
One way to filter out rows with duplicate values in Cells(4) would be to iterate through the grid rows, stuffing items into a Dictionary using Cells(4) values as the Key, and then iterate through the Dictionary to build your "r" string. Such a solution would look something like this:
EmailTableAdapter.Fill(Me.EmailDataset.Email)
Dim EmailDict As New Dictionary(Of String, String)
For i As Integer = 0 To Me.EmailDataGridView.RowCount - 1
If Not EmailDict.ContainsKey(EmailDataGridView.Rows(i).Cells(4).Value.ToString) Then
EmailDict.Add(EmailDataGridView.Rows(i).Cells(4).Value.ToString, EmailDataGridView.Rows(i).Cells(7).Value.ToString)
End If
Next
Dim EmailPair As KeyValuePair(Of String, String)
Dim r As String = String.Empty
For Each EmailPair In EmailDict
r &= EmailPair.Value & " - " & EmailPair.Key & vbNewLine
Next
TextBox2.Text = (r)

for loop for a string variable

this is my code -
for i as integer = 0 to rows.count - 1
output &= "Name =" & row(i)("Name")
output &= "lastName =" & row(i)("lastName")
... 50 more fields
next
i need the output to be like this
Applicant1Name = MikeApplicant1lastName = ditkaApplicant2Name = TomApplicant2lastName = Brady ...
how do i do this without putting the following code 50 times -
output &= "Applicant" & i.tostring() + 1 &"Name =" & row(i)("Name")
... and so on.
is there a way to make a for loop and run applicant 1,2,3,4.... in one shot?
thanks
Try:
Dim output as New StringBuilder("")
For i as Integer = 0 To rows.Count - 1
output.append("Applicant" + i.ToString())
Foreach(col as DataColumn in dt.Columns) ' The datatable where your rows are
Dim colName as string = col.ColumnName
output.append(colName & "=" & rows(i)(colName).ToString())
Next
If i < rows.Count - 1 Then output.Append("|")
Next
StringBuilder is faster for string concatenations, and if you keep your rows in a datatable (which I assume is happening because that's how it looks like you're accessing them), then you can just iterate through the columnnames at the top level.
You really cant as you are trying to append 50 different fields.
The only thing you can shorten is the variable name:
Dim strLN as String = row(i)("lastName")
Dim strFirstName as String = row(i)("firstName")
Then you simply put it all together
output &= strLN & strFirstName...etc
looks like you want to create an array of all the fields you have and then include a nested loop.
Dim fields As String() = {"Name", "LastName", "SSN", "Birthdate"}
Dim output As String = ""
For i As Integer = 1 To rows.count
For Each field As String In fields
output = String.Concat(output, "Applicant ", i, field, "=", row(i)(field), " ")
Next
Next