(Ms Access) Row_Number() Over Partition - sql

How can I convert the row_number() function with an over partition on MS ACCESS? What I want to achieve is:
from this table:
ID | EntryDate
10 | 2016-10-10
10 | 2016-12-10
10 | 2016-12-31
10 | 2017-01-31
10 | 2017-03-31
11 | 2015-01-31
11 | 2017-01-31
To this output, showing only the top 3 latest of each ID:
ID | EntryDate
10 | 2016-12-31
10 | 2017-01-31
10 | 2017-03-31
11 | 2015-01-31
11 | 2017-01-31
On SQL Server, i can achieved this using the following code:
select T.[ID],
T.[AptEndDate],
from (
select T.[ID],
T.[AptEndDate],
row_number() over(partition by T.[ID] order by T.[AptEndDate] desc) as rn
from Table1 as T
) as T
where T.rn <= 3;

Consider a count correlated subquery which can work in any RDBMS.
select T.[ID], T.[EntryDate]
from
(select sub.[ID],
sub.[EntryDate],
(select count(*) from Table1 c
where c.ID = sub.ID
and c.[EntryDate] >= sub.[EntryDate]) as rn
from Table1 as sub
) as T
where T.rn <= 3;

It might be simpler and faster to use Top n - as you mention yourself:
Select T.[ID], T.[EntryDate]
From Table1 As T
Where T.[EntryDate] In
(Select Top 3 S.[EntryDate]
From Table1 As S
Where S.[ID] = T.[ID]
Order By S.[EntryDate] Desc)
Order By T.[ID] Asc, T.[EntryDate] Asc

Anything using the OVER clause is something known as a Windowing Function. Unfortunately, MS Access does not have support for Windowing Functions.The easiest solution in this case may be to back to VBA code :(

Public Const tableName As String = "[TransactionalData$]"
Public Const parentId As String = "parentId"
Public Const elementId As String = "Id"
Public Const informationalField As String = "Label"
Sub TransactionalQuery(Optional ByVal Id As Integer = 0)
Dim rs As New ADODB.Recordset, cn As New ADODB.Connection
Dim sqlString As String
''' setup the connection to the current Worksheet --- this can be changed as needed for a different data source, this example is for EXCEL Worksheet
cn.Open ("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & ThisWorkbook.FullName & ";Extended Properties='Excel 12.0 Macro;HDR=YES;IMEX=1'")
'''' Alternate method for the query
sqlString = "SELECT ParentId, Rank() OVER(PARTITION BY ParentId ORDER BY Label) , vlu.Id, vlu.Label FROM [TransactionalData$] var LEFT JOIN [TransactionalData$] vlu ON vlu.Id=var.ParentId"
''' will need to change the TableName (TransactionalData$]
sqlString = "SELECT DISTINCT " & elementId & " FROM " & tableName & " WHERE " & parentId & " = " & Id
rs.Open sqlString, cn, adOpenStatic, adLockReadOnly
'' Start collecting the SQL UNIONs to run at the end
sqlString = ""
Do While Not rs.EOF
'' Add current Element to the UNION
sqlString = sqlString & "SELECT * FROM " & tableName & " WHERE " & elementId & " = " & rs.Fields(elementId) & " UNION " & vbCrLf
'' Add all children element to the UNION
sqlString = sqlString & subQuery(cn, rs.Fields(elementId))
rs.MoveNext
Loop
rs.Close
'''Debug.Print sqlString
''' Remove the extra UNION keyword at the end
sqlString = Left(sqlString, Len(sqlString) - 8)
''' Exectue the built query
rs.Open sqlString, cn, adOpenStatic, adLockReadOnly
''Do While Not rs.EOF
'' Debug.Print rs.Fields(elementId) & ", " & rs.Fields(informationalField)
'' rs.MoveNext
''Loop
End Sub
Function subQuery(cn As ADODB.Connection, Id As Integer) As String
Dim sqlString As String
Dim subSqlString As String, rs As New ADODB.Recordset
'' Create a list of children for the current element
sqlString = "SELECT DISTINCT " & elementId & " FROM " & tableName & " WHERE " & parentId & " = " & Id
rs.Open sqlString, cn, adOpenStatic, adLockReadOnly
'' start the SQL for current elements children
sqlString = ""
Do While Not rs.EOF
''' add in the current element to the UNION
sqlString = sqlString & "SELECT * FROM " & tableName & " WHERE Id = " & rs.Fields(elementId) & " UNION " & vbCrLf
''' recursively find additional children for the current element
sqlString = sqlString & subQuery(cn, rs.Fields(elementId))
rs.MoveNext
Loop
rs.Close
''' return the SQL for the current element and all its children
subQuery = sqlString
End Function

Related

how to display columns in specific order, according to field value in MS Access

I want to display a query but the column order needs to be sorted according to the value in the column, field with greater value as column 1, then second greater value field as column 2, and so on. The query will only produce one row as it shows data for the current month. I know I could do this in VBA, with a bubble sort or something, and build the SQL query accordingly, but I was wondering if there was a quicker way to do this. Data is like this: DataMonth, Data1, Data2, Data3,... all the way to Data8. Please don't mention data structure is bad, as I am only trying to help someone here, and it is a bit too late to rebuild their tables structures...
No need to sort in the VBA. First get a record set that lists the columns in the right order with the Column Name as one of the columns - order that query by the value in each column. I used a query that got each single column with UNION e.g. here is my entire module
Option Compare Database
Option Explicit
Private Sub ReorderColumns()
Dim mnth As Integer
mnth = 1 'CHANGE MONTH
Dim qry As String
Dim rs As DAO.Recordset
qry = GetSQL(mnth)
Set rs = CurrentDb.OpenRecordset(qry)
Dim i As Integer: i = 0 'Count of columns added to query
qry = "SELECT "
While Not rs.EOF
i = i + 1
qry = qry & rs.Fields("ColName") & IIf(i < 8, ",", "")
rs.MoveNext
Wend
qry = qry & " FROM Table1 WHERE DataMonth = %s"
qry = stuffStr(qry, mnth)
rs.Close
'Get the results in the order you need
Set rs = CurrentDb.OpenRecordset(qry)
'Do something with the data here
rs.Close
Set rs = Nothing
End Sub
Public Function GetSQL(mnth As Integer) As String
Dim outp As String
outp = "SELECT 'Data1' as ColName, Data1 as DataValue FROM Table1 WHERE DataMonth = %s UNION ALL " & _
"SELECT 'Data2' as ColName, Data2 as DataValue FROM Table1 WHERE DataMonth = %s UNION ALL " & _
"SELECT 'Data3' as ColName, Data3 as DataValue FROM Table1 WHERE DataMonth = %s UNION ALL " & _
"SELECT 'Data4' as ColName, Data4 as DataValue FROM Table1 WHERE DataMonth = %s UNION ALL " & _
"SELECT 'Data5' as ColName, Data5 as DataValue FROM Table1 WHERE DataMonth = %s UNION ALL " & _
"SELECT 'Data6' as ColName, Data6 as DataValue FROM Table1 WHERE DataMonth = %s UNION ALL " & _
"SELECT 'Data7' as ColName, Data7 as DataValue FROM Table1 WHERE DataMonth = %s UNION ALL " & _
"SELECT 'Data8' as ColName, Data8 as DataValue FROM Table1 WHERE DataMonth = %s ORDER BY 2"
GetSQL = stuffStr(outp, mnth, mnth, mnth, mnth, mnth, mnth, mnth, mnth)
End Function
Function stuffStr(str As String, ParamArray subs()) As String
Dim i As Long
Dim outp As String
outp = str
For i = 0 To UBound(subs)
outp = Replace(Expression:=outp, Find:="%s", Replace:=subs(i), Count:=1, Compare:=vbTextCompare)
Next
stuffStr = outp
End Function
Not the most efficient way because of all the string manipulation, but it works

MS Access VBA multiple list box search form

I have a requirement to allow a user to search between two dates on a form and filter the data down further using multiple list boxes
Currently I allow the user to search between a from and to date... And also filter by products from a listbox.
If no products are selected in the list box, only display the results of the query between the two dates.
If the selection critera of the listbox is not empty, build the query WHERE with IN clause and then concanenate it to the SELECT statement, then execute the query to give desired results.
My question is... How would I do this for another four or five multi value list boxes? For example: Suppliers, Depots, Countries, Varieties etc etc
SearchAllReject is simply a function to query from and to date with no product filters.
Here is the code I already have:
Dim SQLAllReject As String
Dim strDateFrom As String
Dim strDateTo As String
Dim strFirstDate As Date
Dim strSecondDate As Date
Dim strINPRODUCT As String
Dim strWHERE As String
Dim strSTRING As String
Dim i As Integer
If Len(Me.txtDate.Value & vbNullString) = 0 Then
MsgBox ("Please input date from")
Exit Sub
ElseIf Len(Me.txtDateTo.Value & vbNullString) = 0 Then
MsgBox ("Please input date to")
Exit Sub
End If
strDateFrom = txtDate.Value
strDateTo = txtDateTo.Value
strFirstDate = Format(CDate(strDateFrom), "mm/dd/yyyy")
strSecondDate = Format(CDate(strDateTo), "mm/dd/yyyy")
For i = 0 To lstProduct.ListCount - 1
If lstProduct.Selected(i) Then
strINPRODUCT = strINPRODUCT & "'" & lstProduct.Column(1, i) & "',"
End If
Next i
If Len(strINPRODUCT & vbNullString) = 0 Then
SearchAllReject
Else
strWHEREPRODUCT = "AND dbo_busobj_file_rejections_load_temp5.Tesco_Product_Name IN " & _
"(" & Left(strINPRODUCT, Len(strINPRODUCT) - 1) & "))"
SQLAllReject = "SELECT dbo_busobj_file_rejections_load_temp5.Reject_Date AS [Date], " & _
"dbo_busobj_file_rejections_load_temp5.Depot_Number AS [Depot No], " & _
"dbo_busobj_file_rejections_load_temp5.Depot_Name AS [Depot], dbo_busobj_file_rejections_load_temp5.Tesco_Product_Name AS [Product]," & _
"dbo_busobj_file_rejections_load_temp5.Tesco_Brand_Name AS [Brand], dbo_busobj_file_rejections_load_temp5.Tesco_Packsize AS [Packsize], " & _
"dbo_busobj_file_rejections_load_temp5.TPNB, dbo_busobj_file_rejections_load_temp5.EAN, " & _
"dbo_busobj_file_rejections_load_temp5.Tesco_Country_of_Origin AS [Country], " & _
"dbo_busobj_file_rejections_load_temp5.Tesco_Variety AS [Variety], dbo_busobj_file_rejections_load_temp5.Tesco_Producer AS [Producer], " & _
"dbo_busobj_file_rejections_load_temp5.reject_qty AS [Quantity], dbo_busobj_file_rejections_load_temp5.batch_code AS [Batch Code], " & _
"dbo_busobj_file_rejections_load_temp5.site AS [Site], dbo_busobj_file_rejections_load_temp5.Tesco_Comment AS [Comment], " & _
"dbo_busobj_file_rejections_load_temp5.Tesco_Reason AS [Reason] " & _
"FROM dbo_busobj_file_rejections_load_temp5 " & _
"WHERE (((dbo_busobj_file_rejections_load_temp5.Reject_Date) Between #" & strFirstDate & "# And #" & strSecondDate & "#) "
strSTRING = SQLAllReject & strWHEREPRODUCT
Debug.Print strSTRING
Me.lstDeleteReject.RowSource = strSTRING
Me.lstDeleteReject.Requery
Consider building an entity-attribute table of all possible listbox values and use a saved SQL query which avoids any messy concatenation of SQL in VBA. A parameterized query using QueryDef is used to update the selected options of table of all list box values.
Table (myListBoxValues) (built once and updated with new values/categories)
Category|Value |Selected
--------|----------|--------
Product |Product A | 1
Product |Product B | 1
Product |Product C | 1
...
Country |USA | 1
Country |Canada | 1
Country |Japan | 1
Above can be populated with multiple append queries using SELECT DISTINCT:
INSERT INTO myListBoxValues ([Category], [Value], [Selected])
SELECT DISTINCT 'Product', Tesco_Product_Name, 1
FROM dbo_busobj_file_rejections_load_temp5 b
NOTE: It is very important to default all Selected to 1 for VBA purposes. See further below. Also, if you have a mix of number and string, consider using TextValue and NumberValue columns and adjust in SQL IN clauses. Save above query as a new object and place the named object behind target: lstDeleteReject.
SQL (built once, adjust form name)
Notice the form date values are directly incorporated into WHERE clause without any date formatting conversion or concatenation needs. Also, table alias is used to avoid long name repetition.
SELECT b.Reject_Date AS [Date],
b.Depot_Number AS [Depot No],
b.Depot_Name AS [Depot], b.Tesco_Product_Name AS [Product],
b.Tesco_Brand_Name AS [Brand], b.Tesco_Packsize AS [Packsize],
b.TPNB, b.EAN,
b.Tesco_Country_of_Origin AS [Country],
b.Tesco_Variety AS [Variety], b.Tesco_Producer AS [Producer],
b.reject_qty AS [Quantity], b.batch_code AS [Batch Code],
b.site AS [Site], b.Tesco_Comment AS [Comment],
b.Tesco_Reason AS [Reason]
FROM dbo_busobj_file_rejections_load_temp5 AS b
WHERE b.Reject_Date BETWEEN Forms!myFormName!txtDate
AND Forms!myFormName!txtDateTo
AND b.Tesco_Product_Name IN (
SELECT [Value] FROM myListBoxValues
WHERE [Category] = 'Product' AND [Selected] = 1
)
AND b.site IN (
SELECT [Value] FROM myListBoxValues
WHERE [Category] = 'Site' AND [Selected] = 1
)
AND b.Tesco_Producer IN (
SELECT [Value] FROM myListBoxValues
WHERE [Category] = 'Producer' AND [Selected] = 1
)
AND b.Depot_Name IN (
SELECT [Value] FROM myListBoxValues
WHERE [Category] = 'Depot' AND [Selected] = 1
)
AND b.Tesco_Country_of_Origin IN (
SELECT [Value] FROM myListBoxValues
WHERE [Category] = 'Country' AND [Selected] = 1
)
VBA (adjust list box names to actuals)
Dim qdef As QueryDef
Dim lstname As Variant
Dim sql As String
Dim i As Integer
sql = "PARAMETERS paramValue TEXT, paramCateg INTEGER; " _
& "UPDATE myListBoxes SET [Selected] = 0 " _
& "WHERE [Value] = paramValue AND [Category] = paramCateg"
Set qdef = CurrentDb.CreateQueryDef("", sql)
' ITERATE THROUGH ALL LISTBOXES BY NAME
For Each lstname in Array("lstProduct", "lstSite", "lstProducer", "lstDepot", "lstCountry")
For i = 0 To Me.Controls(lstname).ListCount - 1
' UPDATE IF AT LEAST ONE ITEM IS SELECTED
If Me.Controls(lstname).ItemsSelected.Count > 0
' UPDATE [SELECTED] COLUMN TO ZERO IF VALUES ARE NOT SELECTED
If Me.Controls(lstname).Selected(i) = False Then
qdef!paramValue = Me.Controls(lstname).Value
qdef!paramCategory = Replace(lstName, "lst", "")
qdef.Execute
End If
End If
Next i
Next lstname
Set qdef = Nothing
' REQUERY LISTBOX
Me.lstDeleteReject.Requery
' RESET ALL SELECTED BACK TO 1
CurrentDb.Execute "UPDATE myListBoxValues SET [Selected] = 1"
As you can see, much better readability and maintainability. Also, if users do not select any option, the date range filters are still applied and using your universal table of all list box values, all values will be selected to returns all non-NULL values.

VBScript Excel ADO connection: Get value using column alias in SQL query

In following code, I can get the count from excel using
objTempRecordset.Fields.Item(0).Value
However, I want to use column name alias in SQL.
i.e.
sSQL = "Select Count(*) AS RecCount FROM [NELimits$] A WHERE A.Type = 'A' AND A.ID = " &Chr(39) & "R001" & Chr(39)
and I want to get the result using:
objTempRecordset.Fields.Item("RecCount").Value
I also tried objTempRecordset.Fields.Item("_Count(*)_").Value but no luck
Can someone please let me know how to use column name alias in this case?
Note: Excel has 2 columns
ID: with values such as "R001", "R002"
Type: with values such as "A","B","C"
Sample code:
sSQL = "Select Count(*) FROM [NELimits$] A WHERE A.Type = 'A' AND A.ID = " &Chr(39) & "R003" & Chr(39)
Sqlquery = sSQL
sFilePath = "C:\Temp\DataSheet.xlsx"
Dim objTempConnection : Set objTempConnection = CreateObject("ADODB.Connection")
Dim objTempRecordSet : Set objTempRecordSet = CreateObject("ADODB.Recordset")
Dim strPath
'Define constants for objTempRecordset
Const adOpenStatic=3
Const adLockOptimistic=3
Const adLockPessimistic=2
Const adCmdText = &H001
'Open connection
objTempConnection.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source="& sFilePath &";Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
objTempRecordset.ActiveConnection = objTempConnection
objTempRecordset.CursorType = adOpenStatic
objTempRecordset.LockType = adLockOptimistic
objTempRecordset.Open Sqlquery
If objTempRecordset.EOF Or objTempRecordset.BOF Then
msgbox "no record"
End If
msgbox "Record Count: "&objTempRecordset.RecordCount
msgbox "Value:" & objTempRecordset.Fields.Item(0).Value
With the ACE SQL engine (used here in querying workbook), original field names, column aliases, or table names with spaces, special characters (non-alphanumeric), or reserved words need to be wrapped in square brackets or backticks to properly escape them.
Spaces
sSQL = "Select Count(*) AS [Rec Count] FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
sSQL = "Select Count(*) AS `Rec Count` FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
Special Characters (e.g., hyphen and pound/hashtag sign)
sSQL = "Select Count(*) AS [Rec-Count] FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
sSQL = "Select Count(*) AS `Rec-Count` FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
sSQL = "Select Count(*) AS [Rec#] FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
sSQL = "Select Count(*) AS `Rec#` FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
Reserved words (e.g., Count)
sSQL = "Select Count(*) AS [Count] FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
sSQL = "Select Count(*) AS `Count` FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
Otherwise, any field name or column alias is legitimate in query and can be read in record set in following formats:
objTempRecordset.Fields.Item(0).Value ' BY INDEX IN ITEM '
objTempRecordset.Fields.Item("Rec Count").Value ' BY NAME IN ITEM '
objTempRecordset.Fields("Rec Count").Value ' BY NAME IN FIELD COLLECTION '
objTempRecordset![Rec Count].Value ' BY NAME (EXCLAMATION POINT QUALIFIER) '
Furthermore, missing column aliases are handled in a special manner with ACE:
Missing Alias on Query Expression (e.g., Count function aggregation)
sSQL = "Select Count(*) FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
Missing Alias on Duplicate Field
sSQL = "Select ID, ID FROM [NELimits$] A" _
& " WHERE A.Type = 'A' AND A.ID = 'R003'"
For above two missing aliases, the ACE engine creates a column alias usually starting at Expr1 (inside MS Access -the usual interface to the ACE Engine) or Expr1000 for ODBC connections and incrementing for all other unnamed expressions or unnamed duplicate field references.

vb.net - using case select query in sql for jtable

If anyone knows how to use case in query for jtable, pls take a look my code.
Dim batch_status As String = " case when status = 0 then 'Created' when status = 1 then 'Scanning' when status = 2 then 'Scan Saved' end as status"
cmd.CommandText = "SELECT * FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY " & jtSorting & " ) AS RowNum, batch_id, batch_name, date_created, profile_id, total_page, " & batch_status & ", First_ScanID, file_id " & _
" FROM [ip_ent_site].[dbo].[tbl_batch] WHERE STATUS IN (0,1,2) ) AS RowConstrainedResult " & _
" WHERE RowNum >= #jtStartIndex AND RowNum < #jtEndIndex ORDER BY RowNum ; "
if i use like this, it is working:
Dim batch_status As String = "status"
but with case, not.
whats problem here?
another part of code:
If dt.Rows.Count > 0 Then
students = (From item In dt.AsEnumerable() Select New Class1 With { _
.No = Convert.ToInt32(item(1)), _
.batch_id = Convert.ToInt64(item(2)), _
.batch_name = DirectCast(item(3), String), _
.date_created = item(4).ToString, _
.profile_id = Convert.ToInt32(item(5)), _
.total_page = Convert.ToInt32(item(6)), _
.status = Convert.ToInt32(item(7)), _
.First_ScanID = Convert.ToInt32(item(8)), _
.file_id = CheckDBNullInteger(item(9)) _
}).ToList()
End If
Use your declare statement like below.
Dim batch_status As String = " case when status = 0 then ''Created'' when status = 1 then ''Scanning'' when status = 2 then ''Scan Saved'' end as status"
may be the problem with #jtStartIndex AND #jtEndIndex.
Just found, change .status = Convert.ToInt32(item(7)) to .status = item(7).ToString,
displaying in Jtable is quite different

adding results of query against datatable to a column in another datatable in visual basic

Problem: I have populated a datatable using a query on a sql server database. The query is:
Select ID, startDate, codeID, Param,
(select top 1 startDate from myTable
where ID='" & ID & "' and Param = mt.param and codeID = 82 and startDate >= '" & startDate & "' and startDate >=mt.startDate
ORDER BY startDate)endTime,
from myTable mt where ID = '" & ID & "'
AND (startDate between '" & startDate & "' AND '" & endDate & "' AND (codeID = 81))
I want a new column called duration that will be the difference between endTime and startDate in milliseconds. I can't just add another subquery to the above query since the endTime column didn't exist until the subquery was ran.
So, is there a way to maybe run the first query to populate the datatable, then run a query like:
Select DateDiff(ms,endTime,startDate)
On its own and add its results to a new column in my datatable?
You can always nest that in another query:
select *, datediff(ms, startDate, endTime)
from (
<your existing query here>
) t
And while I'm here, it seems you need a lesson in parameterized queries:
Dim result As New DataTable
Dim Sql As String = _
"SELECT *, datediff(ms, startDate, endTime) FROM (" & _
"SELECT ID, startDate, codeID, Param, " & _
"(select top 1 startDate from myTable " & _
"where ID= #ID and Param = mt.param and codeID = 82 and startDate >= #startDate and startDate >=mt.startDate " & _
"ORDER BY startDate) endTime " & _
" FROM myTable mt " & _
" WHERE ID = #ID AND startDate between #startDate AND #endDate AND codeID = 81" & _
") t"
Using cn As New SqlConnection("connection string"), _
cmd As New SqlCommand(sql, cn)
cmd.Parameters.Add("#ID", SqlDbType.VarChar, 10).Value = ID
cmd.Parameters.Add("#startDate", SqlDbType.DateTime).Value = Convert.ToDateTime(startDate)
cmd.Parameters.Add("#endDate", SqlDbType.DateTime).Value = Convert.ToDateTime(endDate)
cn.Open()
Using rdr = cmd.ExecuteReader()
result.Load(rdr)
rdr.Close()
End Using
End Using