I'm trying to write bits of code to a Microsoft access database from Delphi. I'm getting data from a TStringGrid. The first column has the ItemID, and the 2nd column has the Quantity. I'd like it to loop through the TStringGrid and save each row as a reperate row in my database and also save the Order ID with it on every column (The order ID stays the same for each order so that doesn't need to change) .
I'm getting an error when running which says
"Project Heatmat.exe raised an exception class EVarientInvalidArgError with message 'Invalid Argument'. Process Stopped."
I can't figure out why it's giving me this error, and as you can probably see i'm not very good at coding yet. Any help would be appreciated!
Thank you.
procedure TCreateNewOrder.btnSaveClick(Sender: TObject);
var
intNumber, count : integer;
begin
Count:= 0;
if messagedlg ('Are you sure?', mtWarning, [mbyes, mbno], 0) = mryes then
begin
with HeatmatConnection.HeatmatDatabase do
begin
intNumber:= TBLOrder.RecordCount;
TBLOrder.Append;
TBLOrder['CustomerID']:= CompanyName.ItemIndex+1;
TBLOrder['OrderID']:= intNumber +1;
for count:= 1 to StringGrid1.RowCount-1 do
begin
TBLOrderedItem.Append;
TBLOrderedItem['OrderID']:= intNumber+1;
TBLOrderedItem['ItemID']:= StringGrid1.Cells[1, count];
TBLOrderedItem['Quantity']:= StringGrid1.Cells[2, count];
TBLOrderedItem.Post;
end;
end;
end;
end;
TStringGrid cells are strings. trying to assign a string directly to a numeric field will raise an Exception.
So a good practice is to assign values to database fields via AsString, AsInteger, AsBoolean etc... this will make the correct conversion.
In your code use:
TBLOrderedItem.FieldByName('ItemID').AsString := StringGrid1.Cells[1, count];
The same is true for Quantity.
To assign an Integer value use:
TBLOrderedItem.FieldByName('OrderID').AsInteger := intNumber + 1;
BTW, you are forgetting TBLOrder.Post i.e:
....
TBLOrder.Append;
TBLOrder.FieldByName('CustomerID').AsInteger := CompanyName.ItemIndex + 1;
TBLOrder.FieldByName('OrderID').AsInteger := intNumber + 1;
TBLOrder.Post;
...
Finally, I would also suggest to rename TBLOrder to tblOrder so that it's name wont imply that it is a Type.
Related
I am trying to make an e-mail sender with Delphi. I have database with statuses and emails and I need to get the number of elements in my database so that I can make a for loop. This is part of my code:
procedure TForm1.bDataBaseClick(Sender: TObject);
var
i : integer;
begin
FDConnection1.StartTransaction;
FDConnection1.Open();
//FDQuery1.ExecSQL('UPDATE Vasko '
//+' SET BatchNumber = 112233 '
//+' WHERE StatusOfProd = 4');
//FDQuery1.ExecSQL('select * from Vasko');
for i := 0 to FDQuery1.ComponentCount - 1 do
showmessage('hello');
FDConnection1.Commit;
FDConnection1.Close();
end;
For now I just want to make the loop that shows message several times. I think that I have mistake in this line:
for i := 0 to FDQuery1.ComponentCount - 1 do
Thank you in advance!
I wasted a bit of time trying to work out something I figured would be simple.
I've got a database with multiple tables (MySQL). One table containing "Components" and another containing "Products". Products are built using Components, for example; Product ABC might be made up of 3 x Screws, 4 x bolts, 1 kilogram of fresh air... etc! Am I making sense so far?
The components are displayed in a DBGrid. If the user makes a mistake and wants to add another "Component" to a "Product" a Picklist appears listing all Components (from a different table) for them to select from.
Now, here's my problem! When something is selected from the column[i].picklist (this is part of a DBGrid) how do I know what was selected. I thought there would be an event fired, but there doesn't seem to be.
I need to know which item was selected so I can retrieve an appropriate description for the next field.
There are 3 fields, they are COMPONENT, DESCRIPTION, QUANTITY. Only COMPONENT and QUANTITY can be edited by the user.
I hope I'm making some sense here.
Here is the code I'm using now (as messy as it is);
procedure TForm1.CompletePolesSourceStateChange(Sender: TObject);
var
loop: Integer;
Tmp: string;
begin
case CompletePolesSource.state of
dsInsert:
begin
CompVals.Clear; // Is a tstringlist created elsewhere
CompVals.Delimiter := '|';
CompVals.QuoteChar := '"';
PoleComponentsGrid.Columns[0].readonly := false; // Is readonly when not in DSInsert
PoleComponentsGrid.Columns[0].PickList.Clear; // Clear other crap
{
Now add Parts to a Name / Value list (CODE / DESCRIPTION) so I can later
get the description without looking it up in the other table.
}
for loop := 1 to componentstable.RecordCount do // Get CODE from other table
begin
componentstable.RecNo := loop;
tmp := componentstable.Fieldbyname('CODE').asstring + '=' + componentstable.Fieldbyname('ITEM').asstring;
CompVals.Add(tmp);
PoleComponentsGrid.Columns.Items[0].PickList.Add(tmp);
end;
PoleComponentsGrid.Columns.Items[0].readonly := true;
end;
end;
end;
This will show the data of the selected rows of the DBGrid
procedure TFrmPrincipal.btnShowSelectedRowsClick(Sender: TObject);
var
i: Integer;
aux: string;
begin
for i := 0 to DBGrid1.SelectedRows.Count - 1 do
begin
ClientDataSet1.GotoBookmark(pointer(DBGrid1.SelectedRows.Items[i]));
aux := aux + IntToStr(ClientDataSet1.RecNo) + ' - ' +
ClientDataSet1.FieldByName('CUSTOMER').AsString + #13;
end;
ShowMessage(‘Selected Rows: ‘ + #13 + aux);
end;
We are in the process of upgrading one of our projects from Delphi XE to XE8. Our audit code makes use of a TIMESTAMP field in a MSSQL (2012 in this instance) database and selects from a table using this as a parameter in the WHERE clause.
We now are no longer getting any results running the following code:
procedure TForm2.Button1Click(Sender: TObject);
begin
ADODataset1.CommandText := 'SELECT * FROM CURRENCYAUDIT';
ADODataset2.CommandText := 'SELECT * FROM CURRENCYAUDIT WHERE Audit_Timestamp = :Timestamp';
ADODataset2.Parameters.Refresh;
ADODataset1.Open;
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
begin
showmessage('nothing to compare');
end;
ADODataset2.Parameters[0].Value := ADODataset1.FieldByName('audit_timestamp').Value;
ADODataset2.Open;
caption := inttostr(ADODataset2.RecordCount);
end;
Where CurrencyAudit is any old MSSQL table containing an notnull timestamp audit_timestamp field.
The caption of the form is 0 with no message shown.
Any idea how I can get this to work? Tried AsString (nonsense string, 0 results), AsSQLTimestamp (parameter doesn't accept) and AsBytes (0 return). Unfortunately the return of the .Value only evalates as 'variant array of byte' which isn't helpful to visualise/see what it is.
Edit: Running it as .AsBytes and viewing that in the debugger I can see that the XE verison is returning 0,0,0,0,0,8,177,22 whereas the XE8 is returning 17,32,0,0,0,0,0,0. Checking other fields of the (real) database shows the record is the same. Looks like a bug in reading TIMESTAMPs from the DB
I'm using two AdoQueries. The following works fine for me in D7, correctly returning 1 row in AdoQuery2, but 0 records in XE8, so obviously has the same XE8 problem as you've run into.
var
S : String;
V : Variant;
begin
AdoQuery1.Open;
S := AdoQuery1.FieldByName('ATimeStamp').AsString;
V := AdoQuery1.FieldByName('ATimeStamp').AsVariant;
Caption := S;
AdoQuery2.Parameters.ParamByName('ATimeStamp').Value := V;
AdoQuery2.Open;
Just for testing, I'm running my AdoQuery1 and AdoQuery2 against the same server table.
Update : I've got a similar method to the one in your answer working that avoids the need for your Int64ToByteArray, at the expense of some slightly messier (and less efficient) Sql, which may not be to your taste.
In my source AdoQuery, I have this Sql
select *, convert(int, atimestamp) as inttimestamp from timestamps
and in the destination one
select * from timestamps where convert(int, atimestamp) = :inttimestamp
which of course avoids the need for a varBytes parameter on the second AdoQuery, since one can pick up the integer version of the timestamp column value and assign it to the inttimestamp param.
Btw, in your original q
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
the two expressions would better be written the other way around. Unless ADODataset1 has persistent fields, if it contains no records when opened, referring to the audit_timestamp should raise a "Field not found" exception.
It appears that EMBT have broken the conversion of a TIMESTAMP into a byte array. The XE version of the bytearray is correct and manually pulling down the data as an int64 and then building the bytearray by hand (is there an out-of-the-box function for this?) and using that as the parameter works in XE8.
I've no idea whether this is a similar issue with other binary data types. Hope not!
Working code:
procedure TForm2.Button1Click(Sender: TObject);
var
TestArray: TArray<Byte>;
j: integer;
function Int64ToByteArray(const inInt: uint64): TArray<Byte>;
var
i: integer;
lInt: int64;
begin
SetLength(result, 8);
lInt := inint;
for i := low(result) to high(result) do
begin
result[high(result)-i] := lInt and $FF;
lInt := lInt shr 8;
end;
end;
begin
ADODataset1.CommandText := 'SELECT *, cast(audit_timestamp as bigint) tmp FROM CURRENCYAUDIT';
ADODataset2.CommandText := 'SELECT * FROM CURRENCYAUDIT WHERE Audit_Timestamp = :Timestamp';
ADODataset2.Parameters.Refresh;
ADODataset1.Open;
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
begin
showmessage('nothing to compare');
end;
ADODataset2.Parameters[0].Value := Int64ToByteArray(ADODataset1.FieldByName('tmp').asInteger);
ADODataset2.Open;
caption := inttostr(ADODataset2.RecordCount);
end;
I've also checked this going down my entire (real) table and ensuring all other fields match to make sure it's not a one-off!
I'll raise a ticket with EMBT for them to sit on and ignore for 5 years ;-)
https://quality.embarcadero.com/browse/RSP-11644
I am testing out Absolute Database by ComponentAce
I have on my Form a TABSTable, TABSDatabase and a TDataSource and the data is being displayed in a TDBAdvListView, MultiSelect and RowSelect are True. I have only one Table.
When either one or more of the Items in the TDBAdvListView are selected I want to have the Database Delete the selected Records.
I have tried this way in the code below:
procedure TMain.DeleteEntry2Click(Sender: TObject);
var
i: Integer;
begin
with DBAdvListView1.DataSource.DataSet do
begin
for i := DBAdvListView1.Items.Count - 1 downto 0 do begin
if DBAdvListView1.Items[i].Selected then
begin
DBAdvListView1.DataSource.DataSet.GotoBookmark(Pointer(DBAdvListView1.Items[i]));
DBAdvListView1.DataSource.DataSet.Delete;
end;
end;
end;
end;
This always results in an Error Message:
Cannot retrieve record - Native error: 10026
I have very little experience with database programming, what am I doing wrong?
Edit:
I have added a new field into the database named ID as an integer starting from 0 in the hopes that I can reference them with the Locate method and tried with the code below. This produces no error but will only delete the top record in the ListView and if I select more than one it will delete different records than selected.
My new code:
procedure TMain.DeleteEntry2Click(Sender: TObject);
var
i: Integer;
begin
with DBAdvListView1.DataSource.DataSet do
begin
DBAdvListView1.BeginUpdate;
First;
for i := DBAdvListView1.Items.Count - 1 downto 0 do begin
if DBAdvListView1.Items[i].Selected then
begin
if dbTable.Locate('ID',DBAdvListView1.Items[i].Selected,[]) then
dbTable.Delete;
Next;
end;
end;
dbTable.Close;
dbTable.Open;
DBAdvListView1.EndUpdate;
end;
end;
The dbTable has to be Closed and Opened to see changes for some strange reason - I have tried Refresh to no avail...
Edit:
// To include Table Structure as requested...
ID integer 0
Title string 200
Author string 100
Date string 20
Location string 60
Category string 100
ISBN-13 string 20
ISBN-10 string 20
In the Absolute Database Utils directory there is a DatabaseManager.exe which I used to create the actual table with and in here I have also now set a Primary Key of the type:
Type - Primary
Name - ID
The fields for the Index:
ColumnName - ID
CaseInsensitive - False
ASC - True
MaxIndexSize - 20
If you know the primary keys of all the records to be deleted, then you can use one SQL query statement in order to delete all the selected records in one go -
delete from table
where id in (1, 7, 15, 23, 45);
You would have to build this query manually, i.e. create the string which holds the id numbers.
Answer to own question... I remove the selected records with the code below:
procedure TMain.Button5Click(Sender: TObject);
var
i: Integer;
begin
with DBAdvListView1 do
for i := 0 to Items.Count - 1 do
if Items[i].Selected then
begin
Memo1.Lines.Add(Items[i].Caption + ' - Selected!'); //Test for Correct ID's!
if dbTable.Locate('ID', Items[i].Caption, []) then
DBAdvListView1.Datasource.DataSet.Delete;
end;
dbTable.Close;
dbTable.Open;
end;
second code will work if you just remove the Next; from the if section. Since u're already inside a 'for' loop, there's no need to use the Database.Next.
I have a problem with a old Delphi system, this system insert data into a SQL Server table.
After 10 years change a field of the table from 100 to 255 chars long.
The system select all the registris of the table, and put them on an other table after a transformation. That works fine.
The problems are when the system update a field.
That show me the error
EDBEngineError with message 'Couldn't perform the edit because another user changed the record.
sConsulta:='SELECT * FROM cuentas WHERE (WALL= 2) AND (SEND_DATE = '01/01/1970')';
m_oQryLeg.Close;
m_oQryLeg.SQL.Clear;
m_oQryLeg.SQL.Add(sConsulta);
m_oQryLeg.Open;
m_oTblNov.Close;
m_oTblNov.TableName:='des_table';
m_oTblNov.Open;
with m_oTblNov do
begin
while (not m_oQryLeg.EOF) do
begin
Insert;
FieldbyName('COD_HOME').AsString:= m_oQryLeg.FieldByName('USR_HOME').AsString;
(...)
Post;
m_oQryLeg.Edit;
m_oQryLeg.FieldByName('SEND_DATE').AsDateTime:= Date; //<-- HERE THE ERROR
m_oQryLeg.Post;
m_oQryLeg.First;
m_oQryLeg.MoveBy(i);
inc(i);
end;
end;
m_oTblNov.Close;
m_oQryLeg.Close;
UpdateMode: upWhereAll
cuentas table:
NUM_SOL nvarchar 6 *PK
WALL tinyint 1
SEND_DATE smalldatetime 4
OBS_CRED nvarchar 255
FLCC real 4
STREET nvarchar 30
You're trying to update a query you're using on a table you're editing at the same time, and it's not going to work.
Since you know you're going to insert every row from the query into m_oTblNov, why not just do it like this instead?
sConsultaSELECT :='SELECT * FROM cuentas';
sConsultaUPDATE := 'UPDATE cuentas SET Send_Date = :New_Date';
// Separate WHERE so you can use it twice. Note the leading space
// between the first ' and WHERE.
sWhere := ' WHERE (WALL = 2) and (SEND_DATE = ''01/01/1970'')';
m_oQryLeg.Close;
m_oQryLeg.SQL.Text := sConsulta + sWhere;
m_oQryLeg.Open;
m_oTblNov.Close;
m_oTblNov.TableName:='des_table';
m_oTblNov.Open;
with m_oTblNov do
begin
while (not m_oQryLeg.EOF) do
begin
Insert;
FieldbyName('COD_HOME').AsString:= m_oQryLeg.FieldByName('USR_HOME').AsString;
(...)
Post;
// Don't run these any more. See below.
// m_oQryLeg.Edit;
// m_oQryLeg.FieldByName('SEND_DATE').AsDateTime:= Date; //<-- HERE THE ERROR
// m_oQryLeg.Post;
m_oQryLeg.First;
m_oQryLeg.MoveBy(i);
inc(i);
end;
end;
m_oTblNov.Close;
m_oQryLeg.Close;
m_oQryLeg.SQL.Text := sConsultaUPDATE + sWHERE;
m_oQryLeg.ParamByName('New_Date').AsDateTime := Date;
try
m_oQryLeg.ExecSQL;
finally
m_oQryLeg.Close;
end;
The problem here is that your object is trying to refresh the row from the database to make sure nothing has changed and more than likely the object truncated or rounded some value that is causing the refresh to return no rows.
This could be caused by either a float value being truncated or some other value being off.
if you don't/can't change that column to fix this issue I would advise changing to either upWhereChanged or upWhereKeyOnly.
Given that dates are treated as doubles in most windows databases, I would think that upWhereKeyOnly would be best.
EDIT:
After looking at your table, it may have to do with the fact you are using a single based smalldatetime. Delphi treats all DateTime data as a double and the conversion back and forth may be causing small rounding issues.