I'm accessing a Microsoft Access 2002 database (MDB) using ASP.NET through the OdbcConnection class, which works quite well albeit very slowly.
My question is about how to implement pagination in SQL for queries to this database, as I know I can implement the TOP clause as:
SELECT TOP 15 *
FROM table
but I am unable to find a way to limit this to an offset as can be done with SQL Server using ROWNUMBER. My best attempt was:
SELECT ClientCode,
(SELECT COUNT(c2.ClientCode)
FROM tblClient AS c2
WHERE c2.ClientCode <= c1.ClientCode)
AS rownumber
FROM tblClient AS c1
WHERE rownumber BETWEEN 0 AND 15
which fails with:
Error Source: Microsoft JET Database Engine
Error Message: No value given for one or more required parameters.
I can't work out this error, but I'm assuming it has something to do with the sub-query that determines a rownumber?
Any help would be appreciated with this; my searches on google have yielded unhelpful results :(
If you wish to apply paging in MS Acces use this
SELECT *
FROM (
SELECT Top 5 sub.ClientCode
FROM (
SELECT TOP 15 tblClient.ClientCode
FROM tblClient
ORDER BY tblClient.ClientCode
) sub
ORDER BY sub.ClientCode DESC
) subOrdered
ORDER BY subOrdered.ClientCode
Where 15 is the StartPos + PageSize, and 5 is the PageSize.
EDIT to comment:
The error you are receiving, is because you are trying to reference a column name assign in the same level of the query, namely rownumber. If you were to change your query to:
SELECT *
FROM (
SELECT ClientCode,
(SELECT COUNT(c2.ClientCode)
FROM tblClient AS c2
WHERE c2.ClientCode <= c1.ClientCode) AS rownumber
FROM tblClient AS c1
)
WHERE rownumber BETWEEN 0 AND 15
It should not give you an error, but i dont think that this is the paging result you want.
See astander's answer for the original answer, but here's my final implementation that takes into account some ODBC parser rules (for the first 15 records after skipping 30):
SELECT *
FROM (
SELECT Top 15 -- = PageSize
*
FROM
(
SELECT TOP 45 -- = StartPos + PageSize
*
FROM tblClient
ORDER BY Client
) AS sub1
ORDER BY sub1.Client DESC
) AS clients
ORDER BY Client
The difference here is that I need the pagination to work when sorted by client name, and I need all columns (well, actually just a subset, but I sort that out in the outer-most query).
I use this SQL code to implement the pagination with Access
Select TOP Row_Per_Page * From [
Select TOP (TotRows - ((Page_Number - 1) * Row_Per_Page)
From SampleTable Order By ColumnName DESC
] Order By ColumnName ASC
I've published an article with some screenshots
on my blog
This is the simple method of pagination using OleDbDataAdapter and Datatable classes. I am using a different SQL command for simplicity.
Dim sSQL As String = "select Name, Id from Customer order by Id"
Dim pageNumber As Integer = 1
Dim nTop As Integer = 20
Dim nSkip As Integer = 0
Dim bContinue As Boolean = True
Dim dtData as new Datatable
Do While bContinue
dtData = GetData(sSQL, nTop, nSkip, ConnectionString)
nSkip = pageNumber * nTop
pageNumber = pageNumber + 1
bContinue = dtData.Rows.Count > 0
If bContinue Then
For Each dr As DataRow In dtData.Rows
'do your work here
Next
End If
Loop
Here is the GetData Function.
Private Function GetData(ByVal sql As String, ByVal RecordsToFetch As Integer, ByVal StartFrom As Integer, ByVal BackEndTableConnection As String) As DataTable
Dim dtResult As New DataTable
Try
Using conn As New OleDb.OleDbConnection(BackEndTableConnection)
conn.Open()
Using cmd As New OleDb.OleDbCommand
cmd.Connection = conn
cmd.CommandText = sql
Using da As New OleDb.OleDbDataAdapter(cmd)
If RecordsToFetch > 0 Then
da.Fill(StartFrom, RecordsToFetch, dtResult)
Else
da.Fill(dtResult)
End If
End Using
End Using
End Using
Catch ex As Exception
End Try
Return dtResult
End Function
The above codes will return 10 rows from the table Customer each time the loop operate till the end of file.
One easy way to use limit or get pagination working in access is to use ADODB library which support pagination for many DBs with same syntax. http://phplens.com/lens/adodb/docs-adodb.htm#ex8
Its easy to modify/override pager class to fetch required number of rows in array format then.
SELECT *
FROM BS_FOTOS AS TBL1
WHERE ((((select COUNT(ID) AS DD FROM BS_FOTOS AS TBL2 WHERE TBL2.ID<=TBL1.ID)) BETWEEN 10 AND 15 ));
Its result 10 to 15 records only.
Related
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
I am getting 2100 parameter limit error in a LINQ to SQL query. Here is the procedure which returns the list of id's. Parameter l has over 5000 id's.
Public Function GetByID(ByVal l As List(Of Int32)) As List(Of OfficeAccess)
Return (From d In db.OfficeAccess
Where l.Contains(d.ID)
Order By d.ID Ascending Select d).ToList()
End Function
I have tried the solution given in the second part of Hitting the 2100 parameter limit (SQL Server) when using Contains() but I'd need another solution without manually building SQL query string and this doesn't work:
Dim ids = String.Join(" ", l.ToArray())
Return(From d In db.OfficeAccess
Where ids.IndexOf(Convert.ToString(d.ID)) != -1
Order By d.ID Ascending Select d).ToList()
Updated
I have used the following code and works fine.
Dim l As New List(Of OfficeAccess)
Dim f As Int32, t As List(Of Int32)
While (ids.Count > 0)
If (ids.Count < 2000) Then f = ids.Count Else f = 2000
t = ids.GetRange(0, f)
l.AddRange((From d In db.OfficeAccess Where t.Contains(d.ID) Select d).ToList())
ids.RemoveRange(0, f)
End While
You may want to partition your original list into smaller chunks. Let's say each one of 2000 elements, this will affect performance (especially if SQL Server version doesn't support OFFSET n and FETCH NEXT m ROWS) but it'll solve your problem.
Function Partition(ByVal l As List(Of Integer)) As List(Of OfficeAccess)
Const size As Integer = 2000
Dim result As List(Of OfficeAccess) = new List(Of OfficeAccess)
Dim index As Integer = 1
Dim partition As List(Of Integer) = (
From item In l
Take size
).ToList()
While partition.Any()
result.AddRange(
From d In db.OfficeAccess
Where partition.Contains(d.ID)
Order By d.ID Ascending
Select d
)
partition = (
From item in l
Skip index * size
Take size
).ToList()
index += 1
End While
Return result
End Function
Please note code is untested then be ready to syntax errors!
Note: in this example I accumulate partial results into result list but you may concatenate each enumeration with Enumerable.Concat() (declaring result as IEnumerable(Of OfficeAccess) and performing ToList() once: Return result.ToList(). It shouldn't make any (visible) difference unless you have a huge number of records (because, at least, you avoid to resize result for each bulk insertion) or unless LINQ is smart enough to optimize (somehow) full resulting query (as concatenation of multiple queries).
I'd avoid, if possible, to manually build SQL string (also to keep code reusable for other data types and easy to update and refactor) but, from linked post, you should try code from one of answers instead of not working code from question...
This is the code that tries to grab the largest StockID from the database (Access database) , but my problem is that it generates StockID's up to "S10", after this it simply doesn't increment any further. This is the subroutine that generates the StockID:
Sub generate_Stock_ID()
Dim Stock_start As String = "S"
Dim Stock_Gen As String = "SELECT MAX(StockID) FROM tblStock WHERE StockID LIKE '" & Stock_start & "%%%' "
Dim da As OleDbDataAdapter = New OleDbDataAdapter(Stock_Gen, conn)
Dim ds As DataSet = New DataSet
da.Fill(ds, "StockID")
Dim dt As DataTable = ds.Tables("StockID")
Dim count As Integer = ds.Tables("StockID").Rows.Count
If ds.Tables("StockID").rows.count = 0 Then
StockID = "S1"
Else
StockID = ds.Tables("StockID").Rows(0).Item(0)
StockID = StockID.Substring(1, (StockID.Length - 1))
StockID = Stock_start & (StockID + 1)
End If
End Sub
Screenshot of my database
Note* there are multiple ID's for various other subroutines which all share the same incrementation issue, so if i fix this i fix the other ones too. So at the moment i think my problem lies in the syntax of my SQL statement, but im open to suggestions.
Thanks!
Don't treat an Integer as String. Otherwese MAX or ORDER BY will use lexicographical instead of numerical order which means that S11 is "lower" than S2.
So you should make this column an int-column and prepend S only where you display it. Then MAX(StockID) returns an Integer, you just have to cast it and add 1:
Using conn As New OleDbConnection("Connection-String")
Using cmd As New OleDbCommand(Stock_Gen, conn)
conn.Open()
Dim stockIDObj As Object = cmd.ExecuteScalar()
If stockIDObj IsNot Nothing Then
Dim maxStockId As Int32 = DirectCast(stockIDObj, Int32)
maxStockId += 1
' ...... '
End If
End Using
End Using
You should also change OPTION STRICT to ON. Then this would never compile since the same variable cannot be used for an Object, String and Integer which is very good since it prevents errors.
If you want to keep it as string you have to cast the substring always in the database which is less readable and less efficient. I also don't know how to do it in access.
If you want to change the type of column in an already populated table you should first add a new column with a similar name which is of type int. If all have S at the beginning you could first remove that, then you can update the new column with the casted int value. Finally you can delete the old column and rename the new to the old.
The root of this issue that StockID is a STRING and 'S1'>'S10' so for all StockId > 10 you get max = 'S1'.
As a fast fix try to change MAX(StockID) to:
SELECT 'S'+CAST(MAX(CAST(SUBSTRING(StockID,2,100) as int)) as varchar(100))
For ACCESS DB try to use:
SELECT "S" & cstr(MAX(CINT(MID(StockID,2,100))))
How do I run a query with VB that will return the TOP 10 results based on column appClickCount and update column appFAIList to 1 and anything below the TOP 10 will give the column appFAIList a value of 0?
Using sqlCon = New SqlConnection("Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\ITCSDatabase.mdf;Integrated Security=True")
sqlCon.Open()
Dim sqlText = "SELECT top 10 appClickCount " & _
"FROM appTable" & _
"UPDATE appTable SET appFAIList = 1"
Dim cmd = New SqlCommand(sqlText, sqlCon)
cmd.ExecuteScalar()
End Using
Run this SQL statement instead
with t as (
select *, rn=row_number() over (order by 1/0)
from appTable)
update t set
appFAIList = case when rn<=10 then 1 else 0 end
Your vb.net code is quite buggy though. You're using ExecuteScalar which is intended to return a single-row, single-column result. As written, it will give you the first appClickCount value.
The other issue is that using TOP 10 in SQL Server without a corresponding ORDER BY means that it will quite arbitrarily (aka "randomly") return any 10 records from the table.
UPDATE a
SET a.appFAIList = 1
FROM appTable AS a
INNER JOIN
(SELECT top 10 appClickCount FROM appTable) AS b ON a.SomeField = b.SomeField
SomeField - may be primary key of your table
I am dealing with a legacy application which is written in VB.Net 2.0 against a SQL 2000 database.
There is a single table which has ~125,000 rows and 2 pairs of fields with similar data.
i.e. FieldA1, FieldB1, FieldA2, FieldB2
I need to process a combined, distinct list of FieldA, FieldB.
Using SQL I have confirmed that there are ~140,000 distinct rows.
Due to a very restrictive framework in the application I can only retrieve the data as either 2 XML objects, 2 DataTable objects or 2 DataTableReader objects. I am unable to execute custom SQL using the framework.
Due to a very restrictive DB access policy I am unable to add a View or Stored Proc to retrieve as a single list.
What is the most efficient way to combine the 2 XML / DataTable / DataTableReader objects into a single, distinct, IEnumerable object for later processing?
I may have missed something here but could you not combine both DataTables using Merge?
DataTableA.Merge(DataTableB)
You can then use DataTableA.AsEnumerable()
Then see this answer on how to remove duplicates or
You can do this with a DataView as follows: dt.DefaultView.ToTable(True,[Column names])
This is the solution I came up with.
Combine the 2 DataTables using .Merge (thanks to Matt's answer)
Using this as a base I came up with the following code to get distinct rows from the DataTable based on 2 columns:
Private Shared Function GetDistinctRows(sourceTable As DataTable, ParamArray columnNames As String()) As DataTable
Dim dt As New DataTable
Dim sort = String.Empty
For Each columnName As String In columnNames
dt.Columns.Add(columnName, sourceTable.Columns(columnName).DataType)
If sort.Length > 0 Then
sort = sort & ","
End If
sort = sort & columnName
Next
Dim lastValue As DataRow = Nothing
For Each dr As DataRow In sourceTable.Select("", sort)
Dim add As Boolean = False
If IsNothing(lastValue) Then
add = True
Else
For Each columnName As String In columnNames
If Not (lastValue(columnName).Equals(dr(columnName))) Then
add = True
Exit For
End If
Next
End If
If add Then
lastValue = dr
dt.ImportRow(dr)
End If
Next
Return dt
End Function