<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>dbi Blog</title>
	<atom:link href="https://www.dbi-services.com/blog/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.dbi-services.com/blog/</link>
	<description></description>
	<lastBuildDate>Sat, 20 Jun 2026 16:32:20 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/cropped-favicon_512x512px-min-32x32.png</url>
	<title>dbi Blog</title>
	<link>https://www.dbi-services.com/blog/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>M-Files BD &#8211; Queries: objectType, class, filters, date tokens</title>
		<link>https://www.dbi-services.com/blog/m-files-bd-queries-objecttype-class-filters-date-tokens/</link>
					<comments>https://www.dbi-services.com/blog/m-files-bd-queries-objecttype-class-filters-date-tokens/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Sat, 20 Jun 2026 16:32:18 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[Business Dashboard]]></category>
		<category><![CDATA[Class]]></category>
		<category><![CDATA[dateToken]]></category>
		<category><![CDATA[Filters]]></category>
		<category><![CDATA[M-Files]]></category>
		<category><![CDATA[objectType]]></category>
		<category><![CDATA[query]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=45175</guid>

					<description><![CDATA[<p>In the previous posts of this series, I covered the anatomy of a dashboard definition and the seven widget types (KPI &#38; gauge, line &#38; area, donut, bar &#38; table) the engine supports, as of now. In this one, we will go through the Business Dashboard queries, i.e. the query section that I pretty much [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-queries-objecttype-class-filters-date-tokens/">M-Files BD &#8211; Queries: objectType, class, filters, date tokens</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In the previous posts of this series, I covered the <a href="https://www.dbi-services.com/blog/m-files-bd-anatomy-of-a-dashboard-definition/" target="_blank" rel="noreferrer noopener">anatomy of a dashboard definition</a> and the seven widget types (<a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/" target="_blank" rel="noreferrer noopener">KPI &amp; gauge</a>, <a href="https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/" target="_blank" rel="noreferrer noopener">line &amp; area</a>, <a href="https://www.dbi-services.com/blog/m-files-bd-distribution-and-tabular-widgets-donut-bar-table/" target="_blank" rel="noreferrer noopener">donut, bar &amp; table</a>) the engine supports, as of now. In this one, we will go through the Business Dashboard queries, i.e. the <strong><em>query</em></strong> section that I pretty much ignores so far. It is now time to look at it a bit more.</p>



<p class="wp-block-paragraph">A query has four parts: <strong><em>objectType</em></strong>, <strong><em>class</em></strong>, <strong><em>filters</em></strong>, and <strong><em>aggregation</em></strong>. This post covers the first three. The fourth (aggregations, including its reducers) is the topic of the next post.</p>



<p class="wp-block-paragraph">As mentioned before, the principle that drove the design of the Business Dashboard is to <strong>be generic</strong>. The engine never enumerates specific business values, it only translates the structured JSON query into a standard M-Files server search.</p>



<h2 id="h-1-objecttype-the-m-files-object-type-to-query" class="wp-block-heading">1. objectType &#8211; the M-Files object type to query</h2>



<p class="wp-block-paragraph">Every query starts with <strong><em>objectType</em></strong>. It is the M-Files object type the engine searches against, addressed by its display name (the singular form is fine, the engine resolves it and uses it).</p>



<h3 id="h-1-1-single-object-type" class="wp-block-heading">1.1. Single object type</h3>



<p class="wp-block-paragraph">The simplest form is a string:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;objectType&quot;: &quot;Document&quot;
</pre></div>


<p class="wp-block-paragraph">Or any other object type defined in your vault:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
// Looking for Customers
&quot;objectType&quot;: &quot;Customer&quot;

// Looking for Projects
&quot;objectType&quot;: &quot;Project&quot;

// Looking for Employees
&quot;objectType&quot;: &quot;Employee&quot;
</pre></div>


<p class="wp-block-paragraph">The display name is <strong>case-insensitive</strong> but must otherwise match what the vault shows. Please note that, if the vault is let&#8217;s say, in German, you might need to use &#8220;Dokument&#8221; and not &#8220;Document&#8221;. In short, you need to use what the vault defines/shows.</p>



<p class="wp-block-paragraph">If the name does not match, the widget shows a clear <strong><em>✗ Object type &#8216;xxx&#8217; not found in this vault</em></strong> error.</p>



<h3 id="h-1-2-multiple-object-types" class="wp-block-heading">1.2. Multiple object types</h3>



<p class="wp-block-paragraph">When the same logical entity exists across more than one M-Files object type (for example, a &#8220;Proposal&#8221; can be both a Document and a Document collection), you can pass an array:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;objectType&quot;: &#x5B;&quot;Document&quot;, &quot;Document collection&quot;]
</pre></div>


<p class="wp-block-paragraph">The engine resolves every name and builds a single search to find all matching objects. This behaves like the <strong><em>&#8220;is one of&#8221;</em></strong> filter in M-Files Advanced Search. Please note that the Drill-through will also inherit the same scope, automatically.</p>



<p class="wp-block-paragraph">A small but important detail: even in this case, the engine executes a single search. This matters for performance and for consistency (a single <strong><em>serverScanMaxResults</em></strong> cap applies, not two independent caps).</p>



<h2 id="h-2-class-narrowing-the-scope-and-the-perf-impact" class="wp-block-heading">2. class &#8211; narrowing the scope (and the perf impact)</h2>



<p class="wp-block-paragraph">The <strong><em>class</em></strong> field is optional but <strong><em>strongly</em></strong> recommended. It restricts the query to one specific class:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;objectType&quot;: &quot;Document&quot;,
&quot;class&quot;: &quot;Contract or Agreement&quot;
</pre></div>


<p class="wp-block-paragraph">Querying &#8220;all Documents&#8221; in a vault that contains 50&#8217;000 documents of which only 1&#8217;000 are Contracts is pure waste. Adding <strong><em>&#8220;class&#8221;: &#8220;Contract or Agreement&#8221;</em></strong> narrows the scan immediately, which both improves performance and makes the widget result more meaningful.</p>



<p class="wp-block-paragraph">Therefore, both M-Files Administrators and M-Files Users will thank you for selecting the right class to use. It avoids slowness, irrelevant results and reduces resource usage. You should always set a <strong><em>class</em></strong>, when possible.</p>



<p class="wp-block-paragraph">When <strong><em>objectType</em></strong> is an array and <strong><em>class</em></strong> is specified, the engine will find any object, from that class, from any of the object types listed. So the following will match Proposal-class objects from both types:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;objectType&quot;: &#x5B;&quot;Document&quot;, &quot;Document collection&quot;],
&quot;class&quot;: &quot;Proposal&quot;
</pre></div>


<p class="wp-block-paragraph">If the class is misspelled, the widget shows a <strong><em>✗ Class &#8216;xxx&#8217; not found on the specified object type(s)</em></strong> error. As with object types, the name depends on what the vault defines/shows.</p>



<h2 id="h-3-filters-the-and-combined-conditions" class="wp-block-heading">3. filters &#8211; the AND-combined conditions</h2>



<p class="wp-block-paragraph">The <strong><em>filters</em></strong> array, which is optional, contains zero or more conditions. If you specify multiple filters, then <strong>all filters must match</strong>. That means that M-Files applies a AND semantic between each filter. By the way, M-Files doesn&#8217;t directly support &#8220;OR&#8221; conditions, it always joins them. The only &#8220;kind-of-an-exception&#8221;, as far as I know, is the <strong><em>&#8220;is one of&#8221;</em></strong> where you can set multiple values from the Lookup/MultiSelectLookup property values. But that&#8217;s only a single condition, not multiple conditions.</p>



<p class="wp-block-paragraph">Here is an example of a filter:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;filters&quot;: &#x5B;
  { &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;greaterOrEqual&quot;,
    &quot;value&quot;: &quot;@today&quot;, &quot;valueType&quot;: &quot;dateToken&quot; },
  { &quot;property&quot;: &quot;Agreement type&quot;, &quot;operator&quot;: &quot;equals&quot;,
    &quot;value&quot;: &quot;Subcontracting Agreement&quot; }
]
</pre></div>


<p class="wp-block-paragraph">Each filter has up to four fields:</p>



<ul class="wp-block-list">
<li><strong><em>property</em></strong>: the property display name on which to apply the operator / value.</li>



<li><strong><em>operator</em></strong>: one of the operators listed in section 4 below.</li>



<li><strong><em>value</em></strong>: the value to use for the comparison with the property&#8217;s actual value. For the four &#8220;presence&#8221; operators (<strong><em>isEmpty</em></strong>, <strong><em>isNotEmpty</em></strong>, <strong><em>isPresentEmpty</em></strong>, <strong><em>isPresentNotEmpty</em></strong>), this parameter should NOT be defined. For the &#8220;begin-end&#8221; operators (<strong><em>between</em></strong> / <strong><em>notBetween</em></strong>, this parameter should be an array of two elements. For the &#8220;is one of&#8221; operators (<strong><em>inList</em></strong> / <strong><em>notInList</em></strong>), this parameter should be an array of at least two elements. Finally, in all other cases, it&#8217;s simply a string.</li>



<li><strong><em>valueType</em></strong>: either <strong><em>&#8220;literal&#8221;</em></strong> (default, it means to use the value as-is) or a <strong><em>&#8220;dateToken&#8221;</em></strong> (covered in section 5).</li>
</ul>



<h2 id="h-4-the-filter-operators" class="wp-block-heading">4. The filter operators</h2>



<p class="wp-block-paragraph">The engine supports a rich set of operators across two execution paths:</p>



<ul class="wp-block-list">
<li><strong>Native</strong>: runs server-side before the <strong><em>serverScanMaxResults</em></strong> cap is applied. Therefore, this is the preferred option, when possible.</li>



<li><strong>Post-filter</strong>: runs in memory after the server returns up to <strong><em>serverScanMaxResults</em></strong> objects. Therefore, this is correct and useful when you want to do something that M-Files doesn&#8217;t support out-of-the-box but that still makes sense for your Business use-case.</li>
</ul>



<p class="wp-block-paragraph">In short, whenever possible, prefer to apply a <strong><em>native</em></strong> filter. The main reason for this is simple. Let&#8217;s assume that you have 1&#8217;000 Contracts in the vault:</p>



<ul class="wp-block-list">
<li>If you apply a <strong><em>native</em></strong> filter, for example <strong><em>Effective through &gt;= @today</em></strong>, then if only 200 of them match that criteria, then M-Files will only return these 200 objects directly. There is no loss of performance here.</li>



<li>If you apply a <strong><em>post-filter</em></strong>, for example <strong><em>Effective through contains 2026-12</em></strong>, then M-Files will have no other choice than returning all 1&#8217;000 Contracts first. On top of that, the post-filter compares the value of Effective through and checks whether it contains &#8220;2026-12&#8221;. This is because the &#8220;contains&#8221; condition doesn&#8217;t exist in M-Files for Date properties. Therefore, we cannot use what doesn&#8217;t exist, but we still want to provide that possibility / logic, and therefore it is done in memory after returning all results. In that example, you get 5x more results and on top of it, you also need to check which ones match the expected value.</li>
</ul>



<p class="wp-block-paragraph">The performance difference between <strong><em>native</em></strong> and <strong><em>post-filter</em></strong> is imperceptible for small result sets (&lt;few hundreds), but you would probably feel it if you expect to fetch thousands of results. A great catch, if you need to apply a post-filter for a valid business reasons, is to first apply a native one, which highly reduces the result set, and then apply the post-filter one that you need. Also, don&#8217;t forget to specify a <strong><em>class</em></strong>!</p>



<p class="wp-block-paragraph">Then, let&#8217;s proceed with a deep-dive on the different operators (if you only want the &#8220;summary&#8221;, look at the end of section 4 for the cheat sheet). The 22 operators support all data types, without exceptions, contrary to M-Files operators which offer much less capabilities. The only distinction is, as mentioned above, whether the operator is native or post-filter.</p>



<h3 id="h-4-1-equals-and-notequal" class="wp-block-heading">4.1. equals and notEqual</h3>



<p class="wp-block-paragraph">As you would expect, these are for exact match. Only the MultiLineText properties are processed as post-filter.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Agreement type&quot;, &quot;operator&quot;: &quot;equals&quot;,
  &quot;value&quot;: &quot;Subcontracting Agreement&quot; }

{ &quot;property&quot;: &quot;Customer&quot;, &quot;operator&quot;: &quot;notEqual&quot;,
  &quot;value&quot;: &quot;ESTT Corporation (IT)&quot; }
</pre></div>


<p class="wp-block-paragraph">When using display names on Lookup properties (Lookup or MultiSelectLookup), as its the case in above example, the value lists are cached, so that each execution doesn&#8217;t need to re-fetch the things that it already knows of, and it can just use it directly, to ask M-Files what&#8217;s the updated count.</p>



<h3 id="h-4-2-lessthan-greaterthan-lessorequal-greaterorequal" class="wp-block-heading">4.2. lessThan, greaterThan, lessOrEqual, greaterOrEqual</h3>



<p class="wp-block-paragraph">These &#8220;range&#8221; operators support all property types except Boolean, because it doesn&#8217;t make any sense to apply a &#8220;lessThan&#8221; to a Boolean&#8230; In addition, and similarly to above, only the MultiLineText properties are processed as post-filter, everything else is native. Obviously, you can apply these operators on Date and Numeric values, but it also works with Text, Lookup or MultiSelectLookup. When it needs to work on Text-based properties, it does a lexicographic comparison (e.g. ABC &lt; ABD, ACE &gt; ABB).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Due date&quot;, &quot;operator&quot;: &quot;lessThan&quot;,
  &quot;value&quot;: &quot;@today&quot;, &quot;valueType&quot;: &quot;dateToken&quot; }

{ &quot;property&quot;: &quot;Amount&quot;, &quot;operator&quot;: &quot;greaterOrEqual&quot;, &quot;value&quot;: 1000 }
</pre></div>


<h3 id="h-4-3-between-and-notbetween" class="wp-block-heading">4.3. between and notBetween</h3>



<p class="wp-block-paragraph">These as also &#8220;range&#8221; operators but I put them separately because the <strong><em>value</em></strong> must be a two-element array, as previously written. <strong><em>between</em></strong> is, otherwise, exactly the same as lessThan/greaterThan/lessOrEqual/greaterOrEqual. In the array, you would provide the begin and the end of the range. to fetch.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;between&quot;,
  &quot;value&quot;: &#x5B;&quot;@startOfYear&quot;, &quot;@endOfYear&quot;], &quot;valueType&quot;: &quot;dateToken&quot; }

{ &quot;property&quot;: &quot;Amount&quot;, &quot;operator&quot;: &quot;between&quot;, &quot;value&quot;: &#x5B;100, 1000] }
</pre></div>


<p class="wp-block-paragraph"><strong><em>notBetween</em></strong> is the opposite of <strong><em>between</em></strong>, obviously, but it&#8217;s a bit more than that&#8230; As previously mentioned, M-Files does NOT handle <strong><em>OR</em></strong> logic. But if you think about it, a &#8220;notBetween&#8221; is actually an OR, because you want values below the range OR above the range (<strong><em>value &lt; low OR value &gt; high</em></strong>). Because of that, <strong><em>notBetween</em></strong> is always a <strong>post-filter</strong>, without exception.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Amount&quot;, &quot;operator&quot;: &quot;notBetween&quot;, &quot;value&quot;: &#x5B;100, 1000] }
</pre></div>


<h3 id="h-4-4-isempty-isnotempty-ispresentempty-ispresentnotempty" class="wp-block-heading">4.4. isEmpty, isNotEmpty, isPresentEmpty, isPresentNotEmpty</h3>



<p class="wp-block-paragraph">These four operators test whether a property has a value, as the name suggests&#8230; However, there is a catch: M-Files only distinguish between &#8220;the property is present and empty&#8221; or &#8220;the property is present and non-empty&#8221;.</p>



<p class="wp-block-paragraph">You might have faced that with Templates for example. That&#8217;s a pretty common occurrence. In M-Files, there is a parameter &#8220;Is template&#8221; which indicates whether a specific object has been defined as a template. But that parameter is only present in a few select objects, it&#8217;s usualy not present on all of them. Therefore, if you search for &#8220;Is template &#8211; is empty&#8221;, you will most probably find 0 results, because M-Files only check for documents where the property is present and where it is empty.</p>



<p class="wp-block-paragraph">The distinction is simple but it matters a lot. That&#8217;s why in the Business Dashboard, the <strong><em>native</em></strong> M-Files &#8220;is empty&#8221; / &#8220;is not empty&#8221; have been renamed as <strong><em>isPresentEmpty</em></strong> and <strong><em>isPresentNotEmpty</em></strong>. These are the fully native option from M-Files, and therefore they run on the server-side.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;isPresentEmpty&quot; }

{ &quot;property&quot;: &quot;Responsible person&quot;, &quot;operator&quot;: &quot;isPresentNotEmpty&quot; }
</pre></div>


<p class="wp-block-paragraph">In addition, I also defined two other operators, <strong><em>isEmpty</em></strong> and <strong><em>isNotEmpty</em></strong>. These two are <strong>always post-filters</strong>, they scan all matching objects in memory after the server returns them:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;isEmpty&quot; }

{ &quot;property&quot;: &quot;Responsible person&quot;, &quot;operator&quot;: &quot;isNotEmpty&quot; }
</pre></div>


<p class="wp-block-paragraph">Because of that post-processing, <strong><em>isEmpty</em></strong> matches objects where the property is absent OR where it is present but empty. This allows a more &#8220;complete&#8221; result set, which might be required in some specific business use-cases.</p>



<p class="wp-block-paragraph"><strong><em>isNotEmpty</em></strong> matches objects where the property is present AND non-empty. You might think it is exactly the same as <strong><em>isPresentNotEmpty</em></strong>, right? Well, in 99% of the cases, yes it is the same (but slower, since done as post-filter)&#8230; Except when there are bugs in M-Files ;). While developing the Business Dashboard, I found a few bugs, including one with lexicographic processing on MultiSelectLookup properties. The usage of my own operator was giving me a slightly different result and while investigating why, I found the reason and the bug in M-Files&#8217;s own operator.</p>



<p class="wp-block-paragraph">In summary:</p>



<figure class="wp-block-table"><table><thead><tr><th>What you want to find</th><th>Operator</th></tr></thead><tbody><tr><td>Objects where the property is present and empty OR is completely absent</td><td><strong><em>isEmpty</em></strong> (post-filter)</td></tr><tr><td>Objects where the property is present and empty</td><td><strong><em>isPresentEmpty</em></strong> (native)</td></tr><tr><td>Objects where the property has a value</td><td><strong><em>isPresentNotEmpty</em></strong> (native)</td></tr><tr><td>Objects where the property has a value</td><td><strong><em>isNotEmpty</em></strong> (post-filter alternative &#8211; sometimes more accurate)</td></tr></tbody></table></figure>



<h3 id="h-4-5-inlist-and-notinlist" class="wp-block-heading">4.5. inList and notInList</h3>



<p class="wp-block-paragraph">As the name suggests, if you are looking for multiple values, <strong><em>inList</em></strong> is what you should use. It must be an array of at least 2 elements, but you can put 50 if you want to. M-Files only support Lookup for this one, natively. Therefore, all other property types are handled as post-filters.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Agreement type&quot;, &quot;operator&quot;: &quot;inList&quot;,
  &quot;value&quot;: &#x5B;&quot;Subcontracting Agreement&quot;, &quot;Project Agreement&quot;] }

{ &quot;property&quot;: &quot;Workflow state&quot;, &quot;operator&quot;: &quot;inList&quot;,
  &quot;value&quot;: &#x5B;&quot;In review&quot;] }
</pre></div>


<p class="wp-block-paragraph"><strong><em>notInList</em></strong> is simply the opposite, so it just excludes all elements provided. It supports exactly the same thing as inList and works in the same way too.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Workflow state&quot;, &quot;operator&quot;: &quot;notInList&quot;,
  &quot;value&quot;: &#x5B;&quot;Approved&quot;] }
</pre></div>


<p class="wp-block-paragraph">A subtle catch with &#8220;not&#8221; operators (<strong><em>notInList</em></strong> and similar): an object whose Lookup property is present but has no item selected satisfies &#8220;not in list&#8221; by default, because there is no value to match or compare with. If you don&#8217;t want to see these objects, then you can simply add a second filter on that same property with <strong><em>isPresentNotEmpty</em></strong>!</p>



<h3 id="h-4-6-contains-and-doesnotcontain" class="wp-block-heading">4.6. contains and doesNotContain</h3>



<p class="wp-block-paragraph"><strong><em>contains</em></strong> is a substring match (similar to <strong><em>matchesWildcardPattern</em></strong> (c.f. below) with implicit wildcards on both sides), it finds objects where the property value includes the given string anywhere inside.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Title&quot;, &quot;operator&quot;: &quot;contains&quot;, &quot;value&quot;: &quot;NDA&quot; }

{ &quot;property&quot;: &quot;Title&quot;, &quot;operator&quot;: &quot;doesNotContain&quot;,
  &quot;value&quot;: &quot;draft&quot; }
</pre></div>


<p class="wp-block-paragraph">For Text, MultiLineText and Lookup properties, both operators are <strong>native</strong>. The rest is supported as <strong>post-filters</strong>, comparing against the string representation.</p>



<h3 id="h-4-7-startswith-doesnotstartwith-endswith-doesnotendwith" class="wp-block-heading">4.7. startsWith, doesNotStartWith, endsWith, doesNotEndWith</h3>



<p class="wp-block-paragraph"><strong><em>startsWith</em></strong> and <strong><em>doesNotStartWith</em></strong> match objects based on the beginning of a property&#8217;s value. These are native only for Text and Lookup properties.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Name or title&quot;, &quot;operator&quot;: &quot;startsWith&quot;,
  &quot;value&quot;: &quot;PO-&quot;    }

{ &quot;property&quot;: &quot;Name or title&quot;, &quot;operator&quot;: &quot;doesNotStartWith&quot;,
  &quot;value&quot;: &quot;DRAFT-&quot; }
</pre></div>


<p class="wp-block-paragraph"><strong><em>endsWith</em></strong> and <strong><em>doesNotEndWith</em></strong> match based on the end of a property&#8217;s value. This doesn&#8217;t exist in M-Files, there is no equivalent and therefore, these are always <strong>post-filters</strong> for all property types.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Name or title&quot;, &quot;operator&quot;: &quot;endsWith&quot;,
  &quot;value&quot;: &quot;(final)&quot; }

{ &quot;property&quot;: &quot;Name or title&quot;, &quot;operator&quot;: &quot;doesNotEndWith&quot;,
  &quot;value&quot;: &quot;(draft)&quot; }
</pre></div>


<h3 id="h-4-8-matcheswildcardpattern-and-doesnotmatchwildcardpattern" class="wp-block-heading">4.8. matchesWildcardPattern and doesNotMatchWildcardPattern</h3>



<p class="wp-block-paragraph">These are the most expressive pattern operators. The <strong><em>value</em></strong> is a regex string where <strong><em>\</em></strong>* matches any number of characters and <strong><em>?</em></strong> matches exactly one character. This is pretty similar to the contains/startsWith/endsWith (and their opposite), obviously, but it is a bit more powerful if you need to match an exact pattern that you know of.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Title&quot;, &quot;operator&quot;: &quot;matchesWildcardPattern&quot;,
  &quot;value&quot;: &quot;PO-????-2026&quot; }

{ &quot;property&quot;: &quot;Title&quot;, &quot;operator&quot;: &quot;doesNotMatchWildcardPattern&quot;,
  &quot;value&quot;: &quot;ID-*&quot; }
</pre></div>


<h3 id="h-4-9-complete-operator-reference" class="wp-block-heading">4.9. Complete operator reference</h3>



<p class="wp-block-paragraph">You reached this point, so it&#8217;s time to have a cheat sheet of all operators. In the below table:</p>



<ul class="wp-block-list">
<li><strong><em>Text</em></strong> and <strong><em>MultiLineText</em></strong> are NOT interchangeable</li>



<li>&#8220;Lookup&#8221; includes both <strong><em>Lookup</em></strong> and <strong><em>MultiSelectLookup</em></strong> (these ARE interchangeable &#8211; one exception for <strong><em>between</em></strong> on MultiSelectLookup because of a bug in M-Files &#8211; the one I mentioned above)</li>



<li>&#8220;Date&#8221; includes <strong><em>Date</em></strong>, <strong><em>Time</em></strong> and <strong><em>Timestamp</em></strong> (these ARE interchangeable &#8211; one exception for <strong><em>notEqual</em></strong> on <strong><em>Timestamp</em></strong> because of a bug in M-Files &#8211; not the same as the one mentioned above)</li>



<li>&#8220;Numeric&#8221; includes <strong><em>Integer</em></strong>, <strong><em>Integer64</em></strong> and <strong><em>Floating</em></strong> (these ARE interchangeable) (&#8220;integer&#8221;/&#8221;real&#8221;)</li>
</ul>



<figure class="wp-block-table"><table><thead><tr><th>Operator</th><th>Value</th><th>Native support for</th><th>Post-filter support for</th><th>Notes</th></tr></thead><tbody><tr><td><strong><em>equals</em></strong></td><td>string</td><td>Text, Lookup, Date, Numeric, Boolean</td><td>MultiLineText</td><td>Exact match</td></tr><tr><td><strong><em>notEqual</em></strong></td><td>string</td><td>Text, Lookup, Date, Numeric, Boolean</td><td>MultiLineText, <strong><em>Timestamp</em></strong></td><td>Excludes exact match. Timestamp is an exception because of a bug in M-Files</td></tr><tr><td><strong><em>lessThan</em></strong></td><td>string</td><td>Text, Lookup, Date, Numeric</td><td>MultiLineText</td><td>Boolean not supported</td></tr><tr><td><strong><em>greaterThan</em></strong></td><td>string</td><td>Text, Lookup, Date, Numeric</td><td>MultiLineText</td><td>Boolean not supported</td></tr><tr><td><strong><em>lessOrEqual</em></strong></td><td>string</td><td>Text, Lookup, Date, Numeric</td><td>MultiLineText</td><td>Boolean not supported</td></tr><tr><td><strong><em>greaterOrEqual</em></strong></td><td>string</td><td>Text, Lookup, Date, Numeric</td><td>MultiLineText</td><td>Boolean not supported</td></tr><tr><td><strong><em>between</em></strong></td><td>array (<strong><em>[&#8220;low&#8221;, &#8220;high&#8221;]</em></strong>)</td><td>Text, Lookup, Date, Numeric</td><td>MultiLineText</td><td>Two-element array, inclusive: <strong><em>low &lt;= value &lt;= high</em></strong>. Boolean not supported</td></tr><tr><td><strong><em>notBetween</em></strong></td><td>array (<strong><em>[&#8220;low&#8221;, &#8220;high&#8221;]</em></strong>)</td><td>&#8211;</td><td>Text, MultiLineText, Lookup, Date, Numeric</td><td>Two-element array, exclusive: <strong><em>value &lt; low OR value &gt; high</em></strong>. Boolean not supported</td></tr><tr><td><strong><em>inList</em></strong></td><td>array (<strong><em>[&#8220;a1&#8221;, &#8220;a2&#8221;, &#8220;a3&#8221;]</em></strong>)</td><td>Lookup</td><td>Text, MultiLineText, Date, Numeric, Boolean</td><td>&#8220;OR&#8221; logic</td></tr><tr><td><strong><em>notInList</em></strong></td><td>array (<strong><em>[&#8220;a1&#8221;, &#8220;a2&#8221;, &#8220;a3&#8221;]</em></strong>)</td><td>Lookup</td><td>Text, MultiLineText, Date, Numeric, Boolean</td><td>Inverse of <strong><em>inList</em></strong></td></tr><tr><td><strong><em>contains</em></strong></td><td>string</td><td>Text, MultiLineText, Lookup</td><td>Date, Numeric, Boolean</td><td>Implicit wildcards on both sides</td></tr><tr><td><strong><em>doesNotContain</em></strong></td><td>string</td><td>Text, MultiLineText, Lookup</td><td>Date, Numeric, Boolean</td><td>Inverse of <strong><em>contains</em></strong></td></tr><tr><td><strong><em>startsWith</em></strong></td><td>string</td><td>Text, Lookup</td><td>MultiLineText, Date, Numeric, Boolean</td><td>Prefix match</td></tr><tr><td><strong><em>doesNotStartWith</em></strong></td><td>string</td><td>Text, Lookup</td><td>MultiLineText, Date, Numeric, Boolean</td><td>Inverse of <strong><em>startsWith</em></strong></td></tr><tr><td><strong><em>endsWith</em></strong></td><td>string</td><td>&#8211;</td><td>All</td><td>Suffix match</td></tr><tr><td><strong><em>doesNotEndWith</em></strong></td><td>string</td><td>&#8211;</td><td>All</td><td>Inverse of <strong><em>endsWith</em></strong></td></tr><tr><td><strong><em>matchesWildcardPattern</em></strong></td><td>string</td><td>Text, Lookup</td><td>MultiLineText, Date, Numeric, Boolean</td><td><em><strong>*</strong> = any chars, <strong><em>?</em></strong> = one char, e.g. <strong><em>PO</em>-??-??-202?</strong></em></td></tr><tr><td><strong><em>doesNotMatchWildcardPattern</em></strong></td><td>string</td><td>Text, Lookup</td><td>MultiLineText, Date, Numeric, Boolean</td><td>Inverse of <strong><em>matchesWildcardPattern</em></strong></td></tr><tr><td><strong><em>isEmpty</em></strong></td><td>&#8211;</td><td>&#8211;</td><td>All</td><td>Property is absent OR (present AND empty)</td></tr><tr><td><strong><em>isNotEmpty</em></strong></td><td>&#8211;</td><td>&#8211;</td><td>All</td><td>Property is present AND non-empty</td></tr><tr><td><strong><em>isPresentEmpty</em></strong></td><td>&#8211;</td><td>All</td><td>&#8211;</td><td>Property is present AND empty</td></tr><tr><td><strong><em>isPresentNotEmpty</em></strong></td><td>&#8211;</td><td>All</td><td>&#8211;</td><td>Property is present AND non-empty</td></tr></tbody></table></figure>



<h3 id="h-4-10-pattern-operators-on-date-properties" class="wp-block-heading">4.10. Pattern operators on date properties</h3>



<p class="wp-block-paragraph">All pattern operators (<strong><em>contains</em></strong>, <strong><em>startsWith</em></strong>, <strong><em>endsWith</em></strong>, <strong><em>matchesWildcardPattern</em></strong> and their negations) work on date properties via the post-filter path, comparing against the ISO <strong><em>yyyy-MM-dd</em></strong> string representation (or <strong><em>yyyy-MM-dd HH:mm</em></strong> for timestamp or <strong><em>HH:mm:ss</em></strong> for time properties). This opens up some convenient patterns, even with <strong><em>&#8220;valueType&#8221;: &#8220;literal&#8221;</em></strong>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;startsWith&quot;,
  &quot;value&quot;: &quot;2026&quot; }

{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;startsWith&quot;,
  &quot;value&quot;: &quot;2026-04&quot; }

{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;endsWith&quot;,
  &quot;value&quot;: &quot;-31&quot; }

{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;matchesWildcardPattern&quot;,
  &quot;value&quot;: &quot;2026-??-??&quot; }
</pre></div>


<p class="wp-block-paragraph">And with <strong><em>&#8220;valueType&#8221;: &#8220;dateToken&#8221;</em></strong>, the token resolves to a full ISO date used as the exact string pattern:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;contains&quot;,
  &quot;value&quot;: &quot;@today&quot;, &quot;valueType&quot;: &quot;dateToken&quot; }

{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;startsWith&quot;,
  &quot;value&quot;: &quot;@startOfMonth+5d&quot;, &quot;valueType&quot;: &quot;dateToken&quot; }
</pre></div>


<p class="wp-block-paragraph">A resolved token produces a full ISO date for the operators, c.f. next section for a deeper dive into dateToken details.</p>



<h2 id="h-5-date-tokens-relative-filters-that-always-make-sense" class="wp-block-heading">5. Date tokens &#8211; relative filters that always make sense</h2>



<p class="wp-block-paragraph">Hardcoding a date like <strong><em>&#8220;2026-01-01&#8221;</em></strong> in a filter works, but it ages badly. The dashboard built today shows different data on January 2nd than it did on December 31st, because the filter is now relative to a different &#8220;today&#8221;.</p>



<p class="wp-block-paragraph">Date tokens solve this. Any string value used with <strong><em>&#8220;valueType&#8221;: &#8220;dateToken&#8221;</em></strong> is resolved to an absolute date at query execution time, so the dashboard stays meaningful as time passes.</p>



<h3 id="h-5-1-anchor-tokens" class="wp-block-heading">5.1. Anchor tokens</h3>



<p class="wp-block-paragraph">The following anchors are recognized:</p>



<figure class="wp-block-table"><table><thead><tr><th>Token</th><th>Resolves to</th></tr></thead><tbody><tr><td><strong><em>@now</em></strong></td><td>Current date AND time</td></tr><tr><td><strong><em>@today</em></strong></td><td>Midnight of the current day</td></tr><tr><td><strong><em>@startOfDay</em></strong></td><td>Same as <strong><em>@today</em></strong></td></tr><tr><td><strong><em>@endOfDay</em></strong></td><td>23:59:59 of the current day</td></tr><tr><td><strong><em>@startOfWeek</em></strong></td><td>Monday of the current ISO week at 00:00:00</td></tr><tr><td><strong><em>@endOfWeek</em></strong></td><td>Sunday of the current ISO week at 23:59:59</td></tr><tr><td><strong><em>@startOfMonth</em></strong></td><td>First day of the current month at 00:00:00</td></tr><tr><td><strong><em>@endOfMonth</em></strong></td><td>Last day of the current month at 23:59:59</td></tr><tr><td><strong><em>@startOfQuarter</em></strong></td><td>First day of the current quarter at 00:00:00</td></tr><tr><td><strong><em>@endOfQuarter</em></strong></td><td>Last day of the current quarter at 23:59:59</td></tr><tr><td><strong><em>@startOfYear</em></strong></td><td>January 1st of the current year at 00:00:00</td></tr><tr><td><strong><em>@endOfYear</em></strong></td><td>December 31st of the current year at 23:59:59</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">So <strong><em>@startOfYear</em></strong>, at the time of writing this blog (i.e. in 2026) is <strong><em>2026-01-01 00:00:00</em></strong>. Starting from January 1st 2027, the same token will automatically resolve to <strong><em>2027-01-01 00:00:00</em></strong> instead. Therefore, the dashboard rolls forward automatically.</p>



<p class="wp-block-paragraph">If there is a need to add more date tokens, it&#8217;s always possible.</p>



<h3 id="h-5-2-offsets-days-hours-minutes-and-seconds" class="wp-block-heading">5.2. Offsets: days, hours, minutes, and seconds</h3>



<p class="wp-block-paragraph">Any anchor presented above can be followed by one or more offsets to add or subtract time:</p>



<figure class="wp-block-table"><table><thead><tr><th>Token</th><th>Meaning</th></tr></thead><tbody><tr><td><strong><em>@today-30d</em></strong></td><td>30 days ago</td></tr><tr><td><strong><em>@today+7d</em></strong></td><td>One week from today</td></tr><tr><td><strong><em>@startOfMonth+14d</em></strong></td><td>14 days into the current month</td></tr><tr><td><strong><em>@endOfYear-7d</em></strong></td><td>One week before year-end</td></tr><tr><td><strong><em>@today+10h</em></strong></td><td>10:00 today</td></tr><tr><td><strong><em>@today+10h+30m</em></strong></td><td>10:30 today</td></tr><tr><td><strong><em>@today+2d+10h+30m</em></strong></td><td>2 days from today at 10:30</td></tr><tr><td><strong><em>@now+2h</em></strong></td><td>Two hours from now</td></tr><tr><td><strong><em>@now-30m</em></strong></td><td>30 minutes ago</td></tr><tr><td><strong><em>@now+45s</em></strong></td><td>45 seconds from now</td></tr><tr><td><strong><em>@now-8h+30m</em></strong></td><td>7 hours and 30 minutes ago</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Each offset uses <strong><em>d</em></strong> for days, <strong><em>h</em></strong> for hours, <strong><em>m</em></strong> for minutes, and <strong><em>s</em></strong> for seconds. As you can see above, offsets can be chained together in any order, so <strong><em>@today+2d+10h+30m</em></strong> is valid, as is <strong><em>@now-8h+30m</em></strong>.</p>



<p class="wp-block-paragraph">The day offset is calendar days, not business days. There is no built-in concept of holidays in the engine as of now.</p>



<h3 id="h-5-3-fixed-iso-dates-with-optional-offset" class="wp-block-heading">5.3. Fixed ISO dates (with optional offset)</h3>



<p class="wp-block-paragraph">A literal ISO date works as both literal as well as a date token. However, if you want to apply an offset, then only dateToken can be used. As previously mentioned, &#8220;literal&#8221; really means a literal strings, so there is no computation done on it.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Effective through&quot;, &quot;operator&quot;: &quot;greaterOrEqual&quot;,
  &quot;value&quot;: &quot;2026-01-01+90d&quot;, &quot;valueType&quot;: &quot;dateToken&quot; }
</pre></div>


<p class="wp-block-paragraph">This resolves to <strong><em>2026-04-01 00:00:00</em></strong>, at query time. This can be useful when you want a stable absolute anchor but with an offset relative to it.</p>



<h3 id="h-5-3a-when-to-use-now-instead-of-today" class="wp-block-heading">5.3a. When to use @now instead of @today</h3>



<p class="wp-block-paragraph">The key difference between <strong><em>@now</em></strong> and <strong><em>@today</em></strong> is that <strong><em>@now</em></strong> captures the current time including hours, minutes, and seconds, while <strong><em>@today</em></strong> is truncated to midnight.</p>



<p class="wp-block-paragraph">Use <strong><em>@today</em></strong> for day-level filters (most common): &#8220;contracts expiring within 30 days&#8221;, &#8220;documents created this month&#8221;. Use <strong><em>@now</em></strong> when you need intra-day precision: &#8220;events logged in the last 2 hours&#8221;, &#8220;tasks to process in next 4 hours&#8221;. Examples:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
// All activity since this morning at 7am (a fixed time each day)
{ &quot;property&quot;: &quot;Modified&quot;, &quot;operator&quot;: &quot;greaterOrEqual&quot;,
  &quot;value&quot;: &quot;@today+7h&quot;, &quot;valueType&quot;: &quot;dateToken&quot; }

// Flagged tasks created in the last 8 hours
{ &quot;property&quot;: &quot;Created&quot;, &quot;operator&quot;: &quot;greaterOrEqual&quot;,
  &quot;value&quot;: &quot;@now-8h&quot;, &quot;valueType&quot;: &quot;dateToken&quot; }
</pre></div>


<h2 id="h-6-boolean-filter-values" class="wp-block-heading">6. Boolean filter values</h2>



<p class="wp-block-paragraph">For boolean-typed properties (M-Files &#8220;Boolean (yes/no)&#8221;), the engine accepts both <strong><em>&#8220;Yes&#8221;</em></strong> / <strong><em>&#8220;No&#8221;</em></strong> and <strong><em>&#8220;True&#8221;</em></strong> / <strong><em>&#8220;False&#8221;</em></strong> (case-insensitive):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{ &quot;property&quot;: &quot;Accepted&quot;, &quot;operator&quot;: &quot;equals&quot;,
  &quot;value&quot;: &quot;Yes&quot; }

{ &quot;property&quot;: &quot;Accepted&quot;, &quot;operator&quot;: &quot;equals&quot;,
  &quot;value&quot;: &quot;True&quot; }
</pre></div>


<p class="wp-block-paragraph"><strong><em>&#8220;Yes&#8221;</em></strong> / <strong><em>&#8220;No&#8221;</em></strong> is the preferred form because that is what M-Files displays in the UI and what users see in their property cards. Worth noting: the M-Files Admin label &#8220;Boolean (yes/no)&#8221; is always English even on a localized vault. So you do not need <strong><em>&#8220;Ja&#8221;</em></strong> / <strong><em>&#8220;Nein&#8221;</em></strong> mappings on a German vault (at least as of today).</p>



<h2 id="h-7-wrap-up" class="wp-block-heading">7. Wrap-up</h2>



<p class="wp-block-paragraph">The query model covers the questions that come up in practice. <strong><em>objectType</em></strong> and <strong><em>class</em></strong> define the scope, <strong><em>filters</em></strong> narrow it with a rich set of operators spanning native server conditions and post-filter cases. Date tokens make filters relative without effort.</p>



<p class="wp-block-paragraph">What is <strong>not</strong> in this post: the <strong><em>aggregation</em></strong> block. That is the topic of Post 6, which covers the aggregation types, the reducers, and the multi-series <strong><em>seriesProperty</em></strong> feature. Once those are explained, every JSON block in the widget posts will make complete sense.</p>



<p class="wp-block-paragraph">Want to know more about this Business Dashboard? <a href="https://www.dbi-services.com/company/contact/" target="_blank" rel="noreferrer noopener">Contact us</a> and we will be happy to showcase it on <a href="https://www.m-files.com/" target="_blank" rel="noreferrer noopener">M-Files</a>.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-queries-objecttype-class-filters-date-tokens/">M-Files BD &#8211; Queries: objectType, class, filters, date tokens</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/m-files-bd-queries-objecttype-class-filters-date-tokens/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Administer Oracle Database Appliance (ODA) with odacli and Ansible</title>
		<link>https://www.dbi-services.com/blog/administer-oracle-database-appliance-oda-with-odacli-and-ansible/</link>
					<comments>https://www.dbi-services.com/blog/administer-oracle-database-appliance-oda-with-odacli-and-ansible/#respond</comments>
		
		<dc:creator><![CDATA[Martin Bracher]]></dc:creator>
		<pubDate>Fri, 19 Jun 2026 12:44:40 +0000</pubDate>
				<category><![CDATA[Non classifié(e)]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=43293</guid>

					<description><![CDATA[<p>Introduction To administer an Oracle Database Appliance (ODA), you probably use the odacli commandline tool. The principle of this tool is to run jobs in background. Fire-and-forget&#8230; or Fire-and-poll This command returns a jobId (e.g. f1338963-87a2-4cb9-8a3e-06e104270203) and the job is executed in background. To see if this job is (sucessfully) completed, you have to poll [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/administer-oracle-database-appliance-oda-with-odacli-and-ansible/">Administer Oracle Database Appliance (ODA) with odacli and Ansible</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 id="h-introduction" class="wp-block-heading">Introduction</h2>



<p class="wp-block-paragraph">To administer an Oracle Database Appliance (ODA), you probably use the <code><a href="https://docs.oracle.com/en/engineered-systems/oracle-database-appliance/19.30/cmtrx/faqs-odacli-commands.html">odacli</a> </code>commandline tool.</p>



<p class="wp-block-paragraph">The principle of this tool is to run jobs in background. Fire-and-forget&#8230; or Fire-and-poll</p>



<pre class="wp-block-code"><code>odacli update-repository -f "/tmp/odacli-dcs-19.30.0.0.0-260210-GI-19.30.0.0.zip"</code></pre>



<p class="wp-block-paragraph">This command returns a jobId (e.g. f1338963-87a2-4cb9-8a3e-06e104270203) and the job is executed in background. To see if this job is (sucessfully) completed, you have to poll for the above jobId and wait for Status: Success.</p>



<pre class="wp-block-code"><code>odacli describe-job -i f1338963-87a2-4cb9-8a3e-06e104270203
watch -n 2 odacli describe-job -i f1338963-87a2-4cb9-8a3e-06e104270203</code></pre>



<h2 id="h-automation-with-ansible" class="wp-block-heading">Automation with Ansible</h2>



<p class="wp-block-paragraph">For automation, e.g. with Ansible, that is not optimal. We have to implement a polling mechanism for the odacli commands. </p>



<p class="wp-block-paragraph">For example. we will add new software to the ODA-repository.  (version=19.30, software_zip=/tmp/odacli-dcs-19.30.0.0.0-260210-GI-19.30.0.0.zip)</p>



<p class="wp-block-paragraph">First of all, we run odacli to create the job to add the software to the  repository and extract the jobId from the output.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
  - name: import software in repository
    ansible.builtin.shell: |
      /opt/oracle/dcs/bin/odacli describe-dbsystem-image -j \
        | jq -e &#039;.&#x5B;].dbSystemImageComponents&#x5B;]|select(.componentName==&quot;DB&quot;)|.availableVersions&#x5B;]|select(startswith(&quot;{{version2}}&quot;)) &#039; \
        |grep  {{version2}} &gt;&amp;2 &amp;&amp; echo &#039;ALREADY_INSTALLED&#039; &amp;&amp; exit 0
      /opt/oracle/dcs/bin/odacli update-repository -f &quot;{{software_zip}}&quot;
    register: repo
    changed_when: &quot;&#039;ALREADY_INSTALLED&#039; not in repo.stdout&quot;

  - name: set job-id
    set_fact:
      jobid: &quot;{{ (repo.stdout | from_json).jobId }}
    when: &quot;&#039;ALREADY_INSTALLED&#039; not in repo.stdout&quot;
</pre></div>


<p class="wp-block-paragraph">Hint: the 1st command is to check if this software is already imported</p>



<p class="wp-block-paragraph">Now, we can poll the job until it is completed. Ansible is optimized to work with json. So we will enforce odacli to return the output in json format (-j):</p>



<pre class="wp-block-code"><code>/opt/oracle/dcs/bin/odacli describe-job -i f1338963-87a2-4cb9-8a3e-06e104270203 -j
{
  "jobId" : "f1338963-87a2-4cb9-8a3e-06e104270203",
  "status" : "Created",
  "message" : "/tmp/odacli-dcs-19.30.0.0.0-260210-GI-19.30.0.0.zip",
  "reports" : &#091; ],
  "createTimestamp" : "February 24, 2026 13:58:30 PM CET",
  "resourceList" : &#091; ],
  "description" : "Repository Update",
  "updatedTime" : "February 24, 2026 13:58:30 PM CET",
  "jobType" : null,
  "cpsMetadata" : null
}</code></pre>



<p class="wp-block-paragraph">For polling, we can use the ansible loop control, see the <a href="https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_loops.html#retrying-a-task-until-a-condition-is-met">Ansible documentatioon</a></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
  - name: check until job completed
    ansible.builtin.shell: /opt/oracle/dcs/bin/odacli describe-job -j -i {{jobid}}
    register: check_status
    until: &quot;(check_status.stdout|from_json).status == &#039;Success&#039;&quot;
    retries: 10
    delay: 8
    changed_when: false
    when: &quot;&#039;ALREADY_INSTALLED&#039; not in repo.stdout&quot;
</pre></div>


<p class="wp-block-paragraph">That means, Ansible will run the command every 8 seconds until status Success is returned (Success) or after 10 attemts (Failed)</p>



<pre class="wp-block-code"><code>TASK &#091;check until job completed] ************************************************************************
FAILED - RETRYING: check until job completed (10 retries left).
FAILED - RETRYING: check until job completed (9 retries left).
FAILED - RETRYING: check until job completed (8 retries left).
FAILED - RETRYING: check until job completed (7 retries left).
FAILED - RETRYING: check until job completed (6 retries left).
FAILED - RETRYING: check until job completed (5 retries left).
FAILED - RETRYING: check until job completed (4 retries left).
ok: &#091;server01]</code></pre>



<h3 id="h-re-usability-with-roles" class="wp-block-heading">Re-usability with roles</h3>



<p class="wp-block-paragraph">For re-usablility, I recommend to move the job-polling to a role, so that it can be used for all asynchronous jobs</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
# roles/odacli_job/tasks/main.yml
  - name: set job-id
    set_fact:
      jobid: &quot;{{ (job_stdout | from_json).jobId |default(&#039;&#039;) }}&quot;
    when: job_stdout|default(&#039;&#039;) != &#039;&#039;
  - debug: var=jobid

  - name: check until repo job completed
    ansible.builtin.shell: /opt/oracle/dcs/bin/odacli describe-job -j -i {{jobid}}
    register: check_status
    failed_when: false
    until: &quot;(check_status.stdout|from_json).status == &#039;Success&#039; or (check_status.stdout|from_json).status == &#039;Failure&#039;&quot;
    retries: &quot;{{retries}}&quot;
    delay:   &quot;{{delay}}&quot;
    changed_when: false
</pre></div>


<p class="wp-block-paragraph">The role can be used as follows:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
  - name: include role to poll job
    include_role:
      role: odacli_job
    when: &quot;&#039;ALREADY_INSTALLED&#039; not in repo.stdout&quot;
    vars:
      job_stdout: repo.stdout 
      retries: 50
      delay: 3
</pre></div>


<p class="wp-block-paragraph">The job to poll can be specified by variable &#8220;jobid&#8221;, or you can provide the json-output of launching the job via &#8220;job_stdout&#8221;, then the role extracts the jobid</p>



<p class="wp-block-paragraph">After the import of the software (you will see it in /opt/oracle/oak/pkgrepos/orapkgs/clones/), you can now deploy an ORACLE_HOME with it. For that, we can use the same role to poll this asynchronous job:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
  - name: create database home
    ansible.builtin.shell: |
      /opt/oracle/dcs/bin/odacli create-dbhome -j -v {{version}}
    register: home

  - name: include role to poll job
    include_role:
      role: odacli_job
    vars:
      job_stdout: home.stdout
      retries: 80
      delay: 10
</pre></div>


<h3 id="h-errorhandling" class="wp-block-heading">Errorhandling</h3>



<p class="wp-block-paragraph">What is not done in the role is the error-handling. It is up to you to define an adequate error-handling. You will get the result of the asynchronous job in the variable check_status.stdout which is of json format. </p>



<pre class="wp-block-code"><code>(check_status.stdout|from_json).status</code></pre>



<p class="wp-block-paragraph">If the status is </p>



<ul class="wp-block-list">
<li>&#8220;Success&#8221;, it is OK</li>



<li>&#8220;Failure&#8221;, it is not OK</li>



<li>Any other value (e.g. &#8220;Created&#8221;), you got a timeout (&gt;retries*timeout sec.), then the result it is unknown (maybe the job completes at a later time, sucessful or not).</li>
</ul>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/administer-oracle-database-appliance-oda-with-odacli-and-ansible/">Administer Oracle Database Appliance (ODA) with odacli and Ansible</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/administer-oracle-database-appliance-oda-with-odacli-and-ansible/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>A project that looked successful. But…</title>
		<link>https://www.dbi-services.com/blog/a-project-that-looked-successful-but/</link>
					<comments>https://www.dbi-services.com/blog/a-project-that-looked-successful-but/#respond</comments>
		
		<dc:creator><![CDATA[Guillaume Meunier]]></dc:creator>
		<pubDate>Fri, 19 Jun 2026 05:15:56 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[digitalization]]></category>
		<category><![CDATA[Enterprise Content Management]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=45142</guid>

					<description><![CDATA[<p>As You know, I will talk about ECM projects again. This time we will focus on what comes after. When everything was delivered on time and within the budget. Yep it&#8217;s not fiction, it happens really 🙂 Every KPI is green! At the last steering committee meeting, congratulations were in order. A slide presentation highlighted [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/a-project-that-looked-successful-but/">A project that looked successful. But…</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">As You know, I will talk about ECM projects again. This time we will focus on what comes after.</p>



<p class="wp-block-paragraph">When everything was delivered on time and within the budget. Yep it&#8217;s not fiction, it happens really <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">Every KPI is green!</p>



<p class="wp-block-paragraph">At the last steering committee meeting, congratulations were in order. A slide presentation highlighted the project’s key milestones, its features, and its smooth rollout. Everyone agreed that it was an exemplary project.</p>



<p class="wp-block-paragraph">However, month after month, interest gradually declined and fewer employees used it.</p>


<div class="wp-block-image">
<figure class="aligncenter size-medium"><img fetchpriority="high" decoding="async" width="300" height="300" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/ellipses-300x300.png" alt="Digital transformation illusion" class="wp-image-45168" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/ellipses-300x300.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/ellipses-150x150.png 150w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/ellipses-768x768.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/ellipses.png 796w" sizes="(max-width: 300px) 100vw, 300px" /></figure>
</div>


<p class="wp-block-paragraph">There was no official postmortem or formal acknowledgment. It just gradually faded into irrelevance.</p>



<p class="wp-block-paragraph">This is the story of a project that seemed successful but actually wasn’t.</p>



<h2 id="h-why-does-it-look-successful" class="wp-block-heading">Why does it look successful?</h2>



<p class="wp-block-paragraph">At first glance, everything seems perfect:</p>



<ul class="wp-block-list">
<li>Deadlines are respected.</li>



<li>Budget is controlled.</li>



<li>Scope is delivered.</li>



<li>Stakeholders are informed.</li>
</ul>



<p class="wp-block-paragraph">All of the classic project management success criteria were met.</p>



<p class="wp-block-paragraph">However, those metrics measured output, not outcome.</p>



<p class="wp-block-paragraph">No one asked the simple question:</p>



<p class="wp-block-paragraph">Did the project actually solve the intended problem?</p>



<h2 id="h-behind-the-illusion" class="wp-block-heading">Behind the illusion</h2>



<p class="wp-block-paragraph">Under the surface, cracks were real, if you knew where to look.</p>



<p class="wp-block-paragraph">Here are some key points:</p>



<h3 id="h-users" class="wp-block-heading">Users</h3>



<p class="wp-block-paragraph">The training sessions were completed. The documentation was published.</p>



<p class="wp-block-paragraph">But what about adoption? It remains minimal.</p>



<p class="wp-block-paragraph">Employees continued to use spreadsheets, emails, and old tools. Though technically superior, the new system felt like a constraint rather than support.</p>



<p class="wp-block-paragraph">It wasn’t part of their workflow. It was an extra step.</p>



<h3 id="h-requirements" class="wp-block-heading">Requirements</h3>



<p class="wp-block-paragraph">The team implemented everything according to the original specifications.</p>



<p class="wp-block-paragraph">However, no one ever questioned those specifications.</p>



<p class="wp-block-paragraph">Also, the team wrote them too early, based on assumptions and disconnected them from actual user behavior.</p>



<p class="wp-block-paragraph">As a result, the project delivered exactly what stakeholders requested,but not what users needed.</p>



<h3 id="h-success-was-narrowly-defined" class="wp-block-heading">Success was narrowly defined</h3>



<p class="wp-block-paragraph">The project team optimized for:</p>



<ul class="wp-block-list">
<li>Delivery speed</li>



<li>Cost control</li>



<li>Scope completion</li>
</ul>



<p class="wp-block-paragraph">What they didn’t optimize for:</p>



<ul class="wp-block-list">
<li>User adoption</li>



<li>Business impact</li>



<li>Long-term value</li>
</ul>



<p class="wp-block-paragraph">When success is defined narrowly, failure has space to hide.</p>



<h3 id="h-feedback-ignored" class="wp-block-heading">Feedback ignored</h3>



<p class="wp-block-paragraph">During the pilot phase, users raised concerns:</p>



<ul class="wp-block-list">
<li>&#8220;This doesn&#8217;t fit our daily work.&#8221;</li>



<li>&#8220;This adds complexity.&#8221;</li>



<li>&#8220;We don&#8217;t see the benefit.&#8221;</li>
</ul>



<p class="wp-block-paragraph">Rather than viewing these signals as valuable insight, they were labeled as resistance to change.</p>



<p class="wp-block-paragraph">It&#8217;s easier to push forward than to pause and rethink.</p>



<h3 id="h-premature-victory" class="wp-block-heading">Premature victory</h3>



<p class="wp-block-paragraph">Once the project was delivered, attention shifted elsewhere.</p>



<p class="wp-block-paragraph">There was no plan for:</p>



<ul class="wp-block-list">
<li>Adoption tracking</li>



<li>Continuous improvement</li>



<li>Measuring real outcomes</li>
</ul>



<p class="wp-block-paragraph">The project ended when it should have evolved.</p>



<h2 id="h-the-cost-behind-that" class="wp-block-heading">The cost behind that</h2>



<p class="wp-block-paragraph">Because everything appeared successful, the failure went unnoticed.</p>



<p class="wp-block-paragraph">But the cost was real:</p>



<ul class="wp-block-list">
<li>Lost investment</li>



<li>Frustrated users</li>



<li>Erosion of trust in future initiatives</li>



<li>Reinforcement of shadow IT (spreadsheets, workarounds, etc.)</li>
</ul>



<p class="wp-block-paragraph">Even worse, the organization learned the wrong lesson:</p>



<p class="wp-block-paragraph">“We know how to deliver successful projects.”</p>



<h2 id="h-lessons-learned" class="wp-block-heading">Lessons learned</h2>



<p class="wp-block-paragraph">As I&#8217;ve written several times in previous blog posts, ECM failures are rarely due to the solution itself.</p>



<p class="wp-block-paragraph">Rather, they are due to how it was implemented and the lack of improvements after it went live, an ECM is a living system.</p>



<p class="wp-block-paragraph">Here are some point that need to be appreanded differently:</p>



<h3 id="h-redefine-success" class="wp-block-heading">Redefine success</h3>



<p class="wp-block-paragraph">Move beyond the classic triangle of time, cost, and scope.</p>



<p class="wp-block-paragraph">Focus on what brings business value:</p>



<ul class="wp-block-list">
<li>adoption</li>



<li>user satisfaction</li>



<li>simplification and consolidation of processes</li>
</ul>



<p class="wp-block-paragraph">If people don&#8217;t use it, it&#8217;s not successful.</p>



<h3 id="h-be-realistic" class="wp-block-heading">Be realistic</h3>



<p class="wp-block-paragraph">Specifications should not be set in stone.<br>They should evolve based on the following:</p>



<ul class="wp-block-list">
<li>user feedback</li>



<li>iterations</li>



<li>real-world testing</li>
</ul>



<p class="wp-block-paragraph">Projects fail when assumptions take precedence over observation.</p>



<h3 id="h-think-critically" class="wp-block-heading">Think critically</h3>



<p class="wp-block-paragraph">A fully green status report can be misleading.<br>Ask deeper questions:</p>



<p class="wp-block-paragraph">Are users engaged?<br>Are behaviors changing?<br>Is there a measurable impact?</p>



<p class="wp-block-paragraph">If the answers are unclear, the project isn’t finished.</p>



<h3 id="h-go-live-is-the-beginning" class="wp-block-heading">Go-Live is the beginning</h3>



<p class="wp-block-paragraph">Delivery is not the finish line, but rather the starting point.<br>Real success happens after adoption, iteration, and proven value.</p>



<h3 id="h-listen-to-resistance" class="wp-block-heading">Listen to resistance</h3>



<p class="wp-block-paragraph">Resistance is often misinterpreted.</p>



<p class="wp-block-paragraph">It is rarely a matter of resistance to change, but rather a matter of recognizing that every complaint reveals a discrepancy and provides a clue for improvement.</p>



<h2 id="h-finally" class="wp-block-heading">Finally</h2>



<p class="wp-block-paragraph">The most dangerous projects are not the ones that fail visibly. When a project fails, we know why.</p>



<p class="wp-block-paragraph">The real danger lies in projects that succeed&#8230;on paper.</p>



<p class="wp-block-paragraph">Everything looks good because we aren&#8217;t considering what makes sense.</p>



<p class="wp-block-paragraph">It&#8217;s important to keep in mind who we&#8217;re doing those kinds of projects for: the users, and why: to help them with their daily work.</p>



<p class="wp-block-paragraph">Everything else is nonsense.</p>



<p class="wp-block-paragraph">If you’re interested in making your ECM project with <a href="https://www.m-files.com/" target="_blank" rel="noreferrer noopener">M-Files</a> a true success, feel free to contact us at <a href="https://www.dbi-services.com/expertises/digitalization-with-ecm/" target="_blank" rel="noreferrer noopener">dbi services</a>, we’d be happy to help.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/a-project-that-looked-successful-but/">A project that looked successful. But…</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/a-project-that-looked-successful-but/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>M-Files BD &#8211; Distribution and tabular widgets: donut, bar, table</title>
		<link>https://www.dbi-services.com/blog/m-files-bd-distribution-and-tabular-widgets-donut-bar-table/</link>
					<comments>https://www.dbi-services.com/blog/m-files-bd-distribution-and-tabular-widgets-donut-bar-table/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Tue, 16 Jun 2026 17:55:41 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[bar]]></category>
		<category><![CDATA[Business Dashboard]]></category>
		<category><![CDATA[donut]]></category>
		<category><![CDATA[M-Files]]></category>
		<category><![CDATA[table]]></category>
		<category><![CDATA[widget]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=45109</guid>

					<description><![CDATA[<p>This is the third and final widget post of the series. The previous two covered scalar widgets (Post 4a) and trend widgets (Post 4b). What is left is the distribution and tabular widgets: donut, bar, and table. The grouping is loose but useful: donut and bar answer questions like &#8220;how does my population break down [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-distribution-and-tabular-widgets-donut-bar-table/">M-Files BD &#8211; Distribution and tabular widgets: donut, bar, table</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This is the third and final widget post of the series. The previous two covered scalar widgets (<a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/" target="_blank" rel="noreferrer noopener">Post 4a</a>) and trend widgets (<a href="https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/" target="_blank" rel="noreferrer noopener">Post 4b</a>). What is left is the <strong>distribution and tabular</strong> widgets: <strong><em>donut</em></strong>, <strong><em>bar</em></strong>, and <strong><em>table</em></strong>.</p>



<p class="wp-block-paragraph">The grouping is loose but useful: <strong><em>donut</em></strong> and <strong><em>bar</em></strong> answer questions like &#8220;how does my population break down into categories?&#8221;, while <strong><em>table</em></strong> can answer pretty much any question&#8230; Together, they cover the rest of the use cases, where you want either a visual distribution or the actual details of what is in the vault.</p>



<h2 id="h-1-donut-distribution-across-categories" class="wp-block-heading">1. donut &#8211; distribution across categories</h2>



<p class="wp-block-paragraph">The <strong><em>donut</em></strong> chart shows how a population splits across the distinct values of a property. Labels and values appear on the slices, while the legend is placed next to the donut when the pane is wide enough (otherwise below it). The engine computes a layout that estimates the legend width from the longest label, then centers the donut in the remaining space, with the ring filling the main part of the available area.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;Contracts by Agreement Type&quot;,
  &quot;type&quot;: &quot;donut&quot;,
  &quot;gridColumnSpan&quot;: 6,
  &quot;gridRowSpan&quot;: 2,
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Document&quot;,
    &quot;class&quot;: &quot;Contract or Agreement&quot;,
    &quot;aggregation&quot;: {
      &quot;type&quot;: &quot;groupByProperty&quot;,
      &quot;propertyName&quot;: &quot;Agreement type&quot;,
      &quot;resolveValueListLabels&quot;: &quot;Yes&quot;,
      &quot;includeEmptyResults&quot;: &quot;No&quot;
    }
  }
}
</pre></div>


<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e5d07c&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e5d07c" class="wp-block-image size-full wp-lightbox-container"><img decoding="async" width="836" height="472" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.0-1.png" alt="Distribution and tabular widget - Donut" class="wp-image-45138" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.0-1.png 836w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.0-1-300x169.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.0-1-768x434.png 768w" sizes="(max-width: 836px) 100vw, 836px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h3 id="h-1-1-aggregations-the-donut-accepts" class="wp-block-heading">1.1. Aggregations the donut accepts</h3>



<p class="wp-block-paragraph">Two aggregation types work with <strong><em>donut</em></strong>:</p>



<ul class="wp-block-list">
<li><strong><em>groupByProperty</em></strong>: group by the values of that specific property, often the natural choice for category distributions.</li>



<li><strong><em>groupByDateBucket</em></strong>: group by dates based on a certain bucket size. The date buckets will be ordered chronologically, where usually donut slices would be ordered by decreasing counts of objects.</li>
</ul>



<h3 id="h-1-2-resolvevaluelistlabels" class="wp-block-heading">1.2. resolveValueListLabels</h3>



<p class="wp-block-paragraph">For properties backed by an M-Files value list, <strong><em>resolveValueListLabels: &#8220;Yes&#8221;</em></strong> (the default) shows the human display names on the slices&#8217; tooltip and in the legend. On the other hand, <strong><em>&#8220;No&#8221;</em></strong> shows the internal IDs. The default is correct for almost every dashboard, <strong><em>&#8220;No&#8221;</em></strong> might be useful for technical investigations.</p>



<h3 id="h-1-3-seriesproperty-the-multi-mini-pie-grid" class="wp-block-heading">1.3. seriesProperty: the multi-mini-pie grid</h3>



<p class="wp-block-paragraph">This is the most distinctive feature of the donut widget. When you set a <strong><em>seriesProperty</em></strong>, the widget switches to a <strong>multi-mini-pie grid</strong>: one donut per distinct value of the series property, all sharing a single legend. Each small donut shows the distribution of the primary <strong><em>propertyName</em></strong> within that series slice.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;Contracts by Agreement Type x Year&quot;,
  &quot;type&quot;: &quot;donut&quot;,
  &quot;gridColumnSpan&quot;: 12,
  &quot;gridRowSpan&quot;: 4,
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Document&quot;,
    &quot;class&quot;: &quot;Contract or Agreement&quot;,
    &quot;aggregation&quot;: {
      &quot;type&quot;: &quot;groupByDateBucket&quot;,
      &quot;propertyName&quot;: &quot;Effective through&quot;,
      &quot;bucketSize&quot;: &quot;year&quot;,
      &quot;seriesProperty&quot;: &quot;Agreement type&quot;,
      &quot;includeEmptyResults&quot;: &quot;No&quot;
    }
  }
}
</pre></div>


<p class="wp-block-paragraph">This produces, for example, one donut per Agreement type, with each donut showing the distribution by year for their Effective through property. As you can see below, it takes more space, since multiple donuts will be displayed. Depending on what you want to see, you could also simply display multiple donut widgets with the associated filters, instead of a single widget with a multi-series. That is, if you know how many series you need (and therefore how many widgets you need with their dedicated filters) and if it&#8217;s not a dynamic number.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e5db71&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e5db71" class="wp-block-image size-full wp-lightbox-container"><img decoding="async" width="1246" height="926" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.3-1.png" alt="M-Files distribution chart - multi-series donuts" class="wp-image-45140" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.3-1.png 1246w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.3-1-300x223.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.3-1-1024x761.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.1.3-1-768x571.png 768w" sizes="(max-width: 1246px) 100vw, 1246px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p class="wp-block-paragraph">A few practical notes:</p>



<ul class="wp-block-list">
<li>The donuts are all <strong>resize-aware</strong>: when the right pane is resized, the engine recomputes the size of all donuts. The number of donuts per row is fixed and defined at first rendering, based on the size that the widget has available (based on its configuration) and the number of series that were found.</li>



<li>A series where every visible slice is zero is shown as a <strong>grey ring</strong>, so the user does not have to wonder whether the chart loaded. This is the same for single-series donuts too.</li>



<li>Like every <strong><em>seriesProperty</em></strong>, this should be a <strong>low-cardinality</strong> property. A grid of six donuts is most probably fine, but a grid of fifty might be difficult to display properly, unless if you have a very big screen!</li>
</ul>



<h3 id="h-1-4-the-tooltip-touch" class="wp-block-heading">1.4. The tooltip touch</h3>



<p class="wp-block-paragraph">As all other tooltips, the one on the Donut will show the numbers that match the query with a thousand separator (e.g. <strong><em>1&#8217;520</em></strong> or <strong><em>1,520</em></strong> (depending on your regional settings) rather than <strong><em>1520</em></strong>). But in addition, it will also include the percentage that this slice represents. It is a small detail, but it usually helps and avoids the need to calculate that yourself.</p>



<h2 id="h-2-bar-vertical-and-horizontal-bar-charts" class="wp-block-heading">2. bar &#8211; vertical and horizontal bar charts</h2>



<p class="wp-block-paragraph">The <strong><em>bar</em></strong> widget covers a large surface and deserves its own deep dive. It supports both vertical and horizontal orientations, both single-series and multi-series modes, and per-bar threshold coloring in single-series too. Most of the configuration lives in two <strong><em>display</em></strong> keys: <strong><em>barLayout</em></strong> and <strong><em>thresholds</em></strong>.</p>



<h3 id="h-2-1-a-first-vertical-bar" class="wp-block-heading">2.1. A first vertical bar</h3>



<p class="wp-block-paragraph">Taking the same example as for the previous blog post on the Invoices/Revenue:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;Monthly revenue&quot;,
  &quot;type&quot;: &quot;bar&quot;,
  &quot;gridColumnSpan&quot;: 12,
  &quot;gridRowSpan&quot;: 2,
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Document&quot;,
    &quot;class&quot;: &quot;Invoice&quot;,
    &quot;filters&quot;: &#x5B;
      { &quot;property&quot;: &quot;Invoice date&quot;, &quot;operator&quot;: &quot;between&quot;,
        &quot;value&quot;: &#x5B;&quot;@startOfYear&quot;, &quot;@endOfYear&quot;], &quot;valueType&quot;: &quot;dateToken&quot; }
    ],
    &quot;aggregation&quot;: {
      &quot;type&quot;: &quot;groupByDateBucket&quot;,
      &quot;propertyName&quot;: &quot;Invoice date&quot;,
      &quot;bucketSize&quot;: &quot;month&quot;,
      &quot;reducer&quot;: &quot;sum&quot;,
      &quot;reducerProperty&quot;: &quot;Amount&quot;,
      &quot;includeEmptyResults&quot;: &quot;Yes&quot;
    }
  }
}
</pre></div>


<p class="wp-block-paragraph">The X-axis labels rotate at <strong><em>-15 degrees</em></strong> always (not conditionally), so they never overlap the bars or the chart area below them. This was a small thing needed to avoid strange display when the space for the widget is very small or when there are long labels to be displayed. As you can see below, it is very similar to the line/area widgets in its essence:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e5e64c&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e5e64c" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1658" height="472" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.1.png" alt="Distribution and tabular widget - table" class="wp-image-45112" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.1.png 1658w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.1-300x85.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.1-1024x292.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.1-768x219.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.1-1536x437.png 1536w" sizes="auto, (max-width: 1658px) 100vw, 1658px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h3 id="h-2-2-display-thresholds-in-single-series-mode" class="wp-block-heading">2.2. display.thresholds in single-series mode</h3>



<p class="wp-block-paragraph">In single-series mode, <strong><em>display.thresholds</em></strong> colors <strong>each bar individually</strong> based on its own value. You can either use text values representing colors or the HTML color code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: {
  &quot;thresholds&quot;: &#x5B;
    { &quot;value&quot;: 0, &quot;color&quot;: &quot;red&quot; },
    { &quot;value&quot;: 40000, &quot;color&quot;: &quot;orange&quot; },
    { &quot;value&quot;: 100000, &quot;color&quot;: &quot;yellow&quot; },
    { &quot;value&quot;: 120000, &quot;color&quot;: &quot;green&quot; }
  ]
}
</pre></div>


<p class="wp-block-paragraph">So bars with value 0 to 40&#8217;000 will be displayed in red (when value is 0, there is no bar, so only the tooltip will show a red &#8220;0&#8221;). Bars with 40&#8217;000 to 100&#8217;000 will become orange, etc.:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e5edb7&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e5edb7" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1658" height="472" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.2.png" alt="M-Files distribution chart - table with threshold colors" class="wp-image-45113" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.2.png 1658w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.2-300x85.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.2-1024x292.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.2-768x219.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.2-1536x437.png 1536w" sizes="auto, (max-width: 1658px) 100vw, 1658px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p class="wp-block-paragraph">As with <strong><em>line</em></strong> / <strong><em>area</em></strong>, thresholds are <strong>ignored in multi-series mode</strong> (each series uses the automatic palette so the legend color stays consistent).</p>



<h3 id="h-2-3-display-barlayout" class="wp-block-heading">2.3. display.barLayout</h3>



<p class="wp-block-paragraph">Bar charts have two orthogonal choices: orientation (vertical vs horizontal) and multi-series mode (stacked vs grouped). The <strong><em>display.barLayout</em></strong> key combines them into a single value:</p>



<figure class="wp-block-table"><table><thead><tr><th>Value</th><th>Orientation</th><th>Multi-series mode</th></tr></thead><tbody><tr><td><strong><em>&#8220;vertical-stacked&#8221;</em></strong> <em>(default)</em></td><td>Vertical</td><td>Stacked segments</td></tr><tr><td><strong><em>&#8220;vertical-grouped&#8221;</em></strong></td><td>Vertical</td><td>Side-by-side bars per category</td></tr><tr><td><strong><em>&#8220;horizontal-stacked&#8221;</em></strong></td><td>Horizontal</td><td>Stacked segments</td></tr><tr><td><strong><em>&#8220;horizontal-grouped&#8221;</em></strong></td><td>Horizontal</td><td>Side-by-side bars per category</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">For <strong>single-series</strong> bars (i.e. when no <strong><em>seriesProperty</em></strong> is used), only orientation matters. The stacked / grouped distinction has no visible effect, since there is a single value anyway). So a single-series chart with <strong><em>vertical-stacked</em></strong> and one with <strong><em>vertical-grouped</em></strong> render identically.</p>



<p class="wp-block-paragraph">Horizontal charts place categories on the Y axis (top to bottom in the same order they would appear left to right on a vertical chart) and values on the X axis. This is useful when category labels are long enough that the -15 degree rotation still does not fit or when you want more space to display the values rather than the different categories.</p>



<p class="wp-block-paragraph">See the screenshot on the following section for the distinction between the vertical/horizontal and stacked/grouped.</p>



<h3 id="h-2-4-seriesproperty-stacked-or-grouped" class="wp-block-heading">2.4. seriesProperty: stacked or grouped</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;groupByDateBucket&quot;,
  &quot;propertyName&quot;: &quot;Invoice date&quot;,
  &quot;bucketSize&quot;: &quot;month&quot;,
  &quot;reducer&quot;: &quot;sum&quot;,
  &quot;reducerProperty&quot;: &quot;Amount&quot;,
  &quot;seriesProperty&quot;: &quot;Country&quot;,
  &quot;includeEmptyResults&quot;: &quot;Yes&quot;
}

</pre></div>


<p class="wp-block-paragraph">With <strong><em>seriesProperty: &#8220;Country&#8221;</em></strong> and <strong><em>display.barLayout: &#8220;vertical-stacked&#8221;</em></strong>, you get a stacked bar chart where each month bar is split into each values found for the &#8220;Country&#8221; property (here: Switzerland &amp; USA). With <strong><em>vertical-grouped</em></strong>, you get two side-by-side bars per month (one for each &#8220;Country&#8221; value). The chart tells the same story with two (or rather four) different displays. Note: for the below screenshot, I reduced the filtering to only the first 6 months of the year, to be able to do a side-by-side comparison of how these charts look. The goal isn&#8217;t to see the full year here, only to show you how these different display options render:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e5f84b&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e5f84b" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1658" height="934" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.4.png" alt="M-Files distribution chart - bar with multi-series" class="wp-image-45114" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.4.png 1658w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.4-300x169.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.4-1024x577.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.4-768x433.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.2.4-1536x865.png 1536w" sizes="auto, (max-width: 1658px) 100vw, 1658px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h3 id="h-2-5-aggregations-the-bar-widget-accepts" class="wp-block-heading">2.5. Aggregations the bar widget accepts</h3>



<p class="wp-block-paragraph">The bar widget accepts <strong><em>groupByProperty</em></strong> and <strong><em>groupByDateBucket</em></strong>, same as the donut and trend widgets, and the date-valued reducer rule from posts 4a and 4b applies here too: a reducer that returns a date string cannot be plotted on a numeric axis, so the widget will show a placeholder.</p>



<h2 id="h-3-table-sortable-paginated-multipurpose" class="wp-block-heading">3. table &#8211; sortable, paginated, multipurpose</h2>



<p class="wp-block-paragraph">The <strong><em>table</em></strong> widget is the last, and most flexible of the catalog. It accepts <strong>all four aggregation types</strong> (<strong><em>summary</em></strong>, <strong><em>groupByProperty</em></strong>, <strong><em>groupByDateBucket</em></strong>, <strong><em>list</em></strong>), and it is the <strong>only</strong> widget that supports <strong><em>list</em></strong>. This makes it the natural fallback when other widget types cannot display a specific combination, and the obvious choice when the user wants to see the raw objects (without the drill-through).</p>



<h3 id="h-3-1-a-list-aggregation-table" class="wp-block-heading">3.1. A list-aggregation table</h3>



<p class="wp-block-paragraph">This is the &#8220;give me the details&#8221; case. The aggregation is <strong><em>list</em></strong> and with the <strong><em>displayProperties</em></strong> parameter, it is possible to control which columns are shown alongside the object name.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;All Projects&quot;,
  &quot;type&quot;: &quot;table&quot;,
  &quot;gridColumnSpan&quot;: 12,
  &quot;gridRowSpan&quot;: 4,
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Project&quot;,
    &quot;aggregation&quot;: {
      &quot;type&quot;: &quot;list&quot;,
      &quot;displayProperties&quot;: &#x5B;&quot;Class&quot;, &quot;In progress&quot;, &quot;Customer&quot;, &quot;Project Manager&quot;]
    }
  }
}
</pre></div>


<p class="wp-block-paragraph">The result is a sortable table with one row per <strong><em>Project</em></strong>, and five columns (object name, Class, In progress, Customer and Project Manager). Above, there was no Class enforced, so all classes from M-Files will apply. That&#8217;s why in the screenshot, you can see both <strong><em>Internal Project</em></strong> and <strong><em>Customer Project</em></strong> objects, which are the two classes in question. Only the <strong><em>Customer Project</em></strong> has a <strong><em>Customer</em></strong> property, but it isn&#8217;t a problem, it is simply displayed as <strong><em>&#8220;-&#8220;</em></strong> for the <strong><em>Internal Project</em></strong> objects. Clicking a row name navigates the user directly to the object in M-Files, no drill-through modal is opening, since the row already represents one object.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e601e4&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e601e4" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="2044" height="934" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.1.png" alt="Distribution and tabular widget - table" class="wp-image-45115" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.1.png 2044w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.1-300x137.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.1-1024x468.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.1-768x351.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.1-1536x702.png 1536w" sizes="auto, (max-width: 2044px) 100vw, 2044px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h3 id="h-3-2-sorting-and-pagination" class="wp-block-heading">3.2. Sorting and pagination</h3>



<p class="wp-block-paragraph">Every column header is clickable to sort ascending or descending. In above screenshot, the second column is sorted in descending order. The sort applies across <strong>all fetched rows</strong>, not just the currently visible page, which (I believe) is the behavior users would expect when sorting.</p>



<p class="wp-block-paragraph"><strong><em>Note:</em></strong> This is NOT how M-Files behaves in its own search! In M-Files, by default, when you search for something and you get more than 1 page of results, if you sort a column, only what is displayed on the screen gets sorted&#8230; Which is a problem from my point of view, since it sorts only partial data and you might see things you don&#8217;t expect. Therefore, M-Files is doing a page-by-page sorting and not a global sorting. On the other hand, the <strong><em>Business Dashboard</em></strong> will sort correctly and apply the sorting on ALL data, and not just the current page.</p>



<p class="wp-block-paragraph">Pagination kicks in at 15 rows per page, by default (configurable), and the pagination bar at the bottom shows the current range and the total fetched.</p>



<h3 id="h-3-3-display-details" class="wp-block-heading">3.3. Display details</h3>



<p class="wp-block-paragraph">Numeric columns honor <strong><em>display.decimals</em></strong> and both numeric and dates honors regional settings as well. In above example, you can see the 4th row with a <strong><em>Customer</em></strong> column that shows a value <strong><em>&#8220;Tennessee Land Surveyors (Nashville) || Lance Smith Engineering (Surveying)&#8221;</em></strong>. This is actually a concatenation of two customers. This project, in the M-Files Sample Vault, is assigned to two different customers, and therefore, it is shown in the table (or any drill-through modal) as a concatenation of <strong><em>Customer1 || Customer2</em></strong>. No data is silently cut or hidden, everything that is actually part of the metadata model is shown as it should. That&#8217;s how multi-select lookup values are displayed.</p>



<h3 id="h-3-4-seriesproperty-the-cross-tab-pivot" class="wp-block-heading">3.4. seriesProperty: the cross-tab pivot</h3>



<p class="wp-block-paragraph">As other widgets, the table supports multi-series (via the same <strong><em>seriesProperty</em></strong>) when it is using a <strong><em>groupByProperty</em></strong> or <strong><em>groupByDateBucket</em></strong> aggregation. In this case, it becomes a <strong>cross-tab pivot</strong>. This means that rows will remain the groups for the primary group property, while additional columns will be added for each group of the series property. Therefore, both rows (primary group) and columns (series group) are fully dynamic. Here are two table widgets definitions, the first one without multi-series and the second one with it:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;groupByProperty&quot;,
  &quot;propertyName&quot;: &quot;Project manager&quot;,
  &quot;displayProperties&quot;: &#x5B;&quot;Class&quot;, &quot;In progress&quot;, &quot;Customer&quot;, &quot;Project Manager&quot;]
}
...
...
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;groupByProperty&quot;,
  &quot;propertyName&quot;: &quot;Project manager&quot;,
  &quot;seriesProperty&quot;: &quot;Class&quot;,
  &quot;displayProperties&quot;: &#x5B;&quot;Class&quot;, &quot;In progress&quot;, &quot;Customer&quot;, &quot;Project Manager&quot;]
}
</pre></div>


<p class="wp-block-paragraph">With the above, you get the count of <strong><em>Projects</em></strong> that each <strong><em>Project Manager</em></strong> handles for the first one. While the second widget, adds another dimension that is the <strong><em>&#8220;Class&#8221;</em></strong> of the objects, i.e. whether the <strong><em>Projects</em></strong> that the managers handle is an <strong><em>Internal Project</em></strong> or a <strong><em>Customer Project</em></strong>. The result of these two widgets is the following:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e60b6a&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e60b6a" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1658" height="934" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.4.png" alt="M-Files distribution chart - table with multi-series" class="wp-image-45116" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.4.png 1658w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.4-300x169.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.4-1024x577.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.4-768x433.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4c.3.4-1536x865.png 1536w" sizes="auto, (max-width: 1658px) 100vw, 1658px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p class="wp-block-paragraph">In this multi-series case, all series columns include a link on their count. Therefore, each number is clickable, and it will display the drill-through that matches this series AND this primary group. E.g. which <strong><em>Customer Projects</em></strong> are handled by <strong><em>Tommy Hart</em></strong>.</p>



<h3 id="h-3-5-drill-through-on-tables" class="wp-block-heading">3.5. Drill-through on tables</h3>



<p class="wp-block-paragraph">Table drill-through behavior depends on the aggregation type, which is documented in detail in <a href="https://www.dbi-services.com/blog/m-files-bd-end-user-experience/" target="_blank" rel="noreferrer noopener">Post 2</a> (the end-user post) but worth recapping here:</p>



<ul class="wp-block-list">
<li>For <strong><em>summary</em></strong>, <strong><em>groupByProperty</em></strong>, <strong><em>groupByDateBucket</em></strong>: clicking a row (or a specific column from a row in case of multi-series as mentioned above) opens the drill-through modal showing the underlying objects.</li>



<li>For <strong><em>list</em></strong>: clicking a row name navigates directly to the object. No modal, because the row already represents one object.</li>
</ul>



<h3 id="h-3-6-the-safe-fallback-role" class="wp-block-heading">3.6. The &#8220;safe fallback&#8221; role</h3>



<p class="wp-block-paragraph">I mentioned earlier that <strong><em>table</em></strong> accepts every aggregation type. This makes it the safe choice for cases where other widgets refuse. The most common one is <strong>date-valued reducers</strong>: if the answer to your question is a date per group (e.g. &#8220;latest invoice date per customer&#8221;), the chart widgets cannot plot that, but the <strong><em>table</em></strong> widget will happily render it with one row per customer and a date value for each row.</p>



<p class="wp-block-paragraph">Whenever the validation flags a date-valued reducer warning on a chart widget, switching the widget type to <strong><em>kpiNumber</em></strong> or <strong><em>gauge</em></strong> might be the right answer if you need only one date. Otherwise, switching to <strong><em>table</em></strong> is the only remaining and valid option.</p>



<h2 id="h-4-the-compatibility-cheat-sheet" class="wp-block-heading">4. The compatibility cheat sheet</h2>



<p class="wp-block-paragraph">To close the widget posts, here is the full compatibility matrix the validator enforces, as of now. You can use that as a quick reference when authoring:</p>



<figure class="wp-block-table"><table><thead><tr><th class="has-text-align-center" data-align="center">Widget type</th><th class="has-text-align-center" data-align="center">summary</th><th class="has-text-align-center" data-align="center">groupByProperty</th><th class="has-text-align-center" data-align="center">groupByDateBucket</th><th class="has-text-align-center" data-align="center">list</th><th class="has-text-align-center" data-align="center">seriesProperty</th></tr></thead><tbody><tr><td class="has-text-align-center" data-align="center"><strong><em>kpiNumber</em></strong></td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">ignored</td></tr><tr><td class="has-text-align-center" data-align="center"><strong><em>gauge</em></strong></td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">ignored</td></tr><tr><td class="has-text-align-center" data-align="center"><strong><em>donut</em></strong></td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">multi-mini-pie</td></tr><tr><td class="has-text-align-center" data-align="center"><strong><em>bar</em></strong></td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">stacked or grouped</td></tr><tr><td class="has-text-align-center" data-align="center"><strong><em>line</em></strong></td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">multi-series lines</td></tr><tr><td class="has-text-align-center" data-align="center"><strong><em>area</em></strong></td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">multi-series areas</td></tr><tr><td class="has-text-align-center" data-align="center"><strong><em>table</em></strong></td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">yes</td><td class="has-text-align-center" data-align="center">cross-tab pivot</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">When the validation reports an incompatible combination, the message usually includes the right alternative (typically &#8220;use <strong><em>kpiNumber</em></strong>, <strong><em>gauge</em></strong> or <strong><em>table</em></strong>&#8221; for date-valued reducers, or &#8220;use <strong><em>table</em></strong> for list aggregations&#8221;).</p>



<h2 id="h-5-closing-thoughts" class="wp-block-heading">5. Closing thoughts</h2>



<p class="wp-block-paragraph">That closes the widget catalog. The seven widget types cover the full range of business questions I typically see: scalar answers (<strong><em>kpiNumber</em></strong>, <strong><em>gauge</em></strong>), trends (<strong><em>line</em></strong>, <strong><em>area</em></strong>), distributions (<strong><em>donut</em></strong>, <strong><em>bar</em></strong>), and raw enumerations or whatever else (<strong><em>table</em></strong>).</p>



<p class="wp-block-paragraph">Two observations from real customer dashboards:</p>



<ul class="wp-block-list">
<li><strong>Most useful dashboards mix widget types.</strong> A row of KPIs at the top, a couple of distribution charts in the middle, a list table at the bottom. The mental model the user builds reading top to bottom is &#8220;summary, distribution, details&#8221;.</li>



<li><strong>Most pitfalls live at the boundary between aggregation and widget type.</strong> If a widget shows a placeholder, the explanation is almost always &#8220;this aggregation produces a shape this widget cannot render&#8221;. The validation spells it out before save, so trust it.</li>
</ul>



<p class="wp-block-paragraph">Post 5 shifts from &#8220;what widgets exist&#8221; to &#8220;how to write the query that feeds them&#8221;: object types, classes, filter operators, and date tokens. After that, Post 6 covers aggregations and reducers in detail. With those two, every JSON block in the widget posts will make complete sense.</p>



<p class="wp-block-paragraph">Want to know more about this Business Dashboard? <a href="https://www.dbi-services.com/company/contact/" target="_blank" rel="noreferrer noopener">Contact us</a> and we will be happy to showcase it on <a href="https://www.m-files.com/" target="_blank" rel="noreferrer noopener">M-Files</a>.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-distribution-and-tabular-widgets-donut-bar-table/">M-Files BD &#8211; Distribution and tabular widgets: donut, bar, table</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/m-files-bd-distribution-and-tabular-widgets-donut-bar-table/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>LOB Replication With GoldenGate and Importance of Primary Keys</title>
		<link>https://www.dbi-services.com/blog/lob-replication-with-goldengate-and-importance-of-primary-keys/</link>
					<comments>https://www.dbi-services.com/blog/lob-replication-with-goldengate-and-importance-of-primary-keys/#respond</comments>
		
		<dc:creator><![CDATA[Julien Delattre]]></dc:creator>
		<pubDate>Tue, 16 Jun 2026 15:13:07 +0000</pubDate>
				<category><![CDATA[GoldenGate]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[26ai]]></category>
		<category><![CDATA[bad_column]]></category>
		<category><![CDATA[blob]]></category>
		<category><![CDATA[CLOB]]></category>
		<category><![CDATA[dba_goldengate_not_unique]]></category>
		<category><![CDATA[keycols]]></category>
		<category><![CDATA[LOB]]></category>
		<category><![CDATA[migration]]></category>
		<category><![CDATA[ogg]]></category>
		<category><![CDATA[primary key]]></category>
		<category><![CDATA[Replication]]></category>
		<category><![CDATA[support]]></category>
		<category><![CDATA[table]]></category>
		<category><![CDATA[unsupported]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44906</guid>

					<description><![CDATA[<p>Every time I work on database migrations with GoldenGate, I have the same talks with application teams about missing primary keys. Especially when it comes to LOB replication with GoldenGate. In a previous blog, I wrote about how to interpret the DBA_GOLDENGATE_NOT_UNIQUE view, which lists tables with no primary key or non-null unique indexes. There [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/lob-replication-with-goldengate-and-importance-of-primary-keys/">LOB Replication With GoldenGate and Importance of Primary Keys</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Every time I work on database migrations with GoldenGate, I have the same talks with application teams about missing primary keys. Especially when it comes to LOB replication with GoldenGate. In a <a href="https://www.dbi-services.com/blog/goldengate-row-identification-misconceptions-and-solutions/" target="_blank" rel="noreferrer noopener">previous blog</a>, I wrote about how to interpret the <code><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/refrn/DBA_GOLDENGATE_NOT_UNIQUE.html" target="_blank" rel="noreferrer noopener">DBA_GOLDENGATE_NOT_UNIQUE</a></code> view, which lists tables with <strong>no primary key or non-null unique indexes</strong>. There are two main groups of tables listed here:</p>



<ul class="wp-block-list">
<li>Tables where all columns can be used to identify uniqueness (<code>bad_column='N'</code>)</li>



<li>Tables where not all columns can be used (<code>bad_column='Y'</code>)</li>
</ul>



<p class="wp-block-paragraph">To show what happens when primary keys are missing in GoldenGate replication, let&#8217;s see a few examples. In all the examples, I will extract data from <code>APP_SOURCE</code> schema and replicate it into an <code>APP_TARGET</code> schema.</p>



<p class="wp-block-paragraph">The parameters of the replication do not really matter here, but to be precise, here is the extract parameter file:</p>



<pre class="wp-block-code"><code>EXTRACT EXT1
USERIDALIAS source_cdb DOMAIN OracleGoldenGate
EXTTRAIL pdb1/aa
SOURCECATALOG PDB1
DDL INCLUDE MAPPED
TABLE APP_SOURCE.*;</code></pre>



<p class="wp-block-paragraph">And here is the replicat parameter file:</p>



<pre class="wp-block-code"><code>REPLICAT REP2
USERIDALIAS target_pdb DOMAIN OracleGoldenGate
DDL INCLUDE MAPPED
MAP PDB1.APP_SOURCE.*, TARGET PDB2.APP_TARGET.*;</code></pre>



<h2 id="h-first-test-simple-table-with-primary-key" class="wp-block-heading">First test : simple table with primary key</h2>



<p class="wp-block-paragraph">First, let&#8217;s try the most common example, with a table <code>APP_SOURCE.TEST_PK</code> created <strong>with a primary key</strong>. You can skip this part if you know how GoldenGate works, but I just wanted to include this for comparison.</p>



<pre class="wp-block-code"><code>CREATE TABLE APP_SOURCE.TEST_PK
(
    id   NUMBER PRIMARY KEY,
    data VARCHAR2(100)
);</code></pre>



<p class="wp-block-paragraph">Let&#8217;s <strong>insert a few rows</strong> in this table and look at the content of the table in both the source and the target.</p>



<pre class="wp-block-code"><code>INSERT INTO APP_SOURCE.TEST_PK VALUES (1,'A');
INSERT INTO APP_SOURCE.TEST_PK VALUES (2,'B');
COMMIT;

-- Verify source/target
SELECT * FROM APP_SOURCE.TEST_PK ORDER BY id;
SELECT * FROM APP_TARGET.TEST_PK ORDER BY id;</code></pre>



<p class="wp-block-paragraph">You will see the same content in both tables.</p>



<pre class="wp-block-code"><code>SQL&gt; SELECT * FROM APP_SOURCE.TEST_PK ORDER BY id;

        ID DATA
---------- --------------------
         1 A
         2 B</code></pre>



<p class="wp-block-paragraph">Now, if you update the row with <code>id=1</code>, it will be updated in both tables as well.</p>



<pre class="wp-block-code"><code>UPDATE APP_SOURCE.TEST_PK SET data='A_UPDATED' WHERE id=1;
COMMIT;

-- Verify
SELECT * FROM APP_SOURCE.TEST_PK ORDER BY id;
SELECT * FROM APP_TARGET.TEST_PK ORDER BY id;</code></pre>



<p class="wp-block-paragraph">Checking the tables will give you the updated data.</p>



<pre class="wp-block-code"><code>SQL&gt; SELECT * FROM APP_TARGET.TEST_PK ORDER BY id;

        ID DATA
---------- --------------------
         1 A_UPDATED
         2 B</code></pre>



<p class="wp-block-paragraph">Until then, nothing really interesting, but let&#8217;s look at the <strong>report file of the extract</strong> on the source. You will see the following information:</p>



<pre class="wp-block-code"><code>2026-06-06 07:00:52&nbsp; INFO&nbsp;&nbsp;&nbsp; OGG-06509&nbsp; Using the following key columns for source table PDB1.APP_SOURCE.TEST_PK: ID.</code></pre>



<p class="wp-block-paragraph">Because the <strong>table has a primary key</strong>, GoldenGate knows that it should <strong>only use this column</strong> to identify unique rows.</p>



<h2 id="h-second-test-table-with-no-primary-key-and-no-lob-columns" class="wp-block-heading">Second test : table with no primary key and no LOB columns</h2>



<p class="wp-block-paragraph">In this second test, I will create a table without a primary key, but using only types that GoldenGate can use to identify uniqueness.</p>



<pre class="wp-block-code"><code>CREATE TABLE APP_SOURCE.TEST_NOPK
(
    id   NUMBER,
    data VARCHAR2(100)
);</code></pre>



<p class="wp-block-paragraph">We can already query the <code>DBA_GOLDENGATE_NOT_UNIQUE</code> view to find this new table listed with <code>bad_column='N'</code> :</p>



<pre class="wp-block-code"><code>SELECT *
FROM dba_goldengate_not_unique
WHERE owner='APP_SOURCE'
ORDER BY owner, table_name;

OWNER                          TABLE_NAME                     BAD_COLUMN
------------------------------ ------------------------------ ----------
APP_SOURCE                     TEST_NOPK                      N</code></pre>



<p class="wp-block-paragraph">Let&#8217;s do the same steps as before, inserting data and updating the content.</p>



<pre class="wp-block-code"><code>INSERT INTO APP_SOURCE.TEST_NOPK VALUES (1,'A');
INSERT INTO APP_SOURCE.TEST_NOPK VALUES (2,'B');
COMMIT;

SELECT * FROM APP_SOURCE.TEST_NOPK ORDER BY id;
SELECT * FROM APP_TARGET.TEST_NOPK ORDER BY id;

UPDATE APP_SOURCE.TEST_NOPK SET data='A_UPDATED' WHERE id=1;
COMMIT;</code></pre>



<p class="wp-block-paragraph">The content of the table is the same on source and target, as expected.</p>



<pre class="wp-block-code"><code>SQL&gt; SELECT * FROM APP_TARGET.TEST_NOPK ORDER BY id;

        ID DATA
---------- --------------------
         1 A_UPDATED
         2 B</code></pre>



<p class="wp-block-paragraph">And looking at the <strong>extract report file</strong>, we can see that <strong>all viable columns were used</strong>. In that case, it meant taking <code>ID</code> and <code>DATA</code>.</p>



<pre class="wp-block-code"><code>2026-06-06 07:08:13  INFO    OGG-06508  Wildcard MAP (TABLE) resolved (entry PDB1.APP_SOURCE.*): TABLE "PDB1"."APP_SOURCE"."TEST_NOPK".

2026-06-06 07:08:13  WARNING OGG-06439  No unique key is defined for table TEST_NOPK. All viable columns will be used to represent the key, but may not guarantee uniqueness. KEYCOLS may be used to define the key. If using KEYCOLS, make sure that you create an INDEX in the target database for those column(s) as well.

2026-06-06 07:08:13  INFO    OGG-06509  Using the following key columns for source table PDB1.APP_SOURCE.TEST_NOPK: ID, DATA.</code></pre>



<p class="wp-block-paragraph">As mentioned in the other blog about primary keys in GoldenGate, this will work, but you will of course have <strong>performance issues</strong> when replicating data.</p>



<h2 id="h-third-test-table-with-no-pk-and-a-clob-column" class="wp-block-heading">Third test : Table with no PK and a <code>CLOB</code> column</h2>



<p class="wp-block-paragraph">Now, let me show you the bad use case, where you don&#8217;t have a primary key and the <strong>table contains a non-viable column for uniqueness identification</strong>. In this example, I will use a <code>CLOB</code> column, but it could be another unbounded data type.</p>



<pre class="wp-block-code"><code>CREATE TABLE APP_SOURCE.TEST_CLOB
(
    id   NUMBER,
    code VARCHAR2(10),
    txt  CLOB
);</code></pre>



<p class="wp-block-paragraph">If we check the <code>DBA_GOLDENGATE_NOT_UNIQUE</code> view, we see the new table listed with <code>bad_column='Y'</code>.</p>



<pre class="wp-block-code"><code>SELECT *
FROM dba_goldengate_not_unique
WHERE owner='APP_SOURCE'
ORDER BY owner, table_name;

OWNER                          TABLE_NAME                     BAD_COLUMN
------------------------------ ------------------------------ ----------
APP_SOURCE                     TEST_CLOB                      Y
APP_SOURCE                     TEST_NOPK                      N</code></pre>



<p class="wp-block-paragraph">Let&#8217;s insert some data. But this time, I will add <strong>two more rows that are unique</strong>, but for which only the <strong><code>TXT</code> content differs</strong>.</p>



<pre class="wp-block-code"><code>INSERT INTO app_source.test_clob VALUES (1,'A',TO_CLOB('LOB_1'));
INSERT INTO app_source.test_clob VALUES (2,'B',TO_CLOB('LOB_2'));
INSERT INTO app_source.test_clob VALUES (100,'DUP',TO_CLOB('LOB_1'));
INSERT INTO app_source.test_clob VALUES (100,'DUP',TO_CLOB('LOB_2'));
COMMIT;</code></pre>



<p class="wp-block-paragraph">The data was correctly inserted, and the content is the same on the source and target.</p>



<pre class="wp-block-code"><code>SQL&gt; SELECT id,code,DBMS_LOB.SUBSTR(txt,40,1) txt FROM app_target.test_clob ORDER BY id;

        ID CODE       TXT
---------- ---------- ----------------------------------------
         1 A	      LOB_1
         2 B	      LOB_2
       100 DUP	      LOB_1
       100 DUP	      LOB_2</code></pre>



<p class="wp-block-paragraph">In the <strong>report file</strong>, we do not see any difference between this example and the one before. GoldenGate mentions using <strong>two columns for uniqueness identification</strong> : <code>ID</code> and <code>CODE</code>. <strong>GoldenGate never mentions that a third column existed and was not used</strong>. The warning displayed is the same as before.</p>



<pre class="wp-block-code"><code>2026-06-06 07:10:13  INFO    OGG-06508  Wildcard MAP (TABLE) resolved (entry PDB1.APP_SOURCE.*): TABLE "PDB1"."APP_SOURCE"."TEST_CLOB".

2026-06-06 07:10:13  WARNING OGG-06439  No unique key is defined for table TEST_CLOB. All viable columns will be used to represent the key, but may not guarantee uniqueness. KEYCOLS may be used to define the key. If using KEYCOLS, make sure that you create an INDEX in the target database for those column(s) as well.

2026-06-06 07:10:13  INFO    OGG-06509  Using the following key columns for source table PDB1.APP_SOURCE.TEST_CLOB: ID, CODE.</code></pre>



<p class="wp-block-paragraph">And now, the main problem I wanted to discuss regarding LOB replication with GoldenGate. If you <strong>try to delete one row by specifying all three columns</strong>, it will <strong>not always work</strong>.</p>



<pre class="wp-block-code"><code>DELETE FROM app_source.test_clob
WHERE id = 100
AND code = 'DUP'
AND DBMS_LOB.COMPARE(txt,TO_CLOB('LOB_1')) = 0;</code></pre>



<p class="wp-block-paragraph">Sometimes, the line with <code>LOB_1</code> will be deleted:</p>



<pre class="wp-block-code"><code>        ID CODE       TXT
---------- ---------- ----------------------------------------
         1 A	      LOB_1
         2 B	      LOB_2
       100 DUP	      LOB_2</code></pre>



<p class="wp-block-paragraph">But sometimes, the line with <code>LOB_2</code> will be deleted:</p>



<pre class="wp-block-code"><code>        ID CODE       TXT
---------- ---------- ----------------------------------------
         1 A	      LOB_1
         2 B	      LOB_2
       100 DUP	      LOB_1</code></pre>



<p class="wp-block-paragraph">More precisely, it will just <strong>delete the first line that was inserted</strong>. You then have two cases. If you insert <code>LOB_1</code> first, it will be deleted correctly:</p>



<pre class="wp-block-code"><code>DROP TABLE app_source.test_clob PURGE;

CREATE TABLE app_source.test_clob (id NUMBER, code VARCHAR2(10), txt CLOB);

INSERT INTO app_source.test_clob VALUES (100,'DUP',TO_CLOB('LOB_1'));
INSERT INTO app_source.test_clob VALUES (100,'DUP',TO_CLOB('LOB_2'));
COMMIT;

DELETE FROM app_source.test_clob
WHERE id = 100
AND code = 'DUP'
AND DBMS_LOB.COMPARE(txt,TO_CLOB('LOB_1')) = 0;
COMMIT;

SQL&gt; SELECT id,code,DBMS_LOB.SUBSTR(txt,40,1) txt
FROM app_target.test_clob
WHERE code='DUP';

        ID CODE       TXT
---------- ---------- ----------------------------------------
       100 DUP	      LOB_2</code></pre>



<p class="wp-block-paragraph">And if you insert <code>LOB_2</code> first, the incorrect line will be deleted:</p>



<pre class="wp-block-code"><code>DROP TABLE app_source.test_clob PURGE;

CREATE TABLE app_source.test_clob (id NUMBER, code VARCHAR2(10), txt CLOB);

INSERT INTO app_source.test_clob VALUES (100,'DUP',TO_CLOB('LOB_2'));
INSERT INTO app_source.test_clob VALUES (100,'DUP',TO_CLOB('LOB_1'));
COMMIT;

DELETE FROM app_source.test_clob
WHERE id = 100
AND code = 'DUP'
AND DBMS_LOB.COMPARE(txt,TO_CLOB('LOB_1')) = 0;
COMMIT;

SQL&gt; SELECT id,code,DBMS_LOB.SUBSTR(txt,40,1) txt
FROM app_target.test_clob
WHERE code='DUP';

        ID CODE       TXT
---------- ---------- ----------------------------------------
       100 DUP	      LOB_1</code></pre>



<p class="wp-block-paragraph">You now know why <strong>you should always pay attention</strong> to tables in the <code><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/refrn/DBA_GOLDENGATE_NOT_UNIQUE.html" target="_blank" rel="noreferrer noopener">DBA_GOLDENGATE_NOT_UNIQUE</a></code> view with <code>bad_column='Y'</code>. If you have such tables, there are several scenarios that I presented in the <a href="https://www.dbi-services.com/blog/goldengate-row-identification-misconceptions-and-solutions/" target="_blank" rel="noreferrer noopener">last blog on the topic</a>. But <strong>ignoring them is not one of them</strong>. This way, you will avoid issues regarding LOB replication with GoldenGate.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/lob-replication-with-goldengate-and-importance-of-primary-keys/">LOB Replication With GoldenGate and Importance of Primary Keys</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/lob-replication-with-goldengate-and-importance-of-primary-keys/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>M-Files BD &#8211; Trend widgets: line and area</title>
		<link>https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/</link>
					<comments>https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Sat, 13 Jun 2026 16:34:04 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[area]]></category>
		<category><![CDATA[Business Dashboard]]></category>
		<category><![CDATA[line]]></category>
		<category><![CDATA[M-Files]]></category>
		<category><![CDATA[widget]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=45076</guid>

					<description><![CDATA[<p>This is the second widget post of the series, focused on the two trend widgets: line and area. The previous one covered scalar widgets (Post 4a). Since these two widgets are closely related, it made sense to bundle them together in the same post. Both surface a single value per category or per time bucket, [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/">M-Files BD &#8211; Trend widgets: line and area</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This is the second widget post of the series, focused on the two trend widgets: <strong><em>line</em></strong> and <strong><em>area</em></strong>. The previous one covered scalar widgets (<a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/" id="45018" target="_blank" rel="noreferrer noopener">Post 4a</a>). Since these two widgets are closely related, it made sense to bundle them together in the same post. Both surface a single value per category or per time bucket, plotted across a horizontal axis. They are best used with the <strong><em>groupByDateBucket</em></strong> aggregation to show evolution over time, but they also accept <strong><em>groupByProperty</em></strong>.</p>



<h2 id="h-1-why-these-two-are-in-the-same-post" class="wp-block-heading">1. Why these two are in the same post</h2>



<p class="wp-block-paragraph">The wire format and the rendering pipeline for <strong><em>line</em></strong> and <strong><em>area</em></strong> are nearly identical. They produce the same JSON on the server, they use the same client code path, they accept the same aggregation types, the same reducers, the same <strong><em>display</em></strong> options. The only meaningful difference is visual: <strong><em>area</em></strong> fills the region under the line with a translucent color, while <strong><em>line</em></strong> leaves it transparent.</p>



<p class="wp-block-paragraph">In other words: choose <strong><em>area</em></strong> when you want to emphasize cumulative volume or magnitude, choose <strong><em>line</em></strong> when you want to emphasize the trajectory. Both communicate the same thing, it&#8217;s more about what you want to focus on.</p>



<h2 id="h-2-a-first-example-monthly-revenue-trend" class="wp-block-heading">2. A first example: monthly revenue trend</h2>



<p class="wp-block-paragraph">Here is the canonical example: total invoice amount per month for the ongoing year, displayed as a line chart.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;Monthly revenue&quot;,
  &quot;type&quot;: &quot;line&quot;,
  &quot;gridColumnSpan&quot;: 12,
  &quot;gridRowSpan&quot;: 2,
  &quot;display&quot;: { &quot;smooth&quot;: &quot;Yes&quot;, &quot;decimals&quot;: 0 },
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Document&quot;,
    &quot;class&quot;: &quot;Invoice&quot;,
    &quot;filters&quot;: &#x5B;
      { &quot;property&quot;: &quot;Invoice date&quot;, &quot;operator&quot;: &quot;between&quot;,
        &quot;value&quot;: &#x5B;&quot;@startOfYear&quot;, &quot;@endOfYear&quot;], &quot;valueType&quot;: &quot;dateToken&quot; }
    ],
    &quot;aggregation&quot;: {
      &quot;type&quot;: &quot;groupByDateBucket&quot;,
      &quot;propertyName&quot;: &quot;Invoice date&quot;,
      &quot;bucketSize&quot;: &quot;month&quot;,
      &quot;reducer&quot;: &quot;sum&quot;,
      &quot;reducerProperty&quot;: &quot;Amount&quot;,
      &quot;includeEmptyResults&quot;: &quot;Yes&quot;
    }
  }
}
</pre></div>


<p class="wp-block-paragraph">A few things to note in this JSON:</p>



<ul class="wp-block-list">
<li>The filter restricts the data to the current calendar year using two date tokens. The full date-token vocabulary is covered in the future Post 5.</li>



<li>The aggregation is <strong><em>groupByDateBucket</em></strong> on the same date property, bucketed by <strong><em>month</em></strong>. Each bucket is reduced with <strong><em>sum</em></strong> of the <strong><em>Amount</em></strong> property. In other words, this means that all invoices with a date within this calendar year will be grouped by month and the value coming from the Amount property will all be summed within each month to arrive to a Total per month.</li>



<li><strong><em>includeEmptyResults: &#8220;Yes&#8221;</em></strong> ensures that months with no invoices appear as zero values, so the trend line is continuous from January to December rather than skipping empty months (e.g. you were closed for vacations or weren&#8217;t able to sell anything for some other reasons).</li>
</ul>



<p class="wp-block-paragraph">Switching the widget <strong><em>type</em></strong> from <strong><em>line</em></strong> to <strong><em>area</em></strong> in this exact JSON gives the area-chart variant, nothing else changes:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e78940&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e78940" class="wp-block-image size-full is-style-default wp-lightbox-container"><img loading="lazy" decoding="async" width="1658" height="936" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1.png" alt="Line and area widgets" class="wp-image-45084" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1.png 1658w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1-300x169.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1-1024x578.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1-768x434.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1-1536x867.png 1536w" sizes="auto, (max-width: 1658px) 100vw, 1658px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 id="h-3-the-aggregation-types-these-widgets-accept" class="wp-block-heading">3. The aggregation types these widgets accept</h2>



<p class="wp-block-paragraph"><strong><em>line</em></strong> and <strong><em>area</em></strong> are valid with two aggregation types:</p>



<ul class="wp-block-list">
<li><strong><em>groupByDateBucket</em></strong>: the natural fit for time series. It groups a date / timestamp property into a certain bucket: <strong><em>day</em></strong>, <strong><em>week</em></strong>, <strong><em>month</em></strong>, <strong><em>quarter</em></strong>, or <strong><em>year</em></strong>.</li>



<li><strong><em>groupByProperty</em></strong>: groups by the distinct values of any property. It supports date/time properties, but it&#8217;s uncommon, as each day would be a data-point. It also support any other type of properties, to display them as categorical trends (e.g. &#8220;Monthly revenue per region&#8221; rendered as a line for some reason). Depending on requirements, a <strong><em>bar</em></strong> display might be more adapted.</li>
</ul>



<p class="wp-block-paragraph">The visual designer will only allow you to select one of these two aggregations. But if you try to set a <strong><em>summary</em></strong> or <strong><em>list</em></strong> aggregation in the JSON editor directly, then an error will be thrown, as it isn&#8217;t possible.</p>



<h2 id="h-4-display-smooth" class="wp-block-heading">4. display.smooth</h2>



<p class="wp-block-paragraph">The single most common <strong><em>display</em></strong> option for these widgets is <strong><em>smooth</em></strong>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: { &quot;smooth&quot;: &quot;Yes&quot; }
</pre></div>


<p class="wp-block-paragraph">When set to <strong><em>&#8220;Yes&#8221;</em></strong>, the line/area charts draws a Bézier-smoothed curve between data points (c.f. example above). When <strong><em>&#8220;No&#8221;</em></strong> (or omitted), the connecting line is made of straight segments. Smoothing makes monthly or quarterly trends look more visually pleasant. It can also slightly hide outliers, so use it where the trend story is more important than the exact per-bucket value.</p>



<h2 id="h-5-display-decimals" class="wp-block-heading">5. display.decimals</h2>



<p class="wp-block-paragraph">For numeric reducers (<strong><em>sum</em></strong>, <strong><em>avg</em></strong>, <strong><em>min</em></strong>, <strong><em>max</em></strong>, <strong><em>median</em></strong>), this controls how many decimal places are shown in tooltips (default <strong><em>0</em></strong>).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: { &quot;smooth&quot;: &quot;Yes&quot;, &quot;decimals&quot;: 2 }
</pre></div>


<p class="wp-block-paragraph">In the monthly-revenue example above, if amounts are stored as floats (which is probably the case), you may want <strong><em>decimals: 2</em></strong> so the tooltip would show <strong><em>116&#8217;520.43</em></strong> rather than <strong><em>116&#8217;520</em></strong>.</p>



<h2 id="h-6-display-thresholds-in-single-series-mode" class="wp-block-heading">6. display.thresholds in single-series mode</h2>



<p class="wp-block-paragraph">In single-series mode (no <strong><em>seriesProperty</em></strong>), the <strong><em>thresholds</em></strong> array colors <strong>each data point marker</strong> based on its own value. The connecting line (and the area below) itself stays brand blue. The configuration is the same as on <strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong>. You can either use text values representing colors or the HTML color code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: {
  &quot;smooth&quot;: &quot;Yes&quot;,
  &quot;thresholds&quot;: &#x5B;
    { &quot;value&quot;: 0, &quot;color&quot;: &quot;red&quot; },
    { &quot;value&quot;: 40000, &quot;color&quot;: &quot;orange&quot; },
    { &quot;value&quot;: 100000, &quot;color&quot;: &quot;yellow&quot; },
    { &quot;value&quot;: 120000, &quot;color&quot;: &quot;green&quot; }
  ]
}
</pre></div>


<p class="wp-block-paragraph">This is useful to highlight specific buckets that crossed a business threshold without changing the overall trend visualization. As mentioned in <a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/">Post 4a</a>, the default falls back to brand blue for values below the lowest threshold.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e79687&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e79687" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1658" height="936" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1.png" alt="Line and area widgets with thresholds" class="wp-image-45087" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1.png 1658w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1-300x169.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1-1024x578.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1-768x434.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1-1536x867.png 1536w" sizes="auto, (max-width: 1658px) 100vw, 1658px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 id="h-7-seriesproperty-multiple-lines-on-the-same-chart" class="wp-block-heading">7. seriesProperty: multiple lines on the same chart</h2>



<p class="wp-block-paragraph">This is where these widgets become genuinely powerful. When you set a <strong><em>seriesProperty</em></strong> on the aggregation, the engine produces <strong>one line per distinct value</strong> of that additional property, with an automatic legend and color palette.</p>



<p class="wp-block-paragraph">For instance, splitting the monthly revenue line into separate lines per country:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;groupByDateBucket&quot;,
  &quot;propertyName&quot;: &quot;Invoice date&quot;,
  &quot;bucketSize&quot;: &quot;month&quot;,
  &quot;reducer&quot;: &quot;sum&quot;,
  &quot;reducerProperty&quot;: &quot;Amount&quot;,
  &quot;seriesProperty&quot;: &quot;Country&quot;,
  &quot;includeEmptyResults&quot;: &quot;Yes&quot;
}
</pre></div>


<figure data-wp-context="{&quot;imageId&quot;:&quot;6a38fc0e79e40&quot;}" data-wp-interactive="core/image" data-wp-key="6a38fc0e79e40" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1656" height="1392" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1.png" alt="Line and area widgets total as well as multi-series per country" class="wp-image-45085" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1.png 1656w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1-300x252.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1-1024x861.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1-768x646.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1-1536x1291.png 1536w" sizes="auto, (max-width: 1656px) 100vw, 1656px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p class="wp-block-paragraph">A few notes that matter in practice:</p>



<ul class="wp-block-list">
<li><strong><em>display.smooth</em></strong> applies to every series. There is no per-series smoothing.</li>



<li><strong><em>display.thresholds</em></strong> are <strong>silently ignored</strong> in multi-series mode. If they were applied per-point, they would override the series palette colors and break the relationship between line color and legend label. So the engine drops them in multi-series and keeps the legend honest.</li>



<li><strong><em>includeEmptyResults</em></strong> in multi-series mode fills gaps across <strong>both dimensions</strong> (every series gets a zero in any time bucket where it has no data). This avoids broken lines.</li>
</ul>



<h3 id="h-7-1-use-low-cardinality-series-only" class="wp-block-heading">7.1. Use low-cardinality series only</h3>



<p class="wp-block-paragraph">The <strong><em>seriesProperty</em></strong> is best used with <strong>low-cardinality</strong> properties: lookup values, booleans, short text ranges. A monthly revenue chart with 5-6 customers is readable, but the same chart with two hundred customers becomes a colored mess where no individual line is distinguishable from the others. If there are too many series, it loses its usefulness. The validation wouldn&#8217;t block it though, since it is technically a valid compatibility. Try it, and see what it looks like!</p>



<h2 id="h-8-includeemptyresults-gap-filling" class="wp-block-heading">8. includeEmptyResults: gap filling</h2>



<p class="wp-block-paragraph">I mentioned this earlier already, and as you could see above, there are actually months with &#8220;0&#8221; values/amounts.</p>



<p class="wp-block-paragraph">With <strong><em>includeEmptyResults: &#8220;Yes&#8221;</em></strong> present, the graph will fill the gaps between the earliest and the latest data points found with zero values. In above example, we can see in March that there was 0 revenue in total. And similarly, in October, there was 0 revenue for the USA. There is no discontinuity in the lines, all months are present, even if there is &#8220;0&#8221; as a result.</p>



<p class="wp-block-paragraph">With <strong><em>includeEmptyResults: &#8220;No&#8221;</em></strong>, the chart will only display the buckets that have at least one matching object (across the series). So, if no invoice was issued in March, the X axis jumps from February to April directly. For above case, it would still display October, since there is a revenue in Switzerland, therefore the USA line will display a &#8220;0&#8221; value still, the line cannot be &#8220;cut&#8221;. Visually, that compresses the time scale and hides the meaningful &#8220;we had no revenue in March&#8221; signal.</p>



<p class="wp-block-paragraph">There are valid use cases for both situations, so just select the one that makes sense depending on what you want to see.</p>



<p class="wp-block-paragraph"><strong><em>Note:</em></strong> Adding <strong><em>includeEmptyResults: &#8220;Yes&#8221;</em></strong> can also add a <strong><em>(none)</em></strong> bucket for objects whose property used by the <strong><em>groupByProperty</em></strong> has no value. For example, if you group by a boolean state Yes/No, and some objects do not have a value on that boolean, they would end up in this <strong><em>(none)</em></strong> group. For <strong><em>groupByDateBucket</em></strong>, they usually don&#8217;t appear since you usually filter on a date in a certain range (like a month, or a year). Therefore if there is no date, usually it is just outside the range, but it technically also behaves in the same way if you fetch all results.</p>



<h2 id="h-9-the-no-date-valued-reducer-rule" class="wp-block-heading">9. The &#8220;no date-valued reducer&#8221; rule</h2>



<p class="wp-block-paragraph">Same rule as on chart widgets in general: <strong><em>line</em></strong> and <strong><em>area</em></strong> cannot render a reducer that returns a date string. If you write something like:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;groupByProperty&quot;,
  &quot;propertyName&quot;: &quot;Customer&quot;,
  &quot;reducer&quot;: &quot;max&quot;,
  &quot;reducerProperty&quot;: &quot;Invoice date&quot;
}
</pre></div>


<p class="wp-block-paragraph">Then, with a <strong><em>line</em></strong> widget, the display would contain a placeholder and the query validation would show a warning. The reason was discussed in the previous post: ISO dates cannot be plotted on a numeric axis at the moment, so it wouldn&#8217;t make sense as a display.</p>



<p class="wp-block-paragraph">The workaround is the same: use <strong><em>kpiNumber</em></strong> or a <strong><em>gauge</em></strong> in date mode if you only want to display one value, or a <strong><em>table</em></strong> for one or more values.</p>



<h2 id="h-10-drill-through-on-trend-widgets" class="wp-block-heading">10. Drill-through on trend widgets</h2>



<p class="wp-block-paragraph">When <strong><em>drillThroughEnabled</em></strong> is <strong><em>&#8220;Yes&#8221;</em></strong>, clicking a data point opens the drill-through modal with the objects in that bucket. In multi-series mode, the click respects the series the point belongs to: only objects matching both the time bucket <strong>and</strong> the series value are shown. This makes it easy to answer questions like &#8220;show me the May invoices from Switzerland&#8221; with a single click on the right point of the line.</p>



<h2 id="h-11-wrap-up" class="wp-block-heading">11. Wrap-up</h2>



<p class="wp-block-paragraph"><strong><em>line</em></strong> and <strong><em>area</em></strong> are the natural choice for time-series questions. They are fundamentally one widget with two visual modes, sharing the same aggregation contract and the same <strong><em>display</em></strong> vocabulary. The interesting part is what you can do with <strong><em>seriesProperty</em></strong>: turning a single trend into a multi-actor comparison without writing any extra logic.</p>



<p class="wp-block-paragraph">Post <a href="https://www.dbi-services.com/blog/m-files-bd-distribution-and-tabular-widgets-donut-bar-table/" id="45109" target="_blank" rel="noreferrer noopener">4c</a> picks up the remaining three widgets: <strong><em>donut</em></strong>, <strong><em>bar</em></strong>, and <strong><em>table</em></strong>. The grouping rationale will be that they all answer <strong>distribution</strong> questions (how a population breaks down by category) or <strong>tabular</strong> questions (give me the raw rows), as opposed to the time-trend questions that <strong><em>line</em></strong> and <strong><em>area</em></strong> handle.</p>



<p class="wp-block-paragraph">Want to know more about this Business Dashboard? <a href="https://www.dbi-services.com/company/contact/" target="_blank" rel="noreferrer noopener">Contact us</a> and we will be happy to showcase it on <a href="https://www.m-files.com/" target="_blank" rel="noreferrer noopener">M-Files</a>.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/">M-Files BD &#8211; Trend widgets: line and area</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GoldenGate Row Identification: Misconceptions and Solutions</title>
		<link>https://www.dbi-services.com/blog/goldengate-row-identification-misconceptions-and-solutions/</link>
					<comments>https://www.dbi-services.com/blog/goldengate-row-identification-misconceptions-and-solutions/#respond</comments>
		
		<dc:creator><![CDATA[Julien Delattre]]></dc:creator>
		<pubDate>Fri, 12 Jun 2026 15:27:49 +0000</pubDate>
				<category><![CDATA[GoldenGate]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[26]]></category>
		<category><![CDATA[bad_column]]></category>
		<category><![CDATA[CLOB]]></category>
		<category><![CDATA[dba_goldengate_not_unique]]></category>
		<category><![CDATA[index]]></category>
		<category><![CDATA[LOB]]></category>
		<category><![CDATA[ogg]]></category>
		<category><![CDATA[pk]]></category>
		<category><![CDATA[primary key]]></category>
		<category><![CDATA[Replication]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44899</guid>

					<description><![CDATA[<p>GoldenGate is a powerful replication tool, but it is so versatile that you should always check whether what you intend to do is supported or not. Oracle provides a few interesting scripts and views to check your environments before attempting a replication with GoldenGate, and I wanted to write about the DBA_GOLDENGATE_NOT_UNIQUE view. Before explaining [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/goldengate-row-identification-misconceptions-and-solutions/">GoldenGate Row Identification: Misconceptions and Solutions</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">GoldenGate is a <strong>powerful replication tool</strong>, but it is so versatile that <strong>you should always check</strong> whether what you intend to do is <strong>supported or not</strong>. Oracle provides a few interesting scripts and views to check your environments before attempting a replication with GoldenGate, and I wanted to write about the <code><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/refrn/DBA_GOLDENGATE_NOT_UNIQUE.html" target="_blank" rel="noreferrer noopener">DBA_GOLDENGATE_NOT_UNIQUE</a></code> view.</p>



<p class="wp-block-paragraph">Before explaining what this view contains and <strong>how to interpret it</strong>, you should remember that GoldenGate replicates data by replaying changes that occurred on the source database. One of the <strong>most important problems</strong> GoldenGate must solve is to <strong>identify the exact row</strong> that must be updated or deleted on the target database. This means that GoldenGate needs a way to <strong>uniquely identify</strong> every row in a table.</p>



<p class="wp-block-paragraph">The simplest solution is a <strong>primary key</strong>, but GoldenGate row identification can also be done through a <strong>non-null unique index</strong>. When neither exists, GoldenGate must <strong>fall back to less efficient mechanisms</strong>. This means <strong>using all available columns</strong> to identify a row.</p>



<p class="wp-block-paragraph">The <code>DBA_GOLDENGATE_NOT_UNIQUE</code> view contains tables that have <strong>no primary key</strong> and <strong>no non-null unique index</strong>. It is there to help you <strong>identify potential issues</strong> in your database schema and prepare in the best possible way a migration with GoldenGate or any replication work.</p>



<p class="wp-block-paragraph">Here is an example of the content of the view:</p>



<pre class="wp-block-code"><code>OWNER			       TABLE_NAME		      BAD_COLUMN
------------------------------ ------------------------------ ----------
APP_PDB1		       TEST_NOPK		      N
APP_PDB1		       TEST_CLOB		      Y</code></pre>



<p class="wp-block-paragraph">The view is very simple, yet very often misunderstood. The <code>owner</code> and <code>table_name</code> column names are self-explanatory, but let&#8217;s talk about <code>bad_column</code>. This column has two possible values: <code>Y</code> and <code>N</code>.</p>



<h2 id="h-first-case-bad-column-n" class="wp-block-heading">First case &#8211; <code>bad_column = 'N'</code></h2>



<p class="wp-block-paragraph" id="h-n-is-what-you-will-most-often-see-in-this-view-you-should-treat-these-as-warnings"><code>N</code> is what you will most often see in this view. You should treat these as <strong>warnings</strong>. If you intend to replicate a table from this list, you can choose to ignore the warning if you don&#8217;t care about <strong>replication performance</strong>.</p>



<p class="wp-block-paragraph" id="h-n-is-what-you-will-most-often-see-in-this-view-you-should-treat-these-as-warnings">Tables in this list will be <strong>replicated using all columns</strong> as a substitute key. GoldenGate can usually handle these tables without any functional issue. However, <code>UPDATE</code> and <code>DELETE</code> operations become more expensive because <strong>every column must be used</strong> to identify the target row.</p>



<p class="wp-block-paragraph">From my experience, you should still always <strong>discuss with the application owner</strong> or someone who knows the underlying database schema to try to <strong>avoid performance issues</strong>. A list of solutions is given at the end of the blog.</p>



<h2 id="h-second-case-bad-column-y" class="wp-block-heading">Second case &#8211; <code>bad_column = 'Y'</code></h2>



<p class="wp-block-paragraph">If there is one point to remember from this blog, it’s this: <strong>you should never replicate data from tables with <code>bad_column='Y'</code> without thorough investigation</strong>.</p>



<p class="wp-block-paragraph">Tables listed here contain one or more columns that GoldenGate cannot safely use as part of a substitute key, such as <code>LONG</code> or <code>CLOB</code>.</p>



<p class="wp-block-paragraph">Since GoldenGate cannot build a complete substitute key from all columns, row identification becomes problematic. Depending on the table structure and replication configuration, GoldenGate may be <strong>unable to correctly locate rows</strong> for <code>UPDATE</code> or <code>DELETE</code> operations.</p>



<p class="wp-block-paragraph">In practice, a table with <code>bad_column='Y'</code> should be considered a <strong>candidate for schema remediation</strong> before replication (see the list of options below).</p>



<h2 id="h-will-there-be-an-error-if-goldengate-cannot-identify-uniqueness" class="wp-block-heading">Will there be an error if GoldenGate cannot identify uniqueness ?</h2>



<p class="wp-block-paragraph">This is a very <strong>common misconception</strong> when talking about these primary key issues. <strong>GoldenGate does not verify</strong> that the columns used for row identification are truly unique. If duplicate rows exist, a replicat may update or delete an unintended row. In many cases no explicit GoldenGate error will be generated because, from GoldenGate’s perspective, a matching row was found. It could <strong>silently delete the wrong row</strong>, and you will never hear a word about it. In the logs, you won&#8217;t see the difference.</p>



<h2 id="h-what-can-be-done-to-solve-these-issues" class="wp-block-heading">What can be done to solve these issues ?</h2>



<p class="wp-block-paragraph">Essentially, there are four possible approaches:</p>



<ul class="wp-block-list">
<li>If you have the ability to <strong>create a primary key</strong> or a <strong>non-null unique index</strong> on the <strong>source database</strong>, go for it before the initial load of your target database. This is the <strong>safest approach</strong> because uniqueness is enforced by the database itself.</li>



<li>If you <strong>cannot alter the source</strong> schema but are <strong>100% sure</strong> that a <strong>subset of columns</strong> that already exists in the table <strong>can uniquely identify every row</strong>, you can use the <code>KEYCOLS</code> keyword in your parameter files.</li>



<li>Sometimes, the <strong>application</strong> design <strong>prevents duplicates</strong> from being generated. Again, you need to be sure about this. But sometimes, it means that you could <strong>rely on the application design</strong>.</li>



<li>When bad_column is <code>N</code>, you could decide to <strong>not do anything</strong>. For tables with a lot of activity, it could slow down your replication. During a migration, it could even completely block it in production. This should only be done in <strong>last resort</strong>.</li>
</ul>



<p class="wp-block-paragraph">A common misconception is that every table listed in <code>DBA_GOLDENGATE_NOT_UNIQUE</code> is unsupported by GoldenGate. This is not the case. At the other end of the spectrum, I&#8217;ve heard countless of times that these warnings can be ignored. Do that, and you will have nightmares solving issues once you hit production. The view merely highlights tables for which GoldenGate cannot rely on a primary key or a non-null unique index for row identification. The real concern is the <code>bad_column</code> flag: <code>N</code> is a <strong>performance warning</strong>, while <code>Y</code> deserves <strong>immediate investigation</strong> before any replication project.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/goldengate-row-identification-misconceptions-and-solutions/">GoldenGate Row Identification: Misconceptions and Solutions</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/goldengate-row-identification-misconceptions-and-solutions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Zabbix &#8211; Monitoring full cluster Patroni/PostgreSQL</title>
		<link>https://www.dbi-services.com/blog/zabbix-monitoring-full-cluster-patroni-postgresql/</link>
					<comments>https://www.dbi-services.com/blog/zabbix-monitoring-full-cluster-patroni-postgresql/#respond</comments>
		
		<dc:creator><![CDATA[Aurélien Py]]></dc:creator>
		<pubDate>Fri, 12 Jun 2026 14:06:13 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Zabbix]]></category>
		<category><![CDATA[Patroni]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44284</guid>

					<description><![CDATA[<p>Introduction Monitoring a PostgreSQL environment involves much more than simply checking whether the database is running. In a modern high availability architecture, multiple components work together to keep the service available and stable. In this article, we will explain how to monitor a PostgreSQL cluster environment with Zabbix: The goal of this blog is to [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/zabbix-monitoring-full-cluster-patroni-postgresql/">Zabbix &#8211; Monitoring full cluster Patroni/PostgreSQL</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading" id="h-introduction">Introduction</h1>



<p class="wp-block-paragraph">Monitoring a PostgreSQL environment involves much more than simply checking whether the database is running. In a modern high availability architecture, multiple components work together to keep the service available and stable.</p>



<p class="wp-block-paragraph">In this article, we will explain how to monitor a PostgreSQL cluster environment with Zabbix:</p>



<ul class="wp-block-list">
<li>PostgreSQL</li>



<li>Patroni</li>



<li>HAProxy</li>



<li>ETCD</li>



<li>Keepalived</li>
</ul>



<p class="wp-block-paragraph">The goal of this blog is to provide a simple and clear overview that can be understood by both beginners and experienced administrators.</p>



<span id="more-44284"></span>



<h2 class="wp-block-heading" id="h-postgresql-high-availability-architecture">PostgreSQL High Availability Architecture</h2>



<p class="wp-block-paragraph">A typical PostgreSQL high availability environment often contains several components:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Component</th><th>Role</th></tr></thead><tbody><tr><td>PostgreSQL</td><td>Database engine</td></tr><tr><td>Patroni</td><td>PostgreSQL cluster management and failover</td></tr><tr><td>ETCD</td><td>Distributed key-value store used by Patroni</td></tr><tr><td>HAProxy</td><td>Load balancing and traffic routing</td></tr><tr><td>Keepalived</td><td>Virtual IP management and failover</td></tr><tr><td>Zabbix</td><td>Monitoring and alerting platform</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">In a PostgreSQL HA environment, monitoring only the database is not enough because every component directly impacts cluster stability and application availability. Moreover, a failures in components like HAProxy, ETCD, or Keepalived can directly impact application availability and cluster stability. Comprehensive monitoring ensures the entire ecosystem remains healthy and operational.</p>



<h2 class="wp-block-heading" id="h-monitoring-postgresql-with-zabbix">Monitoring PostgreSQL with Zabbix</h2>



<p class="wp-block-paragraph">Zabbix already provides an official PostgreSQL <a href="https://www.zabbix.com/integrations/postgresql#postgresql_agent2">template</a>.</p>



<p class="wp-block-paragraph">This template allows administrators to monitor:</p>



<ul class="wp-block-list">
<li>Database availability</li>



<li>Connections</li>



<li>Transactions</li>



<li>Cache hit ratio</li>



<li>Replication status</li>



<li>Locks</li>



<li>Deadlocks</li>



<li>Database size</li>



<li>Query statistics</li>



<li>WAL activity</li>



<li>Performance metrics</li>
</ul>



<p class="wp-block-paragraph">Therefore, using the official template is usually the fastest and easiest way to start monitoring PostgreSQL.</p>



<h2 class="wp-block-heading" id="h-etcd">ETCD</h2>



<p class="wp-block-paragraph">Zabbix already provides an official template for <a href="https://www.zabbix.com/integrations/etcd">ETCD</a> monitoring.</p>



<p class="wp-block-paragraph">The template uses HTTP Agent items to collect metrics directly from the <code>/metrics</code> endpoint exposed by ETCD.</p>



<p class="wp-block-paragraph">In addition, the template works without external scripts and uses Prometheus-style metrics collection.</p>



<p class="wp-block-paragraph">The official template already includes many useful triggers and metrics for monitoring ETCD health, performance, and cluster activity.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="269" height="300" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image.png" alt="" class="wp-image-44286" style="aspect-ratio:0.896661284441898;width:220px;height:auto" /></figure>



<h2 class="wp-block-heading" id="h-haproxy">HAProxy</h2>



<p class="wp-block-paragraph">HAProxy plays a critical role in a PostgreSQL high availability environment, ensuring that client connections are automatically routed to the correct PostgreSQL node and maintaining application connectivity during failovers.</p>



<p class="wp-block-paragraph">Zabbix already provides an official HAProxy monitoring template that can be used as a base for monitoring and customization: <a href="https://www.zabbix.com/integrations/haproxy">HAProxy by HTTP</a></p>



<p class="wp-block-paragraph">The number of active servers in HAProxy is also monitored by the template. However, the template does not create a trigger by default for this metric. This means that a backend could lose one or more active servers without generating any alert in Zabbix. For this reason, it is highly recommended to create custom triggers on this item to immediately detect backend availability issues and avoid unnoticed service degradation.</p>



<p class="wp-block-paragraph">To implement this properly, a new macro should be created in the template to define the minimum expected number of active servers. Then, a custom trigger must be added on the item prototype available in the Low-Level Discovery (LLD) section of the template. Consequently, Zabbix can automatically monitor and alert on all discovered HAProxy backends.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="497" height="115" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-1.png" alt="" class="wp-image-44287" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-1.png 497w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-1-300x69.png 300w" sizes="auto, (max-width: 497px) 100vw, 497px" /></figure>



<h2 class="wp-block-heading" id="h-patroni">Patroni</h2>



<p class="wp-block-paragraph">Zabbix does not directly include Patroni monitoring in the standard PostgreSQL templates.</p>



<p class="wp-block-paragraph">One approach is to use the Zabbix <a href="https://www.zabbix.com/integrations/systemd">Systemd </a>template, which allows monitoring of services running on the server. In this case, it is simply necessary to filter on the <code>patroni</code> service name to verify that the service is active.</p>



<p class="wp-block-paragraph">Another approach is to create a custom item in a template using a Zabbix agent item with the following key:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
net.tcp.service&#x5B;&quot;{$HAPROXY.STATS.SCHEME}&quot;,&quot;{$HAPROXY.STATS.HOST}&quot;,&quot;{$HAPROXY.STATS.PORT.PATRONI}&quot;]
</pre></div>


<p class="wp-block-paragraph">This item performs a direct HTTP TCP connection test on port <code>5000</code>, which is commonly used by the Patroni API. This configuration allows Zabbix to verify that the Patroni service responds correctly and remains reachable.</p>



<p class="wp-block-paragraph">Additionally, combining both monitoring methods significantly improves monitoring reliability.</p>



<h2 class="wp-block-heading" id="h-monitoring-keepalived">Monitoring Keepalived</h2>



<p class="wp-block-paragraph">Keepalived monitoring is not included by default in the standard Zabbix templates.<br>To properly monitor the VIP management layer, administrators should create custom items whether the Virtual IP (VIP) is correctly present on one of the cluster nodes.</p>



<p class="wp-block-paragraph">A common approach is to create three different items:</p>



<ul class="wp-block-list">
<li>Two items using the <code>net.tcp.service</code> key to test TCP connectivity for:
<ul class="wp-block-list">
<li>the READ-ONLY access</li>



<li>the READ-WRITE access</li>
</ul>
</li>



<li>One additional item using a <code>system.run</code> command to directly verify the VIP status on the server.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="637" height="98" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-2.png" alt="" class="wp-image-44288" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-2.png 637w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-2-300x46.png 300w" sizes="auto, (max-width: 637px) 100vw, 637px" /></figure>



<p class="wp-block-paragraph">Together, these checks help ensure that:</p>



<ul class="wp-block-list">
<li>the VIP is correctly assigned</li>



<li>the PostgreSQL services are reachable</li>



<li>the failover mechanism is working properly</li>
</ul>



<p class="wp-block-paragraph">Finally, administrators should create the appropriate triggers for these items in order to immediately detect VIP failover or connectivity issues.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="380" height="191" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-3.png" alt="" class="wp-image-44289" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-3.png 380w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-3-300x151.png 300w" sizes="auto, (max-width: 380px) 100vw, 380px" /></figure>



<h2 class="wp-block-heading" id="h-conclusion">Conclusion</h2>



<p class="wp-block-paragraph">In conclusion, monitoring PostgreSQL alone is not enough in a high availability environment.</p>



<p class="wp-block-paragraph">A complete monitoring strategy must include:</p>



<ul class="wp-block-list">
<li>PostgreSQL</li>



<li>Patroni</li>



<li>HAProxy</li>



<li>ETCD</li>



<li>Keepalived</li>
</ul>



<p class="wp-block-paragraph">Using Zabbix with both official and custom templates provides a centralized and efficient monitoring solution.</p>



<p class="wp-block-paragraph">The PostgreSQL template available in Zabbix already offers excellent database monitoring capabilities.</p>



<p class="wp-block-paragraph">Additional templates for ETCD and HAProxy help extend monitoring to the full HA architecture and improve visibility across the entire platform.</p>



<p class="wp-block-paragraph">As a result, this approach helps administrators detect failures earlier, improve troubleshooting, and ensure better service availability.</p>



<p class="wp-block-paragraph">If you need you can try other blogs regarding Zabbix or postgreSQL here: <a href="https://www.dbi-services.com/blog/">dbi Blog</a></p>
<p>L’article <a href="https://www.dbi-services.com/blog/zabbix-monitoring-full-cluster-patroni-postgresql/">Zabbix &#8211; Monitoring full cluster Patroni/PostgreSQL</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/zabbix-monitoring-full-cluster-patroni-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Temporary tablespace and ODA 19.31.</title>
		<link>https://www.dbi-services.com/blog/temporary-tablespace-and-oda-19-31/</link>
					<comments>https://www.dbi-services.com/blog/temporary-tablespace-and-oda-19-31/#respond</comments>
		
		<dc:creator><![CDATA[Clemens Bleile]]></dc:creator>
		<pubDate>Thu, 11 Jun 2026 06:55:15 +0000</pubDate>
				<category><![CDATA[Oracle]]></category>
		<category><![CDATA[autoextend]]></category>
		<category><![CDATA[oda]]></category>
		<category><![CDATA[oracle database appliance]]></category>
		<category><![CDATA[tempfile]]></category>
		<category><![CDATA[temporary tablespace]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=45038</guid>

					<description><![CDATA[<p>I recently patched Oracle Database Appliances (ODAs) to 19.31. For DBs with more than 1 tempfile in the temporary tablespace I got an error during the &#8220;odacli update-dbhome&#8221; : The issue is that Oracle tries to &#8220;set autoextend on&#8221; on the temporary files in 19.31., but runs a syntactically wrong command. E.g. in the alert.log [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/temporary-tablespace-and-oda-19-31/">Temporary tablespace and ODA 19.31.</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I recently patched Oracle Database Appliances (ODAs) to 19.31. For DBs with more than 1 tempfile in the temporary tablespace I got an error during the &#8220;odacli update-dbhome&#8221; :</p>



<pre class="wp-block-code"><code># odacli describe-job -i 2ff3fd5d-f611-4ae6-aae0-63df7c08ec71
 
Job details
----------------------------------------------------------------
                     ID:  2ff3fd5d-f611-4ae6-aae0-63df7c08ec71
            Description:  DB Home Patching to 19.31.0.0.0: Home ID is 1c101fd3-2ca8-55f6-9415-22ec5abc53da
                 Status:  Failure (To view Error Correlation report, run "odacli describe-job -i 2ff3fd5d-f611-4ae6-aae0-63df7c08ec71 --ecr" command)
                Created:  June 09, 2026 15:07:23 CEST
                Message:  DCS-10001:Internal error encountered: Unable to autoextend tablespace TEMP.</code></pre>



<p class="wp-block-paragraph">The issue is that Oracle tries to &#8220;set autoextend on&#8221; on the temporary files in 19.31., but runs a syntactically wrong command. E.g. in the alert.log I could see</p>



<pre class="wp-block-code"><code>2026-06-09T15:54:14.898088+02:00
alter database tempfile '/u02/app/oracle/oradata/mydbt_dc2/MYDBT_DC2/datafile/o1_mf_temp_m2d4kz32_.tmp /u02/app/oracle/oradata/mydbt_dc2/MYDBT_DC2/datafile/o1_mf_temp_m2d4l3wf_.tmp' autoextend on next 100m maxsize 20g
ORA-1516 signalled during:  alter database tempfile '/u02/app/oracle/oradata/mydbt_dc2/MYDBT_DC2/datafile/o1_mf_temp_m2d4kz32_.tmp /u02/app/oracle/oradata/mydbt_dc2/MYDBT_DC2/datafile/o1_mf_temp_m2d4l3wf_.tmp' autoextend on next 100m maxsize 20g ...</code></pre>



<p class="wp-block-paragraph">I.e. Oracle just listed all tempfiles separated by blank in one string. This is of course wrong and produces the error.</p>



<p class="wp-block-paragraph">The workaround was to&nbsp;</p>



<p class="wp-block-paragraph">&nbsp;&#8211; create a temporary tablespace temp2</p>



<p class="wp-block-paragraph">&nbsp;&#8211; make temp2 the default temporary tablespace</p>



<p class="wp-block-paragraph">&nbsp;&#8211; drop temporary tablespace temp</p>



<p class="wp-block-paragraph">&nbsp;&#8211; create the temporary tablespace temp with just 1 tempfile</p>



<p class="wp-block-paragraph">&nbsp;&#8211; make temp the default temporary tablespace</p>



<p class="wp-block-paragraph">&nbsp;&#8211; drop temporary tablespace temp2</p>



<p class="wp-block-paragraph">Then run the &#8220;odacli update-dbhome&#8221; again and it will be successful. Afterwards add your temporary files to tablespace temp again.&nbsp;</p>



<p class="wp-block-paragraph">Please consider that it is not obvious in the produced errors of odacli in what DB the error happened (odacli update-dbhome patches all DBs running in the specified ORACLE_HOME). To find out what DB is affected it&#8217;s a good idea to scan the alert-logs for the error:</p>



<pre class="wp-block-code"><code>$ find /u01/app/odaorabase/oracle/diag/rdbms/*/*/trace -name "alert*.log" -exec grep -il "ORA-1516" {}\;</code></pre>



<p class="wp-block-paragraph">To proactively avoid the issue when patching to 19.31. on the ODA, I&#8217;d recommend to check your databases in advance if they have more than 1 tempfile in the temporary tablespace and implement the workaround mentioned above.</p>



<pre class="wp-block-code"><code>select count(*) from dba_temp_files where tablespace_name='TEMP';</code></pre>



<p class="wp-block-paragraph">We opened a SR with Oracle to get the issue fixed for the next release.</p>



<p class="wp-block-paragraph">Update 18.06.2026: </p>



<p class="wp-block-paragraph">If you do use a Standby DB (Data Guard) and use the workaround mentioned above, then please consider that you have to add the tempfiles at the standby site as well after recreating TEMP. See <a href="https://www.dbi-services.com/blog/dataguard-environment-and-database-tempfiles/">here</a>, <a href="https://database-heartbeat.com/2023/09/26/temp-file-dg/">here</a> and <a href="https://www.ludovicocaldara.net/blog/dg26ai-tempfile-creation/">here</a>.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/temporary-tablespace-and-oda-19-31/">Temporary tablespace and ODA 19.31.</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/temporary-tablespace-and-oda-19-31/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>The ROI mirage in ECM projects</title>
		<link>https://www.dbi-services.com/blog/the-roi-mirage-in-ecm-projects/</link>
					<comments>https://www.dbi-services.com/blog/the-roi-mirage-in-ecm-projects/#respond</comments>
		
		<dc:creator><![CDATA[Guillaume Meunier]]></dc:creator>
		<pubDate>Thu, 11 Jun 2026 05:58:52 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[digitalization]]></category>
		<category><![CDATA[Enterprise Content Management]]></category>
		<category><![CDATA[M-Files]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44963</guid>

					<description><![CDATA[<p>In my fifteen-plus years working in the ECM world, I have seen the promise of a high ROI (return on investment) numerous times. Faster processes, fewer errors, reduced storage, and improved compliance can all be quantified and multiplied across the organization to create a compelling business case. The benefit is real; otherwise, I would have [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/the-roi-mirage-in-ecm-projects/">The ROI mirage in ECM projects</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In my fifteen-plus years working in the <a href="https://info.aiim.org/what-is-ecm" target="_blank" rel="noreferrer noopener">ECM</a> world, I have seen the promise of a high ROI (return on investment) numerous times. Faster processes, fewer errors, reduced storage, and improved compliance can all be quantified and multiplied across the organization to create a compelling business case.</p>



<p class="wp-block-paragraph">The benefit is real; otherwise, I would have moved on to something more meaningful. However, this promise isn&#8217;t so straightforward.</p>


<div class="wp-block-image">
<figure class="aligncenter size-medium"><img loading="lazy" decoding="async" width="300" height="300" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/we-need-to-talk-300x300.png" alt="ROI ECM myth" class="wp-image-45034" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/we-need-to-talk-300x300.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/we-need-to-talk-150x150.png 150w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/we-need-to-talk-768x768.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/we-need-to-talk.png 796w" sizes="auto, (max-width: 300px) 100vw, 300px" /></figure>
</div>


<p class="wp-block-paragraph">Most ECM ROI models are overly optimistic and lack objectivity.<br>It&#8217;s not always intentional, though. But they are systematically so. They persistently misrepresent the facts. Often, they are politically motivated. After all, this is business.</p>



<p class="wp-block-paragraph">As a vendor-free ECM consultant, I will try to take an objective look at it.</p>



<h2 class="wp-block-heading" id="h-the-myth-of-measurable-value">The myth of measurable value</h2>



<p class="wp-block-paragraph">ECM vendors and internal champions love spreadsheets.<br>They’re full of neat formulas like:</p>



<ul class="wp-block-list">
<li>Save 5 minutes per document</li>



<li>Process 20,000 documents per year</li>



<li>Average employee cost: 75 CHF/hour</li>
</ul>



<p class="wp-block-paragraph">Result: 125000 CHF annual saving.</p>



<p class="wp-block-paragraph">Looks solid, right?</p>



<p class="wp-block-paragraph">But it&#8217;s not!</p>



<h3 class="wp-block-heading" id="h-the-problem">The problem</h3>



<p class="wp-block-paragraph">This model assumes that time savings are perfectly converted into productive output, that organizational friction magically disappears, and that users actually change their behavior.</p>



<p class="wp-block-paragraph">In reality, things are more nuanced. Saved time becomes micro-idle time rather than reinvested capacity. Users continue to work around the system, and the complexity simply shifts elsewhere instead of disappearing.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">If your ECM project “saves time,” ask yourself: where exactly does that time go?</p>
</blockquote>



<p class="wp-block-paragraph">If you cannot demonstrate a real reduction in costs or an increased throughput, then your ROI is theoretical.</p>



<h2 class="wp-block-heading" id="h-fake-precision">Fake precision</h2>



<p class="wp-block-paragraph">ROI models often disguise assumptions as facts.</p>



<p class="wp-block-paragraph">Typical inputs:</p>



<ul class="wp-block-list">
<li>“Average handling time before ECM”: estimated</li>



<li>“Handling time after ECM”: projected</li>



<li>“Error rate reduction”: assumed</li>



<li>“Adoption rate”: overestimated</li>
</ul>



<p class="wp-block-paragraph">These are not actual measurements, but rather assumptions (sometimes bordering on wishful thinking) that are expressed as numbers.</p>



<p class="wp-block-paragraph">When you multiply uncertain assumptions together, you don&#8217;t get accuracy, you get amplified uncertainty.</p>



<p class="wp-block-paragraph">Yet the final number is presented with:</p>



<ul class="wp-block-list">
<li>Two decimal places</li>



<li>A confident tone</li>



<li>A fancy PowerPoint slide</li>
</ul>



<p class="wp-block-paragraph">This creates the illusion of rigor.<br>However, it&#8217;s still just guesswork in a better format.</p>



<h2 class="wp-block-heading" id="h-the-political-nature-of-roi">The political nature of ROI</h2>



<p class="wp-block-paragraph">That&#8217;s where things get complicated.</p>



<p class="wp-block-paragraph">The return on investment for ECM projects often doesn&#8217;t reflect reality.</p>



<p class="wp-block-paragraph">The main goal is to convince decision-makers.</p>



<p class="wp-block-paragraph">Who needs ROI?</p>



<p class="wp-block-paragraph">• Project sponsors → need funding<br>• Vendors → need deals<br>• Consultants → need positive vibes (Yep, I don&#8217;t want to work with customers who don&#8217;t recognize the value I bring.)<br>• Managers → need justification</p>



<p class="wp-block-paragraph">So what? the ROI model becomes a political artifact:</p>



<p class="wp-block-paragraph">• Inflated enough to pass governance<br>• Flexible enough to survive scrutiny<br>• Vague enough to avoid accountability</p>



<p class="wp-block-paragraph">No one says:</p>



<p class="wp-block-paragraph">“We don’t really know the value, but we think it’s worth doing.”</p>



<p class="wp-block-paragraph">Instead, they say:</p>



<p class="wp-block-paragraph">“This project will deliver €2.4M in savings over 3 years.”</p>



<p class="wp-block-paragraph">Everyone knows it&#8217;s lax. But (very) few people are willing to question it.</p>



<h2 class="wp-block-heading" id="h-misleading-indicators-that-seem-convincing">Misleading indicators (that seem convincing)</h2>



<p class="wp-block-paragraph">Let’s look at the usual suspects.</p>



<h3 class="wp-block-heading" id="h-time-saved">Time saved</h3>



<p class="wp-block-paragraph">Most overused metric in ECM.</p>



<ul class="wp-block-list">
<li>Highly variable</li>



<li>Rarely measurable at scale</li>



<li>Almost never translated into realized financial gain</li>
</ul>



<p class="wp-block-paragraph">The truth is that saving time and saving money are not the same thing.</p>



<h3 class="wp-block-heading" id="h-user-satisfaction">User satisfaction</h3>



<p class="wp-block-paragraph">It often increases temporarily, mainly due to the initial gains associated with novelty. Then, it declines as complexity sets in.</p>



<p class="wp-block-paragraph">It is difficult to establish a link to business results.</p>



<p class="wp-block-paragraph">Satisfied users alone do not justify platforms that cost millions!</p>



<h3 class="wp-block-heading" id="h-documents-processed-faster">Documents Processed Faster</h3>



<p class="wp-block-paragraph">It sounds powerful, but is it really challenging? Does faster generation actually generate more revenue? Is there a bottleneck at this stage?</p>



<p class="wp-block-paragraph">Just because you do things faster doesn&#8217;t mean you&#8217;re creating value!</p>



<h2 class="wp-block-heading" id="h-the-real-economic-impact">The real economic impact</h2>



<p class="wp-block-paragraph">The question to ask is: Which economic variable is actually changing?</p>



<p class="wp-block-paragraph">Real ROI should come from:</p>



<ul class="wp-block-list">
<li>Avoid hiring and even reduce headcount.</li>



<li>Increased transaction volume</li>



<li>Shorter revenue cycles</li>



<li>Lower regulatory penalties (measured, not hypothetical)</li>



<li>Reduced external costs (printing, storage, outsourcing)</li>
</ul>



<p class="wp-block-paragraph">If your ECM project doesn’t impact at least one of these directly, your ROI is likely cosmetic.</p>



<h2 class="wp-block-heading" id="h-why-the-lie-persists">Why the &#8220;lie&#8221; persists</h2>



<p class="wp-block-paragraph">If the flaws are so obvious, why does this narrative persist?</p>



<p class="wp-block-paragraph">Because it delivers, at least in the ways that matter internally. It secures budget approval, aligns stakeholders around a common narrative, and provides a simple, reassuring story.</p>



<p class="wp-block-paragraph">To be fair, many ECM projects do create value, just not in a way that can be easily captured in a spreadsheet.<br>Thus, the industry quietly sustains a shared illusion. It&#8217;s a cycle where belief matters more than proof and ROI becomes a tool for getting things done rather than a measure of value.</p>



<h2 class="wp-block-heading" id="h-what-to-do-instead">What to do instead?</h2>



<p class="wp-block-paragraph">Don&#8217;t get me wrong, ECM is one of the most important business applications.</p>



<p class="wp-block-paragraph">The problem lies in how the project is presented, which seems dishonest to me.</p>



<p class="wp-block-paragraph">As soon as human beings are involved, we can no longer rely solely on mathematical formulas. Consequently, ROI calculations are theoretical and idealized.</p>



<p class="wp-block-paragraph">Nevertheless, the benefits are very real. Rather than claiming users will save five minutes per document, it&#8217;s better to state that the approval process has sped up by 20 or 30 percent and that the follow-up system is now automatic and systematic rather than dependent on an employee&#8217;s availability for that task.</p>



<p class="wp-block-paragraph">Just because not everything can be monetized doesn&#8217;t mean we aren&#8217;t delivering operational value. ECM goals are to improve and simplify daily work or add strategic features.</p>



<p class="wp-block-paragraph">Instead of hiding your assumptions, list them explicitly and present various possible scenarios based on confidence levels.</p>



<p class="wp-block-paragraph">To avoid surprises after implementation, we must ask the tough questions.</p>



<ul class="wp-block-list">
<li>What will happen if the adoption rate isn’t as high as expected?</li>



<li>Will this project reduce costs, or will they simply be shifted elsewhere?</li>



<li>Who is responsible for delivering this value?</li>
</ul>



<p class="wp-block-paragraph">Ultimately, an ECM project is never just a financial equation. At <a href="https://www.dbi-services.com/expertises/digitalization-with-ecm/" target="_blank" rel="noreferrer noopener">dbi services</a>, we refuse to reduce a project’s value to an artificial or approximate ROI. We focus on the real values that ECM delivers on a daily basis: human, operational, and strategic. These values are realized long before the attractive but often misleading numbers. Our goal is not to promise theoretical savings, but to tangibly transform the way teams work, collaborate, and make decisions.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/the-roi-mirage-in-ecm-projects/">The ROI mirage in ECM projects</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/the-roi-mirage-in-ecm-projects/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Lazy Loading (feed)

Served from: www.dbi-services.com @ 2026-06-22 11:10:38 by W3 Total Cache
-->