Modules:Analytics FactTables/fr
Languages: |
English | Français | Translate this article... |
Contents |
Tables de Faits versus Tables de Transactions pour les Cubes
Openbravo facilite la définition de cubes à partir de toute table définie dans le Dictionnaire Applicatif d'Openbravo. Cela inclut toutes les tables transactionnelles existantes.
Il existe cependant un accord de principe dans le milieu des tables OLAP/Analytics, qui dit que définir des cubes sur la base de tables transactionnelles n'est pas la meilleure approche. Il existe plusieurs raisons à cela:
- une moins bonne performance:
- les tables transactionnelles sont souvent normalisées, ce qui signifie que l'extraction d'informations dérivées nécessite l'utilisation de nombreuses jointures (communément appelé schémas en flocon); par exemple : pour utiliser la Catégorie de Tiers comme Dimension pour la table transactionnelle Orderline (ligne de commande), la jointure suivante est requise: depuis la ligne de commande, vers l'entête de la commande, puis vers le tiers puis vers la catégorie de tiers
- les tables transactionnelles tendent à avoir des index spécifiques pour les besoins transactionnels mais pas à des fins de rapports, ajouter des index peut dégrader les performances transactionnelles.
- informations pré-calculées: souvent dans le but de reporting, il est utile de pré-calculer des nombres spécifiques. L'exemple le plus simple est de faire des conversions de données vers une devise de rapport commun. Ceci ne peut pas être réalisé efficacement sur une table transactionnelle utilisant les schémas Mondrian.
Donc, cela prend souvent tout son sens de définir une table à part dans le Dictionnaire Applicatif pour couvrir vos besoins analytiques. Openbravo fournit une infrastructure comportant des mécanismes standards pour mettre à jour les tables de faits et permet à du code personnalisé de participer au traitement standard de mise à jour des tables de faits. Ceci est expliquer dans ce document wiki.
Infrastructure table de faits Openbravo
L'infrastructure de la table de faits Openbravo est constituée de 2 parties:
- la possibilité d'implémenter votre propre classe updater (étendant une classe de base existante). Cette class participera automatiquement dans le traitement de mise à jour du fait Openbravo.
- ajouter de nouvelles colonnes/dimensions aux tables de fait existantes et mettre à jour la table de faits.
Définir votre propre table de faits
Lors de la définition de votre table de faits vous devez suivre les étapes suivantes:
- créer la table de faits dans la base de données et la définir dans le Dictionnaire Applicatif, c'est fait de la même façon que pour tout autre table Openbravo, voir ce howto.
- implémenter une classe OBAnalyticsFactUpdater qui se charge de la création des enregistrements de faits à partir d'une table transactionnelle.
Dans cette section, nous approfondirons ce second sujet, en créant notre propre classe OBAnalyticsFactUpdater. D'abord elle doit étendre la classe standard OBAnalyticsFactUpdater. Étendre cette classe comporte plusieurs avantages:
- En étendant cette classe Openbravo trouvera automatiquement votre classe et l'appellera en tant que partie du traitement de mise à jour des faits.
- La classe de base fournit une approche standard pour manipuler les enregistrements de fait, elle prend en charge la copie de valeurs depuis l'enregistrement transactionnel vers l'enregistrement de fait, et gère les dimensions de temps spéciales. Vous devez seulement faire un enrichissement (override) des méthodes spécifiques pour obtenir l'implémentation d'une mise à jour de fait complètement opérationnelle.
Nous allons maintenant parcourir un exemple d’implémentation d'updater de fait, l'updater de fait que nous utilisons pour le Cube des Commandes de Ventes.
Vous commencez par étendre la classe OBAnalyticsFactUpdater:
public class OBAnalyticsFactOrderUpdater extends OBAnalyticsFactUpdater<OrderLine, AnalyticsFactOrder> {
Comme vous pouvez le voir, vous étendez la classe avec des génériques, elle définit à la fois la table transactionnelle principale et la table de faits comme des paramètres génériques. Dans cet exemple OrderLine est la table transactionnelle principale et AnalyticsFactOrder est la table de faits cible. De nombreuses méthodes dont vous aurez besoin pour implémenter/enrichir, font usage des mêmes génériques.
Dans l'étape suivante, vous devez implémenter plusieurs méthodes abstraites héritées de la classe de base. Nous allons les explorer séparément. D'abord, la méthode getData, qui doit retourner les enregistrements de table transactionnelle principale, dans ce cas les instances Orderline:
protected ScrollableResults getData() { final Date fromDate = getLastUpdateRunDate(); final String qryStr = "select ol from " + OrderLine.ENTITY_NAME + " ol where ol.client.id = :client and ol.active=true and ol." + OrderLine.PROPERTY_SALESORDER + ".active=true and ol." + OrderLine.PROPERTY_SALESORDER + "." + Order.PROPERTY_DOCUMENTSTATUS + "='CO' and (ol.updated >= :updated or ol." + OrderLine.PROPERTY_SALESORDER + ".updated >= :updated)"; final Query qry = OBDal.getInstance().getSession().createQuery(qryStr); qry.setDate("updated", fromDate); qry.setString("client", OBContext.getOBContext().getCurrentClient().getId()); return qry.scroll(ScrollMode.FORWARD_ONLY); }
Remarquer comment la méthode getLastUpdateRunDate est utilisée dans des requêtes pour toute modification/création d'objet depuis la dernière mise à jour de cette table de faits.
Donc l'updater nécessite aussi de supprimer les tables de fait qui sont devenus obsolètes, la méthode suivante fait cela:
protected void purgeFacts() { // Note: no implicit joins are allowed in the where clause, an inner select is however allowed final String qryStr = "delete " + getFactEntityName() + " olFact where (olFact.updated < (select ol.updated from " + OrderLine.ENTITY_NAME + " as ol where ol.id = olFact." + AnalyticsFactOrder.PROPERTY_SALESORDERLINE + ") or olFact.updated < (select o.updated from " + Order.ENTITY_NAME + " as o where olFact." + AnalyticsFactOrder.PROPERTY_SALESORDERLINE + "." + OrderLine.PROPERTY_SALESORDER + ".id = o.id)) and olFact.client.id=:client"; OBDal.getInstance().getSession().createQuery(qryStr) .setString("client", OBContext.getOBContext().getCurrentClient().getId()).executeUpdate(); }
Remarquer la comparaison sur la colonne 'updated', vous pouvez choisir une même approche dans votre 'factupdater'.
La méthode suivante décrit la façon dont la classe de base permet la copie de la table transactionnelle vers la table de faits. Juste avant le nom de propriété, vous pouvez utiliser un chemin de propriété: salesOrder.businessPartner.businessPartnerCategory par exemple. Dans l'exemple ci-dessous le code utilise les noms de propriétés générés depuis les entités générées, cela donne plus de sécurité au moment de la compilation.
protected Map<String, String> getPropertyMap() { final String orderPath = OrderLine.PROPERTY_SALESORDER; final String bpPath = OrderLine.PROPERTY_SALESORDER + "." + Order.PROPERTY_BUSINESSPARTNER; final String productPath = OrderLine.PROPERTY_PRODUCT; final Map<String, String> result = new HashMap<String, String>(); result.put(OrderLine.PROPERTY_CLIENT, AnalyticsFactOrder.PROPERTY_CLIENT); result.put(OrderLine.PROPERTY_ORGANIZATION, AnalyticsFactOrder.PROPERTY_ORGANIZATION); result.put(bpPath, AnalyticsFactOrder.PROPERTY_BUSINESSPARTNER); ... return result; }
La dernière méthode qui peut être enrichie permet simplement de fournir à la classe de base le nom de l'entité de la table de faits:
protected String getFactEntityName() { return AnalyticsFactOrder.ENTITY_NAME; }
Il existe plusieurs méthodes que vous pouvez enrichir dans votre classe de base, elles ont toutes une javadoc associée pour expliquer leur signification et leur but.
Etendre une Table de faits existante
Vous pouvez aussi ajouter de nouvelles colonnes de dimension à une table de faits. Openbravo lui-même fait cela en ajoutant des dimensions retail au cube des ventes ERP. Pour étendre une table de faits existante vous devez procéder aux étapes suivantes:
- ajouter la colonne dimension à la table de fait et la définir dans le Dictionnaire Applicatif. Cela peut être fait de la même façon que pour toute colonne personnalisée; voir ce howto pour plus d'information.
- ajouter la nouvelle colonne en tant que dimension, ceci se fait comme pour les autres dimensions (la dimension supplémentaire doit être dans son propre module) voir ici pour plus d'information
- Implément un updater de fait afin que vos nouvelles colonnes de faits soient aussi renseignées quand le reste de la tables de faits est mis à jour.
Dans cette section nous discuterons uniquement du troisième alinea, les deux autres sont couverts dans d'autres documents wiki (voir les liens dans les chacun de ces points).
Vous démarrez en créant une classe qui étend la classe OBAnalyticsFactObjectUpdater:
public class OBRetailAnalyticsOrderLineFactObjectUpdater extends OBAnalyticsFactObjectUpdater<OrderLine, AnalyticsFactOrder> {
Comme vous pouvez le voir vous enrichissez une classe avec des paramètres génériques. Ce sont la table transactionnelle principale (la source de la table de faits) et le type de la table de faits. En étendant la classe de base, Openbravo détectera automatiquement votre implémentation et l'appellera lorsque vous mettrez à jour les tables de faits.
Vous devez ensuite implémenter deux méthodes. La première demande si votre casse peut manipuler une table de faits spécifique. Cette méthode est nécessaire car tous les updaters étendant des faits sont appelés pour toutes les tables de faits.
public boolean canHandleFact(OBAnalyticsFactUpdater<?, ?> factUpdater, Object sourceBob, Object factBob) { return sourceBob instanceof OrderLine && factBob instanceof AnalyticsFactOrder; }
Elle devrait retourner 'vrai' si votre classe est capable de traiter l'objet fait spécifique. Si vous retournez 'vrai' alors la méthode suivante est appelée par Openbravo. Cette méthode vous permet de mettre à jour la table de faits avec des propriétés supplémentaires:
public void handleFact(OBAnalyticsFactUpdater<OrderLine, AnalyticsFactOrder> factUpdater, OrderLine orderLine, AnalyticsFactOrder orderLineFact) { if (orderLine.getSalesOrder().getObposApplications() != null) { orderLineFact.setOBRETANPOSTerminal(orderLine.getSalesOrder().getObposApplications()); orderLineFact.setObretanTerminaltype(orderLine.getSalesOrder().getObposApplications() .getObposTerminaltype()); } orderLineFact.setObretanBusinessdate(TimeDimensionProvider.getInstance().getTimeDimension( orderLine.getSalesOrder().getPOSSBusinessDate())); }
Valeur de Dimension Date/Heure dans les tables de faits
Les tables transactionnelles ont pratiquement toujours des propriétés/colonnes de date à considérer comme une dimension. Pour les dimensions date dans votre table de faits, vous ne devriez pas utiliser le type/référence date mais plutôt utiliser une référence de clé étrangère à la table TimeDimension. Remarquer que pour les tables de faits, il n'est pas nécessaire de créer une colonne date, seule la référence à l'entité TimeDimension est nécessaire.
Une clé étrangère vers la table TimeDimension a un avantage au niveau performance (ce n'est pas une fonction base de donnée) et un autre avantage qui est qu'il existe des membres dimension pour toutes les dates, et de ce fait 'aucun trou' dans les données dans les rapports.
Voir l'exemple de la table de faits des commandes de ventes, qui a plusieurs colonnes 'dates' qui sont en fait des références à la table TimeDimension.
Openbravo convertira automatiquement les valeurs dates/heures vers l'enregistrement TimeDimension lors de l'enrichissement de l'updtaer de fait. Donc il n'est pas nécessaire d'avoir une gestion personnalisée.
Comportement des Devises dans les Rapports Openbravo
De nombreuses mesures sont des montants évalués dans une devise spécifique. Si votre société utilise une seule devise ou si vous analysez toujours des données dans la devise transactionnelle alors il n'est pas utile de définir une devise spécifique pour les rapports.
Cette section décrit la fonctionnalité qui permet d'utiliser des devises dans les rapport et qui offre la souplesse de stocker les faits dans différentes devises.
Remarque pour les développeurs la fonctionnalité suivante doit être encouragée:
- des rapports avec de multiples devises doivent être définis par des tables de faits
- faites le choix d'utiliser les taux de conversion standards par date ou de créer/définir des taux de conversion dans les devises par des tables de faits.
- dans la classe OBAnalyticsFactUpdater ajouter une infrastructure générique pour créer différentes instances pour chaque devise de rapport et enregistrer cette instance. Par exemple une méthode générique qui fait la chose suivante :
- protected List<F> createCurrencyInstances(S sourceBob, F factBob)
--> cette méthode devrait créer/copier l'instance, convertir tous les champs Bigdecimal qui ont la référence montant (ou d'autres références orientées montant)
--> ajouter éventuellement une méthode enrichie spécifique pour obtenir toutes les propriétés qui ont besoin d'être converties.
Les deux méthodes:
protected Date getCurrencyConversionDate(S sourceBob, F factBob) { return null; }
protected Currency getCurrency(S sourceBob, F factBob) { // default behavior could be to find a property which is a currency and then return it return null; }
Donc dans la boucle dans le OBAnalyticsFactUpdater vérifier si les Devises de rapport doivent être supportées, dans ce cas, créer différentes instances pour chaque devise de rapport et enregistrer-les.
Les colonnes Date/Heure représentent un mapping spécial dans Analytics. En effet, l'information est souvent analysée sur une année, un trimestre, un mois ou un ensemble de ces membres dimensionnels.