MS Access VBA multiple list box search form - sql

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.

Related

filter report based on date

I have a table with date column a query for the same table and a report, on my form I have a button which loads the report using vba code which is below, I have two text fields to filter date on the report, my issue is with the date it is not being filtered. my code is below
Public Function makeMWO_RPT(Optional excelMode As Boolean = False)
Dim ctlArr(3) As String
ctlArr(0) = "Combo79"
ctlArr(1) = "Combo82"
ctlArr(2) = "WOPRCO"
ctlArr(3) = "Combo165"
Dim fldArr(3) As String
fldArr(0) = "[Group]"
fldArr(1) = "[User status]"
fldArr(2) = "[Priority]"
fldArr(3) = "iif([orders].[User status] In (""Closed"",""Open""),""YES"",""NO"")"
'for date -- for date -- for date -- for date -- for date
Dim qDef, sqlStr, frm As Form
Set frm = Forms("Statusfrm")
Set qDef = CurrentDb.QueryDefs("OrderNewQry")
sqlStr = "SELECT orders.[Group], orders.Order, orders.Description,orders.Post_Form, orders.Attachment, orders.Video_link, orders.[Estimated costs], orders.[Total], orders.Location, orders.[System status], orders.Priority, orders.date, orders.[User status], " & _
"orders.Equipment, orders.Diff, orders.Remarks,[orders].[User status] In (""Closed *"",""Open *"") AS Completed
"FROM orders"
Dim filtrStr As String
If Not IsNull(frm("Text489")) Then
filtrStr = "[date] >= #" & frm("Text489") & "#"
End If
If Not IsNull(frm("Text491")) Then
filtrStr = filtrStr & " AND [date] <= #" & frm("Text491") & "#"
End If
If filtrStr <> "" Then
sqlStr = sqlStr & " WHERE " & filtrStr
End If
qDef.SQL = sqlStr
'for date -- for date -- for date -- for date -- for date
Dim baseSQL, qName, rName
baseSQL = "SELECT orders.[Group], orders.Order, orders.Description,orders.Post_Form, orders.Attachment, orders.Video_link, orders.[Estimated costs], orders.[Total], orders.Location, orders.[System status], orders.Priority, orders.date, orders.[User status], " & _
"orders.Equipment, orders.Diff, orders.Remarks,[orders].[User status] In (""Closed *"",""Open *"") AS Completed
"FROM orders"
qName = "OrderNewQry"
rName = "FUllOpenOrdersRpt"
makeGenericRPT ctlArr, fldArr, baseSQL, qName, rName, excelMode
End Function

Is there a way to combine all the outputs into one in MS-ACCES?

I want to make a SQL Filter in MS-Acces where I have the Option to filter all the Elements in the table. But when I make the UNION SELECT, I can show only 1 column in each row, so I made database_geräte.* to database_geräte.ID .
This works fine now, but I want all the Outputs from the database_geräte.ID select into one row, so that I can Filter all of them at once.
I tried to make a GROUPCONCAT, but that gives me an error.
SELECT database_geräte.ID, dbo.GROUPCONCAT (STRINGVALUE) FROM database_geräte
UNION
SELECT database_geräte.Gerät FROM database_geräte;
I also tried to make a count on the
database_geräte.ID
But then I get the value of the database_geräte.ID select, which doesn't fit in the filter because a ID with that number doesn't exist...
The SQL Select:
SELECT database_geräte.ID, dbo.GROUPCONCAT FROM database_geräte
UNION
SELECT database_geräte.Gerät FROM database_geräte;
The SQL filter in VBA:
sql = "SELECT* FROM database1 WHERE Gerät = '" & Me.GeräteFilter & "'"
Me.sb_1.Form.RecordSource = sql
Me.sb_1.Form.Requery
So the Filter should show an option where I can filter all the elements of the table and show it in the subform.
I just made an UNION that gives me all the data at once.
The SQL Select:
SELECT ID, Gerät FROM database_geräte UNION select "*" as ID, "Alle" as Gerät from database_geräte;
Then I made a function, that I can give to two Filters:
sql = "SELECT database_geräte.Gerät, database1.Name, database1.Grund, database1.Gerät_ID" _
& " FROM database_geräte INNER JOIN database1 ON database_geräte.ID = database1.Gerät_ID " _
& "" & IIf(Me.GeräteFilter <> "*", "Where database1.Gerät_ID = " & Me.GeräteFilter & " ", "") & " " _
& "" & IIf(Me.Person <> "Alle", IIf(Me.GeräteFilter <> "*", " AND database1.Name = '" & Me.Person & "'", "WHERE database1.Name = '" & Me.Person & "'"), "") & ""
Me.sb_1.Form.RecordSource = sql
Me.sb_1.Form.Requery

(Ms Access) Row_Number() Over Partition

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

How to merge cells and remove blank spaces in my DataGrid using proper loop

My title is still broad so i'll explain here further.
This is my current output using my code:
.
But I want to make it look like this..
As you can see on the pictures, i want to remove the blank spaces. Because if I selected MORE data, let's say I selected 7 more days, it will go DIAGONALLY not horizontally.
I think I have a problem regarding my loops. Hope you can help me trace because I've been stuck here for a week debugging. (nevermind my long query, i just want to post all my code. I've also added comments for easier debugging.)
Here's my code:
Private Sub LoadDateAndUser()
Dim SqlStr As String = ""
Dim sqlConn As New SqlConnection(DataSource.ConnectionString)
Dim sqlComm As New SqlCommand(SqlStr, sqlConn)
Dim sqlAdapter As New SqlDataAdapter(sqlComm)
Dim o_Dataset As New DataSet()
SqlStr = " SELECT convert(varchar(10), A.TransDate, 101) as TransDate,ADMMED.TransNum, ADMMED.AdministeredDate, D.Dosage [Dosage], ISNULL(C.GenericName, ' ') + ' (' + IsNull(B.ItemName,'') + ' ' + IsNull(B.ItemDesc,'') + ')' [Medication], ADMMED.UserID" & _
" FROM INVENTORY..tbInvStockCard as A" & _
" LEFT OUTER JOIN INVENTORY..tbInvMaster as B On A.ItemID = B.ItemID " & _
" LEFT OUTER JOIN Inventory.dbo.tbForGeneric as C On B.GenericID = C.GenericID" & _
" LEFT OUTER JOIN Station..tbNurse_AdministeredMedicines ADMMED on a.idnum= ADMMED.idnum " & _
" LEFT OUTER JOIN build_file.dbo.tbCoDosage as D on A.DosageID = D.DosageID" & _
" LEFT OUTER JOIN Station.dbo.tbNurseCommunicationFile as E on A.IdNum = E.IDnum and E.ReferenceNum = A.RefNum" & _
" WHERE A.IdNum = '" & Session.Item("IDNum") & "' and ( A.RevenueID = 'PH' or A.RevenueID = 'PC' ) " & _
" AND A.LocationID = '20' and Not IsNull(ADMMED.AdministeredDate, '') = ''" & _
" AND A.RefNum = ADMMED.ReferenceNum and ADMMED.ItemID = A.itemid" & _
" AND (B.ItemClassificationID = '1' or B.ItemClassificationID = '10' or B.ItemClassificationID = '11' or B.ItemClassificationID = '16' or B.ItemClassificationID = '2' or B.ItemClassificationID = '9')" & _
" order by TransDate desc,ADMMED.AdministeredDate desc"
sqlComm.CommandText = SqlStr
sqlAdapter.Fill(o_Dataset, "Table")
Dim o_Row As DataRow
Dim o_AdmDates As New Collection()
Dim s_FormattedLastAdmDate As String = ""
Dim s_FormattedAdmDate As String = ""
Dim o_DerivedTable As New DataTable()
With o_DerivedTable
.Columns.Add("TransDate")
.Columns.Add("Medication")
.Columns.Add("Dosage")
.Columns.Add("TransNum")
End With
'Select all unformatted administered dates from the query
Dim o_UnformattedAdmDates As DataRow() = o_Dataset.Tables(0).Select("", "AdministeredDate Desc")
'Extract distinct administered dates and change its format
For Each o_Row In o_UnformattedAdmDates
s_FormattedAdmDate = Format(CDate(o_Row.Item("AdministeredDate")), KC_Date_Format) 'eg. Jan 01 15
If s_FormattedLastAdmDate <> s_FormattedAdmDate Then
s_FormattedLastAdmDate = s_FormattedAdmDate
o_AdmDates.Add(s_FormattedLastAdmDate) 'add all formatted dates in o_AdmDates
End If
Next
'Add formatted administred dates to derived table
Dim o_Item As String
For Each o_Item In o_AdmDates
o_DerivedTable.Columns.Add(o_Item)
Next
'Loop through the administred date
Dim o_NewRow As DataRow
Dim o_NextRow As DataRow
Dim i_Ctr As Integer
Dim x_isNewRow As Boolean = True
Dim i_MaxRec As Integer
i_MaxRec = o_Dataset.Tables(0).Rows.Count - 1
For i_Ctr = 0 To i_MaxRec
o_Row = o_Dataset.Tables(0).Rows(i_Ctr)
If i_Ctr <> i_MaxRec Then
o_NextRow = o_Dataset.Tables(0).Rows(i_Ctr + 1)
End If
If x_isNewRow Then
o_NewRow = o_DerivedTable.NewRow()
End If
o_NewRow("TransDate") = o_Row("TransDate")
o_NewRow("Medication") = o_Row("Medication")
o_NewRow("Dosage") = o_Row("Dosage")
o_NewRow("TransNum") = o_Row("TransNum")
'Fill approriate result date column based on query
For Each o_Item In o_AdmDates
s_FormattedAdmDate = Format(CDate(o_Row.Item("AdministeredDate")), KC_Date_Format)
Dim AdmTim As DateTime = DateTime.Parse(o_Row("AdministeredDate"))
If s_FormattedAdmDate = o_Item Then
o_NewRow(s_FormattedAdmDate) = AdmTim.ToString("hh:mm tt") + " - " + o_Row("UserID")
End If
Next
If i_Ctr < i_MaxRec _
And Not o_NextRow Is Nothing _
And o_Row("TransDate") = o_NextRow("TransDate") _
And o_Row("Medication") = o_NextRow("Medication") _
And o_Row("Dosage") = o_NextRow("Dosage") _
And o_Row("AdministeredDate") = o_NextRow("AdministeredDate") Then
x_isNewRow = False
Else
o_DerivedTable.Rows.Add(o_NewRow)
x_isNewRow = True
End If
Next
'Bind derived table
dgSheet.DataSource = o_DerivedTable
dgSheet.DataBind()
If o_Dataset.Tables(0).Rows.Count > 0 Then
GroupGridView(dgSheet.Items, 0, 3)
Else
End If
End Sub
I think you must review your programming logic:
After that huge ugly SqlStr : you will have a DataSet, with a Table with all rows mixed !?
Let's try a pseudo-code:
I think is better to create in that DataSet, 2 Tables:<br>
**first** table with: id, DateOrder, Medication, Dosage <br>
and **second** table with: idDate, FirstTable.id, AdministeredDate
after that you know how many ADMMED.AdministeredDate.Count are, because you must know how manny columns you need to add
create a 3-rd table from iteration of first table, nested with second by ID.
Set as Datasource for DataGridView the Third DataTable.
So you have 2 datasets, and generate this one .. one to many ..
.. I have no time now, if you don't get the ideea .. forget it !

SQLite terribly slow compared to MS Access

Following the advice of fellow SO-ers, I converted an MS Access database I had (a small one, for test reasons) to SQLite. It has two tables, one with 5k entries and another with 50k entries.
Now, the queries I will present below QuLimma and QLexeis took about 60ms (total time of the function below) with Access, but a whopping 830ms with SQLite.
Dim i As Integer
Dim ms As Integer
ResultPin(0) = ""
ResultPin(1) = ""
ResultPin(2) = ""
ResultPin(3) = ""
ResultPin(4) = ""
i = 0
Multichoice = 0
ms = 0
Dim rsTblEntries As ADODB.Recordset
Set rsTblEntries = New ADODB.Recordset
Dim QuLimma As String, QLexeis As String
QuLimma = "SELECT Words.limma, Words.limmabody, Words.limmapro " & _
"FROM Words " & _
"GROUP BY Words.limma, Words.limmabody, Words.limmapro " & _
"HAVING (((Words.limma)='" & StrLexeis & "'));"
QLexeis = "SELECT Limma.limmalexeis, Words.limma, Limma.limmabody, Words.limmapro, Limma.limmaexp " & _
"FROM Limma INNER JOIN Words ON Limma.limmabody = Words.limmabody " & _
"GROUP BY Limma.limmalexeis, Words.limma, Limma.limmabody, Words.limmapro, Limma.limmaexp " & _
"HAVING (((Limma.limmalexeis)='" & StrLexeis & "'));"
rsTblEntries.Open QuLimma, CnDataParSQLite ', adOpenStatic, adLockOptimistic
If rsTblEntries.EOF = True Then
rsTblEntries.Close
rsTblEntries.Open QLexeis, CnDataParSQLite ', adOpenStatic, adLockOptimistic
If rsTblEntries.EOF = True Then
SearchQParagSQLite = False
Else
SearchQParagSQLite = True
Do While rsTblEntries.EOF = False
ms = ms + 1
rsTblEntries.MoveNext
Loop
rsTblEntries.MoveFirst
If ms > 1 Then
Do While rsTblEntries.EOF = False
ResultTemp(0, i) = rsTblEntries.Fields("limma").Value & "" 'rsWordPar!limma
ResultTemp(1, i) = rsTblEntries.Fields("limmalexeis").Value & "" 'rsWordPar!limmalexeis
ResultTemp(2, i) = rsTblEntries.Fields("limmabody").Value 'rsWordPar!limmabody
If IsNull(rsTblEntries.Fields("limmapro").Value) = False Then ResultTemp(3, i) = rsTblEntries.Fields("limmapro").Value 'rsWordPar!limmapro
rsTblEntries.MoveNext
i = i + 1
Multichoice = 1
Loop
Else
Do While rsTblEntries.EOF = False
ResultPin(0) = rsTblEntries.Fields("limma").Value & "" 'rsWordPar!limma
ResultPin(1) = rsTblEntries.Fields("limmalexeis").Value & "" 'rsWordPar!limmalexeis
ResultPin(2) = rsTblEntries.Fields("limmabody").Value 'rsWordPar!limmabody
If IsNull(rsTblEntries.Fields("limmapro").Value) = False Then ResultPin(3) = rsTblEntries.Fields("limmapro").Value 'rsWordPar!limmapro
rsTblEntries.MoveNext
Multichoice = 0
Loop
End If
End If
Else
SearchQParagSQLite = True
rsTblEntries.MoveFirst
Do While rsTblEntries.EOF = False
ResultPin(0) = rsTblEntries.Fields("limma").Value & "" 'rsWordPar!limma
ResultPin(1) = "#"
ResultPin(2) = rsTblEntries.Fields("limmabody").Value 'rsWordPar!limmabody
If IsNull(rsTblEntries.Fields("limmapro").Value) = False Then ResultPin(3) = rsTblEntries.Fields("limmapro").Value 'rsWordPar!limmapro
rsTblEntries.MoveNext
i = i + 1
Loop
End If
i = 0
rsTblEntries.Close
Set rsTblEntries = Nothing
With connection string:
CnDataParSQLite.ConnectionString = "DRIVER=SQLite3 ODBC Driver;" & _
"Database=" & strDataPath & "u.sl3;LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;"
CnDataParSQLite.Open
Now, before someone asks "wasn't 60ms fast enough?", I'd like to say that I did this because I have other Access files and queries which take 3-4 seconds and would like to lower them down, so yes, I was hoping to go down from 60ms to 30 or less in this one.
Do I have a misconfiguration or is it just that SQLite is not faster? I have checked, both return correct results, there is no weird looping issue.
Edit: most of the time is consumed by the second query.
Edit 2: (copy/paste from the db.sql)
Table Limma:
CREATE TABLE Limma ( id INTEGER PRIMARY KEY, limmabody INTEGER DEFAULT 0, limmalexeis VARCHAR2(100), limmastat VARCHAR2(50), limmaexp VARCHAR2(250));
INSERT INTO Limma VALUES (1, 1, 'υψικάμινος', 'ΣΠ', NULL);
INSERT INTO Limma VALUES (2, 1, 'υψίκορμος', 'ΣΠ', NULL);
INSERT INTO Limma VALUES (3, 1, 'υψίπεδο', 'ΑΠ', '<αρχ. υψίπεδον, ουδ. του επιθ. υψίπεδος<ύψι "ψηλά" + πέδον');
Total: 64k entries
Table Words:
CREATE TABLE Words ( id INTEGER PRIMARY KEY, limma VARCHAR2(100), limmabody INTEGER DEFAULT 0, limmapro VARCHAR2(200));
INSERT INTO Words VALUES (1, 'υψι (αχώριστο μόριο)', 1, NULL);
INSERT INTO Words VALUES (2, 'ομο (αχώριστο μόριο)', 2, NULL);
INSERT INTO Words VALUES (3, 'διχο (αχώριστο μόριο)', 3, NULL);
Total: 6k entries
The first field "id" is unique.
You almost never want to use HAVING where you can use WHERE criteria. You're evaluating all possible results and then culling them down after aggregation. You mainly want to use HAVING criteria where you're trying to cull down based upon the aggregated results. You can achieve the same thing by moving the HAVING logic to a WHERE criteria before the aggregation in this case. This should greatly speed up your query.
There is also no need to use GROUP BY logic since you're not returning any aggregates, just use DISTINCT.
I would write it like this:
QuLimma = "SELECT DISTINCT Words.limma, Words.limmabody, Words.limmapro " & _
"FROM Words " & _
"WHERE Words.limma ='" & StrLexeis & "';"
QLexeis = "SELECT DISTINCT Limma.limmalexeis, Words.limma, Limma.limmabody, Words.limmapro, Limma.limmaexp " & _
"FROM Limma INNER JOIN Words ON Limma.limmabody = Words.limmabody " & _
"WHERE Limma.limmalexeis ='" & StrLexeis & "';"
For these two queries with your table schema these indexes should optimize the queries:
CREATE NONCLUSTERED INDEX ix_words_1 ON Words (Limma) INCLUDE (Limmabody, Limmapro)
CREATE NONCLUSTERED INDEX ix_words_2 ON Words (Limmabody) INCLUDE (Limma, Limmapro)
CREATE NONCLUSTERED INDEX ix_limma_1 ON Limma (Limmabody, Limmalexeis) INCLUDE (Limmaexp)
Keep in mind there is a cost at the time of insert for each additional index you have. You have to weigh this cost against the benefit of the index. If your tables contain static data then there is no harm.