cashew
asked on
How Save Query result to DBF?
In my application, my users are constantly asking for new information and reports faster than I can generate them. So I set up a simple SQL-interface. I just write the SQL template for them, and have a module that loads and executes them (executes the query, not the user). So far, I
have a dataset pointing to the TQuery, so they can SEE the result set.
But now they need to save the results off somewhere else, and it needs to be in the DBF format so they can use Excel to get at the data and do whatever they want.
QUESTION: After a Query is opened, how do I copy the result set off to a DBF table? The structure of the query result my vary, so I can't pre-create a table to have the answers coppied into them via UpdateSQL. I imagine there must be some simple call that I'm missing.
have a dataset pointing to the TQuery, so they can SEE the result set.
But now they need to save the results off somewhere else, and it needs to be in the DBF format so they can use Excel to get at the data and do whatever they want.
QUESTION: After a Query is opened, how do I copy the result set off to a DBF table? The structure of the query result my vary, so I can't pre-create a table to have the answers coppied into them via UpdateSQL. I imagine there must be some simple call that I'm missing.
ASKER
Thank you for your response. However, I think the Batchmove still copies the table to a paradox table. Also, that much code was a bit of overkill for the task at hand. To get around the problem, I used the following instead:
// SaveAnswer is a SaveDialog on the form.
procedure TFM_SQL.btnSaveAnsClick(Se nder: TObject);
Var
OutFile : TextFile;
Col : integer;
begin
SaveAnswer.FileName := 'Answer.CSV';
if SaveAnswer.Execute then
With Query1 do begin
assignFile(OutFile,SaveAns wer.FileNa me);
Rewrite(OutFile);
// write out the header
for Col := 0 to FieldCount-1 do
If Col = 0 then
Write(OutFile,'"',Fields[C ol].FieldN ame,'"')
else
Write(OutFile,',"',Fields[ Col].Field Name,'"');
Writeln(OutFile);
// write out the data
First;
While not EOF do Begin
for Col := 0 to FieldCount-1 do
If Col = 0 then
Write(OutFile,'"',Fields[C ol].AsStri ng,'"')
else
Write(OutFile,',"',Fields[ Col].AsStr ing,'"');
writeln(OutFile);
Next;
End;
CloseFile(OutFile);
End;
end;
// SaveAnswer is a SaveDialog on the form.
procedure TFM_SQL.btnSaveAnsClick(Se
Var
OutFile : TextFile;
Col : integer;
begin
SaveAnswer.FileName := 'Answer.CSV';
if SaveAnswer.Execute then
With Query1 do begin
assignFile(OutFile,SaveAns
Rewrite(OutFile);
// write out the header
for Col := 0 to FieldCount-1 do
If Col = 0 then
Write(OutFile,'"',Fields[C
else
Write(OutFile,',"',Fields[
Writeln(OutFile);
// write out the data
First;
While not EOF do Begin
for Col := 0 to FieldCount-1 do
If Col = 0 then
Write(OutFile,'"',Fields[C
else
Write(OutFile,',"',Fields[
writeln(OutFile);
Next;
End;
CloseFile(OutFile);
End;
end;
Actually, my answer was in the first paragraph. The rest of the answer was (at least I thought) a nice to have. Simply copying the text as a pas file meant you didn't have to write code to do batchmove & queries. Simply tell the compoenent to do a batchmove or not - two compoenents in one. Also, BatchMove will handle dBASE. Sorry for the confusion, I thought I'd give you a little more information to possibly solve future problems.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
You can use the SQL CreateTable statement (I know this works for Paradox, not sure with DBase) or you can use the Batchmove method. If your using the batch move method, simply save the results of the SQL as myfile.sql and use the Batchmove component to move the records from one table to another. This means you don't have to create a 'Answer' table before hand or modify its structure every time you make a change to the source table.
An even easier way is to use a component that runs the SQL string, and based on what whether you set a property as true or false at runtime, automatically does the BatchMove for you - to either Text, Paradox or dBase. I have included code below from Brendan Delumpa of the Delphi Corner. Simply copy the code below and save it as a PAS file (using Notepad). Compile it into Delphi and presto! A new component that runs the query and automatically move the data appears on your palette. Any questions, let me know.
**************************
Properties: DestinationTable - Name of destination table. Defaults to 'INIT.DB'
DestDatabaseName - Name destination database. If a component is dropped into a form, you can set this interactively with a property editor I created for it.
DestBatchMoveMode - This is a property of type TBatchMode.
Defaults
to batCopy.
DoBatchMove - Determines if a batch move should take place at all. If it should (value = True), the SQL result set will be moved to a persistent data store. Otherwise, a regular Open will occur.
Methods: Execute (virtual) This is what you will call when using this component. However, since this is a descendant of TQuery, you can always use Open or ExecSQL to go around this function. Notice that this is virtual, which means that you can add more functionality if you wish.
DoEnhQueryOpen: This takes the place of the Open method, but
(virtual) since it's private, it can only be called by Execute. It too is virtual, so you can override its functionality. I suggest you keep it private to avoid people inadvertently using it.
Notes:
You may get a couple of compiler warnings stating that the vars "btc" and "tbl" may
not have been initialized. Ignore them. The reason for the warning is because the
vars are declared but only initialized if the Open succeeded. No use in creating
them if they aren't needed.
==========================
unit enhquery;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DB, DBTables, DSGNINTF{, alnames};
type
TDBStringProperty = class(TStringProperty)
public
function GetAttributes: TPropertyAttributes; override;
procedure GetValueList(List: TStrings); virtual; abstract;
procedure GetValues(Proc: TGetStrProc); override;
end;
TDestDBProperty = class(TDBStringProperty)
public
procedure GetValueList(List: TStrings); override;
end;
{Main type information for TEnhQuery}
TEnhQuery = class(TQuery)
private
FDestTblName : String;
FDestDBName : String;
FBatchMode : TBatchMode;
FDoBatchMove : Boolean;
procedure SetDestTblName(Value : String);
procedure DoEnhQueryOpen; virtual;
public
constructor Create(AOwner : TComponent); override;
procedure Execute; virtual; {Let people override this}
published
property DestinationTable : String read FDestTblName write SetDestTblName;
property DestDatabaseName : String read FDestDBName write FDestDBName;
property DestBatchMoveMode: TBatchMode read FBatchMode write FBatchMode;
property DoBatchMove : Boolean read FDoBatchMove write FDoBatchMove;
end;
procedure Register;
implementation
constructor TEnhQuery.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FDestTblName := 'INIT.DB'; {Set initial value of Destination Table on Create}
FDestDBName := Session.PrivateDir;
FBatchMode := batCopy;
FDoBatchMove := True;
end;
procedure TEnhQuery.SetDestTblName(V
begin
if (FDestTblName <> Value) then
FDestTblName := Value;
end;
{=========================
This is a very simple routine that will determine which route to take with
respect to executing the SQL query. It gives the component a bit of
intelligence, so the user need only use one call. Essentially, it looks
at the first line of the query; if it finds the word SELECT, then it
knows to call OpenProc, which will open the query and perform a batch move.
==========================
procedure TEnhQuery.Execute;
begin
if (SQL.Count > 0) then
if DoBatchMove then {Check to see if a batch move is desired}
if (Pos('SELECT', SQL[0]) > 0) then
if (DestinationTable <> '') AND (DestDatabaseName <> '') then
try
DoEnhQueryOpen;
except
raise Exception.Create('Enhanced
Abort;
end
else
MessageDlg('You must supply a Destination Table and DatabaseName', mtError, [mbOK], 0)
else
Open
else
try
ExecSQL;
except
raise Exception.Create('ExecSQL did not execute properly. Aborting');
Abort;
end
else
MessageDlg('You have not provided any SQL to execute' + #13 +
'so there is nothing to process. Load the' + #13 +
'SQL property with a query', mtError, [mbOk], 0);
end;
procedure TEnhQuery.DoEnhQueryOpen;
var
btc : TBatchMove;
tbl : TTable;
begin
try
Open;
try
tbl := TTable.Create(Application)
btc := TBatchMove.Create(Applicat
with tbl do begin
Active := False;
DatabaseName := DestDatabaseName;
TableName := DestinationTable;
end;
with btc do begin
Source := Self;
Destination := tbl;
Mode := DestBatchMoveMode;
Execute;
end;
finally
btc.Free;
tbl.Free;
end;
except
Abort;
end;
end;
{=========================
TDestDBProperty property editor override functions. Since the property editor
is derived from TStringProperty, we only need to override the functions
associated with displaying our dialog box.
==========================
function TDBStringProperty.GetAttri
begin
Result := [paValueList, paSortList, paMultiSelect];
end;
procedure TDBStringProperty.GetValue
var
I: Integer;
Values: TStringList;
begin
Values := TStringList.Create;
try
GetValueList(Values);
for I := 0 to Values.Count - 1 do Proc(Values[I]);
finally
Values.Free;
end;
end;
procedure TDestDBProperty.GetValueLi
begin
(GetComponent(0) as TDBDataSet).DBSession.GetD
end;
procedure Register;
begin
RegisterPropertyEditor(Typ
RegisterComponents('BD', [TEnhQuery]);
end;
end.
{With this component, here's all you do to perform a basic extract query:
1.Create an instance of the component 2.Set the SQL property 3.Set the Destination TableName (it defaults to 'INIT.DB') 4.Set the Destination DatabaseName (it defaults to Session.PrivateDir)
As you can see, it's all a matter of setting properties. You'll notice in the properties section of the code, I've got a property called DoBatchMove. This is a Boolean property that defaults to True. If you set it to false, the batch move will not occur, but the query will be opened. This ensures that you can use the component like a regular TQuery. You'd set this to False when you are using the component in conjunction with a TDataSource and TDBGrid.
As mentioned in the code comments, we have a custom property editor. For those of you who have wanted to learn how to do custom drop-down list property editors, study the code above. You'll be amazed at how incredibly easy it is to do.
}