26

(3 replies, posted in Script)

Hello chrisyoco,


If you have an edit field with a barcode you just scanned in it you could do

procedure Form1_Button1_OnClick (Sender: TObject; var Cancel: boolean);
var
    i : Integer;
    excluded_scan : String;
begin
    excluded_scan := Form1.Edit1.Text;

    i := SQLExecute('SELECT COUNT(ScanExcluded) FROM Excluded WHERE ScanExcluded = "'+excluded_scan+'"');
        if i = 0 Then ShowMessage('No result')
        else ShowMessage('This person is excluded');
end;

But why two tables ? This is doing things the hard way don't you think ? What if you want to get the name of the person whose is excluded ? You need to perform a JOIN on the first table and something like :

SELECT
Members.Line2
FROM
Members
INNER JOIN Excluded ON Members.Barscan = Excluded.ScanExcluded

What I would have built is just one table like :

CREATE TABLE "main"."member" (
"id"  INTEGER PRIMARY KEY AUTOINCREMENT,
"member_names"  TEXT,
"member_scan"  TEXT,
"is_excluded"  INTEGER NOT NULL DEFAULT 0
)

This way, each time you save a duo member / barcode, the boolean field is_excluded is set by default to 0. Set it to 1 if the code is excluded.


Or if a member can have multiple scancodes, you could create two tables :
- a member table with names, and miscellaneous info (address, date of birth...)
- a code table with a boolean field and a reference to the member table


See the attached screenshot


Hope this helped a little


Cheers


Mathias

27

(1 replies, posted in General)

Hello sambatyin and welcome on the forum,


Currently, MVD can only use sqlite database (stored by default in the application folder) or connect to a remote MYSQL Database.
I am not sure whether or not Dmitry plans on offering other database support.


As for the scripting language used, this is Delphi (Object Oriented Pascal). Not the most recent language of all, but strongly typed and structured, and fun to use because, after all, that's what is important right ?


You'll find plenty of tutorials and examples on the forum, and do not hesitate to ask for help.


Cheers


Mathias

28

(5 replies, posted in Script)

Hello,


Just a quick explanation and answer as I'm pretty sure Derek had it all covered already.


The component in which you display some graphics is a Timage and it's content is the picture.


So in order to manually load a picture in a TImage you just need to write :

procedure Form1_Button1_OnClick (Sender: TObject; var Cancel: boolean);
begin
    Form1.Image1.Picture.LoadFromFile('.\001.jpg');
end;

Pay attention to the path to the image and the extension (I know MVD handles jpegs and pngs but for the others I don't remember).


You can of course trigger such a command on an edit field change event.


Have a good day both of you


Cheers


Math

Hello gonpublic2k,


You are right, sometimes, the simple "Report" button is not enough and you need more flexibility on the data you send to the report.
You have to more options at your disposal to do so.


1 - The first one you already known : it's the "Report SQL" button.
With this option, you can write the query you want and add all the joints and calculated fields you need, cases and so on.
The main problem with this approach being that a complex query will narrow down the data you get in return.

Look at the query bellow :

SELECT DISTINCT
asset.asset_sku,
asset.asset_name,
asset.asset_price,
asset.asset_status,
keyword.kw_text
FROM
asset
INNER JOIN asset_kw ON asset_kw.id_asset = asset.id
INNER JOIN keyword ON asset_kw.id_keyword = keyword.id
ORDER BY keyword.kw_text,
CAST(asset_sku AS INTEGER) ASC  

This query returns every asset in my database as long as they are linked with a keyword (or more) and prints out a report.
(I have 3 tables : a table called asset, a table called keywords and a joining table called asset_kw in between)


The INNER JOIN instruction does filter a lot the result of the query because it's result is exclusive. All my assets that are not linked with a keyword will not show up in the report. If I want them all, I need to replace the INNER JOIN with LEFT JOIN. This way, all assets are reported, and those having an assigned a keyword will show this extra information.


As for calculated fields, they are added the same way as you did them in you database except the come directly in the query. If I add

(SELECT COUNT(id_keyword) FROM asset_kw WHERE asset_kw.id_asset = asset.id) AS NBK    

in my query, I have one more result column in my report counting how many keyword has been assigned to each asset.


But sometimes, one query for too many tables is too complicated to write and you can not link all needed tables with JOIN and so on.


2 - Then you can simply write you queries in script and send them to the report.
Add an Onclick event to a button and use datasets.

You will need two different kind of datasets, the regular one you probably already use, and the ones for the reports.


Here is an example on how you could send the results of 3 different queries to a single report :

procedure Form2_Button1_OnClick (Sender: string; var Cancel: boolean);
var
    frxDBDataSet1, frxDBDataSet2, frxDBDataSet3 : TfrxDBDataset;
    Results1, Results2, Results3 : TDataSet;
    asset_id : Integer;
begin
    //the variable I need to trigger the queries
    asset_id := mark_list.TableGrid1.dbItemID;
      
      //qyery one stored in dataset1
      SQLQuery('SELECT '+
                'asset_sku,'+
                'asset_old_sku,'+
                'asset_name,'+
                'asset_url,'+
                'asset_price,'+
                'vendor_name,'+
                'asset_status,'+
                'asset_detail '+
                'FROM '+
                'asset '+
                'INNER JOIN vendor ON asset.id_vendor = vendor.id '+
                'WHERE asset.id = "'+IntToStr(asset_id)+'"', Results1);

        //query two stored in dataset 2
        SQLQuery('SELECT '+
                 'asset.asset_sku,'+
                 'asset.asset_name,'+
                 'asset.asset_status '+
                 'FROM '+
                 'asset '+
                 'INNER JOIN asset_req ON asset_req.id_asset1 = asset.id '+
                 'WHERE asset_req.id_asset = "'+IntToStr(asset_id)+'" ORDER BY CAST(asset_sku AS INTEGER) ASC ', Results2);

      //query three stored in dataset 3
      SQLQuery('SELECT '+
                'artist.artist_name '+
                'FROM '+
                'asset '+
                'INNER JOIN artist_asset ON artist_asset.id_asset = asset.id '+
                'INNER JOIN artist ON artist_asset.id_artist = artist.id '+
                'WHERE artist_asset.id_asset = "'+IntToStr(asset_id)+'"', Results3);

      
    //transfer standard datasets results to report datasets
    frxDBDataSet1 := TfrxDBDataset.Create(Form2);
    frxDBDataSet1.UserName := 'Asset';
    frxDBDataSet1.CloseDataSource := True;
    frxDBDataSet1.OpenDataSource := True;
    frxDBDataSet1.DataSet := Results1;

    frxDBDataset2 := TfrxDBDataset.Create(Form2);
    frxDBDataset2.UserName := 'Reqs';
    frxDBDataset2.CloseDataSource := True;
    frxDBDataset2.OpenDataSource := True;
    frxDBDataset2.DataSet := Results2;

    frxDBDataset3 := TfrxDBDataset.Create(Form2);
    frxDBDataset3.UserName := 'Artists';
    frxDBDataset3.CloseDataSource := True;
    frxDBDataset3.OpenDataSource := True;
    frxDBDataset3.DataSet := Results3;


    //assign the three report datasets to the same report
    Form1.frxReport.Clear;
    Form1.frxReport.DataSets.Clear;
    Form1.frxReport.DataSets.Add(frxDBDataSet1);
    Form1.frxReport.DataSets.Add(frxDBDataset2);
    Form1.frxReport.DataSets.Add(frxDBDataset3);

    //DESIGN MODE - comment the following two lines and uncomment the preview mode lines to preview report

    Form1.frxReport.LoadFromFile('.\Report\full_asset_report-backup.fr3');
    Form1.frxReport.DesignReport;


    //PREVIEW MODE - comment the following four lines and uncomment the above design mode lines to design report

    //frxDBDataset1.DataSet.Close;
    //frxDBDataset2.DataSet.Close;
    //Form1.frxReport.LoadFromFile(ExtractFilePath(Application.ExeName)+'Report\MyReport.fr3'); //this is my path and my report name, you need to use yours
    //Form1.frxReport.ShowReport;

    Results1.Free;
    frxDBDataSet1.Free;
    Results2.Free;
    frxDBDataset2.Free;
    Results3.Free;
    frxDBDataset3.Free;
    
    Form1.frxReport.Clear;
    Form1.frxReport.DataSets.Clear;
end;

NOTE : the customized report is called from Form2, but you will see calls to Form1 (the main application form). This is because the report handling is done by the main form of your application. Don't forget that smile

This example is built with 3 queries, but you can use as many as you want. The datasets produced will appear in design mode in your report under the names you'll have given them (here Asset, Reqs and Artists).


I can not send you this example because it is part of a very large project that is more than 200 Mo once compressed but if things are unclear, I'll arrange something to give you an example.


Hope this helped a little and was not too long to read.


have a good day


Cheers



Mathias

30

(1 replies, posted in General)

Hello lectrond,


You'll see that calculated fields are pretty straightforward. As you already known, calculated fields are special database table fields that can calculate whatever you want (to some extend) and in MVD they contain a SQL query.


Let's say you have three tables in you database:
table writer with id and writer_name
table books with id and book_title
table writer_books with id_writer and id_books


This third table is there to link writers and books.


Now, in the writer table you can had a calculated field to count for each writer the number of books they are linked to, for example to display that number in a tablegrid.


The calculated field that we will had to the writer table and call nb_books looks like :


(SELECT (SELECT COUNT(id_book) FROM writer_books WHERE writer_books.id_writer = writer.id)

And that's it. By referencing the writer id in the calculated field you are making a direct reference to each writer and thus calculating the number of books for each of them.


All that is left to do now, is to include that calulated field in your tablegrids.


Hope this helped


Cheers


Mathias

PS : if you need an example project, I can fast build one for you, just now I'm off to work, so that was just a race again my fellow forum members that are usually quite fast to answer smile , they will recognize themselves

31

(12 replies, posted in Script)

Hello VascoMorais,


Imagine you have 3 components on which you want to filter :

  • a checkbox which can be checked or not

  • a combobox which can have an element selected or not

  • and edit field that can be empty or not


If you use the onChange event of those components to filter your query, ALL conditions must be specified in ALL onChange events of ALL components, with a specific SQL qury for each case.
So you end up with a lot of conditions in each onCange events (if checkbox is checked and edit field is empty or checkbox is not checked and combobox index > -1 and so on...). This is difficult to keep track of all the chained conditions and their effect. Not talking when you have more than 3 components for filtering.


But if you regroup all conditions in one procedure like I did, the logic is only in one place and you don't have to worry about AND and OR, just code what each component does and that's it. You only need one single filtering query and each element can be nullified with '1 = 1' (which is always true) or the filter issued by the component state.


Hope this makes things a little clearer.


Wish you a good week


Cheers


Mathias

32

(12 replies, posted in Script)

Hello VascoMorais and derek,


The problem with multiple search fields is that it can become complicated when you have to test 3 or 4 fields for user inputs, leading to a veryyyyyyy looooong sequence of

if... then... else...

in order ton cover all the combinations.


That is.... if you put the search logic in each "OnChange" events of your input components.


What I usually do is regroup all the search logic in one place, a procedure, that will take care of all the conditions.
From there, all I have to do is link your component's OnChange events to this single procedure.


In the attached example, your have two tables.
The first one is called asset and contains more or less 26.000 rows with for each
- a serial number (SKU number)
- an asset name
- an asset status (is it owned, wanted...)
- a reference to the second table
The second table only contains a text field, with the name of the vendor of the assets


Searching through 26.000 rows (my actual database has more than 300.000) can be heavy, especially if you want to be able to search on all table fields


In the fully commented code attached, you will see that :
- the search can be performed on 4 criteria at the same time
- when a field is empty the database filter is neutalized
- I define 4 variables for each possible filters and concatenate them in one single filter activated by each OnChange events of the components


Hope this helps a little

have a good day olds friends smile


Cheers


Mathias


PS :
1 - my database is 15 times larger than this sample, so I added a Timer event to delay the effect of the filter until the user stops typing
2 - The SKU filter operates with a LIKE only on the end of the serial number
3 - The asset name filter operates with a LIKE from both ends of the name
4 - The formatting of the TableGrid takes place only in it's OnChange event, this way I don't have to replicate this formatting for each changes
5 - I use Radiobuttons because the little hassle of creating them all is better than having to handle the logic behind 5 comboboxes when only one can be checked at a single time

33

(2 replies, posted in General)

Hello kkalgidim,


As I am the one usually finding complex solutions to simple problems, you might want to wait for others advises smile


I see two workarounds for your problem (not sure you can implement the second one in your MVD version though...).


The first solution (and the simplest) would be to (manually) create an index on your table containing the names. This usually speeds up searches quite a bit. I recommend you search for sqlite and indexes in google, you will see that this is pretty straightforward.


The second solution would be to implement a delay (timer) in your application. Like the user is typing letter in the search field but the search does not trigger before say 0,5 second. This way, the user has time to type in his search criteria without the query being triggered every time et types in a letter (which is the cause of your application slowing down).


First you have to declare a timer in you application. This is a global variable meaning you have to place it at the very beginning of your script, like this for example :

var
     TimerA : TTimer; //----->Timer for name search

Now that the timer is declared, you will need to create it. This is done on the OnShow event of the form where it will be used like this :

Formx_Onshow (Sender: string; Action: string);
begin
    TimerA := TTimer.Create(nil); // create timer
    TimerA.OnTimer := @OnTimerA;  // event procedure the timer points to
    TimerA.Interval := 500; // interval in milliseconds of your timer


....
end;

Do not declare it in the variable section of the form since you already declared it as a global variable


Now, what does the timer do ? Declare the procedure like this :

procedure OnTimerA (Sender: TObject);
var
    //any variable you need for your search query if any
begin
    // you code here with your own search logic
     TimerA.enabled := False;
end;

Don't forget to stop (enabled := false) your timer at the end of the logic, it is the typing of the user that will trigger it after each character type din.


Finally, in the OnChange event of your search field disable the timer and start it again to reset the interval in milliseconds you set up previously.

procedure edit1_OnChange(Sender: string);
     TimerA.Enabled := False; //stops the timer and reset the time interval
     TimerA.Enabled := True; //starts the timer again for the given time interval

Since MVD creates all forms at startup and not dynamically, do not forget to free the timer when the corresponding form is closed :

procedure FormX_OnClose (Sender: string; Action: string);
begin
     TimerA.Free;
end;

Now each time the user types in a character, the system will wait 0,5 seconds before firing the search query. each time the user type sin a new character, the timer is reset for another interval.


Hope this helps.

Cheers


Mathias

34

(10 replies, posted in General)

Hello ljhurtado,


You can use an SQL query to do this.


I don't remember the exact name of the field generated by MVD for the file path, but suppose it is called my_file_path and you table is called documents, you can do it like this :

SELECT REPLACE(my_file_path,'192.168.5.6','172.25.3.5') FROM documents

I suggest you do it with a SQL tool rather than code a function in MVD to do it.
I also suggest you make a backup of you database file before you proceed smile


Have a nice day


Cheers


Math

35

(2 replies, posted in General)

Hello hosam14,


It's easy :
- plug your bar-code scanner on your computer
- put an edit field on a form
- launch your application and click in the edit field (put your cursor in it)
- use your scanner to read the bar-code
- the code appears in the edit field


Easy right ? smile


Have a good day


Cheers


Mathias

36

(2 replies, posted in Script)

Hello livexox,


If you want to intercept the exception, I think you need to add the "True" flag at the end of your HTTPGet instruction, like this :

procedure Get_content (Sender: string; var Cancel: boolean);
begin
     url:=frm_main.Edit1.Text;

     try
         get_url:= HTTPGet(url,True);
         frm_main.Memo1.Text:= get_url;
    except
        if ExceptionMessage <> 'Socket Error # 10054' then  frm_main.Memo1.Lines.Add('Unknown Error') else frm_main.Memo1.Lines.Add(ExceptionMessage);
    end;

No need to free the HTTP con, I think Dmitry frees it on Form close.


As for the inactivity delay, unfortunately I have no clue...


Wish you a good week-end


Cheers


Mathias

37

(3 replies, posted in General)

Hello wenchester21,


Just one thing to add to what Dmitry answered.


You might have noticed that Dmitry used

for i := c downto 0 do

This is very important to process your StringList in reversed order like Dmitry said because if you happen to delete some strings (lines), the order of the stringlist changes and you might miss some of the strings. The reverse order processing of the list avoids this problem.


Have a good day


Math

38

(2 replies, posted in General)

Hello manixs2013,


Are you talking about deleting the files on the drive that are not "linked" into the database, or deleting links in database that do not exist as files where they are supposed to be ?


If you used the File field type or the Image field type, you will notice that MVD creates to fields in database : a blob one and a text one. The text one will contain the full path to the file/image and can be use to check if the said file or image is still where it is supposed to be.


Let's say I have a simple table like
Asset with asset_name as TEXT and asset_image as IMAGE (I have in fact two fields in database for the image, a BLOB and a TEXT)


On a button click here is whar you could do :

procedure Form1_Button2_OnClick (Sender: string; var Cancel: boolean);
var
    PathList : TDataSet;
    ImgURL : String;
begin
    //Build a dataset with all image path found in db
     SQLQuery('SELECT asset_image_filename FROM asset',PathList);
        //Loop through all the results you got from the query one by one
        while not PathList.Eof do
            begin
                //for ease of reading assign the result to a string variable
                ImgURL := PathList.FieldByName('asset_image_filename').AsString;
                    if ImgURL <> '' then //if the path is not empty
                        begin
                            //check if the file exists and do what you want accordingly
                            if FileExists(ImgURL) then ShowMessage('File OK') else ShowMessage('Missing File');
                        end;
                //Don't forget this instruction to go to next result set, or you procedure will get stuck
                PathList.Next;
            end;
        //Free the dataset after use
        PathList.Free;
end;

Here I just display a message to inform the user the the file exists or not, but you could very well delete the database info if the file is not present on the disk.


To do so, I would add an important info to the query : the id of the record.
Based on this, I just update the database table where needed.


Without the comments, this could look like :

procedure Form1_Button2_OnClick (Sender: string; var Cancel: boolean);
var
    PathList : TDataSet;
    ImgURL, ImgID : String;
begin
    SQLQuery('SELECT id, asset_image_filename FROM asset',PathList);
        while not PathList.Eof do
            begin
                ImgURL := PathList.FieldByName('asset_image_filename').AsString;
                    if ImgURL <> '' then
                        begin
                            if FileExists(ImgURL) then ShowMessage('File OK') else
                                begin
                                    ImgID := PathList.FieldByName('id').AsString;
                                    SQLExecute('UPDATE asset SET asset_image = NULL, asset_image_filename = NULL WHERE asset.id ="'+ImgID+'"');
                                end;
                        end;
                PathList.Next;
            end;
        PathList.Free;
end;

This is not the prettiest code I've written (I just arrived at the office) and there is no security whatsoever (not try catch, not user confirmation...) but I hope it will get you started. This is just to clean database from path that no more lead to image on the drive.

I also cleaned the BLOB field, which should not be needed if you used the "link file option".


From time to time, if you delete a lot of info from you database, a simple

SQLExecute('vacuum');

can reclaim a lot of lost space in your database smile


Have a good day


Cheers


Mathias

39

(5 replies, posted in Script)

Hello Jean,

No, the missing Button1 was a attempt at creating a search button instead of a SQL Query button, but the total duration of each album was not correctly calculated by the calculated field so I changed my mind.


The groups, albums and tracks data was filled... by hand directly into the database smile


Math

40

(5 replies, posted in Script)

Hello Jean,


What I post here is a solution, but not the only one.
The database structure approach is very "standard" where I usually tend to put as many information as I can in one table and use trigger nowadays.


Anyway, I'm quite proud of the track duration handling, and the calculation to sum all albums tracks in minutes and seconds smile


Have a good day old friend


Cheers


Mathias

41

(23 replies, posted in General)

Hello Dmitry,


I had a look at your new beta version, and what you added is not a treeview... this is a DBTreeView and THIS IS AWESOME !!!


Very very nice my friend, I love it !


From what I see, it's better used with a recursive table containing ID's and parent ID's, right ?


I'll play with it and try to write a tutorial this week-end.


Nice job Dmitry,


Cheers


Mathias


PS : did not find the autoexpand property but I only looked fast

42

(18 replies, posted in General)

Hello Adam,


I have a project for you but it is nearly 1 in the morning and I have to clean and comment the code.
Tomorrow is a day of for me, I’ll clean up everything and post it for you first thing with morning coffee smile


Export, import with duplicate check via stringlists and progressbar.


Cheers


Math

43

(18 replies, posted in General)

Hello Adam,


You can not (I think) through 2 SQLExecute at the same time in the same function. If you really want to query multiple tables at the same time in one query, the best option is to use the UNION keyword between your queries. But this will only work if the tables have the same fields, which is not the case. I think you'll have to consider working with stringlists.


Then, when you call (on import)

sFileName := 'ExportedData-' + FormatDateTime('dd-mm-yyyy hh-nn-ss', now)+'.csv';

you refer to a file which name is composed of the date and time of your search, probably a few seconds after the export, so the systme can not find it because it does not exist. The export file was generated a few seconds earlier, thus the name is different. The problem here is the 'now' variable you are using.


A few lines down, you write :

CopyFile(ExtractFilePath(Application.ExeName)+'ExportedData.csv' + FormatDateTime('dd-mm-yyyy hh-nn-ss', now)+'.csv', OpenDialog.FileName);
   ShowMessage('Data has been imported.');

Wrong name again, because you added '.csv' twice : once just after Application.Exename and another time at the end.


Come on my friend, you know better than that, I'm sure smile


Anyway, I'll try to through a couple of lines for you tonight.


Cheers


Mathias

44

(4 replies, posted in General)

Hello David,


Yes, what you are asking for makes sens smile


Have a look at this slightly modified example from Dmitry.


I added a checkbox to the form, linked it to the "smoker" boolean field in the employees table and linked it to the search button. No code needed.


- Checked, the checkbox returns only smokers
- Unchecked, the checkbox returns non smokers
- Grayed, the checkbox returns both


Hope this helps


Cheers


Mathias

45

(18 replies, posted in General)

Hello Adam,


Using StringList for exporting was only to get all data from X tables in a single List and exporting them to a single CSV file.


When importing, you need to differentiate the DB tables where the imported data will be saved, the StringList trick does not work anymore, at least not this way.


Furthermore, I see in your Form1_bImport_OnClick procedure a bunch of SELECT, while you are importing from a CSV file and saving to a database. Shouldn't those instructions be INSERT instead of SELECT ?


Importing is a whole different story. Using StringList would only bee useful if you want to compare to lists : the one to be imported, and the one built from database data to compare and find duplicates before saving.


Have a look at the IndexOf() function in TStrings : it lets you check for the presence of a certain string in another one. If indexOf =  - 1, the string was not found and then it's not a duplicate.


The general idea is this :
1 - build a stringlist with database data (this will give you all your fields separated by commas)
2 - build a second stringlist with data from the CSV file
3 - compare second list with first one to try and find sequence of this list in the original one
4 - if IndexOf = - 1 then you can save to DB because this is new data
5 - if IndexOf <> - 1 then this is duplicate data, discard it.


Try your hands on that, it's a very good exercise and you will need that often. if you encounter some problems, I'll try to find some time to build a simple example for you.


Furthermore, those lists have known counts, which could help you the progressbar you talked about in your first post.


Cheers


Mathias

46

(18 replies, posted in General)

Hello AD,


This is a quick and "disrty" answer because I'm soon leaving home for a motorcycle ride with friends (Sunday morning here).


For the first part of your question : exporting multiple query to 1 csv file. This can not work the way you do it because the CSV file you save to is created for each query, the last one overwriting the previous one, thus you only have part of the result in your csv file.


One workaround would be to pass to your export procedure, not a Filename, but a TStringList.
This list is created BEFORE calling the export procedure, thus each results are appended to it, and destroyed after the export. This way, all data of all queries are in the StringList and saved to the same file.


I made some changes to your code as well :
- I deleted the image field from the second query because the result was ugly in the csv file (but you can add it back if needed)
- I hard-coded the export filename because I lacked time to do it properly.


The whole process is now
1 - Create the StringList
2 - Call export procedure that append it's results to the StringList as many times as needed
3 - Save the StringList to file
4 - Cleanup everything and end.


Export Procedure :

Procedure ExportToSl (SQL: string; Sl: TstringList);
var
Results: TDataSet;
i,c: integer;
sValue, sValues: string;

begin
SQLQuery(sql, Results);
c := Results.FieldCount-1;

  while not Results.eof do
  begin
  sValues := '';
  for i := 0 to c do
      begin
      sValue := Results.Fields.Fields[i].AsString;
      if (Results.Fields.Fields[i].DataType = ftFloat) or (Results.Fields.Fields[i].DataType = ftLargeint) then
          sValue := ReplaceStr(sValue, ',', '.')
          else
          begin
          sValue := ReplaceStr(sValue, ',', '&comma;');
          sValue := ReplaceStr(sValue, #13#10, '|');
          sValue := '''' + escape_special_characters(sValue) + '''';
          end;

          sValues := sValues + sValue+',';
        end;

  if sValues <> '' then SetLength(sValues, Length(sValues)-1);
  sl.Add(sValues);
  Results.next;
  end;
Results.Close;
end;

As you can see, the StringList is passed as parameter, just like you did wit the filename, but not created in it. It's the calling procedure that will create and free it, like thi :

procedure Form1_bExport_OnClick (Sender: string; var Cancel: boolean);
var
    Sl : TstringList;
begin
Sl := TstringList.Create;
sFileName := 'ExportedData-' + FormatDateTime('dd-mm-yyyy hh-nn-ss', now)+'.csv';
SaveDialog := TSaveDialog.Create(Form1);
SaveDialog.FileName := sFileName;
SaveDialog.DefaultExt := 'csv';
SaveDialog.Filter := 'Database|*.csv|Any file|*.*';
SaveDialog.Options := ofOverwritePrompt+ofHideReadOnly+ofEnableSizing;

if SaveDialog.Execute then
   begin
       ExportToSl('SELECT lastname, firstname, salary, dateofbirth, isSmoke, comment FROM employees', Sl);
       ExportToSl('SELECT coDate,  coName, coActive FROM Company', Sl);
       Sl.SavetoFile('.\export.csv');

       ShowMessage('Data has been exported in to the specified folder');
   end;
SaveDialog.Free;
Sl.Free;
en;

This way, you can call the ExportToSl procedure as many times as you want, on as many queries as you want, everything is added to the stringlist (appended) and saved in one shot.


Hope this helps, I leave the rest to our fellow coders on the forum.


Cheers


Mathias

Hello teco049,


You wraper is a good idea, but you can make it shorter with the use of ReplaceStr.
This command will do what you do but natively, that is to say checking each character in the string and replace it if matched.


To keep you double quotes or convert simple quotes to double, you could do :


procedure Form1_bt1_OnClick (Sender: string; var Cancel: boolean);
var
    text : String;
begin
    text := wraper(Form1.Edit1.text);

    SQLExecute('INSERT INTO customers(comment) VALUES ('''+text+''')');
    Form1.TableGrid1.dbUpdate;
    Form1.Edit1.Clear;
end;

//WRAPER FUNCTION
function wraper(input : string) : string;
begin
    Result := ReplaceStr(input,'''','"');
end;

Have a good week-end


Cheers


Math

48

(3 replies, posted in General)

Hello VascoMorais,


Yes, you can use two SQLite databases at the same time, but this can only be done programmaticaly : you will have to issue all the commands to this DB by code.


First, you need a database located somewhere and you will have to inform you application of it's availability or not. This is done by using the ATTACH and DETACH commands. You also need a name for this DB. I advise you to choose a simple one.


Connecting your database is done like this :

SQLExecute('ATTACH database ''.\DB_OLD\old_sqlite.db'' AS OLD_DB');

And disconnecting the Db (if needed) is done like this :

SQLExecute('DETACH OLD_DB');

To attach, you need a path to the database, to detach, you just refer to the database via the alias you gave it (it's name).


From now on, if you want to execute some SQL but on this secondary database, you need to precede all your command with the alias of the database.

If you wright for example :

SQLExecute('INSERT INTO my_table(field1, field2) VALUES(X,Y)');

this will be executed on the main database.
If you want to target the attached database, the command becomes :

SQLExecute('INSERT INTO OLD_DB.my_table(field1, field2) VALUES(X,Y)');

Some links on basic attach command :
http://www.sqlitetutorial.net/sqlite-attach-database/
and
https://www.tutorialspoint.com/sqlite/s … tabase.htm


Hope this helps


Math


PS : my code examples have been written from memory and might not compile, this was just to give you the general idea

49

(2 replies, posted in SQL queries)

Hello ehwagner,


If feel totally dumb not to have thought about this combination. I have tried a lot of them but not this one.


Thanks you my friend, works like a charm smile


Math

Hello both of you smile


If you want to convert a type of data into another before exporting it to Excel, you might want to look at the CAST instruction.


For example, if you want to retrieve a text field composed of numbers to a number, you can try :

     SQLExecute(‘SELECT CAST(my_field AS INTEGER) FROM my_table’);

THis should give you an integer recognized as such by Excel, provided the data is an integer. If it is a REAL you can use the CAST AS REAL.


Have a good weekend both of you


Cheers


Math