# 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.

```create table STRUCTURED_MATERIAL (
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

static function ODCIAggregateInitialize(sctx IN OUT ProbabilityUnionImpl) return number is
begin
sctx := ProbabilityUnionImpl(0);
return ODCIConst.Success;
end;

/* Local iteration implements fusion under independence */
member function ODCIAggregateIterate(self IN OUT ProbabilityUnionImpl
, value IN number) return number is
begin
self.probability:=self.probability+value-self.probability*value;
return ODCIConst.Success;
end;

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

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

end;
/

/* finally, the aggregation function is defined in terms of the type */
create function union_indep_prop (input number) return number
PARALLEL_ENABLE AGGREGATE USING ProbabilityUnionImpl;```

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.

```insert into REPAIR_ESTIMATIONS
with
/* we just need the root - leaf material relations */
DECOMPOSITION as (
select
CONNECT_BY_ROOT
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
from DECOMPOSITION
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
select BASE_EXPERIMENT.*
, 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
from BASE_EXPERIMENT
;

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

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'}]} ] };```

mapOptions);

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), title:"Start", icon: 'tower.png' }); // To add the marker to the map, call setMap(); start.setMap(map);```

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).

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.

For 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

Wer 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

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

```<property>
<name>dfs.datanode.data.dir.perm</name>
<value>700</value>
<description>The permissions that should be there on dfs.data.dir
directories. The datanode will not come up if the permissions are
different on existing dfs.data.dir directories. If the directories
don't exist, they will be created with this permission.
</description>
</property>```

# 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

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.