Showing posts with label SQL. Show all posts
Showing posts with label SQL. Show all posts

2013-09-23

Splitting XML-reports zip & CIFS copy them to Windows

The other day I had a meeting with the project manager for an application that receives files from the Data Warehouse . The procedure for sending files to this application is interesting. First we create a complex XML file, then we zip it and lastly we connect to a Windows server share and copy the zip file. Three jobs to create the file, zip it and send it. The first job creates the XML file by execute a SQL Select and then hand result over to the converter  sqlconverter_xml02.php .

 

The second job zips the output file report0.xml.

The third job ships the zip file ItemMessage.xml to the Windows share:

The job navettiCopy2WIN.xml mounts the windows share as a CIFS file and the copy the zip file to the share and finally unmounts the Windows share:

 If you follow the init actions  you see how it’s done. Note I put in a sleep before unmount, in the past we have had some timing problems. If something goes wrong we execute exit failure .

XML wrong tool for data-interchange.

This job schedule is complex and I was not happy when the project manager of the receiving application told me ‘ The file is too large, can you please chop it up in smaller chunks ’. It’s probably their XML parser that cannot cope with large input. Most XML encoding/decoding I’ve seen is done in memory, so very large XML files tend to exhaust RAM. Actually XML code large data streams is a bad idea since XML encoding is verbose and parsing inefficient. I had problems with my SQL to XML conversion. First I based it on PHP simplexml  but when I exhausted the memory, I took the very bad decision to create my own XML coder sqlconverter_xml02.php (see above), it’s a piece of crap but it works. But I should have said no to large XML data-interchange, Google Buffers or CSV or even JSON are better alternatives.

Job Iterator to the rescue.

Anyway to make the other application work I had to split the XML file. Due to the XML mess I thought the split would be problematic, but after some thinking I realized I could use a  job iterator  and it turned out to be very easy to chop the file. The XML converter sqlconverter_xml02.php do not append the result to an existing file but creates a new sequenced numbered file. If you look below you see the target for the sqlconverter is  ItemMessage . The sqlconverter_xml02.php will name the first result file ItemMessage 0 .xml, the next ItemMessage 1 .xml etc. Subsequently the zipfiles job sweep them all up by the glob wildcard ‘ItemMessage * ’. This test was  done in minutes and worked right out of the box. In production the iterator would be SQL generated based on the actual result size.

First I created a job iterator <forevery> with offset and limit for each chunk, then I added the limit clause  to the SELECT statement and changed the SQL target to ItemMessage. (This SQL converter adds a sequence number to the filename if the file already exists.)

Then I just had to slurp up all created output tables in the zip job and send the zipfile to the Windows share.

If you scrutinize the schedule XML  you find there are some functionality under the hood. I’m still very happy with my PHP job scheduler  and the Integration Tag Language.

I end this post with the zipfile.php  script. The ZipArchive and Zipper classes I found somewhere on the net but I forgot to credit the authors, if you know who should have the honor for these classes please tell me.

$ziplib = fullPathName($context,$job['library']);

$ziplibcreate = $job['create'];

$log->enter('Info',"Zip library=$ziplib, create=$ziplibcreate");

$zip = new Zipper;

$ok = $zip->open("$ziplib", ZipArchive::CREATE);

if ($ok === FALSE) {

        $log->logit('Error',"Failed to create/open zip library=$ziplib, create=$ziplibcreate");

        return FALSE;

}

foreach($job['file'] as $finx => $file) {

  foreach (glob($file['name']) as $globfilename) {

      if ($file['name'] == "$globfilename") $localname=$file['localname'];

      else $localname = '';

      $path=$globfilename;

      if ($localname == '') {

              $path_parts = pathinfo("$path");

              $localname = $path_parts['filename'].'.'.$path_parts['extension'];

      }

      if (is_file($path)) {

          $log->logit('Note',"Zip $path into $localname");

          $zip->addFile("$path", "$localname");

      } else {

          $zip->addDir("$path");

      }        

  }

}

$zip->close();

$_RESULT = TRUE;

return $_RESULT;

class Zipper extends ZipArchive  {

    public function addDir($path) {

//        print 'adding ' . $path . "\n";

        $this->addEmptyDir($path);

        $nodes = glob($path . '/*');

        foreach ($nodes as $node) {

            print $node . '<br>';

            if (is_dir($node)) {

                $this->addDir($node);

            } else if (is_file($node))  {

                $this->addFile($node);

            }

        }

    }

} // class Zipper

2013-02-12

RFC READ TABLE and count(*)

Sometimes you need to know the amount of rows in a table before you access the table. If your only weapon in your arsenal is rfc_read_table, you have a problem, it can’t be done. I circumvented this limitation by inserting this piece of code:

Instead of returning the selected part of the table the program now returns the row count, if the parameter ROWCOUNT is set to a negative number. The result is returned in the file TABLE_ROWS.CSV. Admittedly this is a crude hack but it does the job. I am not sure of the (copy)rights of RFC_READ_TABLE so I just publish this addition to the program, but it’s a no-brainer to to figure out how to apply the snippet. (You do not have to be a brain surgeon to create the snippet either.)

This is an example how I pick up the rowcount  for the DD03T table:

In the first job I specify a negative rowcount which activate the ABAP code snippet above in my RFC_READ_TABLE and the rowcount is stored in the TABLE_ROWS.CSV file which is picked up by the ‘TABLEROWS’ tag in the subsequent job ‘calcIterator’ job. When you know how many rows there are in table it’s easy to download the table in chunks. And that I show in another post.

 

2013-01-06

SQL foreign key access and tree structures

Relation  in relational database theory means table  and nothing else, table with rows, columns and cells. Relational databases are tables, SQL is the programing language for manipulating and accessing those tables. The relational database is based on the theory of sets, this is a mixed blessing. On the positive side the theory of sets is mathematically proven and that is a good thing in itself at least it says the foundation is sound. But databases in applications are more than sets, a database is a model of the real world the application is supposed to support. E.g. sets do not have structure or order, the real world has. The structure the relational database have is the table structure and simple ‘relational links’ between tables, that can be used to restrict creation or deletion of rows in linked tables. Unfortunately SQL do not understand these links well, you cannot implicitly access linked data or show hierarchies.

Access via foreign keys.

Something I miss in SQL is the ability to walk through children and infer through parents via foreign key links. (You can to a certain extent compensate for these shortages with database views and virtual columns.)

For this database I would like to be able to say:

  1. select sono, customername , quantity sum-throu  SOlines as qty for SalesOrder;
  1. customername is the customername of the SalesOrder infer through the customerid foreign key link and in case of ambiguity can be qualified as SalesOrder.customername . Customer is a ‘link owner’ of SalesOrder.
  2. sum-throu is an infix function that sums the quantity of the SOlines children of the SalesOrder through sono foreign key link. Some other throu functions are max, min & mean. SalesOrder is a ‘link owner’ of the SOlines.

Compare this with the more correct SQL:

  1. select sono,customername, sum(quantity) as qty for SalesOrder left join Customer on customerid, left join SOlines on sono group by sono, customername;

I do not like specifying things the computer should be able to figure out. Getting rid of explicit joins is an advantage, even trivial joins are complex for most people. Ever since the introduction of foreign key links in DB2 I wondered when SQL should include access via foreign key links. I have thought of creating a preprocessor in MySQL proxy, but we still use MyISAM storage engine which do not support foreign keys. MyISAM is very convenient for BI system, and historically it has been the fastest storage engine, with the latest MySQL versions this may not be true anymore, I will  have a look at InnoDB when MySQL 5.6 is released.

(Foreign keys are the SQL feature I miss the most in MyISAM. Other features I miss in MySQL are computed/virtual columns and materialized views.)  

Tree structures.

The support for tree structures in SQL is at the best crappy.Good tree structure support includes:

  1.  Ability to go up and down the tree, starting from the top or the bottom.
  2. Stop traverse the tree at certain nodes or exclude nodes from the result tree.
  3. Tree level indicator so you know where in the tree you are.
  4. Ability to intercept updating if loops are found in the tree structure

Tree structures are very useful but unfortunately also very complex, so they are ‘under-utilized’. Simple and powerful support for tree structures including the bullets above would take SQL to a new level. Tree structures are a big topic that deserves a post of its own. I end this post with a link to a post  describing a tree structure we use.

 

 

2012-11-08

The € operator - supporting the Euro and SAP RFC_READ_TABLE

Recently we have started to use a new technique for extracting data from SAP. We dynamically create predicates for RFC_READ_TABLE . This technique has been very successful and we use it more and more. But there are some problems with our technique and RFC_READ_TABLE. We have to specify the entire predicate in one statement for each search. An example show you what I mean. We like to  find to parts A and B in factory 1100.
A natural predicate for this is:
 “PLANT EQ ’1100’ and (MATNR EQ ‘A’ or MATNR EQ ‘B’)”.
But this is not possible with the current technique, to build the predicate we have to write something like:
“(PLANT EQ ’1100’ and MATNR EQ ‘A’) or (PLANT EQ ’1100’ and MATNR EQ ‘B’)”.
This is not only unnecessary cumbersome, you want to be as precise and succinct as possible since it gives the DB-optimizer a better chance to efficiently walk through the database, and also the buffer in SAP for the predicate is limited, to make this even worse the row length for predicates in RFC_READ_TABLE is limited to 72 chars. Why?? Well its the amount of information a good ol’ punch card can carry.
Clearly the functionality described in the link above needed some enhancement, better support for creating dynamic predicates for RFC_READ_TABLE. And that’s where the € operator  comes in. The euro currency can do with some help, and I do what I can by introducing the € operator to popularize the euro. I think I’m first to introduce the € sign in a programming language, (the Integration Tag Language). The € operator syntax:
$ok = €()filename
It is a bit hard to explain the inner workings of the € operator, but some examples will hopefully show how it works. Example:

<sap>
    <rfc>
        <name>Z_LJ_READ_TABLE256</name>                
        <import>
            ('QUERY_TABLE','@TABLE2')
            ,('DELIMITER','')
            ,('NO_DATA',' ')
            ,('ROWSKIPS',0)
            ,('ROWCOUNT',0)
            ,('OPTIONS', € (MANDT EQ '300' and KAPPL EQ 'M' AND WERKS EQ '1100')/path/file)
        </import>
    </rfc>
<sap>
Take a look at the OPTIONS row in the example above, here we see the € operator in action. The expression between the parentheses  is the ‘constant’ part of the predicate  and connected to the expressions in the file with an AND like:
MANDT EQ '300' and KAPPL EQ 'M' AND WERKS EQ '1100'” AND ( filerow1 OR filerow2 OR … last_filerow)
A ‘real’ example:
In this example I extract rows from SAP table A17 for predicates (materials) generated in the first job genArray1 .  When running this example schedule the € operator  expands the OPTIONS expression into  this PHP array:
At last; the € operator conveniently chops the array elements into 72 char strings so RFC_READ_TABLE gets the input in punch card format :)