Progressively Fusing Independent Probabilities using Proprietary SQL Windowing (Oracle version)

This is the sequel post to this proposal how to estimate the lifetime of spare parts using ordinary query language semantics.

This time, we are interested in propagating the hence obtained individual probabities up a more complex material hierarchy. For which we start by the simplifying assumption, that our only variable (“age” could also be named or measured as general “wear and tear”) does already take into account all the influences to a given material’s span of life. And when combining two materials into some higher structure, we assume that they do not influence each other’s durability alltoomuch.

  composite_material int references MATERIAL(material) not null,
  component_material int references MATERIAL(material) not null

insert into STRUCTURED_MATERIAL values (4,1);
insert into STRUCTURED_MATERIAL values (4,2);

insert into STRUCTURED_MATERIAL values (5,4);
insert into STRUCTURED_MATERIAL values (5,3);

Unfortunately, even under these assumptions fusing the individual probabilities turns out to require a “progressive” calculation, i.e., a formula which cannot be stated in terms of an ANSI SQL aggregation.


Fortunately, the expressiveness of the typical Used-Defined-Function (UDF) frameworks, such as the one of the Oracle Database, is well able to handle that requirement even under parallel/distributed execution settings:

/* Oracle needs a type/object definition as the UDF-base */
create type ProbabilityUnionImpl as object
  probability NUMBER, -- memory just keeps current probability
  static function ODCIAggregateInitialize(sctx IN OUT ProbabilityUnionImpl) return number,
  member function ODCIAggregateIterate(self IN OUT ProbabilityUnionImpl
                                     , value IN number) return number,
  member function ODCIAggregateTerminate(self IN ProbabilityUnionImpl
                                       , returnValue OUT number
                                       , flags IN number) return number,
  member function ODCIAggregateMerge(self IN OUT ProbabilityUnionImpl
                                   , ctx2 IN ProbabilityUnionImpl) return number
/* and here are the method implementations */
create or replace type body ProbabilityUnionImpl is 

/* We start with an empty probability */
static function ODCIAggregateInitialize(sctx IN OUT ProbabilityUnionImpl) return number is 
  sctx := ProbabilityUnionImpl(0);
  return ODCIConst.Success;

/* Local iteration implements fusion under independence */
member function ODCIAggregateIterate(self IN OUT ProbabilityUnionImpl
                                   , value IN number) return number is
  return ODCIConst.Success;

/* At the end, just return the stored probability */
member function ODCIAggregateTerminate(self IN ProbabilityUnionImpl
                                     , returnValue OUT number
                                     , flags IN number) return number is
  returnValue := self.probability;
  return ODCIConst.Success;

/* Distributed/Parallel merge, same case as local iteration */
member function ODCIAggregateMerge(self IN OUT ProbabilityUnionImpl
                                 , ctx2 IN ProbabilityUnionImpl) return number is
  return ODCIConst.Success;


/* finally, the aggregation function is defined in terms of the type */
create function union_indep_prop (input number) return number 

With that backing installed, we are now able to complete our estimation table even for complex materials. Since we are Oracle-specific anyway, we also use the established recursion syntax when flattening the bill of material tree.

  /* we just need the root - leaf material relations */
         composite.composite_material as root_material
       , composite.component_material as component_material
       , level as depth
    from STRUCTURED_MATERIAL composite
 connect by prior composite.component_material=composite.composite_material 
 /* for each age of an individual age, find all corresponding probabilities of 
    sibling materials for fusion */
 select DECOMPOSITION.root_material
      , REPAIR.age
      , lead(REPAIR.age-1) over (partition by DECOMPOSITION.root_material order by REPAIR.age) as next_age
      , union_indep_prop(ALL_REPAIRS.probability) as probability
      , union_indep_prop(ALL_REPAIRS.lower_bound_95) as lower_bound_95
      , union_indep_prop(ALL_REPAIRS.upper_bound_95) as upper_bound_95
      , max(DECOMPOSITION.depth) as depth
    join REPAIR_ESTIMATIONS REPAIR on REPAIR.material=DECOMPOSITION.component_material and REPAIR.depth=0
    join DECOMPOSITION ALL_PARTS on ALL_PARTS.root_material=DECOMPOSITION.root_material 
    join REPAIR_ESTIMATIONS ALL_REPAIRS on ALL_REPAIRS.material=ALL_PARTS.component_material and
         REPAIR.age between ALL_REPAIRS.age and coalesce(ALL_REPAIRS.next_age, REPAIR.age)
 group by DECOMPOSITION.root_material
        , REPAIR.age

Stay tuned for the upcoming Teradata formulation.

Estimating Multiplicative Probabilities Using ANSI-SQL Windowing

SQL can be a quite efficient tool to implement particular types of statistics. Since windowing became a part of the ANSI standard, this holds for more and more databases as well as mathematical/structural complexities.

For example, product limits can be incrementally estimated by multiplying the probabilities over age. Only the quite impressive ANSI SQL aggregate function list  does not contain a corresponding operation.

Fortunately, we know (or at least Google knows 😉 that for positive P(i), it holds that ΠP(i) = EXP(Σln(P(i)))

This allows us to realize a (batch) product limit estimation including the determination of confidence intervals such as in the following example which has been generalized from Oracle to Teradata. Already included are some objects needed for the sequel post, just in case you wonder about the verbosity:

create table MATERIAL (
	material int primary key not null,
	description varchar(100)
insert into MATERIAL values (1, 'sit');
insert into MATERIAL values (2, 'aenean');
insert into MATERIAL values (3, 'adipiscing');
insert into MATERIAL values (4, 'nulla');
insert into MATERIAL values (5, 'in');
insert into MATERIAL values (6, 'sed');

create table PRODUCT (
	serial int primary key not null,
	material INT references MATERIAL(material) not null,
	manufacturing_date date not null

insert into PRODUCT  values (1, 2, '2002-08-14');
insert into PRODUCT  values (2, 1, '2001-02-20');
insert into PRODUCT  values (3, 1, '2005-10-27');
insert into PRODUCT  values (4, 1, '2003-10-23');
insert into PRODUCT  values (5, 1, '2001-12-21');
insert into PRODUCT  values (6, 2, '2010-10-11');
insert into PRODUCT  values (7, 1, '2002-06-28');
insert into PRODUCT  values (8, 2, '2009-12-29');
insert into PRODUCT  values (9, 1, '2002-01-10');
insert into PRODUCT  values (10, 2, '2008-10-19');
insert into PRODUCT  values (11, 3, '2007-01-04');
insert into PRODUCT  values (12, 3, '2008-08-06');
insert into PRODUCT  values (13, 4, '2010-08-01');
insert into PRODUCT  values (14, 4, '2011-12-01');
insert into PRODUCT  values (15, 5, '2012-02-03');

create table REPAIR (
	serial_flawed int unique references PRODUCT(serial) not null,
	repair_date date not null

insert into REPAIR values (7,'2004-01-22');
insert into REPAIR values (6,'2011-04-09');
insert into REPAIR values (2,'2001-06-21');
insert into REPAIR values (4,'2010-07-25');
insert into REPAIR values (1,'2011-06-16');
insert into REPAIR values (10,'2009-04-17');
insert into REPAIR values (5,'2003-04-13');
insert into REPAIR values (3,'2011-12-31');
insert into REPAIR values (9,'2002-06-10');
insert into REPAIR values (11,'2010-06-10');

/* Not all ANSI databases cope with my beloved nested CTEs, so here is the explicit version */ 

/* aggregating repair data by part age */ 
create view BASE_EXPERIMENT as
   select PRODUCT.material
        , REPAIR.repair_date-PRODUCT.manufacturing_date as age
        , cast(sum(1) as float) as samples
     from PRODUCT 
       join REPAIR on REPAIR.serial_flawed=PRODUCT.serial
    group by PRODUCT.material
           , REPAIR.repair_date-PRODUCT.manufacturing_date

/* building intervals and base statistics */
create view POPULATION as 
        , row_number() over (partition by BASE_EXPERIMENT.material 
                             order by BASE_EXPERIMENT.age)
          as experiment
        , cast(
             sum(BASE_EXPERIMENT.samples) over (partition by BASE_EXPERIMENT.material 
                                                order by BASE_EXPERIMENT.age 
                                                rows between current row 
                                                   and unbounded following) 
          as float) as population
        , min(BASE_EXPERIMENT.age) over (partition by BASE_EXPERIMENT.material 
                                         order by BASE_EXPERIMENT.age
                                         rows between 1 following 
                                           and unbounded following) 
          as next_age

/* Here is the actual estimation magic done */
create view SURVIVAL as 
   select POPULATION.*
        , case 
             when POPULATION.population<=1 then null 
                        when POPULATION.age is null then 1.0
                        when POPULATION.population=POPULATION.samples then null 
                        else 1.0-POPULATION.samples/POPULATION.population 
                 ) over (partition by POPULATION.material 
                         order by POPULATION.age 
                         rows between unbounded preceding 
                                  and current row 
          end as probability
        , sqrt(
                    when POPULATION.experiment=1 then 0.0
                    when POPULATION.population>=POPULATION.samples 
                         then POPULATION.samples/
                    else null 
             ) over (partition by POPULATION.material 
                     order by POPULATION.age 
                     rows between unbounded preceding and 1 preceding 
          ) as confidence
     from POPULATION

create table REPAIR_ESTIMATIONS as (
   select SURVIVAL.material
        , SURVIVAL.age
        , SURVIVAL.next_age
        , 1-coalesce(SURVIVAL.probability,0) as probability
        , 1-coalesce(least(SURVIVAL.probability+1.96*SURVIVAL.probability*SURVIVAL.confidence,1),1) as lower_bound_95
        , 1-coalesce(greatest(0,SURVIVAL.probability-1.96*SURVIVAL.probability*SURVIVAL.confidence),0) as upper_bound_95
        , 0 as depth
     from SURVIVAL  
WITH DATA -- Teradata slang, remove for Oracle

/* Thats all folks ... until we speak of structured materials next time */
select * 
 order by material
        , age

BTW, Mockaroo is a really nice sample data generation service … if only we could influence random distributions and impose constraints …

Bowdenzug an der FahrertĂŒr des Skoda Oktavia tauschen

Endlich mal wieder ein erfolgreiches Wochenendprojekt 😉

Auf dem !!Nachhauseweg vom  TĂŒV!! machte es vor Weihnachten beim Aussteigen plötzlich “ratsch”. Die FahrertĂŒr ließ sich nicht mehr von innen öffnen – zumindestens nicht auf wĂŒrdige Art und Weise und ohne eine lĂ€chelnde Zuschauerschaft auf dem Discounter-Parkplatz.

Bei der ersten Analyse fiel dann aus der Innenverkleidung sofort ein kleiner Haken mit einem Drahtrest raus, der darauf schliessen ließ, dass es sich dabei um die abgerissene Befestigung des Bowdenzugs handelte.

Bei Ebay wird man schnell fĂŒndig, wenngleich es kein Überangebot an solchen Ersatzteilen gibt. Der Austausch war dann etwas fuddelig, aber selbst mit begrenztem handwerklichen Talent ohne grössere KollateralschĂ€den durchfĂŒhrbar:

  1. Innenverkleidung des Aussenspiegels mit flacher Klinge zart lockern und abheben. Stecker lösen.
  2. Steuerung des Aussenspiegels am Innenöffner mit flacher Klinge zart lockern und heraushebeln. Stecker lösen. Kleine Schraube hinter dem Innenöffner lösen.
  3. Hintere Plastikverschalung am inneren TĂŒrgriff leicht in Richtung AutotĂŒr abwechselnd links und rechts hebeln und abziehen. Dadurch wird nun auch der Rest der Bedienkonsole frei, die man dann nach oben und vorne herausnehmen kann. Stecker abziehen.
  4. Jetzt die 8 Torque-Schrauben an Stirnseite (2 StĂŒck), Fussseite (2 StĂŒck) und unten (3 StĂŒck) lösen.
  5. Schliesslich die drei wesentlichen Kreuz-Schrauben in der verbliebenen Bedienkonsole lösen. Damit wird die Innenverkleidung nun frei.
  6. Wer die Innenverkleidung nicht komplett abreissen will, kann sie nun anheben und mit einem StÀnder, Stuhl o.À. einfach hochgeklappt lassen, um darunter zu arbeiten.
  7. Jetzt die Isolierfolie den alten Bowdenzug und ein paar Zentimeter am linken Schloß entlang mit einem scharfen Messer aufschneiden,  und aufklappen. Die Folie wird noch gebraucht, daher Vorsicht.
  8. Zwei Torque-Schrauben an der Stirnseite, die das Schloß halten, lösen.
  9. Das Schloß muss man nun nach rechts in Richtung der TĂŒröffnung drĂŒcken, bis die AufhĂ€ngung des Bowdenzugs sichtbar wird. Den alten Bowdenzug aus den Halterungen lösen und senkrecht nach unten halten, damit er aus der AufhĂ€ngung gezogen werden kann. Sollte das Schloß nicht genĂŒgend Spiel haben, kann man die Schraube des davor angebrachten Querblechs auch lockern.
  10. Selbes Spiel beim Einbau des neuen Bowdenzugs: Senkrecht halten, in die AufhÀngung einfÀdeln, dann in Zielposition bringen und wieder festhaken!
  11. Alles in umgekehrter Reihenfolge wieder zusammenbauen, Folie an der Schnittnaht wieder sauber zusammenkleben, da sonst Kondenswasser eintreten kann.

GMaps-API + Awesome Screenshot = High-Res Treasure Map

There is a certain positive correlation to observe between

  • the age of a child and
  • the thrill expected from its birthday party.

So last week, I found myself thinking about some kind of Ingress competition for my son’s 10th anniversary. With some intervention of my wife, fortunately, we came up with a more down-to-earth scheme: Two teams of five boys each (the “red knights” versus the “blue monks”)  had to hunt down 20 “magic symbols” (really horrifying tongue tattoos only to be “resolved by a truthful mouth”) which the “white sorcerer” had hidden in a rural area of ~20 square kilometers between our house and a playground/barbecue place in the village nearby.

From the two tasks of the white sorcerer

  • Hiding the treasures only equipped with a bike and a single hour of sparetime
  • Printing two maps on A3 paper detailed enough such that the young lads stay on track

only the latter turned out to be easy enough, thanks to the Google Maps Javascript API.

Treasure Map using Google Maps
Treasure Map using Google Maps

It allows to create a web page with a quite high resolution of

html { width: 4200px; height: 2970px }
body { height: 100%; width:100%; margin: 0; padding: 0 }

For leaving out any unnecessary distractions, you can customize the map options

var mapOptions = {
center: new google.maps.LatLng(49.405500,7.195000),
zoom: 17,
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.ROADMAP,
styles: [
{featureType: 'road',elementType: 'labels.text',stylers: [{visibility: 'off'}]},
{featureType: 'poi',elementType: 'all',stylers: [{visibility: 'off'}]}

var map = new google.maps.Map(document.getElementById(“map_canvas”),

Then, you can place valuable hints on the resulting bitmap once you detected their GPS coordinates (e.g., by using the right-click command “What’s here” in the official Gmaps interface).

var start = new google.maps.Marker({
position: new google.maps.LatLng(49.406500,7.177241),
icon: 'tower.png'
// To add the marker to the map, call setMap();

Finally, you need to capture the single-bitmap-web-page at once, independently of your screen resolution. This is the domain of Awesome screenshot.  Believe me, I tested a whole lot of these extensions in Chrome, but that was the only “awesome” one that didn’t crash and produced a reasonable result (click on the image above).

Trading ORA-30926 for ORA-08006

Trading ORA-30926 for ORA-08006

This one has bitten me this morning when violating against golden rule #3 (‘Never change two not completely predictable things at a common target simultaneously at least unless you are in hazardous mood.’ An even more popular exemplification of that rule would be the slogan ‘Don’t drink & drive.’).

So I changed

  • the semantics of one source table of a merge statement (extract individual phrases out of a text field into multiple rows)
  • the partitioning of the target table (including the enablement row movement)

The result of the former change alone should have produced an ORA-30926 (‘unable to get a stable set of rows’) when some … hmm, say humanoid at least … managed to restate the same phrase over and over again in the source system.

In conjunction with row movement, however, the merge statement will instead issue an ORA-08006 (‘Someone deleted that row. It wasn’t me.’) which leaves one puzzled until one manages to find Todor Botevs helpful investigation.




Sampling consistently out of (unordered, splitted, fragmented) detail data

One of the problems when sampling data out of raw files is that there maybe consistency constraints on the picked lines.

hashrandomFor example, if you like to extract representative sales slips out of TLOG texts, you want all the positions belonging to one sales slip to be sampled as a whole … and you want that property consistently over all processes/machines onto which the texts/sampling has been distributed.

Using the typically seeded pseudo-random sequences will not work as expected, here. You would have to first aggregate the sales slip headers, sample on the level of headers and, again, join the resulting sample with the original TLOG data.

A nice idea that circumvents that necessity with only a minimal bit of overhead is inspired by Chuck Lam’s method of simply constructing Bloom-filter hash-functions. For each line, the pseudo-random generator is seeded with the hash-code of a given key/object. Then, a fixed position of the random sequence (the new “seed”) is read as the observed random value.

For our TLOG case, all the lines carrying the identical sales slip key will get the same random boolean computed and get filtered or passed through allthesame.

Gaggenau DF 240-261 Fehlercode E15

gaggenau_df_frontblende_befestigungenWer eine Gaggenau-Maschine besitzt, weiß um die nicht ganz so gĂŒnstigen Service-Bedingungen. Auch im Netz ist es mit Reparaturinformationen eher schlecht bestellt. Umso Ă€rgerlicher ist es, wenn statt einer Ladung gespĂŒltes Geschirr eine Fehlermeldung auftaucht, die Douglas Adams sicherlich zu weiteren “Per Anhalter”-BĂ€nden inspiriert hĂ€tte.

Offensichtlich gibt es aber baugleiche Bosch-Maschinen mit dem offenkundigen Hinweis auf Wasser in der Bodengruppe. Also flugs die Frontblende abmontiert (lediglich die 4 Schrauben im Bild halten die Blende, ansonsten ist sie lediglich eingehĂ€ngt; rausbohren der unteren Scharniere bringt nur Ärger!) und das Objekt des Anstosses (bspw. ein exakt auf den Pumpenablauf zugeschnittener Mandelsplitter) entfernt.

Hadoop Quick (Default) Port Reference & Help datanode startup on NTFS/Windows 8

Hadoop Quick (Default) Port Reference

This comes quite handy, e.g., when you need to setup firewall rules.

And here is a post related to the filesystem permission checks that the datanode service performs upon startup.

On my Windows 8 machine, I had to add the following lines to the hdfs-site.xml that comes with hortonworks hdp 1 distribution

 <description>The permissions that should be there on
 directories. The datanode will not come up if the permissions are
 different on existing directories. If the directories
 don't exist, they will be created with this permission.

SCD3+1 – Save the Dimensional Bus

The standard ways of modelling dimensional qualitative change (as opposed to factual quantitative dynamics) have been widely accepted since Ralph Kimballs publishing his Data-Warehouse Toolkit bible.

  • SCD0 – Position the changing attribute in the fact table
  • SCD1 – Put the attribute into the dimension table and simply overwrite old attribute values
  • SCD2 – Reify temporal changes into seperate attributes of the dimension table
  • SCD3 – Allow for seperately attributed versions of the dimension entities in the dimension table
Various Types of Slowly Changing Dimensions (SCD)
Various Types of Slowly Changing Dimensions (SCD)

Surely, the latter solution is the most flexible, but it comes at the price of loosing the original granularity of the dimension (now: rather versions or states than static entities) and hence the necessity of remodelling all the dependent fact tables, too – even those whose interpretation is not interesting wrt the changing attribute in the first place. Once your Datawarehouse has grown to a respectable complexity and you reused your dimensions well in the spirit of Mr. Kimballs fruitful bus matrix design, the effort to introduce such a seemingly small dynamic feature into a running and well-fed information layer explodes easily.

An architectural solution to this dilemma is what we call SCD3+1, a combination of the SCD3 and SCD1 techniques. Here, we construct two variants of the dimension, a full-blown SCD3 variant to which all the required facts can be connected and a (now derived) SCD1 view which stays connected to the rest of the Datawarehouse. For that purpose, each”current” version of a SCD3 entity is marked with a special-purpose attribute which can be filtered to obtain the corresponding SCD1 subset. This way, even state changes in the future can be already tracked andm, e.g., get connected to the planning measures.

Smarter than the average MSI (or: Running HDP-1.1.0-GA on Windows 2008 Server)

This is so great.

  1. Like to try out HDInsight Server Preview?
  2. Already got access to some “quite modern” Windows 2008 Server  with most of the SDK-, .NET- and JDK-orgy set up?
  3. Unless … this wonderful piece of hardware just resides in the intranet and the web-installer is not an option.
  4. Ummpf. So, the M$-infrastructure partner also provides its own Hadoop Data Platform for Windows distribution.
  5. You also successfully managed to install the rest of the prerequisites (please don’t let the admins reveil that, especially the remote powershell part) and configuration issues.
  6. And all this %§$”%- MSI responds is “Visual Studio C++ Redistributable (x64) Package not installed” while it is indeed.

This is soo great. See, you are not alone.

I simply can’t believe that a distribution of a managed code framework depends on a particular minor version of the underlying operating systems.

I simply can’t believe that a disfunctioning MSI is a dead end of a once promising long road.

Rightly believed. Here is the fundamental trick how to tweak such a package using the ORCA Tool shipped with the Windows SDK.

Looking into the LaunchCondition Section and see the too malicious tests on the Visual C++ redistributable and the operating system version.


Delete the vc_redist dependency and adapt the operating system version accordingly.


And be sure that your JAVA_HOME (the environment path, not the shell variable, as the installer spawns a new environment during its run) does neither contain spaces (like “Program Files”) nor double quotes – a lesson that can be learned when inspecting closely the resulting installer logs.

Installation completed successfully.