Cálculo del número de valores previos a cada fecha
En este escenario partimos de la siguiente tabla de datos:
Drupal\user\Entity\Role::postLoad(Object, Array) (Line: 423)
Drupal\Core\Entity\EntityStorageBase->postLoad(Array) (Line: 353)
Drupal\Core\Entity\EntityStorageBase->loadMultiple(Array) (Line: 16)
Drupal\user\RoleStorage->isPermissionInRoles('access site in maintenance mode', Array) (Line: 112)
Drupal\Core\Session\UserSession->hasPermission('access site in maintenance mode') (Line: 105)
Drupal\Core\Session\AccountProxy->hasPermission('access site in maintenance mode') (Line: 83)
Drupal\redirect\RedirectChecker->canRedirect(Object) (Line: 120)
Drupal\redirect\EventSubscriber\RedirectRequestSubscriber->onKernelRequestCheckRedirect(Object, 'kernel.request', Object)
call_user_func(Array, Object, 'kernel.request', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.request') (Line: 145)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\views\ManyToOneHelper->__construct(Object) (Line: 54)
Drupal\views\Plugin\views\argument\ManyToOne->defineOptions() (Line: 141)
Drupal\views\Plugin\views\PluginBase->init(Object, Object, Array) (Line: 104)
Drupal\views\Plugin\views\HandlerBase->init(Object, Object, Array) (Line: 82)
Drupal\views\Plugin\views\argument\ArgumentPluginBase->init(Object, Object, Array) (Line: 33)
Drupal\views\Plugin\views\argument\ManyToOne->init(Object, Object, Array) (Line: 894)
Drupal\views\Plugin\views\display\DisplayPluginBase->getHandlers('argument') (Line: 1045)
Drupal\views\ViewExecutable->_initHandler('argument', Array) (Line: 903)
Drupal\views\ViewExecutable->initHandlers() (Line: 2318)
Drupal\views\Plugin\views\display\DisplayPluginBase->preExecute() (Line: 1697)
Drupal\views\ViewExecutable->preExecute(Array) (Line: 1632)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\views\ManyToOneHelper->__construct(Object) (Line: 54)
Drupal\views\Plugin\views\argument\ManyToOne->defineOptions() (Line: 228)
Drupal\views\Plugin\views\PluginBase->unpackOptions(Array, Array) (Line: 144)
Drupal\views\Plugin\views\PluginBase->init(Object, Object, Array) (Line: 104)
Drupal\views\Plugin\views\HandlerBase->init(Object, Object, Array) (Line: 82)
Drupal\views\Plugin\views\argument\ArgumentPluginBase->init(Object, Object, Array) (Line: 33)
Drupal\views\Plugin\views\argument\ManyToOne->init(Object, Object, Array) (Line: 894)
Drupal\views\Plugin\views\display\DisplayPluginBase->getHandlers('argument') (Line: 1045)
Drupal\views\ViewExecutable->_initHandler('argument', Array) (Line: 903)
Drupal\views\ViewExecutable->initHandlers() (Line: 2318)
Drupal\views\Plugin\views\display\DisplayPluginBase->preExecute() (Line: 1697)
Drupal\views\ViewExecutable->preExecute(Array) (Line: 1632)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\views\ManyToOneHelper->__construct(Object) (Line: 54)
Drupal\views\Plugin\views\argument\ManyToOne->defineOptions() (Line: 228)
Drupal\views\Plugin\views\PluginBase->unpackOptions(Array, Array) (Line: 110)
Drupal\views\Plugin\views\HandlerBase->init(Object, Object, Array) (Line: 82)
Drupal\views\Plugin\views\argument\ArgumentPluginBase->init(Object, Object, Array) (Line: 33)
Drupal\views\Plugin\views\argument\ManyToOne->init(Object, Object, Array) (Line: 894)
Drupal\views\Plugin\views\display\DisplayPluginBase->getHandlers('argument') (Line: 1045)
Drupal\views\ViewExecutable->_initHandler('argument', Array) (Line: 903)
Drupal\views\ViewExecutable->initHandlers() (Line: 2318)
Drupal\views\Plugin\views\display\DisplayPluginBase->preExecute() (Line: 1697)
Drupal\views\ViewExecutable->preExecute(Array) (Line: 1632)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\views\ManyToOneHelper->__construct(Object) (Line: 35)
Drupal\views\Plugin\views\argument\ManyToOne->init(Object, Object, Array) (Line: 894)
Drupal\views\Plugin\views\display\DisplayPluginBase->getHandlers('argument') (Line: 1045)
Drupal\views\ViewExecutable->_initHandler('argument', Array) (Line: 903)
Drupal\views\ViewExecutable->initHandlers() (Line: 2318)
Drupal\views\Plugin\views\display\DisplayPluginBase->preExecute() (Line: 1697)
Drupal\views\ViewExecutable->preExecute(Array) (Line: 1632)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('views') (Line: 1316)
Drupal\views\Plugin\views\query\Sql->query() (Line: 1454)
Drupal\views\Plugin\views\query\Sql->build(Object) (Line: 1326)
Drupal\views\ViewExecutable->build() (Line: 392)
Drupal\views\Plugin\views\display\PathPluginBase->execute() (Line: 196)
Drupal\views\Plugin\views\display\Page->execute() (Line: 1635)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('views_substitutions', Array) (Line: 1418)
Drupal\views\Plugin\views\query\Sql->query() (Line: 1454)
Drupal\views\Plugin\views\query\Sql->build(Object) (Line: 1326)
Drupal\views\ViewExecutable->build() (Line: 392)
Drupal\views\Plugin\views\display\PathPluginBase->execute() (Line: 196)
Drupal\views\Plugin\views\display\Page->execute() (Line: 1635)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('views') (Line: 1316)
Drupal\views\Plugin\views\query\Sql->query(1) (Line: 1455)
Drupal\views\Plugin\views\query\Sql->build(Object) (Line: 1326)
Drupal\views\ViewExecutable->build() (Line: 392)
Drupal\views\Plugin\views\display\PathPluginBase->execute() (Line: 196)
Drupal\views\Plugin\views\display\Page->execute() (Line: 1635)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('views_substitutions', Array) (Line: 1418)
Drupal\views\Plugin\views\query\Sql->query(1) (Line: 1455)
Drupal\views\Plugin\views\query\Sql->build(Object) (Line: 1326)
Drupal\views\ViewExecutable->build() (Line: 392)
Drupal\views\Plugin\views\display\PathPluginBase->execute() (Line: 196)
Drupal\views\Plugin\views\display\Page->execute() (Line: 1635)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\views\ManyToOneHelper->__construct(Object) (Line: 54)
Drupal\views\Plugin\views\argument\ManyToOne->defineOptions() (Line: 141)
Drupal\views\Plugin\views\PluginBase->init(Object, Object, Array) (Line: 104)
Drupal\views\Plugin\views\HandlerBase->init(Object, Object, Array) (Line: 82)
Drupal\views\Plugin\views\argument\ArgumentPluginBase->init(Object, Object, Array) (Line: 33)
Drupal\views\Plugin\views\argument\ManyToOne->init(Object, Object, Array) (Line: 894)
Drupal\views\Plugin\views\display\DisplayPluginBase->getHandlers('argument') (Line: 1045)
Drupal\views\ViewExecutable->_initHandler('argument', Array) (Line: 903)
Drupal\views\ViewExecutable->initHandlers() (Line: 1876)
Drupal\views\ViewExecutable->buildTitle() (Line: 338)
Drupal\views\Plugin\views\display\Feed->attachTo(Object, 'page_1', Array) (Line: 1733)
Drupal\views\ViewExecutable->attachDisplays() (Line: 1333)
Drupal\views\ViewExecutable->build() (Line: 392)
Drupal\views\Plugin\views\display\PathPluginBase->execute() (Line: 196)
Drupal\views\Plugin\views\display\Page->execute() (Line: 1635)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\views\ManyToOneHelper->__construct(Object) (Line: 54)
Drupal\views\Plugin\views\argument\ManyToOne->defineOptions() (Line: 228)
Drupal\views\Plugin\views\PluginBase->unpackOptions(Array, Array) (Line: 144)
Drupal\views\Plugin\views\PluginBase->init(Object, Object, Array) (Line: 104)
Drupal\views\Plugin\views\HandlerBase->init(Object, Object, Array) (Line: 82)
Drupal\views\Plugin\views\argument\ArgumentPluginBase->init(Object, Object, Array) (Line: 33)
Drupal\views\Plugin\views\argument\ManyToOne->init(Object, Object, Array) (Line: 894)
Drupal\views\Plugin\views\display\DisplayPluginBase->getHandlers('argument') (Line: 1045)
Drupal\views\ViewExecutable->_initHandler('argument', Array) (Line: 903)
Drupal\views\ViewExecutable->initHandlers() (Line: 1876)
Drupal\views\ViewExecutable->buildTitle() (Line: 338)
Drupal\views\Plugin\views\display\Feed->attachTo(Object, 'page_1', Array) (Line: 1733)
Drupal\views\ViewExecutable->attachDisplays() (Line: 1333)
Drupal\views\ViewExecutable->build() (Line: 392)
Drupal\views\Plugin\views\display\PathPluginBase->execute() (Line: 196)
Drupal\views\Plugin\views\display\Page->execute() (Line: 1635)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\views\ManyToOneHelper->__construct(Object) (Line: 54)
Drupal\views\Plugin\views\argument\ManyToOne->defineOptions() (Line: 228)
Drupal\views\Plugin\views\PluginBase->unpackOptions(Array, Array) (Line: 110)
Drupal\views\Plugin\views\HandlerBase->init(Object, Object, Array) (Line: 82)
Drupal\views\Plugin\views\argument\ArgumentPluginBase->init(Object, Object, Array) (Line: 33)
Drupal\views\Plugin\views\argument\ManyToOne->init(Object, Object, Array) (Line: 894)
Drupal\views\Plugin\views\display\DisplayPluginBase->getHandlers('argument') (Line: 1045)
Drupal\views\ViewExecutable->_initHandler('argument', Array) (Line: 903)
Drupal\views\ViewExecutable->initHandlers() (Line: 1876)
Drupal\views\ViewExecutable->buildTitle() (Line: 338)
Drupal\views\Plugin\views\display\Feed->attachTo(Object, 'page_1', Array) (Line: 1733)
Drupal\views\ViewExecutable->attachDisplays() (Line: 1333)
Drupal\views\ViewExecutable->build() (Line: 392)
Drupal\views\Plugin\views\display\PathPluginBase->execute() (Line: 196)
Drupal\views\Plugin\views\display\Page->execute() (Line: 1635)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\views\ManyToOneHelper->__construct(Object) (Line: 35)
Drupal\views\Plugin\views\argument\ManyToOne->init(Object, Object, Array) (Line: 894)
Drupal\views\Plugin\views\display\DisplayPluginBase->getHandlers('argument') (Line: 1045)
Drupal\views\ViewExecutable->_initHandler('argument', Array) (Line: 903)
Drupal\views\ViewExecutable->initHandlers() (Line: 1876)
Drupal\views\ViewExecutable->buildTitle() (Line: 338)
Drupal\views\Plugin\views\display\Feed->attachTo(Object, 'page_1', Array) (Line: 1733)
Drupal\views\ViewExecutable->attachDisplays() (Line: 1333)
Drupal\views\ViewExecutable->build() (Line: 392)
Drupal\views\Plugin\views\display\PathPluginBase->execute() (Line: 196)
Drupal\views\Plugin\views\display\Page->execute() (Line: 1635)
Drupal\views\ViewExecutable->executeDisplay('page_1', Array) (Line: 81)
Drupal\views\Element\View::preRenderViewElement(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-26f2094f-6fc5-4d8f-9cc2-da5576c21073') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-26f2094f-6fc5-4d8f-9cc2-da5576c21073') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-26f2094f-6fc5-4d8f-9cc2-da5576c21073') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-26f2094f-6fc5-4d8f-9cc2-da5576c21073') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-5b76a210-02d1-4484-a8b9-c3a70422d70c') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-5b76a210-02d1-4484-a8b9-c3a70422d70c') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-5b76a210-02d1-4484-a8b9-c3a70422d70c') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-5b76a210-02d1-4484-a8b9-c3a70422d70c') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-08e7a98a-1bb0-4e89-bba5-5402a6b3a4c0') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-08e7a98a-1bb0-4e89-bba5-5402a6b3a4c0') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-08e7a98a-1bb0-4e89-bba5-5402a6b3a4c0') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-08e7a98a-1bb0-4e89-bba5-5402a6b3a4c0') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-3cb7ced5-598b-408d-9d7d-d253ab959bba') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-3cb7ced5-598b-408d-9d7d-d253ab959bba') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-3cb7ced5-598b-408d-9d7d-d253ab959bba') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-3cb7ced5-598b-408d-9d7d-d253ab959bba') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de la siguiente tabla de datos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0110.jpg"></a>
Obsérvese que en la tabla las fechas se muestran ordenadas (de más antiguas a más modernas) para hacer más sencilla su interpretación, pero tengamos en mente que podría no ser así.
El objetivo es crear una columna calculada que nos indique, para cada fila, cuántas filas de la misma tabla contienen fechas anteriores a la de la fila siendo considerada (teniendo en cuenta, como se ha comentado, que las filas podrían estar no ordenadas según la fecha).
Como queremos añadir una columna calculada, se va a crear por defecto un contexto de fila para cada una de las filas evaluadas. Es decir, para la primera fila (la correspondiente al campo <em>Id</em> 1) se creará un contexto de fila en el que podremos acceder a los valores de sus campos, <em>Id</em> y <em>Date</em>. A partir del valor del campo <em>Date</em> querríamos filtrar la tabla de forma que solo se considerasen las filas con fechas anteriores y contar el número de filas resultantes.
En pseudo código sería algo así:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < fecha-de-la-fila-actual
)
)
Se ha utilizado la etiqueta <em>fecha-de-la-fila-actual</em> para hacer referencia al valor del campo <em>Date</em> de la fila siendo considerada.
La función <a href="/es/dax/function/filter">FILTER</a>, siendo un iterador, va a crear un nuevo contexto de fila en el que evaluar la condición para cada fila de la tabla <em>data</em>:
data[Date] < fecha-de-la-fila-actual
Lógicamente, el siguiente código no funcionaría:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < data[Date])
)
)
...pues dentro del contexto de fila creado por <a href="/es/dax/function/filter">FILTER</a>, el valor de <em>data[Date]</em> es el valor del campo <em>Date</em> de la fila que <a href="/es/dax/function/filter">FILTER</a> esté iterando. Lo que querríamos es que el valor de <em>fecha-de-la-fila-actual</em> fuese el definido en el contexto de fila "anterior", el creado por la columna calculada previo a la ejecución de la función <a href="/es/dax/function/filter">FILTER</a>.
Para ello tenemos dos soluciones:
La primera -la única existente hasta que se introdujeron las variables en DAX en 2015- es recurrir a la función <a href="/es/dax/function/earlier">EARLIER</a>. Esta función nos devuelve exactamente lo que queremos: el valor de un campo para un contexto de fila anterior (pudiendo indicarse cuántos contextos de fila hay que retroceder). Por ejemplo:
Number of Previous values =
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
)
...devolvería el resultado que buscábamos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0111.jpg"></a>
Vemos que, para la primera fila, la función devuelve un <em>Blank</em> pues no hay filas con fechas anteriores y la función <a href="/es/dax/function/countrows">COUNTROWS</a> devuelve este valor para tablas vacías. Podemos forzar que, en este caso, se devuelva un 0 usando la función <a href="/es/dax/function/coalesce">COALESCE</a>:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIER(data[Date], 1)
)
),
0
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0112.jpg"></a>
En este caso, como el contexto "anterior" es el único contexto previo, también podríamos usar la función <a href="/es/dax/function/earliest">EARLIEST</a> que nos devuelve el valor del campo indicado en el último contexto de fila existente:
Number of Previous values =
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < EARLIEST(data[Date])
)
),
0
)
El segundo método es, precisamente, haciendo uso de variables, lo que resulta mucho más sencillo y fácilmente interpretable. Concretamente:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
)
En primer lugar creamos una variable (<em>__currentDate</em>) con la palabra reservada <a href="/es/dax/function/var">VAR</a>. Esta variable se va a crear en el contexto de evaluación existente que, en ese punto del código, es el contexto de fila creado por la columna calculada. Es decir, en este punto, el campo <em>data[Date]</em> toma el valor del campo <em>Date</em> en la fila que se esté evaluando.
Una vez registrado el valor de la fecha se ejecuta el filtrado de la tabla <em>data</em>. Tal y como se ha comentado, la función <a href="/es/dax/function/filter">FILTER</a> crea un nuevo contexto de fila en el que se recorre la tabla en cuestión comparando cada valor del campo <em>Date</em> -evaluado en el contexto de fila de la función <a href="/es/dax/function/filter">FILTER</a>- con el valor registrado en la variable <em>__currentDate</em> (valor que se registró en el contexto de fila de la columna calculada):
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2021-06/dax-0113.jpg"></a>
Comprobamos que el resultado es el mismo que obtuvimos con la función <a href="/es/dax/function/earlier">EARLIER</a> pero más fácil de interpretar.
Una vez más, podríamos usar la función <a href="/es/dax/function/coalesce">COALESCE</a> para forzar ceros allí donde <a href="/es/dax/function/countrows">COUNTROWS</a> devuelva <em>Blanks</em>:
Number of Previous values =
VAR __currentDate = data[Date]
RETURN
COALESCE(
COUNTROWS(
FILTER(
data,
data[Date] < __currentDate
)
),
0
)
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-350e337f-140f-4fa2-bda4-0f03477cb3c2') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-350e337f-140f-4fa2-bda4-0f03477cb3c2') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-350e337f-140f-4fa2-bda4-0f03477cb3c2') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-350e337f-140f-4fa2-bda4-0f03477cb3c2') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-03329b88-76e4-4c9b-8765-af78475b80e1') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-03329b88-76e4-4c9b-8765-af78475b80e1') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-03329b88-76e4-4c9b-8765-af78475b80e1') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-03329b88-76e4-4c9b-8765-af78475b80e1') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-e53eaa7f-c9ca-4db7-8f55-7fbf3b792f47') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-e53eaa7f-c9ca-4db7-8f55-7fbf3b792f47') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-e53eaa7f-c9ca-4db7-8f55-7fbf3b792f47') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-e53eaa7f-c9ca-4db7-8f55-7fbf3b792f47') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-5c3969df-7095-40d4-b46e-af97df4d2797') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-5c3969df-7095-40d4-b46e-af97df4d2797') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-5c3969df-7095-40d4-b46e-af97df4d2797') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-5c3969df-7095-40d4-b46e-af97df4d2797') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-0c5b04ec-58bd-46b2-a673-efde628eabbb') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-0c5b04ec-58bd-46b2-a673-efde628eabbb') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-0c5b04ec-58bd-46b2-a673-efde628eabbb') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-0c5b04ec-58bd-46b2-a673-efde628eabbb') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-b68efa25-a5d8-4ce5-8aa3-96568aef9119') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-b68efa25-a5d8-4ce5-8aa3-96568aef9119') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-b68efa25-a5d8-4ce5-8aa3-96568aef9119') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-b68efa25-a5d8-4ce5-8aa3-96568aef9119') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-a1dd98da-72fb-45f2-a860-f2a054bc67d1') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-a1dd98da-72fb-45f2-a860-f2a054bc67d1') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-a1dd98da-72fb-45f2-a860-f2a054bc67d1') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', 'insert-max_800_px-a1dd98da-72fb-45f2-a860-f2a054bc67d1') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('En este escenario partimos de un listado de clientes:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0024.jpg"></a>
...y de una tabla de compras de cada uno de ellos:
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0025.jpg"></a>
El objetivo es obtener un listado de clientes en el que se muestre el número de días (por ejemplo, podría ser otro período) entre compras. Para hacer el escenario sencillo los clientes reciben como nombre "Cliente x", siendo x el número de compras realizadas (de esta forma, el <em>Cliente 0</em> no ha realizado ninguna, el <em>Cliente 1</em> ha realizado una, etc.), y la frecuencia de compra en días para cada cliente coincide también con x. Así, el cliente 2 realizó una primera compra el 3 de enero, y una segunda dos días más tarde, el 5 de enero.
Una vez hemos asegurado que el modelo de datos incluye las relaciones adecuadas (incluyendo una relación entre la tabla de ventas y el necesario calendario), podemos crear una tabla calculada que, usando la función <a href="/dax/function/selectcolumns">SELECTCOLUMNS</a>, extraiga el listado de clientes y agregue la información necesaria. Partimos, por lo tanto, de la siguiente expresión DAX que define esta primera versión de nuestra tabla de resultados:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre]
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0026.jpg"></a>
Aunque podríamos calcular directamente la frecuencia de compra para cada uno de ellos, hagámoslo paso por paso, incluyendo una columna adicional para cada uno de los datos que necesitamos.
Si entendemos por frecuencia de compra el número de días transcurridos entre la primera compra y la última dividido entre el número de compras realizadas, en primer lugar sería preciso calcular las fechas de primera y de última compra por cliente, para lo que vamos a recurrir a las funciones <a href="/dax/function/firstdate">FIRSTDATE</a> y <a href="/dax/function/lastdate">LASTDATE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha])
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0027.jpg"></a>
El cálculo del rango de días entre ambas fechas se reduce a una sencilla resta, que realizaremos usando la función <a href="/dax/function/datediff">DATEDIFF</a>, función que nos permite especificar el período a considerar -días, en nuestro ejemplo-:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0028.jpg"></a>
Debemos entender que estamos realizando un enfoque un tanto ingenuo del problema al presuponer que el cliente existe como tal desde su primera compra y solo hasta la última. Más realista sería considerar como comienzo del período el día en el que el cliente "se dio de alta como tal", hubiese realizado o no una compra, y/o considerar como fin del período el día actual, por ejemplo.
Considérese también que, al crearse la tabla calculada en un entorno de contexto de fila, para cada cliente se extrae la información de la tabla remota relacionada con él. Así, cuando se está calculando la primera fecha de compra para el cliente X, la expresión
FIRSTDATE(Calendario[Fecha])
...filtra el campo <em>Fecha</em> de la tabla <em>Calendario</em> de forma que solo incluya las fechas para las que existen compras asociadas al cliente que corresponda.
El siguiente paso es contar el número de compras por cliente, para lo que podemos extraer la tabla <em>Ventas</em> (una vez filtrada por el contexto, para lo que usaremos la función <a href="/dax/function/relatedtable">RELATEDTABLE</a>) y contar el número de filas que tiene con <a href="/dax/function/countrows">COUNTROWS</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas))
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0029.jpg"></a>
Por último queda lo más sencillo, que es dividir el rango de fechas entre el número de compras, para lo que recurriremos a la función <a href="/dax/function/divide">DIVIDE</a>:
Frecuencia de compra =
SELECTCOLUMNS(
Clientes;
"Nombre"; Clientes[Nombre];
"Primera compra"; FIRSTDATE(Calendario[Fecha]);
"Última compra"; LASTDATE(Calendario[Fecha]);
"Rango"; DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
"Nº de compras"; COUNTROWS(RELATEDTABLE(Ventas));
"Frecuencia de compra"; DIVIDE(
DATEDIFF(FIRSTDATE(Calendario[Fecha]); LASTDATE(Calendario[Fecha]); DAY);
COUNTROWS(RELATEDTABLE(Ventas))
)
)
<a class="colorbox insert-colorbox" data-colorbox-gallery="gallery-node" data-insert-class="" data-insert-type="image" href="/sites/default/files/2019-10/dax-0030.jpg"></a>
Comprobamos que, en esta primera versión de la expresión, si un cliente no ha realizado ninguna compra se muestra un BLANK, y si ha realizado una única compra de devuelve una frecuencia de 1. Podríamos completar nuestro DIVIDE con alguna función lógica que realizase un cálculo u otro en función del número de compras y, de esta forma, gestionar estas excepciones.
Puede descargarse el fichero Excel y el informe Power BI resultante <a href="https://www.interactivechaos.com/sites/default/files/data/calculo_de_la_frecuencia_de_compra_de_los_clientes.zip">aquí</a>.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '287975a2-fe80-44b5-9dcf-c0688d1cb295') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '287975a2-fe80-44b5-9dcf-c0688d1cb295') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '287975a2-fe80-44b5-9dcf-c0688d1cb295') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '287975a2-fe80-44b5-9dcf-c0688d1cb295') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '35ac43d6-5558-435a-92dd-57d4cdf90d7e') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '35ac43d6-5558-435a-92dd-57d4cdf90d7e') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '35ac43d6-5558-435a-92dd-57d4cdf90d7e') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '35ac43d6-5558-435a-92dd-57d4cdf90d7e') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '61008365-5edc-4674-815b-f93cad57427c') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '61008365-5edc-4674-815b-f93cad57427c') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '61008365-5edc-4674-815b-f93cad57427c') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '61008365-5edc-4674-815b-f93cad57427c') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '0709eb31-d6ce-4f96-928d-b2406e1dd240') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '0709eb31-d6ce-4f96-928d-b2406e1dd240') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '0709eb31-d6ce-4f96-928d-b2406e1dd240') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '0709eb31-d6ce-4f96-928d-b2406e1dd240') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '751866e4-492e-4d27-b171-65584857be69') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '751866e4-492e-4d27-b171-65584857be69') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '751866e4-492e-4d27-b171-65584857be69') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '751866e4-492e-4d27-b171-65584857be69') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '72e2356c-64a3-4d8e-abdc-b9411aeca29c') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '72e2356c-64a3-4d8e-abdc-b9411aeca29c') (Line: 95)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addMetaData('entity_type', 'file') (Line: 115)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '72e2356c-64a3-4d8e-abdc-b9411aeca29c') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\Core\Database\Query\Select->addTag('entity_query') (Line: 147)
Drupal\Core\Entity\Query\Sql\Query->prepare() (Line: 80)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 640)
Drupal\Core\Entity\EntityStorageBase->loadByProperties(Array) (Line: 63)
Drupal\Core\Entity\EntityRepository->loadEntityByUuid('file', '72e2356c-64a3-4d8e-abdc-b9411aeca29c') (Line: 124)
Drupal\editor\Plugin\Filter\EditorFileReference->process('La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo. Para ello vamos a utilizar la función <a href="/dax/function/weekday">WEEKDAY</a> que acepta como argumento una fecha y devuelve un número identificando el día de la semana correspondiente.
Partimos de una tabla "<em>Calendar</em>" que incluye únicamente una columna con las fechas:
Creamos una nueva columna calculada en la que extraemos el número de día de la semana:
Nº de día de la semana =
WEEKDAY('Calendar'[Date];2)
En este caso se ha optado por la opción 2 (de las tres que se ofrecen), opción que asigna el valor 1 al lunes y el 7 al domingo:
Podemos comparar los nombres de los días de la semana que se muestran en la función <em>Date</em> con el valor mostrado en la columna recién creada para confirmar que el criterio es el comentado. Así, por ejemplo, la primera fecha mostrada en la del jueves 1 de enero de 2015, día al que se asigna correctamente el valor 4 en la recién creada columna calculada.
En realidad no hubiera sido necesario crear esta columna, apenas nos sirve para confirmar los valores que reciben cada uno de los días. Lo que sí es necesario en la creación de una columna -a la que podemos llamar <em>Día laboral</em>- que, por ejemplo, contenga el booleano TRUE cuando el día sea laboral, y el booleano FALSE cuando no lo sea. Y para esta asignación vamos a comparar el valor numérico asignado por la función WEEKDAY a cada uno de los días: cuando tome los valores 1, 2, 3, 4 o 5, se asignará el valor TRUE, y cuando tome los valores 6 o 7, FALSE. Lógicamente estos valores son los correctos solo si estamos usando la función WEEKDAY con la mencionada opción 2. Si escogiésemos otra opción, habría que escoger las cifras adecuadamente:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6;
TRUE();
FALSE()
)
En un segundo paso, si quisiéramos añadir como días no laborables otras festividades, podríamos recurrir a mantener otra tabla con este listado. Por ejemplo, supongamos que las fechas no laborables adicionales son las mostradas en la siguiente tabla ("<em>Festivos</em>"):
Relacionamos ambas tablas:
Y, a continuación, modificamos el código que hemos creado de forma que asigne el valor TRUE si y solo si el número de día es menor que 6 y no aparece en la tabla de festivos. Esta última condición puede ser escrita de diversas maneras. Por ejemplo, contando el número de filas de la tabla <em>Festivos</em> asociada a cada fecha del calendario: cuando la fecha en cuestión no esté incluida en <em>Festivos</em>, esta tabla no contendrá ningún registro. Y cuando esté, tendrá uno:
Día laboral =
IF(
WEEKDAY('Calendar'[Date]; 2) < 6 && COUNTROWS(RELATEDTABLE(Festivos)) = 0;
TRUE();
FALSE()
)
El resultado es el mostrado en la siguiente imagen:
Podemos ver que los días 6 y 13 de enero han sido marcados como "no laborables", tal y como esperábamos.
Por supuesto, este escenario podría hacerse más complejo para mostrar diferentes columnas para diferentes localidades o países, etc.
', 'es') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 88)
__TwigTemplate_a7d6005c89ae729617b9a0c2bccb1776->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 46)
__TwigTemplate_804f7948456cfe20e11a34c43439c7c2->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array, Array) (Line: 43)
__TwigTemplate_bd990293b89f3b78c69fe0ee2f7828b5->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/field/field--text-with-summary.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_94047fbdba6937b76a4479dfa1763452->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 74)
__TwigTemplate_43dffa6ad507293d1ceeb24e05ce942c->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/custom/yg_aesthetic/templates/views-view-unformatted.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view_unformatted', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array) (Line: 479)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 110)
__TwigTemplate_349d2f5aada73507d566397721f27ea4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 55)
twig_render_template('themes/contrib/classy/templates/views/views-view.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('views_view', Array) (Line: 433)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 235)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
include('/var/www/vhosts/interactivechaos.ovh/httpdocs/modules/contrib/google_analytics/src/Component/Render/GoogleAnalyticsJavaScriptSnippet.php') (Line: 578)
Composer\Autoload\ClassLoader::Composer\Autoload\{closure}('/var/www/vhosts/interactivechaos.ovh/httpdocs/modules/contrib/google_analytics/src/Component/Render/GoogleAnalyticsJavaScriptSnippet.php') (Line: 432)
Composer\Autoload\ClassLoader->loadClass('Drupal\google_analytics\Component\Render\GoogleAnalyticsJavaScriptSnippet') (Line: 372)
google_analytics_page_attachments(Array) (Line: 313)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}(Object, 'google_analytics') (Line: 405)
Drupal\Core\Extension\ModuleHandler->invokeAllWith('page_attachments', Object) (Line: 310)
Drupal\Core\Render\MainContent\HtmlRenderer->invokePageAttachmentHooks(Array) (Line: 288)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Drupal\user\Entity\Role::postLoad(Object, Array) (Line: 423)
Drupal\Core\Entity\EntityStorageBase->postLoad(Array) (Line: 353)
Drupal\Core\Entity\EntityStorageBase->loadMultiple() (Line: 126)
eu_cookie_compliance_page_attachments(Array) (Line: 313)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}(Object, 'eu_cookie_compliance') (Line: 405)
Drupal\Core\Extension\ModuleHandler->invokeAllWith('page_attachments', Object) (Line: 310)
Drupal\Core\Render\MainContent\HtmlRenderer->invokePageAttachmentHooks(Array) (Line: 288)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 713)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
En este escenario partimos de la siguiente tabla de datos:
En este escenario partimos de un listado de clientes:
La adición de esta información a nuestra tabla de calendario -si cada uno de los días es o no laborable- puede resultar de gran utilidad de muchos casos. Planteémonos, en primer lugar, el caso más sencillo: el marcado de cada uno de los días en función del día de la semana de que se trate. Es decir, marquemos como laborables los días entre el lunes y el viernes, ambos incluidos, y como no laborables el sábado y el domingo.
Se describen a continuación varios ejemplos en los que se utilizan variables dentro de expresiones DAX:
Sales txt =
VAR TotalSales = SUM(Sales[SalesAmount])
VAR Text1 = "Este mes hemos vendido "
VAR Text2 = "Este mes no hemos vendido nada"
RETURN
IF(
TotalSales > 0; -- IF
CONCATENATE(Text1; TotalSales); -- THEN
Text2 -- ELSE
)
Excepto donde se indique otra cosa, los contenidos de este sitio web se ofrecen bajo una licencia Reconocimiento-NoComercial-SinObraDerivada 4.0 Internacional