I've been translating my programs to use Entity Framework 6 for the last two days. For the most part I have it down with simple CRUD operations. Today I hit a snag. I'm trying to convert;
Using SQLSERVER_Connection As New SqlConnection(GlobalVariables.SQLServer_Login_Details)
Using SQLCommand As New SqlCommand
Dim Command As New Text.StringBuilder
Command.AppendLine("SELECT TOP(1) EarningsYear AS PayYear, Max(EarningsAmt) AS EarnAmt, Max(Hours) AS HRS")
Command.AppendLine("FROM interview_payroll")
Command.AppendLine("GROUP BY CLIENTCODE, EarningsYear, SSN")
Command.AppendLine("HAVING CLIENTCODE = #CLIENTCODE AND SSN = #SSN ORDER BY EarningsYear DESC;")
SQLCommand.CommandText = Command.ToString
SQLCommand.Connection = SQLSERVER_Connection
SQLCommand.Parameters.Add("#CLIENTCODE", SqlDbType.VarChar).Value = sCLIENTCODE
SQLCommand.Parameters.Add("#SSN", SqlDbType.VarChar).Value = txtSSN.Text
SQLSERVER_Connection.Open()
Using Reader As SqlDataReader = SQLCommand.ExecuteReader()
While Reader.Read()
If Not IsDBNull(Reader("PayYear")) Then txtPayrollSourceDate.Text = CStr(Reader("PayYear"))
If Not IsDBNull(Reader("EarnAmt")) Then txtPayrollErnAmt.Text = CStr(Reader("EarnAmt"))
If Not IsDBNull(Reader("HRS")) Then txtPayrollHRS.Text = CStr(Reader("HRS"))
End While
End Using
SQLSERVER_Connection.Close()
End Using
End Using
into Linq.
I've got as far as;
Using DB As New wotcEntities
Dim Reader = From payroll In DB.interview_payroll
Where payroll.CONTROL = CONTROL And payroll.CLIENTCODE = sCLIENTCODE
Group payroll By payroll.CLIENTCODE, payroll.EarningsYear, payroll.SSN Into GPayroll = Group
End Using
But I just can't seem to jump the hurdle to get the MAX in EarningsAmt and Hours. I have this program called 'LINQ - Sample Queries' but it shows a MAX query as;
Public Sub LinqToSqlCount08()
Dim latestHire = Aggregate emp In db.Employees _
Into Max(emp.HireDate)
Console.WriteLine(latestHire)
End Sub
Which isn't going to work if I want to include PayYear.
I could just grab each part as separate queries, but that doesn't feel right.
How do I do this correctly?
I highly recommend the application "LINQPad". It can really help with writing queries and seeing the results immediately.
I created a table similar to what I could gather from your query, and was able to write this query which I believe does what you're asking for.
The "select" is where the Max aggregates are applied.
from payroll In Interview_payrolls
where payroll.CLIENTCODE = sClientCode
group payroll By payroll.CLIENTCODE, payroll.EarningsYear, payroll.SSN Into GPayroll = group
select PayYear = EarningsYear, EarnAmt = GPayroll.Max(Function(p) p.EarningsAmt), HRS = GPayroll.Max(Function(p) p.Hours)
Hope it works for you.
Related
In my application, the user should have the option to browse for orders that have been placed by customers - and it should be possible to apply several filters to the search. That means that I need a dynamic SQL query where a variable amount of parameters can be applied.
In a standard WinForms application, what would be the best way to handle this?
So far I've been working with TableAdapters and stored procedures but as far as I know, I can not use these with optional parameters. So for example if a user wants to see all customer orders, this is no problem. But it should also be possible to say for example "Show all orders that have been placed in the last 2 weeks and where at least one product contains the word 'gift code'". So date and product-name would be optional parameters but if I leave those empty in a stored procedure, I get an error.
To fix this, I started building my own queries in a separate class using SqlCommands and parameters. I dynamically generate the commandText for each command depending on the parameters passed into the function, then I add parameters to the SqlCommand, execute it and loop through the SqlDataReader to build a list of items that I will return to my program.
For example (simplified):
Dim cmd As New SqlCommand With {.Connection = con}
cmd.commandText = "SELECT o.id, o.customer_name, o.date, p.productName FROM orders o JOIN order_positions p ON o.id = p.order_id WHERE o.date >= #pDate"
cmd.Parameters.Add("#pDate", SqlDbType.DateTime).Value = searchDate
Dim reader As SqlDataReader = cmd.ExecuteReader()
Dim lstOrderItems As New List(Of OrderDisplayItem)
while reader.read
dim orderId as Integer = reader.Item(0)
dim customerName as String = reader.Item(1)
dim date as Date = reader.Item(2)
dim productName as String = reader.Item(3)
lstOrderItems.add(New OrderDisplayItem With{.id = orderId, .customerName = customerName, .date = date, .productName = productName})
End While
return lstOrderItems
Now obviously this is just to show how I proceed. In reality, I have to create additional loops because one order might contain one or multiple products etc.
My question would be: is this the right way to handle this? It feels like this whole class will grow really big because I have other queries too like looking up invoices, store sales and so on - and for every query I have to write these reader loops which I would have to modify all over again if a slight thing in my database changes.
Is it really not possible to handle this within Visual Studio tableAdapters?
That means that I need a dynamic SQL query where a variable amount of parameters can be applied.
No it doesn't. You can use a single query with a single set of parameters and simply provide NULL for those parameters you want to ignore if you structure your SQL like this:
SELECT *
FROM MyTable
WHERE (#Column1 IS NULL OR Column1 = #Column1)
AND (#Column2 IS NULL OR Column2 = #Column2)
Your VB code might then look something like this:
Using connection As New SqlConnection("connection string here"),
command As New SqlCommand(query, connection)
command.Parameters.Add("#Column1", SqlDbType.VarChar, 50).Value = If(TextBox1.TextLength = 0, CObj(DBNull.Value), TextBox1.Text)
command.Parameters.Add("#Column2", SqlDbType.VarChar, 50).Value = If(TextBox2.TextLength = 0, CObj(DBNull.Value), TextBox2.Text)
'...
End Using
When you provide NULL for a parameter, that effectively matches every record and that parameter is effectively ignored. You can do that with as many parameters as you like of whatever data type that you like.
Select cod,nom from tb_user where cod > #param0 order by #param1
Dim mycod = 3
Dim myorderby = "asc"
Dim _adapter = New SqlDataAdapter
cmd.CommandTimeout = timeout
cmd.Connection = _conn
cmd.CommandText = pSql
cmd.CommandType = CommandType.Text
Dim sqlParameter0 = New SqlParameter("#param0", mycod)
cmd.Parameters.Add(sqlParameter0)
Dim sqlParameter1 = New SqlParameter("#param1", myorderby)
cmd.Parameters.Add(sqlParameter1)
_adapter.SelectCommand = cmd
_adapter.Fill(_ds, "result")
I know I must replace the #param0 by the value of my variable mycod to be safe.
This is possible in the variables like the param0, but the #param1 where I put asc it gives me the following error:
the SELECT item identified by the ORDER BY number 1 contains a variable as part of the expression identitying a column position
PS: By the error it is clear the SqlParameter is not the way to input this kind of order by. Is there a way to input this kind of query safely?
You can do this by selectively ordering on the two columns.
Select cod,nom from tb_user
where cod > #param0
order by
case when #param1=1 then cod else 0 end,
case when #param1=2 then nom else 0 end
There are techniques to dynamically sort by a parameter but this can often lead to a significant slow down. I had a nightmare situation when I tried something similar.
The query worked well most of the time, but the query plan created was completely inappropriate when using a different parameter value, and the results took forever to return..
Since you're binding to a DataSet, you should just sort on the DefaultView after you call Fill().
_ds.Tables(0).DefaultView.Sort = myorderby
net and would to have the Header Text of columns in a datagridview be named after results from the database, e.g the query in my code returns four dates,30/08/2017,04/09/2017,21/09/2017 and 03/02/2018. My aim is to have the column headers in the data grid named after those dates. Your help will highly be appreciated.
sql = "SELECT COUNT (ServiceDate) As NoOfServiceDates FROM (SELECT DISTINCT ServiceDate FROM tblattendance)"
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
reader.Read()
ColumnNo = CInt(reader("NoOfServiceDates")).ToString
End Using
End Using
DataGridView1.ColumnCount = ColumnNo
For i = 0 To DataGridView1.Columns.Count - 1
sql = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
End While
End Using
End Using
Next
The current code re-runs the query each time through the column count loop, meaning it will set the column header for that column to all of the date values in sequence, so the last value in the query shows in the all the columns. You only need to run the query once:
Dim i As Integer = 0
sql = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using command As New OleDbCommand(sql, connection), _
reader As OleDbDatareader = command.ExecuteReader()
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
i+= 1
End While
End Using
Additionally, this still results in two separate trips to the database, where you go once to get the count and again to get the values. Not only is this very bad for performance, it leaves you open to a bug where another user changes your data from one query to the next.
There are several ways you can get this down to one trip to the database: loading the results into memory via a List or DataTable, changing the SQL to include the count and the values together, or adding a new column each time through the list. Here's an example using the last option:
DataGridView1.Columns.Clear()
Dim sql As String = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using connection As New OleDbConnection("string here"), _
command As New OleDbCommand(sql, connection)
connection.Open()
Using reader As OleDbDataReader = command.ExecuteReader()
While reader.Read
Dim column As String = reader("ServiceDate").ToString()
DataGridView1.Columns.Add(column, column)
End While
End Using
End Using
Even better if you can use something like Sql Server's PIVOT keyword in combination with the DataGridView's AutoGenerateColumns feature for DataBinding, where you will write ONE SQL statement that has both column info and data, and simply bind the result set to the grid.
The For Next is incorrect. You execute your command for every column, when you only need to execute it once. The last result from the DataReader will be the header for every column as currently written.
You should iterate through your DataReader and increment the cursor variable there:
Dim i As Integer = 0
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
i += 1
End While
End Using
End Using
I have a parameterized query GET_CUSTOMER:
SELECT * FROM Customer WHERE id = [customer_id]
I want to call this query from another query and pass it a parameter:
SELECT * FROM GET_CUSTOMER(123)
Note the above code is not valid, it is here to give you an idea of what I'm trying to do. Is it possible to do this in MS Access?
UPDATE 1:
The queries I posted are for example. The actual queries are much more complex. I know I can use table joins, but in my specific case it would be much easier if I could run parameterized queries inside other queries (that are parameterized as well). I can't use access forms because I'm using access with my .NET application.
This is how I end up solving this with help of https://stackoverflow.com/a/24677391/303463 . It turned out that Access shares parameters among all queries so there is no need to specifically pass parameters from one query to another.
Query1:
SELECT * FROM Customer WHERE ID > [param1] AND ID < [param2]
Query2:
SELECT * FROM Query1
VB.NET code:
Dim ConnString As String = "Provider=Microsoft.Jet.OleDb.4.0;Data Source=Database.mdb"
Dim SqlString As String = "Query2"
Using Conn As New OleDbConnection(ConnString)
Using Cmd As New OleDbCommand(SqlString, Conn)
Cmd.CommandType = CommandType.StoredProcedure
Cmd.Parameters.AddWithValue("param1", "1")
Cmd.Parameters.AddWithValue("param2", "3")
Conn.Open()
Using reader As OleDbDataReader = Cmd.ExecuteReader()
While reader.Read()
Console.WriteLine(reader("ID"))
End While
End Using
End Using
End Using
You can build the SQL on the fly.
MyID = prompt or get from user some ID
strSQl = "Select * from tblCustomer where ID in " & _
"(select * from tblTestCustomers where id = " & MyID
So you can nest, or use the source of one query to feed a list of ID to the second query.
I am using an Access database, I believe the problem lies in my SQL statement. I have a relational database, with two tables -
StaffDetails [columns(
StaffID,
FirstName,
LastName)]
and
StaffTraining [columns(
StaffID,
Month)].
I have a combobox (cbMonth) and dependent on what month is chosen if the user selects 'January' then I would like the datagrid (DGTraining) to show the First Name and Last Name of the members of staff whose ID are within the chosen month. Sorry if this is not the clearest explanation, hopefully my code below makes my issue clearer:
Dim SqlQuery As String = "SELECT [StaffDetails.StaffID], [StaffDetails.FirstName], [StaffDetails.LastName], [StaffTraining.StaffID] FROM [StaffDetails], [StaffTraining] WHERE StaffTraining.TrainingMonth='" & cbMonth.Text & "'"
Dim da As OleDbDataAdapter = New OleDbDataAdapter(SqlQuery, conn)
Dim ds As DataSet = New DataSet
da.Fill(ds, "Training")
Dim dt As DataTable = ds.Tables("Training")
With DGTraining
.AutoGenerateColumns = True
.DataSource = ds
.DataMember = "Training"
End With
You are missing your join and are getting a cross join. 2 ways of addressing:
FROM [StaffDetails] inner join [StaffTraining] on [StaffDetails].staffID = [StaffTraining].staffID
That is the join logic thats more common and easier to read. You could add to your where clause (old method, harder to read and not as commonly accepted:
...where [StaffDetails].staffID = [StaffTraining].staffID and ...
Your last comment needs amending like this...
Dim SqlQuery As String = "SELECT [StaffDetails.StaffID], [StaffDetails.FirstName],
[StaffDetails.LastName], FROM [StaffDetails]
INNER JOIN [StaffTraining] ON [StaffDetails].StaffID = [StaffTraining].StaffID
WHERE [StaffTraining].TrainingMonth='" & cbMonth.Text & "'"
Also... dependant on how you have set up cbMonth you may want cbMonth.SelectedValue or cbMonth.SelectedText