MySQL SELECT with varying WHERE fields - mysql

At a high-level this sounds trivial, but it turns out I've been scratching my head for a a couple of hours.
Situation:
I have table T, with columns a,b,c,d,e. Column a holds a string, while b,c,d,e each hold a boolean value.
I am allowing a user to perform a kind of search, where I prompt the user to enter values for a,b,c,d,e, and return all the rows where those values all match.
In a perfect world, the user enters all values (lets say a="JavaScript" , b="true", c="false", d="false", e="true") and a resulting query (In Scala, referencing a remote DB running MySQL) might look something like this:
connection.createStatement().executeQuery("SELECT * FROM T
WHERE a = '" + a_input + "'
and b = " + b_input + "
and c = " + c_input + "
and d = " + d_input + "
and e = " + e_input + ";")
Problem:
I give the user the option to 'loosen' the constraints, so it is possible that a_input="" and b_input="", etc... Potentially all fields a,b,c,d,e can be empty ("") If a field is omitted, it should not affect the resulting response. In other words, if c is not entered, the result can contain entries where c is TRUE or FALSE
Question:
How do I write ONE query that covers the situation where potentially all fields can be empty, or just some, or none?

Just build the WHERE dynamically. Instead of always searching for c = 'whatever', only include c in the WHERE if the user supplied a value for it.

you could use
Select *
from table
where a in ('','othervalue','othervalue')
and b in ('','othervalue','morevalues')
and so on.....that is like using an or for each field and it will match even if it's empty

This is tricky because the DB contains booleans but the parms are strings. Are the parms always blank. 'true', or 'false'? If so try
(B_input=''
Or (b_input=''true' and b)
Or (b_input='false' and ((not b)))

Related

Optimizing "OR" operator to get around SQL Exception "maximum number of expressions in a list is 1000"

I have a query using the IN operator, in an array with 1000+ values. I've searched the error so far but couldn't find what I want : my SELECT is not optimized because I'm using the OR operator and it takes quite a lot of time.
I'm chopping my query into different arrays at the moment :
query = "SELECT\n" +
" filename,\n" +
" status\n" +
"FROM\n" +
" table1\n" +
"WHERE\n" +
" filename IN " + allFileNames[0];
for(int m=1; m<allFileNames.length; m++) {
query +=
" OR\n" +
" filename IN " + allFileNames[m];
}
What this does is essentially I have the allFileNames array at the moment, and each element of the array contains a string with 1000 file names. I'm using a OR operator on each element of the array.
How can I optimize all of this ? Is it possible without creating a temporary table ? Maybe using a substring, but I haven't quite found the solution yet.
Thanks in advance!
EDIT: I can't use temporary table since I don't have writing access to the database
Assuming single allFileNames entry is a single file name, then you can flatten everything under one IN statement.
query = "SELECT\n" +
" filename,\n" +
"FROM\n" +
" table1\n" +
"WHERE\n" +
" filename IN (" + allFileNames.stream().collect(Collectors.joining(","))
+")"
Because all of your conditions, joined by OR statements are using same property, you should merge them.
But if your allFileNames collections is too large, then you may consider executing your query in batches:
split your allFileNames collection to batches of 100-1000 then execute query for each batch
aggregate all results

Access will only retrieve data from 1 table in a join with no where clause

I am using MS-Access to get information from 2 tables. I have used inner join, left, right, and outer with all variations, and it will either pull 1 row when 316 are expected, all data for the fields in test with no values for the fields from test 1, or all data for fields from test 1, and no values for test. How do I resolve this? The actual fields had to be changed for privacy, but the below is the exact layout.
SELECT [TEST].a,
[TEST].b,
[TEST].c,
[TEST 1].[D],
[TEST].E,
[TEST].F,
[TEST].G,
[TEST].H,
[TEST 1].[I],
[TEST].J,
[TEST].K,
[TEST 1].L,
[TEST 1].[M]
FROM [TEST 1]
INNER JOIN [TEST] ON [TEST 1].[ID] = [TEST].[CLAIMSNO];
This is a data-validation and debugging exercise, so if you can't share concrete example data then there is really no definite answer to this question. Technically it may not be answerable according to common StackOverflow standards, but I feel generous right now.
Since the joined fields are text, there are various possibilities keeping them from matching: extra spaces, null-terminated strings, case sensitivity (although Access by default should be case insensitive), wide (Unicode) vs narrow (ASCII, UTF-8) encoding, etc. You did not reveal where the data came from, nor how it was loaded into the database, so I make no assumptions. In order to understand the data and determine the reason for the failed matches, you need to investigate the details of the strings. You could likely narrow the problem by investigating the source of the data values and understanding the range of possible characters, encoding, string termination, etc.
Since you are already having troubles matching data AND since you already indicated that the original tables had no primary key or indexes, I highly recommend adding a new AutoNumber field with a unique index to each table, perhaps named [AID] (for AutoNumber ID). Do this even if you have added indices to existing columns. This will at least provide a reliable "handle" to select and refer to a particular row while debugging the other columns.
The big idea is to use VBA or other built-in functions to inspect and report on various attributes of the string values. There are just too many ways you could do this, but my preference is to create a public VBA function in a normal VBA module and then call this function from an SQL query. Although you could do this for every row, instead I suggest manually choosing rows from each table which you think should match... record the [AID] value for each row. If the manually-selected rows don't result in anything enlightening, then run it against an entire table and see what interesting results you get.
Consider these functions:
Public Function CheckSpaces(val As Variant) As String
Dim result As String
If IsNull(val) Then
result = "Null"
ElseIf VarType(val) = VbVarType.vbString Then
If Len(val) = 0 Then
result = "Empty String"
Else
Dim temp As String
Dim n As Integer, m As Integer
n = Len(val)
result = "Length " & n
temp = LTrim(val)
m = Len(temp)
If n <> m Then
result = result & " AND " & (n - m) & " left spaces"
End If
temp = RTrim(val)
m = Len(temp)
If n <> m Then
result = result & " AND " & (n - m) & " right spaces"
End If
End If
Else
result = "Not a string!"
End If
CheckSpaces = result
End Function
Public Function NullChar(val As Variant) As Boolean
Dim result As Boolean
result = False
If Not IsNull(val) Then
If VarType(val) = VbVarType.vbString Then
If InStr(val, vbNullChar) > 0 Then 'vbNullChar = Chr(0)
result = True
End If
End If
End If
NullChar = result
End Function
And execute queries similar to the following. Let's say that [Test 1] row AID = 10 has [ID] == 'name'. Likewise, imagine row AID == 20 of [Test] has [CLAIMSNO] = ' name ':
SELECT [ID], CheckSpaces([ID]), NullChar([ID])
FROM [TEST 1]
WHERE [AID] = 10
and
SELECT [CLAIMSNO], CheckSpaces([CLAIMSNO]), NullChar([CLAIMSNO])
FROM [TEST]
WHERE [AID] = 20
Compare the returned values. Is there anything that indicates a failed match?

Concatenate Multiple Strings While Ignoring Null Values

I'm trying to figure out a solution on how to concatenate strings from about 15 different options. Each result comes from a checkbox that is selected based on the state a person has lived in within a certain area.
I know how to turn the checkbox option into a text result. What I'm looking for is how to take these text results, combine them, then ignore null results so there isn't any weird spacing or formatting.
In short, if someone select 3 of the 15 results it would combine the 3 results cleanly and ignore the rest. Example would be: FL, CA, NY
There are, of course, multiple ways that this can be achieved, and since you didn't provide any code or examples of how you are attempting to do this, I will provide two options.
1 - You can concatenate the values using a combination of the & and + operators.
For example, let's say you have 15 checkboxes, all named similarly like chkState01, chkState02 ... through chkState15. And for the simplicity of my sample code, let's assume that when referencing the checkbox control directly in code as chkState01 that it will return either the 2 letter string abbreviation of the State it represents (i.e. NY) if the checkbox was checked, or it will return Null if the checkbox was not checked. With that, you could get your results in 2 ways:
Option A
StateList = (chkState01 + ",") & (chkState02 + ",") & (chkState03 + ",") ....
If those 3 check boxes returned the following values
chkState01 = "NY"
chkState02 = Null
chkState03 = "FL"
Then the result of that concatenation code would be:
NY,FL,
Notice that the string ends with an extra comma (,) and always would since you can't know ahead of time how many of the checkboxes will be checked. You would then need to trim that comma from your list before using it most likely.
Option B
'Create the list with a trailing comma that will need removed
Dim x as Integer
For x = 1 to 15
StateList = StateList & (Me("chkState" & Format(x, "00")) + ",")
Next x
or, you could do:
'Create the list without a trailing comma
Dim x as Integer
For x = 1 to 15
If Not IsNull(Me("chkState" & Format(x, "00"))) Then
If Len(StateList) > 0 Then
StateList = StateList & "," & Me("chkState" & Format(x, "00"))
Else
StateList = Me("chkState" & Format(x, "00"))
End If
End If
Next x
Notice that you can reference a control on a form by "generating" the name of that control as a string and referencing it in the Me("yourcontrolname") format. This is one advantage to naming controls that are similar in a fashion that lends itself to a looping structure like this. The Format command formats the number returned by x as a 2 digit with leading zeros i.e. 1 becomes 01
Further, using & to concatenate two items, where at least one of them is a string, will always result in a string output (Null & "S" = "S"). However, using the + to concatenate two items, where at least one of them is a Null, will always result in Null output (Null + "S" = Null). Hence the checkboxes where the value returns Null does not cause additional commas to be included in the result.
2 - You can write more complicated code to dynamically loop through the checkboxes and build the output list.
More likely, you are going to need to use additional code to determine which checkbox is which state abbreviation and to return the correct string value. Maybe you made the state abbreviation part of the checkbox name i.e. chkState_NY, chkState_FL or maybe you have put the abbreviation in the Tag property of each checkbox.
Let's say you used special control naming chkState_NY, chkState_FL. You could do the following:
Dim ctl as Access.Control
For Each ctl in Me.Controls
If ctl.Name Like "chkState_??" Then
If ctl.Value = True Then
If Len(StateList) > 0 Then
StateList = StateList & "," & Right(ctl.Name,2)
Else
StateList = Right(ctl.Name,2)
End If
End If
End If
Next ctl

How to write Case Sensitive Query for MS Access?

I want to know the Select Query for MS Access with case sensitive.
I have two values for VitualMonitorName as below
VCode VirtualMonitorName
Row 1 (1, 'VM1');
Row 2 (2, 'Vm1');
Here both values are different.
If I write
"SELECT VCode FROM VirtualMaster WHERE VirtualMonitorName like '" + Vm1 + "'";
It replies VCode = 1 Only.
You can use the StrComp() function with vbBinaryCompare for a case-sensitive comparison. Here is an example from the Immediate window to show how StrComp() works. See the Access help topic for more details.
? StrComp("a", "A", vbBinaryCompare)
1
? StrComp("a", "A",vbTextCompare)
0
StrComp() returns 0 if the first two arguments evaluate as equal, 1 or -1 if they are unequal, and Null if either argument is Null.
To use the function in a query, supply the vbBinaryCompare constant's value (0) rather than its name.
SELECT VCode
FROM VirtualMaster
WHERE StrComp(VirtualMonitorName, "Vm1", 0) = 0;
This approach is also available to queries from other applications if they use the newer Access Database Engine ("ACE") drivers. For example, the following C# code
string myConnectionString =
#"Driver={Microsoft Access Driver (*.mdb, *.accdb)};" +
#"Dbq=C:\Users\Public\Database1.accdb;";
using (OdbcConnection con = new OdbcConnection(myConnectionString))
{
con.Open();
using (var cmd = new OdbcCommand())
{
cmd.Connection = con;
cmd.CommandText =
"SELECT COUNT(*) AS n FROM [VirtualMaster] " +
"WHERE StrComp([VirtualMonitorName],?,?) = 0";
cmd.Parameters.AddWithValue("?", "Vm1");
cmd.Parameters.Add("?", OdbcType.Int);
var vbCompareOptions = new Dictionary<string, int>()
{
{"vbBinaryCompare", 0},
{"vbTextCompare", 1}
};
string currentOption = "";
currentOption = "vbBinaryCompare";
cmd.Parameters[1].Value = vbCompareOptions[currentOption];
Console.WriteLine(
"{0} found {1} record(s)",
currentOption,
Convert.ToInt32(cmd.ExecuteScalar()));
currentOption = "vbTextCompare";
cmd.Parameters[1].Value = vbCompareOptions[currentOption];
Console.WriteLine(
"{0} found {1} record(s)",
currentOption,
Convert.ToInt32(cmd.ExecuteScalar()));
}
}
produces
vbBinaryCompare found 1 record(s)
vbTextCompare found 2 record(s)
Check this out:
https://support.microsoft.com/kb/244693?wa=wsignin1.0
This article describes four methods of achieving a case-sensitive JOIN using the Microsoft Jet database engine. Each of these methods has advantages and disadvantages that should be weighed before choosing an implementation. The methods are:
StrComp
Case-Sensitive IISAM Driver
Hexadecimal Expansion
Binary Field
Using only built-in functions, add an additional custom column in the query design view:
location: InStr(1,[VCode],"VM1",0)
the zero parameter requests binary compare (case sensitive) when finding location of "VM1" within [VCode]
set the criteria in that column to >0 so only records with non-zero location in the vcode matching Like "*vm*" contain the exact VM1 string -
The WHERE clause looks like:
WHERE (((VirtualMaster.VCode) Like "\*vm*") AND ((InStr(1,[VCode],"VM1",0))>0));
Using at a simpler level of coding.
As a condition in a DCOUNT operation, checking on a Field (Column) that has to have the correct Case, and ignoring Blank States/Territories.
' lngcounter will count the all States
' or Territories Field ( Column) with this
' exact case value of 'Ohio'. ([ID] is an Autonumber ID field)
lngCounter = DCount("[id]", Trim(Me!tboDwellingTablename), "[State/territory],'Ohio',0) = 0")
This only does one letter:
MS-ACCESS SQL:
SELECT Asc(Left([Title],1)) AS t FROM Master WHERE (((Asc(Left([Title],1)))=105));
Title is the field you want to search
Master is the Table where Title field is located
105 Ascii code for character..
In this case only Title's that start with i not I
If you want to search for lower case "a" you would change the 105 to 97

How can you check for null in a VBA DAO record set?

I have an optional field in a database that I'm pulling out using a DAO Record Set. I need to check whether or not the field is set before I concatenate it with other fields. So far I have the following code snippet which I've tried with both Is and = (that's the obviously wrong syntax [[Is | =]]) to no avail. It appears that if I use = it will not correctly compare with Null and if I use Is then it complains that it's not comparing with an Object.
While Not rs.EOF
If rs.Fields("MiddleInitial") [[Is | =]] Null Then thisMiddleInitial = "" Else thisMiddleInitial = rs.Fields("MiddleInitial")
If prettyName(myLastName, myFirstName, myMiddleInitial) = prettyName(rs.Fields("LastName"), rs.Fields("FirstName"), thisMiddleInitial) Then
MsgBox "Yay!"
End If
rs.MoveNext
Wend
If there's a simpler way to do this I'm totally open to it. prettyName takes 3 Strings as parameters and initially I was just trying to pass rs.Fields("MiddleName") directly but it threw up at a Null value. I'd prefer to do something more direct like that but this is the best I could come up with.
How about:
IsNull(rs.Fields("MiddleInitial").Value)
You could also have a look at this article which has some explanation about Null values in Access VBA apps and how to handle them.
For the example you show, Nz would work:
thisMiddleInitial = Nz(rs!MiddleInitial,"")
Or simply concatenating the string with an empty string:
thisMiddleInitial = rs!MiddleInitial & ""
Your question has been answered by Remou, seems to me, but it occurs to me that you may just be trying to get proper concatenation of the name fields. In that case, you could use Mid() and Null propagation in VBA to get the result.
I don't use separate middle initial fields, so my usual name concatenation formula is:
Mid(("12" + LastName) & (", " + FirstName), 3)
The "12" string at the beginning is going to be tossed away if LastName is Not Null and ignored if it is null, because the + concatenation operator propagates Nulls.
To extend this to include middle intials would look like this:
Mid(("12" + LastName) & (", " + FirstName) & (" " + MiddleInitial), 3)
Assuming your UDF is not doing some kind of complicated cleanup of nicknames/abbreviations/etc., this could replace it entirely, seems to me.
If rst.Fields("MiddleInitial").Value = "Null" Then
This works for me. I use MS SQL Database.
I think the NoMatch option might work in this situation:
If rs.NoMatch = True Then
I prefer using the below to account for both Null and Empty string values. It's a good check to use you have forms collecting values from users.
If Trim(rs.Fields("MiddleInitial") & "") = "" then