VB.NET - What is wrong with my SQLinsert? Access database - database

I have a database with a bunch of stuff in it, and right now I'm reading in data, doing some processing on it, and then sticking it in a new database. My code generates this string:
query_string = "INSERT INTO OrgPhrase (EXACT_PHRASE,Org_ID) VALUES (HELLO,123)"
Then it's used this way:
Dim InsertCmd = New System.Data.OleDb.OleDbCommand(query_string, connection)
InsertCmd.ExecuteNonQuery()
The associated database (OLEdb connection) exists and opens fine, with all the tables and columns it's trying to work with already existing. The error message I get is "No value given for one or more required parameters"
Am I missing something? Did I spell something wrong? I don't have a ton of experience with database work, but I've never had this trouble inserting before.

I believe the query should be
query_string = "INSERT INTO OrgPhrase (EXACT_PHRASE,Org_ID) VALUES ('HELLO',123)"
Also, it may happen that the table has more than 2 columns that are NOT NUll and the values to them are required.

Consider parameterizing the query string. There are a couple of reasons for this. First, you can pass in the values without having to worry about whether or not you need single quotes. Second, you prevent SQL injection.
query_string = "INSERT INTO OrgPhrase (EXACT_PHRASE,Org_ID) VALUES (#ExactPhrase,#OrgId)"
You then create parametes based on the parameter names in the string. Unless, of course, your query string is always the same values, but that sounds a bit too hardcoded to be good.

Related

ADODB.recordset connection not finding data

I'm trying to retrieve specific data from SQL Server via ADODB.recordset connection, and certain records are not found for some reason, (while they are found when running the same query in SQL SERVER itself) and I can't figure out why.
My code:
Dim adset As Object, cnct As Object
Set adset = CreateObject("ADODB.recordset")
Set cnct = thiscnn
adset.Open "SELECT prt FROM prt WHERE prt = 2204018", cnct, 1
While in other module:
Public thiscnn As Object
...
Set thiscnn = CreateObject("ADODB.Connection")
thiscnn.Open "DSN=...;Description=...;DATABASE=...;;UID=...;PWD=..."
thiscnn.CommandTimeout = 30
I'm talking here about code working well for years.
The thought that such a simple and useful part of my program which is in vast use, doesn't fetch the data loyally, drives me nuts. I need to give my customers proper, real and true information. I can't have such code sometimes returning and sometimes not returning data.
I know other ways of fetching data, but it is very important for me to understand why this simple way doesn't work all of a sudden in specific cases. It's so peculiar.
I would be greatful for any clue as to what could be happening in my case.
I tried fetching the same query in SQL SERVER itself, and worked fine - but that's the problem, it works in SQL SERVER, but not in VBA with this code above.
I tried looking for differences between records getting fetched and those which don't, and didn't see any special difference.
I tried refetching the same record a few times, but the fetching status for those specific records which don't get fetched (via ADODB.connection / recordset) stays the same.
I tried fetching the same records using the BETWEEN operator rather than the equals (=) operator, yet with no success.
I tried ordering by id column (prt), perhaps the problem is from certain id and up.
I tried ordering by the timestamp column thinking maybe from certain timestamp we have a problem - but that didn't give me any clue.
I thought to cast timestamp to date, but I found out that it's only rowversion and doesn't preserve a date or time.
I tried ordering by row size using this, thinking maybe certain ro size is to much for some odd reason, but that didn't help either.
Just a guess, but can it be some dirty data that isn't persisted?
Try selecting with nolock:
SELECT prt FROM prt (NOLOCK) WHERE prt = 2204018
And perhaps change cursor type:
adset.Open "SELECT prt FROM prt WHERE prt = 2204018", cnct, 2
Or you are looking into wrong database. Usually these errors are something stupid :D

How can you create a table (or other object) that always returns the value passed to its WHERE-clause, like a mirror

There is a legacy application that uses a table to translate job names to filenames. This legacy application queries it as follows:
SELECT filename FROM aJobTable WHERE jobname = 'myJobName'
But in reality those jobnames always match the filenames (e.g. 'myJobName.job' is the jobname but also the filename) That makes this table appear unnecessary. But unfortunately, we cannot change the code of this program, and the program just needs to select it from a table.
That's actually a bit annoying. Because we do need to keep this database in sync. If a jobname is not in the table, then it cannot be used. So, as our only way out, right now we have some vbscripts to synchronize this table, adding records for each possible filename. As a result, the table just 2 columns with identical values. -- We want to get rid of this.
So, we have been dreaming about some hack that queries the data with the jobname, but just always returns the jobname again, like a copy/mirror query. Then we don't actually have to populate a table at all.
"Exploits"
The following can be configured in this legacy application. My hunch is that these may open the door for some tricks/hacks.
use of either MS Access or SQL Server (we prefer sql server)
The name of the table (e.g. aJobTable)
The name of the filename column (e.g. filename)
The name of the jobname column (e.g. jobname)
Here is what I came up with:
If I create a table-valued function mirror(a) then I get pretty close to what I want. Then I could use it like
SELECT filename FROM mirror('MyJobName.job')
But that's just not good enough, it would be if I could force it to be like
SELECT filename FROM mirror WHERE param1 = 'MyJobName.job'
Unfortunately, I don't think it's possible to call functions like that.
So, I was wondering if perhaps somebody else knows how to get it working.
So my question is: "How can you create a table (or other object) that always returns the value passed to its WHERE-clause, like a mirror."
It's kinda hard to answer not knowing the code that the application use, but if we assume it only takes strings and concatenate them without any tests whatsoever, I would assume code like this: (translated to c#)
var sql = "SELECT "+ field +" FROM "+ table +" WHERE "+ conditionColumn +" = '"+ searchValue +"'";
As this is an open door for SQL injection, and given the fact that SQL Server allows you two ways of creating an alias - value as alias and alias = value,
you can take advantage of that and try to generate an SQL statement like this:
SELECT field /* FROM table WHERE conditionColumn */ = 'searchValue'
So field should be "field /* ",
and conditionColumn should be "conditionColumn */"
table name doesn't matter, you could leave an empty string for it.

Can DBUtils overcome SQLServer: "The statement must be executed before any results can be obtained."

I am trying to insert data into a SqlServer table that has a generated key. I don't care about the new keys created, but the error I get back seems to suggest I must:
java.sql.SQLException: The statement must be executed before any results can be obtained.
DBUtils has a (thankfully) simple interface but doesn't seem to be able to overcome this.
Anyone have any ideas?
conn = this.dcf.getSqlServerDataSource(database).getConnection();
conn.setAutoCommit(false);
qRunner.insertBatch(conn, sql, new ArrayHandler(), parms);
There is another method in DBUtils:
qRunner.batch(conn, sql, parms);
This does not require a ResultSetHandler and does not seem to care about the created keys.

SQL Server: Parameter is not replaced

I've got a fully functionable (secure) session to a SQL Server database (version 10.50.4000). This is stored in a public variable:
SqlConnection conn = new SqlConnection();
I only want to run SELECT queries. For anything else, the user account got no rights.
The queries are built with only one user entry, which is inserted into a simple Text Box.
Unfortunately I must not tell you the original command text. So I make it simple for you:
function print_users(string filtervalue)
{
SqlCommand cmd = null;
cmd = new SqlCommand("SELECT users From groups WHERE group_name LIKE '%#fv%'", this.conn)
cmd.Parameters.Add("#fv", SqlDbType.NVarChar);
cmd.Parameters["#fv"].Value=filtervalue;
rdr = cmd.ExecuteReader();
while(rdr.Read())
{
//Do something with the answer from the DB
}
}
But this does not do the trick. I also tried AddWithValue, but I got no luck.
When creating a stop-point on the line, where #fv should be replaced, I can go through the code line-by-line. And I can see that the command, where #fv should be replaced, is processed with no error. But #fv is not replaced (or at least I cannot see the replacement in the debug console).
What am I doing wrong?
EDIT:
thank you for your replies. Leaving out the single quotes ( ' ) did the trick.
And I also learned that this is not a string replacement. Thank you.
Just one word: The connection is not left open all the time. It's immediately closed, when it's not needed any more; and re-established when needed again - I just forgot to write that into my sample code.
Again: Thank you for your help!
You can't see it being replaced in your debug session; the replacement occurs in the SQL server code itself...
The client (your code) send both the SQL-string and the value of the parameter as seperate things to the server. There the SQL Engine 'replaces' the parameter with its value while executing it.
You should also put the 'wildcards' inside your parametervalue, not inside the query.
cmd = new SqlCommand("SELECT users From groups WHERE group_name LIKE #fv ", this.conn)
cmd.Parameters.Add("#fv", SqlDbType.NVarChar);
cmd.Parameters["#fv"].Value= "%" + filtervalue + "%";
The parameter is not working because it is inside a string literal. You want to build the string like this:
cmd = new SqlCommand("SELECT users From groups WHERE group_name LIKE '%' + #fv + '%'");
While we're at it, keeping a global connection like that is bad. It can cause strange side effects, especially in web apps. Instead, keep a global connection string, and then use that string to create a new connection on each request.
Also, "replace" is the wrong word here. Sql parameters are never replaced, even when they work. That's the whole point. There is no string replacement into your query at any point, ever. It's more like you declared an #fv variable at the server level in a stored procedure, and assigned your data directly to that variable. In this way, there is no possibility for a vulnerability in parameter replacement code, because the data portion of your query remains separate throughout the execution process. In same way, don't think in terms of "sanitizing" a parameter for a query; instead, think in terms of quarantining the data.

Copy data from lookup column with multiple values to new record Access 2007

I am copying a record from one table to another in Access 2007. I iterate through each field in the current record and copy that value to the new table. It works fine until I get to my lookup column field that allows multiple values. The name of the lookup column is "Favorite Sports" and the user can select multiple values from a dropdown list.
I believe the values of a multivalued field are stored in an array but I cannot access the values in VBA code! I've tried myRecordset.Fields("myFieldName").Value(index) but it didn't work. I don't understand how Access stores multiple values in one field.
I saw something about ItemsSelected on another forum but I don't know what Object is associated with that method.
Thanks for any help!
I would recommend against using multivalue fields for precisely the reason you're running into, because it's extremely complex to refer to the data stored in this simple-to-use UI element (and it's for UI that it's made available, even though it's created in the table design).
From your mention of "ItemsSelected," you seem to be assuming that you access the data in a multivalue field the same way you would in a multiselect listbox on a form. This is not correct. Instead, you have to work with it via a DAO recordset. The documentation for working with multivalue fiels explains how to do it in code, something like this:
Dim rsMyField As DAO.Recordset
Set rsMyField = Me.Recordset("MyField").Value
rsChild.MoveFirst
Do Until rsChild.EOF
Debug.Print rsChild!Value.Value
rsChild.MoveNext
Loop
rsChild.Close
Set rsChild = Nothing
Now, given that you can usually access the properties of a recordset object through its default collections, you'd expect that Me.Recordset("MyField").Value would be returning a recordset object that is navigable through the default collection of a recordset, which is the fields collection. You'd think you could do this:
Me.Recordset("MyField").Value!Value.Value
This should work because the recordset returned is a one-column recordset with the column name "Value" and you'd be asking for the value of that column.
There are two problems with this:
it doesn't actually work. This means that Me.Recordset("MyField").Value is not reallly a full-fledged recordset object the way, say, CurrentDB.OpenRecordset("MyTable") would be. This is demonstrable by trying to return the Recordcount of this recordset:
Me.Recordset("MyField").Value.Recordcount
That causes an error, so that means that what's being returned is not really a standard recordset object.
even if it did work, you'd have no way to navigate the collection of records -- all you'd ever be able to get would be the data from the first selected value in your multivalued field. This is because there is no way in this shortcut one-line form to navigate to a particular record in any recordset that you're referring to in that fashion. A recordset is not like a listbox where you can access both rows and columns, with .ItemData(0).Column(1), which would return the 2nd column of the first row of the listbox.
So, the only way to do this is via navigating the child DAO recordset, as in the code sample above (modelled on that in the cited MSDN article).
Now, you could easily write a wrapper function to deal with this. Something like this seems to work:
Public Function ReturnMVByIndex(ctl As Control, intIndex As Integer) As Variant
Dim rsValues As DAO.Recordset
Dim lngCount As Long
Dim intRecord As Integer
Set rsValues = ctl.Parent.Recordset(ctl.ControlSource).Value
rsValues.MoveLast
lngCount = rsValues.RecordCount
If intIndex > lngCount - 1 Then
MsgBox "The requested index exceeds the number of selected values."
GoTo exitRoutine
End If
rsValues.MoveFirst
Do Until rsValues.EOF
If intRecord = intIndex Then
ReturnMVByIndex = rsValues(0).Value
Exit Do
End If
intRecord = intRecord + 1
rsValues.MoveNext
Loop
exitRoutine:
rsValues.Close
Set rsValues = Nothing
Exit Function
End Function
Using that model, you could also write code to concatenate the values into a list, or return the count of values (so you could call that first in order to avoid the error message when your index exceeded the number of values).
As cool as all of this is, and as nice as the UI that's presented happens to be (it would be really nice if they'd added selection checkboxes as a type for a multiselect listbox), I'd still recommend against using it precisely because it's so much trouble to work with. This just takes the problem of the standard lookup field (see The Evils of Lookup Fields in Tables) and makes things even worse. Requiring DAO code to get values out of these fields is a pretty severe hurdle to overcome with a UI element that is supposed to make things easier for power users, seems to me.
For a quick and dirty way of getting the values out of a multivalued ('complex data') column, you can use an ADO Connection with the Jet OLEDB:Support Complex Data connection property set to False e.g. the connection string should look something like this:
Provider=Microsoft.ACE.OLEDB.12.0;
Data Source=C:\dbs\TestANSI92.accdb;
Jet OLEDB:Engine Type=6;
Jet OLEDB:Support Complex Data=False
The multivaled type column will now be of type MEMO (adLongVarWChar) with each value separated by a semicolon ; character.
But that's only half the problem. How to get data into a multivalued column?
The Access Team seem to have neglected to enhance the Access Database Engine SQL syntax to accommodate multivalued types. The 'semicolon delimiter' trick doesn't work in reverse e.g.
INSERT INTO TestComplexData (ID, weekday_names_multivalued)
VALUES (5, 'Tue;Thu;Sat');
fails with the error, "Cannot perform this operation", ditto when trying to update via ADO recordset :(

Resources