Having trouble getting the average for duration in (mm:ss) - vb.net

For all who assist with this, thank you. My goal is to get the duration of chat conversations for my employees and then calculate their year average. The data is being inserted into a database as short text in the format of (mm:ss or 18:30). I have tried converting and parsing the data several different ways and I have looked through numerous explanations and solutions on SO.com so far with nothing working the way I would like. The below is my display procedure to bring the data into the form. I know I could have done this in an easier way, but I am fairly new to VB.net and coding in general.
Private Sub DisplayJanMetrics()
'Open a connection to the database and then assign the values from the appropriate metric columns to the appropriate labels.
Dim str As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\CoachingDB.accdb"
Dim conn As New OleDbConnection(str)
Try
conn.Open()
Dim sql As String = "SELECT Duration, CSAT, Away, Quality, Development FROM January WHERE Employee =" & "'" & cmbEmployee.SelectedItem.ToString & "'"
Dim cmd As New OleDbCommand(sql, conn)
Dim myReader As OleDbDataReader = cmd.ExecuteReader()
While myReader.Read
lblJanDuration.Text = myReader("Duration").ToString
lblJanCSAT.Text = myReader("CSAT").ToString
lblJanAway.Text = myReader("Away").ToString
lblJanQual.Text = myReader("Quality").ToString
lblJanDev.Text = myReader("Development").ToString
End While
Catch ex As OleDbException
MsgBox(ex.ToString)
Finally
conn.Close()
End Try
End Sub
Once the data has been loaded to the correct labels I have a button and click event to calculate the average from the labels - the other ones I was able to do easily because I could parse them to doubles and then do the calculation from there. Here is an image of what the form looks like, I think it will help all of you get an idea of what I am trying to accomplish.
This is what the form layout looks like

It is a good idea to separate your user interface code from your database code. You can use the same data retrieval function for any month by passing the month as TableName.
Database objects Connection, Command, and DataReader all need to be disposed and closed so they are placed in Using blocks. You don't want to hold the connection open while you update the user interface. Just return a DataTable and update the UI with that.
CalculateAverage first creates an array of the labels you want to include in the average. You can include all 12 but the average will not include empty labels. (IsNullOrEmpty)
Separate the string into minutes and seconds. Get the total seconds and add to the list.
Get the average number of seconds by calling Average on List(Of Integer).
Finally, turn the average seconds back into minutes and seconds and format a string to display.
Private Sub DisplayJanMetrics()
Dim dt As DataTable
Try
dt = GetEmployeeData(cmbEmployee.SelectedItem.ToString, "January")
Catch ex As Exception
MsgBox(ex.Message)
Exit Sub
End Try
If dt.Rows.Count > 0 Then
lblJanDuration.Text = dt(0)("Duration").ToString
lblJanCSAT.Text = dt(0)("CSAT").ToString
lblJanAway.Text = dt(0)("Away").ToString
lblJanQual.Text = dt(0)("Quality").ToString
lblJanDev.Text = dt(0)("Development").ToString
End If
End Sub
Private Function GetEmployeeData(Employee As String, TableName As String) As DataTable
Dim str As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\CoachingDB.accdb"
Dim sql As String = $"SELECT Duration, CSAT, Away, Quality, Development FROM {TableName} WHERE Employee = #Employee;"
Dim dt As New DataTable
Using conn As New OleDbConnection(str),
cmd As New OleDbCommand(sql, conn)
cmd.Parameters.Add("#Employee", OleDbType.VarChar).Value = Employee
conn.Open()
Using reader = cmd.ExecuteReader
dt.Load(reader)
End Using
End Using
Return dt
End Function
Private Function CalculateAverage() As String
Dim lst As New List(Of Integer)
Dim labels() = {Label1, Label2, Label3, Label4} 'an array of 1 thru 12
For Each label In labels
If Not String.IsNullOrEmpty(label.Text) Then
Dim fields = label.Text.Split(":"c)
Dim mins = CInt(fields(0))
Dim secs = CInt(fields(1))
lst.Add(mins * 60 + secs)
End If
Next
Dim avg = lst.Average
Dim avgMins = Math.Truncate(avg / 60)
Dim remainderSec = avg - avgMins * 60
Return $"{avgMins}:{remainderSec}"
End Function

Related

How to read date and time from database and compare it to the system date and time

I am working on a project using vb and one of my forms has to display the current active reminders (reminders that haven't hit their deadline yet) into a datagridview and I have another datagridview for reminders that are past the deadline. The date and time along with reminder information is saved into my access database and I want to read the date and time from database and compare it to the system date and time and then display the reminder information.
This is how my form looks like; the top datagridview is for current reminders and the bottom one is for past/out of date reminders:
This the code for my form and what I’ve tried:
Imports System.Data.OleDb
Public Class frmReminderInfo
Private Sub frmReminderInfo_Load(sender As Object, e As EventArgs) Handles MyBase.Load
DisplayCurrentReminders()
End Sub
Private Sub DisplayCurrentReminders()
Dim ReminderDateTime As Date
Dim CurrentDateTime As Date
CurrentDateTime = Date.Now
CurrentDateTime = FormatDateTime(Date.Now, DateFormat.GeneralDate)
ReminderDateTime = FormatDateTime(ReminderDateTime, DateFormat.GeneralDate)
If DbConnect() Then
DgvCurrentReminders.Rows.Clear()
Dim SQLCmd As New OleDbCommand
With SQLCmd
.Connection = cn
.CommandText = "SELECT ReminderDate FROM TblReminder "
Dim rs As OleDbDataReader = .ExecuteReader()
While rs.Read
ReminderDateTime = (rs(0).ToString)
End While
End With
End If
cn.Close()
If CurrentDateTime = ReminderDateTime Then
Dim SQLCmd As New OleDbCommand
With SQLCmd
Dim rs As OleDbDataReader = .ExecuteReader()
While rs.Read
Dim NewStockRow As New DataGridViewRow()
NewStockRow.CreateCells(DgvCurrentReminders)
NewStockRow.SetValues({rs("ReminderID"), rs("CustomerName"), rs("DeviceInfo"), rs("RepairPrice"), rs("ReminderDate")})
NewStockRow.Tag = rs("ReminderID")
DgvCurrentReminders.Rows.Add(NewStockRow)
End While
rs.Close()
End With
End If
cn.Close()
End Sub
End Class
Disposable database objects like Connection should be declared locally in the method where they are used in a Using block.
You don't seem to have any idea of what the parts of an Sql string mean. The Select portion list the fields you want to retrieve. A star (*) in this clause means select all the fields. Here we are using a Where clause to filter the records. Only records where the field ReminderDate is greater than or equal to a parameter will be returned. This will only work if the data has been inserted properly as a DateTime.
The While loop keeps overwriting the value of ReminderDateTime on each iteration so only the last value returned by the reader will remain. Also, you are trying to force a String into a variable declared as a Date. Won't work.
Assuming the code could get beyond If CurrentDateTime = ReminderDateTime Then you would be working with a closed connection. Commands can't execute on a closed connection.
You also don't seem to have an idea how class objects work. Dim SQLCmd As New OleDbCommand Here you declare a new instance of the Command. You have no connection and no CommandText so it can't possibly be executed.
Take a look at the following code until it starts to make sense. Look up what Using blocks do. Look up the Load method of a DataTable to see what it does. Check out what a DataSource property has to offer.
Private Sub frmReminderInfo_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim dt = GetCurrentReminders()
DgvCurrentReminders.DataSource = dt
End Sub
Private Function GetCurrentReminders() As DataTable
Dim dt As New DataTable
Using cn As New OleDbConnection("Your connection string"),
SQLCmd As New OleDbCommand("SELECT * FROM TblReminder Where ReminderDate >= #Date", cn)
SQLCmd.Parameters.Add("#Date", OleDbType.Date).Value = Now
cn.Open()
Using reader = SQLCmd.ExecuteReader
dt.Load(reader)
End Using
End Using
Return dt
End Function

Performance issue with SQLite database with VB.NET

I am Inserting the data-table into SQLite Database. I am doing like this.
First I Fetch the data with getdata function and insert it into datatable, then with For Each Loop i made the Insert Command and Execute It. I am having 50000 Records it will take 30 Minutes to run.
Please Guide the suitable approach. Here is the Code.
Dim xtable As DataTable = getdata("select * from tablename")
Dim str As String = Nothing
For Each r As DataRow In xtable.Rows ''''HERE IT WILL TAKE TOO MUCH TIME
str = str & ("insert into tablename values(" & r.Item("srno") & "," & r.Item("name"));")
Next
EXECUTEcmd(str)
Public Function getdata(ByVal Query As String) As DataTable
connectionString()
Try
Dim mds As New DataTable
Dim mycommand As New SQLiteCommand(DBConn)
mycommand.CommandText = Query
Dim reader As SQLiteDataReader = mycommand.ExecuteReader()
mds.Load(reader)
Return mds
Catch ex As Exception
MsgBox("DB Error", vbCritical, "")
MsgBox(Err.Description)
Return Nothing
End Try
End Function
Public Sub EXECUTEcmd(ByVal selectcmd As String)
Using cn = New SQLiteConnection(conectionString)
cn.Open()
Using transaction = cn.BeginTransaction()
Using cmd = cn.CreateCommand()
cmd.CommandText = selectcmd
cmd.ExecuteNonQuery()
End Using
transaction.Commit()
End Using
cn.Close()
End Using
End Sub
here the Conncection String is:
conStr = "Data Source=" & dbpath & ";Version=3;Compress=True; UTF8Encoding=True; PRAGMA journal_mode=WAL; cache=shared;"
Use a stringbuilder to build your string, not string concatenation
Dim strB As StringBuilder = New StringBuilder(100 * 50000)
For Each r As DataRow In xtable.Rows
strB.AppendLine($"insert into tablename values({r.Item("srno")},{r.Item("name")});")
Next
Strings cannot be changed in .net. Every time you make a new string VB has to copy everything out of the old string into a new one and add the new bit you want. If each of your insert statements is 100 bytes, that means it copies 100 bytes, then adds 100, then copies 200 bytes and adds 100, then copies 300 bytes, then 400 bytes, then 500 bytes. By the time it has done 10 strings it has made 5.5 kilobytes of copying. By the time it's done 50 thousand strings it has copied 125 gigabytes of data. No wonder it's slow!
Always use a StringBuilder to build massive strings
--
I'm willing to overlook the sql injection hacking nag for this one, because of the nature of the task, but please read http://bobby-tables.com - you should never, ever concatenate values into an SQL as a way of making an sql that has some varying effect.
This entire exercise would be better done as this (pseudocode) kind of thing:
Dim sel as New SQLiteCommand("SELECT a, b FROM table", conn)
Dim ins as New SQLiteCommand("INSERT INTO table VALUES(:a, :b)", conn)
ins.Parameters.Add("a" ...)
ins.Parameters.Add("b" ...)
Dim r = sel.ExecuteReader()
While r.Read()
ins.Parameters("a") = r.GetString(0)
ins.Parameters("b") = r.GetString(1)
ins.ExecuteNonQuery()
End While
That is to say, you minimize your memory by reading rows one at a time out of ther edaer and inserting them one at a time in the insert; the insert command is prepared once, you just change the parameter values, execute it, change them again, execute it ... It's what parameterized queries were designed for (as well as stopping your app getting hacked when someone puts SQL in your variable, or even just stopping it crashing when you have an person named O'Grady
Maybe you must refactor your code like this:
Dim xtable As DataTable = getdata("select * from tablename")
Using cn = New SQLiteConnection(conectionString)
cn.Open()
Using transaction = cn.BeginTransaction()
Try
Using cmd = cn.CreateCommand()
cmd.Transaction = transaction
For Each r As DataRow In xtable.Rows ''''HERE IT WILL TAKE TOO MUCH TIME
cmd.CommandText = "insert into tablename values(" & r.Item("srno") & "," & r.Item("name") & ")"
cmd.ExecuteNonQuery()
Next
End Using
transaction.Commit()
Catch ex As Exception
transaction.Rollback()
End Try
End Using
End Using
Public Function getdata(ByVal Query As String) As DataTable
connectionString()
Try
Dim mds As New DataTable
Dim mycommand As New SQLiteCommand(DBConn)
mycommand.CommandText = Query
Dim reader As SQLiteDataReader = mycommand.ExecuteReader()
mds.Load(reader)
Return mds
Catch ex As Exception
MsgBox("DB Error", vbCritical, "")
MsgBox(Err.Description)
Return Nothing
End Try
End Function
Instead of concatenate an possible giant string, wrap all your inserts into a single transaction, like above. This will reduce the memory used and also make sqlite perform faster.

Error loading sql parameters in a loop, VB.Net

I am currently working in Microsoft visual studio express 2013 with an sql back end. I am trying to run a loop through 2 comboboxes and a datetimepicker for any instance a checkbox is checked. However, I am running into an error that reads "System.ArgumentException: No Mapping exists from Object type system.windows.forms.datetimepicker to a known managed provider native type." When I run the code I have put a watch on the parameter value and it is not saving the data into the variable before the sql command fires. I think I need to store the variable in a different way to allow access to the variable. Here is my code:
Try
Using conn1 As New SqlConnection(connstring)
conn1.Open()
Using comm1 As New SqlCommand("SELECT isnull(max(AuditID) + 1, 1) as 'AuditID' FROM table1", conn1)
Dim reader1 As SqlDataReader = comm1.ExecuteReader
reader1.Read()
Dim AuditID As Integer = reader1("AuditID")
reader1.Dispose()
'Loop through all checkboxes and write into sql tables
Dim Run As Integer
For Run = 1 To 5
Dim LineItem = DirectCast(Me.Controls("CB" & Run), CheckBox)
If LineItem.Checked = True Then
Dim DateTime = DirectCast(Me.Controls("DTP" & Run), DateTimePicker)
Dim Frequency = DirectCast(Me.Controls("CBWeek" & Run), ComboBox)
Dim Repeat = DirectCast(Me.Controls("CBRepeat" & Run), ComboBox)
'sql statements
'select ID
Using conn2 As New SqlConnection(connstring)
conn2.Open()
Using comm2 As New SqlCommand("SELECT isnull(max(AuditID) + 1, 1) as 'ID' FROM table1", conn1)
Dim reader As SqlDataReader = comm2.ExecuteReader
reader.Read()
Dim ID As Integer = reader("ID")
reader.Dispose()
'Insert into table audit line
Using conn3 As New SqlConnection(connstring)
conn3.Open()
Using comm3 As New SqlCommand("INSERT INTO table1 (ID, AuditID, DateStart, Freq, repeats) " _
& "VALUES (#ID, #AuditID, #DateStart, #Freq, #Repeats)", conn3)
With comm3.Parameters
.AddWithValue("#ID", ID)
.AddWithValue("#AuditID", AuditID)
.AddWithValue("#DateStart", DateTime)
.AddWithValue("#Freq", Frequency)
.AddWithValue("#Repeats", Repeat)
End With
comm3.ExecuteNonQuery()
End Using
conn3.Close()
End Using
End Using
conn2.Close()
End Using
End If
Next
End Using
conn1.Close()
End Using
Catch ex As Exception
MsgBox(ex.ToString)
My try statement stop my code on this line:
comm3.ExecuteNonQuery()
However, I know that this error is coming from when I add my parameters, specifically these 3 lines:
.AddWithValue("#DateStart", DateTime)
.AddWithValue("#Freq", Frequency)
.AddWithValue("#Repeats", Repeat)
I am trying to get tese variables with a loop statement based on the design name here:
Dim DateTime = DirectCast(Me.Controls("DTP" & Run), DateTimePicker)
Dim Frequency = DirectCast(Me.Controls("CBWeek" & Run), ComboBox)
Dim Repeat = DirectCast(Me.Controls("CBRepeat" & Run), ComboBox)
It does not seem like the program likes using these dimensions above to be inserted into the sql table. Does anyone know a way I can carry these values over to the sql parameters statement?
There are a number of things I would do differently. First and always, use Option Strict, it will catch some of the type conversion you have going on.
I would get the controls from an explicit list rather then fetching from Controls. Just make a few arrays to hold the control refs so you do not need to to find them in the collection:
Private DTPs As DateTimePicker() = {DTP1, DTP2...}
This will avoid the need to cast them, fewer hoops and implied converts like "DTP" & Run:
Dim dt As DateTime ' vars for the SQL
Dim freq As Integer
For Run As Integer = 0 To 4
dt = DTPs(Run).Value
freq = cboFreq(Run).SelectedValue
...
I have fixed the problem, I needed to put my loop variables into .value type variables. I added this to fix it:
Dim DateTime = DirectCast(Me.Controls("DTP" & Run), DateTimePicker)
Dim Frequency = DirectCast(Me.Controls("CBWeek" & Run), ComboBox)
Dim Repeat = DirectCast(Me.Controls("CBRepeat" & Run), ComboBox)
Dim Time As Date = DateTime.Value
Dim Freq As Integer = Frequency.SelectedValue
Dim Again As Integer = Repeat.SelectedValue

Filling DataTable as source of DataGridView is very slow

So, here is a case where I have more than 40.000 rows in my database.
When I start Select * from Table it gets me result set in less than a second.
But, when I want to fill DataTable with those rows and than show it in DataGridView it takes forever to show (Around 15-20 seconds).
Why is that?
My code:
Public Shared Function FillDTwithSQL(ByVal StoredProc As String, ByVal table As DataTable) As DataTable
Dim cmd As SqlCommand = CreateCommand(SProcedura)
Dim dt As New DataTable("dt")
dt = table
dt.Rows.Clear()
Try
If adoConnection.State = ConnectionState.Closed Then adoConnection.Open()
dt.Load(cmd.ExecuteReader(CommandBehavior.Default))
Catch ex As Exception
MsgBox("Greska: " & ex.ToString)
Error = True
Error_text = ex.ToString
End Try
adoConnection.Close()
Return dt
End Function
Public Shared Function CreateCommand(ByVal SProcedura As String) As SqlCommand
CreateConnection(ConnectionSetup.ConnectionString)
Dim cmd As New SqlCommand(ConnectionSetup.DataBaseName & ".dbo." & SProcedura, adoConnection)
cmd.CommandType = CommandType.StoredProcedure
Return cmd
End Function
Private sub FillDGV()
DataBaseLayer.FillDTwithSQL("SelectProc", ds_Tables.Table)
Me.DataGridView1.DataSource = dds_Tables.Table
Me.DataGridView1.ClearSelection()
End Sub
For large amounts of data it is recommended to use the DataGridView's virtual mode.
Have a look at this link: How to: Implement Virtual Mode in the Windows Forms DataGridView Control
To summarize the link: You have to implement your own data caching by implementing the DataGridView's CellValueNeeded event and set Me.DataGridView1.VirtualMode = true.

Better way to print out rows from a datatable in vb.net

I am new to vb.net and I am trying to query a database and print out the records in the row to the console window. I got it to work, but I have a feeling that there is a more concise way to do this. One thing that I am sure is wrong is that I had to convert the dataset to a datatable to be able to retrieve the values. Is that correct? Could you take a look at the code below (especially the for loop) and let me know what I can improve upon?
Thanks!
Module Module1
Sub Main()
Dim constring As String = "Data Source=C:\Users\test\Desktop\MyDatabase1.sdf"
Dim conn As New SqlCeConnection(constring)
Dim cmd As New SqlCeCommand("SELECT * FROM ACCOUNT")
Dim adapter As New SqlCeDataAdapter
Dim ds As New DataSet()
Try
conn.Open()
cmd.Connection = conn
adapter.SelectCommand = cmd
adapter.Fill(ds, "testds")
cmd.Dispose()
adapter.Dispose()
conn.Close()
Dim dt As DataTable = ds.Tables.Item("testds")
Dim row As DataRow
Dim count As Integer = dt.Columns.Count()
For Each row In dt.Rows
Dim i As Integer = 0
While i <= count - 1
Console.Write(row(i))
i += 1
End While
Console.WriteLine(Environment.NewLine())
Next
Catch ex As Exception
Console.WriteLine("There was an error")
Console.WriteLine(ex)
End Try
Console.ReadLine()
End Sub
End Module
Here is how I would rewrite this for a few reasons:
1) You should always use Using statements with disposable objects to ensure they are correctly cleaned up. You had a good start with the dispose commands, but this way is safer.
2) It is more efficient to use ExecuteReader than loading everything into a dataset.
3) Your try/catch statement should include object creation as well as execution.
Finally, in response to your question about datasets and datatables, that code was absolutely correct: a dataset consists of zero or more datatables, so you were just extracting the existing datatable from the dataset.
Try
Dim constring As String = "Data Source=C:\Users\test\Desktop\MyDatabase1.sdf"
Using conn As New SqlCeConnection(constring)
conn.Open()
Using cmd As New SqlCeCommand("SELECT * FROM ACCOUNT", conn)
Dim reader As SqlCeDataReader
reader = cmd.ExecuteReader()
Do While reader.Read
For i As Integer = 0 To reader.FieldCount - 1
Console.Write(reader.GetString(i))
Next
Console.WriteLine(Environment.NewLine())
Loop
End Using
End Using
Catch ex As Exception
Console.WriteLine("There was an error")
Console.WriteLine(ex)
End Try
Console.ReadLine()
End Sub
One last note: since you are just printing to the console, it doesn't matter as much, but whenever you deal with a lot of strings, especially those that are to be concatenated, you should always consider using System.Text.StringBuilder.
Here is an example rewrite of the loop that prints to the console using stringbuilder (builds the string in memory, then dumps it to the console; I have also added the field name for good measure):
Dim sbOutput As New System.Text.StringBuilder(500)
For i As Integer = 0 To reader.FieldCount - 1
If sbOutput.Length <> 0 Then
sbOutput.Append("; ")
End If
sbOutput.Append(reader.GetName(i)).Append("=").Append(reader.GetString(i))
Next
sbOutput.AppendLine()
Console.Write(sbOutput.ToString)