Analisando as estatísticas do site, obtemos uma visão do que está acontecendo com ele. Comparamos os resultados com outros conhecimentos sobre o produto ou serviço e assim melhoramos nossa experiência.
Quando a análise dos primeiros resultados é concluída, refletimos sobre as informações e tiramos conclusões, iniciando a próxima etapa. Surge a pergunta: o que aconteceria se olhássemos os dados de outra perspectiva?
Nesta etapa, há limitações nas ferramentas de análise. Esta é uma das razões pelas quais a ferramenta Google Analytics não era suficiente para mim, devido à sua capacidade limitada de visualizar e manipular os dados.
Sempre quis carregar rapidamente os dados básicos (dados mestres), adicionar um novo nível de agregação ou interpretar os valores existentes de outra forma.
É fácil fazer isso em com base no arquivo access.log e para isso, basta usar a linguagem SQL.
Então, a quais perguntas eu gostaria de encontrar respostas?
O que e quando mudou no site
A história das mudanças nos dados básicos (dados mestres) sempre é interessante.

SQL consulta de relatório
SELECT
1 as 'SideStackedBar: Atualizações de Conteúdo por Mês',
strftime('%m/%Y', datetime(UPDATE_DT, 'unixepoch')) AS 'Dia',
COUNT(CASE WHEN PAGE_TITLE != 'n.a.' THEN DIM_REQUEST_ID END) AS 'Atualizações de páginas da web',
COUNT(CASE WHEN PAGE_DESCR = 'IMAGES' THEN DIM_REQUEST_ID END) AS 'Uploads de imagens',
COUNT(CASE WHEN PAGE_DESCR = 'VIDEO' THEN DIM_REQUEST_ID END) AS 'Uploads de vídeo',
COUNT(CASE WHEN PAGE_DESCR = 'AUDIO' THEN DIM_REQUEST_ID END) AS 'Uploads de áudio'
FROM DIM_REQUEST
WHERE PAGE_TITLE != 'n.a.' OR PAGE_DESCR != 'n.a.'
GROUP BY strftime('%m/%Y', datetime(UPDATE_DT, 'unixepoch'))
ORDER BY UPDATE_DTPor exemplo, em algum momento, foi feita uma otimização para motores de busca ou novo conteúdo foi adicionado ao site, e por isso espera-se um aumento no tráfego.
Grupos de usuários
O exemplo mais simples de um grupo pode ser o agente do usuário ou o nome do sistema operacional.
A medição dos agentes de usuário acumulou cerca de mil registros e eu estava interessado em ver a dinâmica da distribuição dos agentes dentro do grupo.

SQL consulta de relatório
SELECT
1 AS 'SideStackedBar: Agentes de Usuário',
AGENT_OS AS 'SO',
SUM(CASE WHEN AGENT_BOT = 'n.a.' THEN 1 ELSE 0 END ) AS 'Agente de Usuário de Usuários',
SUM(CASE WHEN AGENT_BOT != 'n.a.' THEN 1 ELSE 0 END ) AS 'Agente de Usuário de Bots'
FROM DIM_USER_AGENT
WHERE DIM_USER_AGENT_ID != -1
GROUP BY AGENT_OS
ORDER BY 3 DESCA maioria das combinações diferentes de agentes que acessam o site vêm do mundo Windows. Entre os agentes indefinidos estão, por exemplo, WhatsApp, PocketImageCache, PlayStation, SmartTV, etc.
Atividade dos grupos de usuários por semanas
Combinando alguns grupos, podemos observar a distribuição de sua atividade.
Por exemplo, os usuários do cluster Linux consomem mais tráfego no site do que todos os outros.

SQL consulta de relatório
SELECT
1 AS 'StackedBar: Volume de Tráfego por Sistema Operacional do Usuário e por Semana',
strftime('%W semana', datetime(FCT.EVENT_DT, 'unixepoch')) AS 'Semana',
SUM(CASE WHEN USG.AGENT_OS IN ('Android', 'Linux') THEN FCT.BYTES ELSE 0 END) / 1000 AS 'Usuários Android/Linux',
SUM(CASE WHEN USG.AGENT_OS IN ('Windows') THEN FCT.BYTES ELSE 0 END) / 1000 AS 'Usuários Windows',
SUM(CASE WHEN USG.AGENT_OS IN ('Macintosh', 'iOS') THEN FCT.BYTES ELSE 0 END) / 1000 AS 'Usuários Mac/iOS',
SUM(CASE WHEN USG.AGENT_OS IN ('n.a.', 'BlackBerry') THEN FCT.BYTES ELSE 0 END) / 1000 AS 'Outro'
FROM
FCT_ACCESS_USER_AGENT_DD FCT,
DIM_USER_AGENT USG,
DIM_HTTP_STATUS HST
WHERE FCT.DIM_USER_AGENT_ID = USG.DIM_USER_AGENT_ID
AND FCT.DIM_HTTP_STATUS_ID = HST.DIM_HTTP_STATUS_ID
AND USG.AGENT_BOT = 'n.a.' /* apenas usuários */
AND HST.STATUS_GROUP IN ('Bem-sucedido') /* boas páginas */
AND datetime(FCT.EVENT_DT, 'unixepoch') > date('now', '-3 meses')
GROUP BY strftime('%W semana', datetime(FCT.EVENT_DT, 'unixepoch'))
ORDER BY FCT.EVENT_DTConsumo intenso de tráfego
A tabela mostra os grupos de usuários mais ativos e os dias de sua atividade.
Os mais ativos pertencem ao cluster Linux.

SQL consulta de relatório
SELECT
1 AS 'Tabela: Agente de Usuário com Alto Uso',
strftime('%d.%m.%Y', datetime(FCT.EVENT_DT, 'unixepoch')) AS 'Dia',
ROUND(1.0 * SUM(FCT.BYTES) / 1000000, 1) AS 'Tráfego MB',
ROUND(1.0 * SUM(FCT.IP_CNT) / SUM(1), 1) AS 'IPs',
ROUND(1.0 * SUM(FCT.REQUEST_CNT) / SUM(1), 1) AS 'Solicitações',
USA.DIM_USER_AGENT_ID AS 'ID',
MAX(USA.USER_AGENT_NK) AS 'Agente de Usuário',
MAX(USA.AGENT_BOT) AS 'Bot'
FROM
FCT_ACCESS_USER_AGENT_DD FCT,
DIM_USER_AGENT USA
WHERE FCT.DIM_USER_AGENT_ID = USA.DIM_USER_AGENT_ID
AND datetime(FCT.EVENT_DT, 'unixepoch') >= date('now', '-30 dias')
GROUP BY USA.DIM_USER_AGENT_ID, strftime('%d.%m.%Y', datetime(FCT.EVENT_DT, 'unixepoch'))
ORDER BY SUM(FCT.BYTES) DESC, FCT.EVENT_DT
LIMIT 10Usando os atributos dia e ID do agente, é possível encontrar e rastrear rapidamente as estatísticas por dias de grupos específicos de usuários. Se necessário, pode-se rapidamente encontrar informações detalhadas na tabela de estágio.
Como obter informações?
pode ser feita ainda mais eficaz se forem integradas fontes de dados adicionais, introduzidos novos níveis de agregação e agrupamento.
Dados e entidades básicas
Os dados básicos incluem informações sobre entidades: páginas da web, imagens, conteúdos de vídeo e áudio, e no caso de lojas — produtos.
As próprias entidades funcionam como dimensões, e o processo de salvar as alterações dos atributos é chamado de historização. Na base de dados, esse processo é frequentemente implementado na forma de dimensões lentamente mutáveis (SCD).
A fonte de dados básicas pode ser a mais variada, portanto, quase sempre elas precisam ser integradas.
Dimensão lentamente mutável
A dimensão DIM_REQUEST conterá informações sobre solicitações no site em forma histórica.
Tabela SCD2
CRIE UMA TABELA DIM_REQUEST (
DIM_REQUEST_ID INTEIRO NÃO NULO CHAVE PRIMÁRIA AUTOINCREMENT,
DIM_REQUEST_ID_HIST INTEIRO NÃO NULO DEFAULT -1,
REQUEST_NK TEXTO NÃO NULO DEFAULT 'n.a.',
PAGE_TITLE TEXTO NÃO NULO DEFAULT 'n.a.',
PAGE_DESCR TEXTO NÃO NULO DEFAULT 'n.a.',
PAGE_KEYWORDS TEXTO NÃO NULO DEFAULT 'n.a.',
DELETE_FLAG INTEIRO NÃO NULO DEFAULT 0,
UPDATE_DT INTEIRO NÃO NULO DEFAULT 0,
ÚNICO (REQUEST_NK, DIM_REQUEST_ID_HIST)
);
INSERIR NA DIM_REQUEST (DIM_REQUEST_ID) VALORES (-1);Além disso, criaremos uma visão que sempre exibe todos os registros em seu estado mais recente. Isso é necessário para carregar a própria medida.

Visão atual SCD2
/* Content: actual view on scd table */
SELECT HI.DIM_REQUEST_ID,
HI.DIM_REQUEST_ID_HIST,
HI.REQUEST_NK,
HI.PAGE_TITLE,
HI.PAGE_DESCR,
HI.PAGE_KEYWORDS,
NK.CNT AS HIST_CNT,
HI.DELETE_FLAG,
strftime('%d.%m.%Y %H:%M', datetime(HI.UPDATE_DT, 'unixepoch')) AS UPDATE_DT
FROM
( SELECT REQUEST_NK, MAX(DIM_REQUEST_ID) AS DIM_REQUEST_ID, SUM(1) AS CNT
FROM DIM_REQUEST
GROUP BY REQUEST_NK
) NK,
DIM_REQUEST HI
WHERE 1 = 1
AND NK.REQUEST_NK = HI.REQUEST_NK
AND NK.DIM_REQUEST_ID = HI.DIM_REQUEST_ID;E a visão, onde para cada registro, foi coletada informação histórica. Isso é necessário para construir uma relação historicamente correta com os fatos.

Visão histórica SCD2
/* Content: actual view on scd table */
SELECT SCD.DIM_REQUEST_ID,
SCD.DIM_REQUEST_ID_HIST,
SCD.REQUEST_NK,
SCD.PAGE_TITLE,
SCD.PAGE_DESCR,
SCD.PAGE_KEYWORDS,
SCD.DELETE_FLAG,
CASE
WHEN HIS.UPDATE_DT IS NULL
THEN 1
ELSE 0 END ACTIVE_FLAG,
SCD.DIM_REQUEST_ID_HIST AS ID_FROM,
SCD.DIM_REQUEST_ID AS ID_TO,
CASE
WHEN SCD.DIM_REQUEST_ID_HIST=-1
THEN 3600
ELSE IFNULL(SCD.UPDATE_DT,3600)
END AS TIME_FROM,
CASE
WHEN HIS.UPDATE_DT IS NULL
THEN 253370764800
ELSE HIS.UPDATE_DT
END AS TIME_TO,
CASE
WHEN SCD.DIM_REQUEST_ID_HIST=-1
THEN STRFTIME('%d.%m.%Y %H:%M', DATETIME(3600, 'unixepoch'))
ELSE STRFTIME('%d.%m.%Y %H:%M', DATETIME(IFNULL(SCD.UPDATE_DT,3600), 'unixepoch'))
END AS ACTIVE_FROM,
CASE
WHEN HIS.UPDATE_DT IS NULL
THEN STRFTIME('%d.%m.%Y %H:%M', DATETIME(253370764800, 'unixepoch'))
ELSE STRFTIME('%d.%m.%Y %H:%M', DATETIME(HIS.UPDATE_DT, 'unixepoch'))
END AS ACTIVE_TO
FROM
DIM_REQUEST SCD
LEFT OUTER JOIN DIM_REQUEST HIS
ON SCD.REQUEST_NK = HIS.REQUEST_NK AND SCD.DIM_REQUEST_ID = HIS.DIM_REQUEST_ID_HIST;Agregação de dados
A compressão (agregação) permite avaliar os dados em um nível mais alto e identificar anomalias e tendências que não são visíveis em relatórios detalhados.
Por exemplo, na medida com códigos de status de solicitações DIM_HTTP_STATUS adicionaremos um grupo:
STATUS / GRUPO
0xx / n.a.
1xx / Informativo
2xx / Bem-sucedido
3xx / Redirecionamento
4xx / Erro do Cliente
5xx / Erro do Servidor
A medida de agentes de usuário DIM_USER_AGENT conterá os atributos AGENT_OS e AGENT_BOT, responsáveis pelos grupos. Eles podem ser preenchidos durante o processo ETL:
Carregamento DIM_USER_AGENT
/* Propagate the user agent from access log */
INSERT INTO DIM_USER_AGENT (USER_AGENT_NK, AGENT_OS, AGENT_ENGINE, AGENT_DEVICE, AGENT_BOT, UPDATE_DT)
WITH CLS AS (
SELECT BROWSER
FROM STG_ACCESS_LOG WHERE LENGTH(BROWSER)>1
GROUP BY BROWSER
)
SELECT
CLS.BROWSER AS USER_AGENT_NK,
CASE
WHEN INSTR(CLS.BROWSER,'Macintosh')>0
THEN 'Macintosh'
WHEN INSTR(CLS.BROWSER,'iPhone')>0
OR INSTR(CLS.BROWSER,'iPad')>0
OR INSTR(CLS.BROWSER,'iPod')>0
OR INSTR(CLS.BROWSER,'Apple TV')>0
OR INSTR(CLS.BROWSER,'Darwin')>0
THEN 'iOS'
WHEN INSTR(CLS.BROWSER,'Android')>0
THEN 'Android'
WHEN INSTR(CLS.BROWSER,'X11;')>0 OR INSTR(CLS.BROWSER,'Wayland;')>0 OR INSTR(CLS.BROWSER,'linux-gnu')>0
THEN 'Linux'
WHEN INSTR(CLS.BROWSER,'BB10;')>0 OR INSTR(CLS.BROWSER,'BlackBerry')>0
THEN 'BlackBerry'
WHEN INSTR(CLS.BROWSER,'Windows')>0
THEN 'Windows'
ELSE 'n.a.' END AS AGENT_OS, -- OS
CASE
WHEN INSTR(CLS.BROWSER,'AppleCoreMedia')>0
THEN 'AppleWebKit'
WHEN INSTR(CLS.BROWSER,') ')>1 AND LENGTH(CLS.BROWSER)>INSTR(CLS.BROWSER,') ')
THEN COALESCE(SUBSTR(CLS.BROWSER, INSTR(CLS.BROWSER,') ')+2, LENGTH(CLS.BROWSER) - INSTR(CLS.BROWSER,') ')-1), 'N/A')
ELSE 'n.a.' END AS AGENT_ENGINE, -- Engine
CASE
WHEN INSTR(CLS.BROWSER,'iPhone')>0
THEN 'iPhone'
WHEN INSTR(CLS.BROWSER,'iPad')>0
THEN 'iPad'
WHEN INSTR(CLS.BROWSER,'iPod')>0
THEN 'iPod'
WHEN INSTR(CLS.BROWSER,'Apple TV')>0
THEN 'Apple TV'
WHEN INSTR(CLS.BROWSER,'Android ')>0 AND INSTR(CLS.BROWSER,'Build')>0
THEN COALESCE(SUBSTR(CLS.BROWSER, INSTR(CLS.BROWSER,'Android '), INSTR(CLS.BROWSER,'Build')-INSTR(CLS.BROWSER,'Android ')), 'n.a.')
WHEN INSTR(CLS.BROWSER,'Android ')>0 AND INSTR(CLS.BROWSER,'MIUI')>0
THEN COALESCE(SUBSTR(CLS.BROWSER, INSTR(CLS.BROWSER,'Android '), INSTR(CLS.BROWSER,'MIUI')-INSTR(CLS.BROWSER,'Android ')), 'n.a.')
ELSE 'n.a.' END AS AGENT_DEVICE, -- Device
CASE
WHEN INSTR(LOWER(CLS.BROWSER),'yandex.com')>0
THEN 'yandex'
WHEN INSTR(LOWER(CLS.BROWSER),'googlebot')>0
THEN 'google'
WHEN INSTR(LOWER(CLS.BROWSER),'bingbot')>0
THEN 'microsoft'
WHEN INSTR(LOWER(CLS.BROWSER),'ahrefsbot')>0
THEN 'ahrefs'
WHEN INSTR(LOWER(CLS.BROWSER),'jobboersebot')>0 OR INSTR(LOWER(CLS.BROWSER),'jobkicks')>0
THEN 'job.de'
WHEN INSTR(LOWER(CLS.BROWSER),'mail.ru')>0
THEN 'mail.ru'
WHEN INSTR(LOWER(CLS.BROWSER),'baiduspider')>0
THEN 'baidu'
WHEN INSTR(LOWER(CLS.BROWSER),'mj12bot')>0
THEN 'majestic-12'
WHEN INSTR(LOWER(CLS.BROWSER),'duckduckgo')>0
THEN 'duckduckgo'
WHEN INSTR(LOWER(CLS.BROWSER),'bytespider')>0
THEN 'bytespider'
WHEN INSTR(LOWER(CLS.BROWSER),'360spider')>0
THEN 'so.360.cn'
WHEN INSTR(LOWER(CLS.BROWSER),'compatible')>0 OR INSTR(LOWER(CLS.BROWSER),'http')>0
OR INSTR(LOWER(CLS.BROWSER),'libwww')>0 OR INSTR(LOWER(CLS.BROWSER),'spider')>0
OR INSTR(LOWER(CLS.BROWSER),'java')>0 OR INSTR(LOWER(CLS.BROWSER),'python')>0
OR INSTR(LOWER(CLS.BROWSER),'robot')>0 OR INSTR(LOWER(CLS.BROWSER),'curl')>0 OR INSTR(LOWER(CLS.BROWSER),'wget')>0
THEN 'other'
ELSE 'n.a.' END AS AGENT_BOT, -- Bot
STRFTIME('%s','now') AS UPDATE_DT
FROM CLS
LEFT OUTER JOIN DIM_USER_AGENT TRG
ON CLS.BROWSER = TRG.USER_AGENT_NK
WHERE TRG.DIM_USER_AGENT_ID IS NULLIntegração de Dados
Inclui a organização da transferência de dados do sistema operacional para o relatório. Para isso, é necessário criar uma tabela de estágio com uma estrutura semelhante à do fonte.
Na etapa de estágio, a informação sobre páginas web chega a partir do backup do CMS na forma de consultas de inserção.
O carregamento da tabela histórica DIM_REQUEST com dados básicos ocorre em três etapas: carregamento de novas chaves e atributos, atualização dos existentes e registro dos registros removidos.
Carregamento de novos registros SCD2
/* Load request table SCD from master data */
INSERT INTO DIM_REQUEST (DIM_REQUEST_ID_HIST, REQUEST_NK, PAGE_TITLE, PAGE_DESCR, PAGE_KEYWORDS, DELETE_FLAG, UPDATE_DT)
WITH CLS AS ( -- prepare keys
SELECT
'/' || NAME AS REQUEST_NK,
TITLE AS PAGE_TITLE,
CASE WHEN DESCRIPTION = '' OR DESCRIPTION IS NULL
THEN 'n.a.' ELSE DESCRIPTION
END AS PAGE_DESCR,
CASE WHEN KEYWORDS = '' OR KEYWORDS IS NULL
THEN 'n.a.' ELSE KEYWORDS
END AS PAGE_KEYWORDS
FROM STG_CMS_MENU
WHERE CONTENT_TYPE != 'folder' -- only web pages
AND PAGE_TITLE != 'n.a.' -- master data which make sense
)
/* new records from stage: CLS */
SELECT
-1 AS DIM_REQUEST_ID_HIST,
CLS.REQUEST_NK,
CLS.PAGE_TITLE,
CLS.PAGE_DESCR,
CLS.PAGE_KEYWORDS,
0 AS DELETE_FLAG,
STRFTIME('%s','now') AS UPDATE_DT
FROM CLS
LEFT OUTER JOIN
(
SELECT
DIM_REQUEST_ID,
REQUEST_NK,
PAGE_TITLE,
PAGE_DESCR,
PAGE_KEYWORDS
FROM DIM_REQUEST_V_ACT
) TRG ON CLS.REQUEST_NK = TRG.REQUEST_NK
WHERE TRG.REQUEST_NK IS NULL -- no such record in data martAtualização de atributos SCD2
/* Load request table SCD from master data */
INSERT INTO DIM_REQUEST (DIM_REQUEST_ID_HIST, REQUEST_NK, PAGE_TITLE, PAGE_DESCR, PAGE_KEYWORDS, DELETE_FLAG, UPDATE_DT)
WITH CLS AS ( -- prepare keys
SELECT
'/' || NAME AS REQUEST_NK,
TITLE AS PAGE_TITLE,
CASE WHEN DESCRIPTION = '' OR DESCRIPTION IS NULL
THEN 'n.a.' ELSE DESCRIPTION
END AS PAGE_DESCR,
CASE WHEN KEYWORDS = '' OR KEYWORDS IS NULL
THEN 'n.a.' ELSE KEYWORDS
END AS PAGE_KEYWORDS
FROM STG_CMS_MENU
WHERE CONTENT_TYPE != 'folder' -- only web pages
AND PAGE_TITLE != 'n.a.' -- master data which make sense
)
/* updated records from stage: CLS and build reference to history: HIST */
SELECT
HIST.DIM_REQUEST_ID AS DIM_REQUEST_ID_HIST,
HIST.REQUEST_NK,
CLS.PAGE_TITLE,
CLS.PAGE_DESCR,
CLS.PAGE_KEYWORDS,
0 AS DELETE_FLAG,
STRFTIME('%s','now') AS UPDATE_DT
FROM CLS,
DIM_REQUEST_V_ACT TRG,
DIM_REQUEST HIST
WHERE CLS.REQUEST_NK = TRG.REQUEST_NK
AND TRG.DIM_REQUEST_ID = HIST.DIM_REQUEST_ID
AND ( CLS.PAGE_TITLE != HIST.PAGE_TITLE /* changes only */
OR CLS.PAGE_DESCR != HIST.PAGE_DESCR
OR CLS.PAGE_KEYWORDS != HIST.PAGE_KEYWORDS )Registros removidos SCD2
/* Load request table SCD from master data */
INSERT INTO DIM_REQUEST (DIM_REQUEST_ID_HIST, REQUEST_NK, PAGE_TITLE, PAGE_DESCR, PAGE_KEYWORDS, DELETE_FLAG, UPDATE_DT)
WITH CLS AS ( -- prepare keys
SELECT
'/' || NAME AS REQUEST_NK,
TITLE AS PAGE_TITLE
FROM STG_CMS_MENU
WHERE CONTENT_TYPE != 'folder' -- only web pages
AND PAGE_TITLE != 'n.a.' -- master data which make sense
)
/* deleted records in data mart: TRG */
SELECT
TRG.DIM_REQUEST_ID AS DIM_REQUEST_ID_HIST,
TRG.REQUEST_NK,
TRG.PAGE_TITLE,
TRG.PAGE_DESCR,
TRG.PAGE_KEYWORDS,
1 AS DELETE_FLAG,
STRFTIME('%s','now') AS UPDATE_DT
FROM (
SELECT
DIM_REQUEST_ID,
REQUEST_NK,
PAGE_TITLE,
PAGE_DESCR,
PAGE_KEYWORDS
FROM DIM_REQUEST_V_ACT
WHERE PAGE_TITLE != 'n.a.' -- track master data only
AND DELETE_FLAG = 0 -- not already deleted
) TRG
LEFT OUTER JOIN CLS ON TRG.REQUEST_NK = CLS.REQUEST_NK
WHERE CLS.REQUEST_NK IS NULL -- no such record in stageCada fonte de dados deve ser acompanhada de uma descrição formal, por exemplo, em um arquivo readme.txt:
Destinatário dos dados formal/técnico: nome, endereço eletrônico
Fornecedor de dados formal/técnico: nome, endereço eletrônico
Fonte de dados: caminho para o arquivo, nomes dos serviços
Informação sobre acesso aos dados: usuários e senhas
Um esquema de movimentação de dados ajudará no processo de manutenção e atualização, por exemplo, em formato de texto:
Movimento do arquivo. Origem: ftp.domain.net: /logs/access.log Destino: /var/www/access.log
Leitura na etapa. Destino: STG_ACCESS_LOG
Carregamento e transformação. Objetivo: FCT_ACCESS_REQUEST_REF_HH
Carregamento e transformação. Objetivo: FCT_ACCESS_USER_AGENT_DD
Relatório. Objetivo: /var/www/report.html
Saída
Assim, o artigo descreve mecanismos como a integração de dados básicos e a introdução de novos níveis de agregação. Eles são necessários para construir armazéns de dados com o objetivo de obter conhecimentos adicionais e melhorar a qualidade da informação.
Fonte: habr.com
