C# Inconsistent work with access databases (OleDb) [duplicate] - database

I would like to use an Insert Into query in Delphi XE2 to insert a user's information into a MS Access Database. The problem is I keep getting the same error:
Syntax error in INSERT INTO statement
I have done some research but there is no definitive answer. my source code is:
opendb('QuizDB.mdb');
DB.Close;
DB.SQL.Add('INSERT INTO tblUsers');
DB.SQL.Add('(FirstName,Surname,Username,Password,Grade)');
DB.SQL.Add('Values (:Firstname, :Surname, :Username, :Password, :Grade)');
Db.Parameters.ParamByName('Firstname').Value := pFirstname;
Db.Parameters.ParamByName('Surname').Value := pSurname;
Db.Parameters.ParamByName('Username').Value := pUsername;
Db.Parameters.ParamByName('Password').Value := pPassword;
Db.Parameters.ParamByName('Grade').Value := pGrade;
DB.ExecSQL;
QuizDB being the database name, DB being a ADOQuery component and then p(var) being variables received as parameters.
How do I make it work?

PASSWORD is a reserved word in Access so if you use it as a column name you must enclose it in square brackets.
Try this instead:
DB.SQL.Add('INSERT INTO tblUsers ');
DB.SQL.Add('(FirstName,Surname,Username,[Password],Grade) ');

Related

Storing Files in SQL Server Table (varbinary(max) column) using ADOQuery Component

I'm using SQL Server 2019 and Delphi 10.3.
I need to store any kind of files ( like pdf, txt, docx, etc) in a 'Personal_Files' table.
This table is composed by a column with the file extension ( as varchar) and a varbinary(max) column to store the file itself.
I did some research on how to store these files on a table, but without success. Below some example:
var
Input,Output: TStream;
FName: TFileName;
begin
...
//Create Streams and encode Base64:
Input := TFileStream.Create(FName,fmOpenRead);
Output := TFileStream.Create(FName+'Temp',fmCreate);
TNetEncoding.Base64.Encode(Input,Output);
... // Some validations
// In the ADOQuery component, I did this:
with ADOQuery, sql do
begin
close;
clear;
add('INSERT INTO MyDatabase.dbo.MyFilesTable (EXTENSION,FILEBIN)');
add('VALUES (:wextension, :wfilebin)');
Parameters.ParamByName('wextension').Value := TPath.GetExtension(FName);
Parameters.ParamByName('wfilebin').Value := Output.toString;
ExecSQL;
end;
In this example, I tried to parse the stream as String, after the encode, but when I look in the SQL Table, it's the same stream for all the archives I tried. The parameter doesn't accept TStream type. Thank you in advance.
After some research, and some advices, I found a way to send the file to my SQL Server table with ADOQuery. Altough, I learned that this isn't recommended, so it's just to answer my question directly:
Just a change on the final part of the code answers my question ( but again, it's not the recommended way to store files, as commented on the question.):
with ADOQuery, sql do
begin
close;
clear;
add('INSERT INTO MyDatabase.dbo.MyFilesTable (EXTENSION,FILEBIN)');
add('VALUES (:wextension, :wfilebin)');
Parameters.ParamByName('wextension').Value := TPath.GetExtension(FName);
Parameters.Items[1].LoadFromStream(Output,ftVarBytes);
ExecSQL;
end;
Just changing the way I was setting the parameter solved the problem. In this example, using the 'LoadFromStream' on the Paremeters.Items[n], where n is the parameter index, it worked very well. The ftVarBytes is the field type parameter.

Why does FireDAC ignore index name?

I'm trying to creating a table in a SQL Server database using FireDAC. However, instead of using the index name I provide, FireDAC uses a bad index name, raising an exception and the table does not get created. Am I doing something wrong? If not, is there a work-around?
Note that I'm using the valid database schema name cnf for TableName. I specifically need to create the table in a schema.
Simplest test case:
var
Connection: TFDConnection;
Table: TFDTable;
begin
Connection := TFDConnection.Create(nil);
Table := TFDTable.Create(nil);
try
Connection.Params.Add ('DriverID=MSSQL');
Connection.Params.Add ('OSAuthent=No');
Connection.Params.Add ('User_Name=sa');
Connection.Params.Add ('Password=XXXXXX');
Connection.Params.Add ('Server=DAVE-DELL\MSSQLSERVER2016');
Connection.Params.Add ('Database=PROJECT_DB');
Connection.Params.Add ('MARS=No');
Connection.Open;
Table.Connection := Connection;
Table.TableName := 'cnf.TestTable';
Table.FieldDefs.Add ('TableID', ftAutoInc, 0, true);
Table.FieldDefs.Add ('Field1', ftInteger, 0, true);
Table.FieldDefs.Add ('Field2', ftstring, 100, true);
Table.IndexDefs.Add ('PK_XYZ', 'TableID', [ixPrimary]); // should use this index name!
Table.CreateTable (true);
finally
Table.Free;
Connection.Free;
end;
end;
An exception is raised:
[FireDAC][Phys][ODBC][Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near '.'.
Running SQL Server Profiler shows me that FireDAC is trying to create the index using the following SQL code:
ALTER TABLE temp.TestTable ADD CONSTRAINT [cnf].[PK_TestTable] PRIMARY KEY (TableID)
And, of course, [cnf].[PK_TestTable] is not a valid index name in T-SQL, which is the crux of the problem.
If I remove the line Table.IndexDefs.Add, the table is created properly, but without the index.
If I replace that line with the following, it gives the same problem:
with Table.IndexDefs.AddIndexDef do begin
Name := 'PK_XYZ';
Options := [ixPrimary];
Fields := 'TableID';
end;
If I replace setting the table name with the following, it gives the same problem:
Table.TableName := 'TestTable';
Table.SchemaName := 'cnf';
Why is it using it's own (wrong) index name, instead of the name I gave it? (i.e. PK_XYZ)
Embarcadero® Delphi 10.1 Berlin Version 24.0.25048.9432
SQL Server 2016 (SP2-CU4) - 13.0.5233.0 (X64)
Am I doing something wrong?
Why is it using it's own (wrong) index name, instead of the name I gave it?
You seem to be doing everything just right. The issue is with the generated SQL command as you have tracked that down. SQL Server doesn't allow schema name in constraint name when adding a constraint using ALTER TABLE. Constraints created this way automatically become part of schema of the related table, however you should later use schema name when referring to the constraint:
SELECT OBJECT_ID('cnf.PK_XYZ')
Now where do the things go wrong? FireDAC uses TFDPhysCommandGenerator and its ancestors to generate SQL commands for specific DBMS. Your call to CreateTable method results in call to TFDPhysCommandGenerator.GetCreatePrimaryKey, which is responsible for generating SQL for primary key. It also contains this code:
sTab := GetFrom;
FConnMeta.DecodeObjName(sTab, rName, nil, [doUnquote]);
rName.FObject := 'PK_' + rName.FObject;
Result := 'ALTER TABLE ' + sTab + ' ADD CONSTRAINT ' +
FConnMeta.EncodeObjName(rName, nil, [eoQuote, eoNormalize]) + ' PRIMARY KEY (';
What this code does is that it takes your fully qualified table name (sTab) splits it (DecodeObjName) into parts (rName) prepends 'PK_' to table name and joins the parts (EncodeObjName) back to fully qualified name, which is then used as the constraint name for your primary key. Now we can clearly see that command generator ignores your index name and generates erroneous T-SQL. This can either be a bug or just a not supported feature. EMBT has to make decision on that. I'd recommend reporting it as a bug.
Is there a work-around?
Yes, you can either hook problematic method or you can override it in your own derived class. Implementation none of these is trivial and due to legal issues I'm not going to extend it here, because I would have to duplicate the original FireDAC code.
As for the syntax error adding these lines to 'TFDPhysCommandGenerator.GetCreatePrimaryKey' implementation after DecodeObjName would fix the issue:
rName.FCatalog := '';
rName.FSchema := '';
rName.FBaseObject := '';
rName.FLink := '';
Fixing constraint name is going to be more cumbersome than that, because the method only receives index column names as argument and has no obvious access to original IndexDefs where you could just use index name as primary key constraint name. Gaining access to index name from there would also allow you to get rid of decoding/encoding table name into index name. This process, however, could be essential for other DMBS's than SQL Server.
PS: If only half of all the questions were written in this manner ... Thank you for this wonderful question.

How to create VIEW in MS Access Database using Delphi Application without installing MSAccess on PC?

I want to create VIEW definitions on MS Access. I have used following CREATE VIEW Statement:
SELECT
MFP.FollowUpPlan_Id,
MFP.FollowUpPlan_Name AS PlanName,
DFP.Sequence_No AS SequenceNo,
MFS.FollowUpSchedule_Name AS ScheduleName
FROM
MAS_FollowUp_Plan AS MFP,
DET_FollowUp_Plan AS DFP,
MAS_FollowUp_Schedule AS MFS
WHERE
(((MFP.FollowUpPlan_Id)=DFP.FollowUpPlan_Id) AND
((DFP.FollowUpSchedule_Id)=MFS.FollowUpSchedule_Id)) AND
MFP.is_Deleted = FALSE AND
DFP.is_Deleted = false
ORDER BY
MFP.FollowUpPlan_Id, DFP.Sequence_No;
but it throw an error:
Only Simple Select Queries are allowed in view.
Please Help, Thanks in Advance.
The issue here, as Jeroen explained, is a limitation of Access' CREATE VIEW statement. For this case, you can use CREATE PROCEDURE instead. It will create a new member of the db's QueryDefs collection --- so from the Access user interface will appear as a new named query.
The following statement worked for me using ADO from VBScript. From previous Delphi questions on here, my understanding is that Delphi can also use ADO, so I believe this should work for you, too.
CREATE PROCEDURE ViewSubstitute AS
SELECT
MFP.FollowUpPlan_Id,
MFP.FollowUpPlan_Name AS PlanName,
DFP.Sequence_No AS SequenceNo,
MFS.FollowUpSchedule_Name AS ScheduleName
FROM
(MAS_FollowUp_Plan AS MFP
INNER JOIN DET_FollowUp_Plan AS DFP
ON MFP.FollowUpPlan_Id = DFP.FollowUpPlan_Id)
INNER JOIN MAS_FollowUp_Schedule AS MFS
ON DFP.FollowUpSchedule_Id = MFS.FollowUpSchedule_Id
WHERE
MFP.is_Deleted=False AND DFP.is_Deleted=False
ORDER BY
MFP.FollowUpPlan_Id,
DFP.Sequence_No;
You cannot mix ORDER BY with JOIN when creating views in Access. It will get you the error "Only simple SELECT queries are allowed in VIEWS." (note the plural VIEWS)
Having multiple tables in the FROM is a kind of to JOIN.
either remove the ORDER BY,
or have only one table in the FROM and no JOINs.
I remember from the past (when I did more Access stuff than now) seeing this for a large query with a single table select with an ORDER BY as well.
The consensus is that you should not have ORDER BY in views anyway, so that is your best thing to do.
Another reason that you can get the same error message is if you add parameters or sub selects. Access does not like those in views either, but that is not the case in your view.
Declare variable olevarCatalog ,cmd as OleVariant in Delphi, Uses ComObj
olevarCatalog := CreateOleObject('ADOX.Catalog');
olevarCatalog.create(YourConnectionString); //This Will create MDB file.
// Using ADO Query(CREATE TABLE TABLEName....) add the required Tables.
// To Insert View Definition on MDB file.
cmd := CreateOleObject('ADODB.Command');
cmd.CommandType := cmdText;
cmd.CommandText := 'ANY Kind of SELECT Query(JOIN, OrderBy is also allowed)';
olevarCatalog.Views.Append('Name of View',cmd);
cmd := null;
This is a best way to Create MS ACCESS File(.MDB) and VIEWs using Delphi.

How to make ADO parameter to update SQL Server datetime column?

edit I should have mentioned that I'm trying to do this with Delphi 2006.
OK, I think I have hit on a question with no previous answers.
I have a SQL Server database with columns of type datetime. When I try to insert a row with a parametrized command, I get
Exception class EOleException with message
'[Microsoft][ODBC SQL Server Driver]Optional feature not implemented'.
My insert procedure looks like this:
procedure TForm1.btnZoomClick(Sender: TObject);
const
InsCmd = 'insert into dbo.foo (added, edited, editor, narrative) ' +
'values (:dateAdded, :dateEdited, :theEditor, :theNarrative);';
begin
dmDbToy2.DataModule2.ADOCommand1.CommandText := InsCmd;
with DataModule2.ADOCommand1.Parameters do
begin
// the following line was an attempt to trick VarAsType into making a
// adDbTimeStamp: VarAsType is having none of it.
// FindParam('dateAdded').Value := VarAsType(VarFromDateTime(Now), 135);
FindParam('dateAdded').Value := VarFromDateTime(Now);
FindParam('dateEdited').Value := Null;
FindParam('theEditor').Value := 'wades';
FindParam('theNarrative').Value := Null;
end;
DataModule2.ADOCommand1.Execute;
end;
I found some postings via google which seem to indicate that SQL Server wants a adDbTimeStamp type to update these columns, but VarAsType does not want to make one for me.
Is there a way to create a value for the dateAdded and dateEdited parameters in the code sample?
In the comments thread on the original question, user RRUZ made a suggestion that turned out to resolve the issue: The problem was with the provider. Namely, I was using the OLEDB Provider for ODBC rather than the OLEDB Provider for SQL Server. Changing the provider as suggested made the 'Optional feature not implemented' error message go away and enabled the insert to work with a simple assignment of TDateTime to TParameter.Value, thusly:
FindParam('dateAdded').Value := Now;
Set the datatype for the parameter, it might do a difference in how the parameters is treated. I would also recommend that you use ParamByName instead of FindParam. With ParamByName you get a Param xx not found exception if the parameters does not exist in the Parameters collection. FindParam returns nil if it is not found. I have never needed to use any variant conversion stuff when assigning parameters for a TADOCommand so think you should remove that as well.
Try this.
with ParamByName('dateAdded') do
begin
DataType := ftDateTime;
Value := Now;
end;
with ParamByName('dateEdited') do
begin
DataType := ftDateTime;
Value := Null;
end;
with ParamByName('theEditor') do
begin
DataType := ftString; // or ftWideString if you use nchar/nvarchar
Value := 'wades';
end;
with ParamByName('theNarrative') do
begin
//DataType := ftString // Don't know datatype here
Value := Null;
end;
Just set the parameter as a datetime. I do it all the time in ADO and other conection layers
DataModule2.ADOCommand1.Parameters.ParamByName('dateAdded').Value := Now();
//other code
DataModule2.ADOCommand1.Parameters.ParamByName('dateEdited').Value := Null;
//other code
DataModule2.AdoCommand1.Execute;

Delphi: "Parameter object is improperly defined. Inconsistent or incomplete information was provided."

I'm trying to insert a record into a table in a 3-tier database setup, and the middle-tier server generates the error message above as an OLE exception when it tries to add the first parameter to the query.
I've Googled this error, and I find the same result consistently: it comes from having a colon in a string somewhere in your query, which b0rks ADO's SQL parser. This is not the case here. There are no spurious colons anywhere. I've checked and rechecked the object definition against the schema for the table I'm trying to insert into. Everything checks out, and this has my coworkers stumped. Does anyone know what else could be causing this? I'm at my wits' end here.
I'm using Delphi 2007 and SQL Server 2005.
I can get this error, using Delphi 2007 and MSSQL Server 2008, and I found a workaround. (which is pretty crappy IMHO, but maybe its useful to you if yours is caused by the same thing.)
code to produce the error:
with TADOQuery.Create(nil)
do try
Connection := ADOConnection;
SQL.Text := ' (SELECT * FROM Stock WHERE InvCode = :InvCode ) '
+' (SELECT * FROM Stock WHERE InvCode = :InvCode ) ';
Prepared := true;
Parameters.ParamByName('InvCode').Value := 1;
Open; // <<<<< I get the "parameter object is...etc. error here.
finally
Free;
end;
I found two ways to fix it:
1) remove the brackets from the SQL, ie:
SQL.Text := ' SELECT * FROM Stock WHERE InvCode = :InvCode '
+' SELECT * FROM Stock WHERE InvCode = :InvCode ';
2) use two parameters instead of one:
with TADOQuery.Create(nil)
do try
Connection := ADOConnection;
SQL.Text := ' (SELECT * FROM Stock WHERE InvCode = :InvCode1 ) '
+' (SELECT * FROM Stock WHERE InvCode = :InvCode2 ) ';
Prepared := true;
Parameters.ParamByName('InvCode1').Value := 1;
Parameters.ParamByName('InvCode2').Value := 1;
Open; // <<<<< no error now.
finally
Free;
end;
I found this thread while searching the previously mentioned Exception message. In my case, the cause was an attempt to embed a SQL comment /* foo */ into my query.sql.text.
(I thought it would have been handy to see a comment go floating past in my profiler window.)
Anyhow - Delphi7 hated that one.
Here a late reply. In my case it was something completely different.
I tried to add a stored procedure to the database.
Query.SQL.Text :=
'create procedure [dbo].[test]' + #13#10 +
'#param int ' + #13#10 +
'as' + #13#10 +
'-- For the parameter you can pick two values:' + #13#10 +
'-- 1: Value one' + #13#10 +
'-- 2: Value two';
When I removed the colon (:) it worked. As it saw the colon as a parameter.
I just encountered this error myself. I'm using Delphi 7 to write to a 2003 MS Access database using a TAdoQuery component. (old code) My query worked fine directly in MS Access, but fails in Delphi through the TAdoQuery object. My error came from a colon (apologies to the original poster) from a date/time value.
As I understand it, Jet SQL date/time format is #mm/dd/yyyy hh:nn:ss# (0 left-padding is not required).
If the TAdoQuery.ParamCheck property is True then this format fails. (Thank you posters!) Two work-arounds are: a) set ParamCheck to False, or b) use a different date/time format, namely "mm/dd/yyyy hh:nn:ss" (WITH the double quotes).
I tested both of these options and they both worked.
Even though that double-quoted date/time format isn't the Jet date/time format, Access is pretty good at being flexible on these date/time formats. I also suspect it has something to do with the BDE/LocalSQL/Paradox (Delphi 7's native SQL and database engine) date/time format (uses double quotes, as above). The parser is probably designed to ignore quoted strings (double quotes are the string value delimiter in BDE LocalSQL), but may stumble somewhat on other non-native date/time formats.
SQL Server uses single quotes to delimit strings, so that might work instead of double quotes when writing to SQL Server tables (not tested). Or maybe the Delphi TAdoQuery object will still stumble. Turning off ParamCheck in that case may be the only option. If you plan to toggle the ParamCheck property value in code, you'll save some processing time by ensuring the SQL property is empty before enabling it, if you're not planning on parsing the current SQL.
I'm facing the same error described in your question. I've traced the error into ADODB.pas -> procedure TParameters.AppendParameters; ParameterCollection.Append(Items[I].ParameterObject).
By using breakpoints, the error was raised, in my case, by a parameter which should fill a DateTime field in the database and I've never filled up the parameter. Setting up the parameter().value:='' resolved the issue (I've tried also with varNull, but there is a problem - instead of sending Null in the database, query is sending 1 - the integer value of varNull).
PS: I know is a 'late late late' answer, but maybe somebody will reach at the same error.
If I remember well, you have to explicit put NULL value to the parameter. If you are using a TAdoStoredProc component, you should do this in design time.
Are you using any threading? I seem to remember getting this error when a timer event started a query while the ADO connection was being used for another synchronous query. (The timer was checking a "system available" flag every minute).
Have you set the DataType of the parameter or did you leave it as ftUnknown?
I have also had the same problem, but with a dynamic command (e.g. an Update statement).
Some of the parameters could be NULL.
The only way i could get it working, was setting the parameter.DataType := ftString and parameter.Size := 1 and not setting the value.
cmdUpdate := TADOCommand.Create(Self);
try
cmdUpdate.Connection := '**Conections String**';
cmdUpdate.CommandText := 'UPDATE xx SET yy = :Param1 WHERE zz = :Param2';
cmdUpdate.Parameters.ParamByName('Param2').Value := WhereClause;
if VarIsNull(SetValue) then
begin
cmdUpdate.Parameters.ParamByName('Param1').DataType := ftString;
cmdUpdate.Parameters.ParamByName('Param1').Size := 1;
end else cmdUpdate.Parameters.ParamByName('Param1').Value := SetValue;
cmdUpdate.Execute;
finally
cmdUpdate.Free;
end;
I just ran into this error today on a TADOQuery which has ParamCheck := False and has no colons in the SQL.
Somehow passing the OLECMDEXECOPT_DODEFAULT parameter to TWebBrowser.ExecWB() was causing this for me:
This shows the problem:
pvaIn := EmptyParam;
pvaOut := EmptyParam;
TWebBrowser1.ExecWB(OLECMDID_COPY, OLECMDEXECOPT_DODEFAULT, pvaIn, pvaOut);
This does not show the problem:
pvaIn := EmptyParam;
pvaOut := EmptyParam;
TWebBrowser1.ExecWB(OLECMDID_COPY, OLECMDEXECOPT_DONTPROMPTUSER, pvaIn, pvaOut);
A single double quote in the query can also raise this error from what I just experienced and I am not using parameters at all ...
You can get this error when attempting to use a time value in the SQL and forget to wrap it with QuotedStr().
I got the same error. Turned out, that it is because a parameter of the stored procedure was declared as varchar(max). Made it varchar(4000) and error disappeared.

Resources