13.6. N:1 follower queries #

13.6.1. Data
13.6.2. C program code
13.6.3. PHP program code
13.6.4. RLIB compatible PHP program code
13.6.5. Report description
13.6.6. Report PDF result

This example below exercises two N:1 (N-to-one) follower queries along with the main query. For more information, see Follower queries.

13.6.1. Data #

Data is created as follows in the same database using the same user as the first example.

create table data (id serial unique, name text);

create table more_data (
id serial unique,
boss_id int,
name text,
foreign key (boss_id) references data (id));

create table moar_data (
sk_id int,
name text,
foreign key (sk_id) references more_data (id));

insert into data (name)
values
('Snow White'),
('Batman'),
('Cinderella'),
('Hansel'),
('Little Red Riding Hood'),
('Robin Hood');

insert into more_data (boss_id, name)
values
(1, 'Doc'),
(1, 'Dopey'),
(1, 'Sneezy'),
(1, 'Happy'),
(1, 'Bashful'),
(1, 'Sleepy'),
(1, 'Grumpy'),
(2, 'Robin'),
(3, 'Fairy Godmother'),
(3, 'Mice'),
(3, 'Pidgeons'),
(4, 'Gretel'),
(6, 'Little John');

insert into moar_data (sk_id, name)
values
(3, 'Coughy'),
(3, 'Crippley'),
(9, 'Prince Charming'),
(9, 'Shrek'),
(13, 'Will Scarlet'),
(13, 'Brother Tuck');

The query that the N:1 followers in this report simulate is:

ocrpttest=> select * from data left outer join more_data on (data.id = more_data.boss_id)
ocrpttest-> left outer join moar_data on (more_data.id = moar_data.sk_id)
ocrpttest-> order by data.id, more_data.id;
 id |          name          | id | boss_id |      name       | sk_id |      name       
----+------------------------+----+---------+-----------------+-------+-----------------
  1 | Snow White             |  1 |       1 | Doc             |       | 
  1 | Snow White             |  2 |       1 | Dopey           |       | 
  1 | Snow White             |  3 |       1 | Sneezy          |     3 | Coughy
  1 | Snow White             |  3 |       1 | Sneezy          |     3 | Crippley
  1 | Snow White             |  4 |       1 | Happy           |       | 
  1 | Snow White             |  5 |       1 | Bashful         |       | 
  1 | Snow White             |  6 |       1 | Sleepy          |       | 
  1 | Snow White             |  7 |       1 | Grumpy          |       | 
  2 | Batman                 |  8 |       2 | Robin           |       | 
  3 | Cinderella             |  9 |       3 | Fairy Godmother |     9 | Shrek
  3 | Cinderella             |  9 |       3 | Fairy Godmother |     9 | Prince Charming
  3 | Cinderella             | 10 |       3 | Mice            |       | 
  3 | Cinderella             | 11 |       3 | Pidgeons        |       | 
  4 | Hansel                 | 12 |       4 | Gretel          |       | 
  5 | Little Red Riding Hood |    |         |                 |       | 
  6 | Robin Hood             | 13 |       6 | Little John     |    13 | Will Scarlet
  6 | Robin Hood             | 13 |       6 | Little John     |    13 | Brother Tuck
(17 rows)

13.6.2. C program code #

The program code adds the three queries and establishes the follower links between them. Note that the match expressions can be anything, just like in SQL using the LEFT OUTER JOIN ON ( ... ) clause.

#include <stdio.h>
#include <opencreport.h>

int main(int argc, char **argv) {
    opencreport *o = ocrpt_init();
    struct ocrpt_input_connect_parameter conn_params[] = {
        { .param_name = "dbname", .param_value = "ocrpttest" },
        { .param_name = "user", .param_value = "ocrpt" },
        { NULL }
    };
    ocrpt_datasource *ds = ocrpt_datasource_add(o, "pgsql", "postgresql", conn_params);
    ocrpt_query *q1 = ocrpt_query_add_sql(ds, "q1", "select * from data order by id;");
    ocrpt_query *q2 = ocrpt_query_add_sql(ds, "q2", "select * from more_data order by id;");
    ocrpt_query *q3 = ocrpt_query_add_sql(ds, "q3", "select * from moar_data order by sk_id;");

    ocrpt_expr *match = ocrpt_expr_parse(o, "q1.id = q2.boss_id", NULL);
    ocrpt_query_add_follower_n_to_1(q1, q2, match);

    ocrpt_expr *match2 = ocrpt_expr_parse(o, "q2.id = q3.sk_id", NULL);
    ocrpt_query_add_follower_n_to_1(q2, q3, match2);

    if (!ocrpt_parse_xml(o, "example6.xml")) {
        printf("XML parse error\n");
        ocrpt_free(o);
        return 0;
    }

    ocrpt_set_output_format(o, OCRPT_OUTPUT_PDF);
    ocrpt_execute(o);
    ocrpt_spool(o);
    ocrpt_free(o);

    return 0;
}}

13.6.3. PHP program code #

Here's the equivalent program code in PHP.

<?php
$o = new OpenCReport();

$conn_params = [
        "dbname" => "ocrpttest",
        "user" => "ocrpt"
];

$ds = $o->datasource_add("pgsql", "postgresql", $conn_params);
$q1 = $ds->query_add("q1", "select * from data order by id;");
$q2 = $ds->query_add("q2", "select * from more_data order by id;");
$q3 = $ds->query_add("q3", "select * from moar_data order by sk_id;");

$match1 = $o->expr_parse("q1.id = q2.boss_id");
$q1->add_follower_n_to_1($q2, $match1);

$match2 = $o->expr_parse("q2.id = q3.sk_id");
$q2->add_follower_n_to_1($q3, $match2);

if (!$o->parse_xml("example6.xml")) {
    echo "XML parse error" . PHP_EOL;
    exit(0);
}

$o->execute();
$o->spool();

13.6.4. RLIB compatible PHP program code #

Here's the equivalent program code in PHP, using the RLIB compatibility functions. Note that the RLIB compatible API is more limited as it expects a single field name matching.

<?php
$r = rlib_init();

rlib_add_datasource_postgres($r, "pgsql", "dbname=ocrpttest user=ocrpt");
rlib_add_query_as($r, "pgsql", "select * from data order by id;", "q1");
rlib_add_query_as($r, "pgsql", "select * from more_data order by id;", "q2");
rlib_add_query_as($r, "pgsql", "select * from moar_data order by sk_id;", "q3");

rlib_add_resultset_follower_n_to_1($r, "q1", "id", "q2", "boss_id");
rlib_add_resultset_follower_n_to_1($r, "q2", "id", "q3", "sk_id");

if (!rlib_add_report($r, "example6.xml")) {
    echo "XML parse error" . PHP_EOL;
    exit(0);
}

rlib_execute($r);
rlib_spool($r);

13.6.5. Report description #

The program code uses this file contents from example6.xml.

Note that when using multiple queries in the same report, column names may be identical. Because of this, using queryname.columnname will indicate which one is needed. When using columnname then it will mean the first query's column.

<?xml version="1.0"?>
<!DOCTYPE report >
<OpenCReport>
<Report orientation="landscape">
    <Detail>
        <FieldHeaders>
            <Output>
                <Line>
                    <literal width="30">Boss name</literal>
                    <literal width="1"/>
                    <literal width="30">Sidekick name</literal>
                    <literal width="1"/>
                    <literal width="30">Sidekick's sidekick name</literal>
                </Line>
            </Output>
        </FieldHeaders>
        <FieldDetails>
            <Output>
                <Line>
                    <field value="q1.name" width="30" align="left" />
                    <literal width="1"/>
                    <field value="q2.name" width="30" align="left" />
                    <literal width="1"/>
                    <field value="q3.name" width="30" align="left" />
                </Line>
            </Output>
        </FieldDetails>
    </Detail>
</Report>
</OpenCReport>

13.6.6. Report PDF result #

Note that compared to RLIB, OpenCReports likely do not produce the same output. This is due to the incomplete and faulty implementation of follower queries in RLIB. OpenCReports faithfully implements LEFT OUTER JOIN.