20th Aug 2024 21 minutos de leitura Projeto SQL para portfólio: Loja Northwind Tihomir Babic aprender sql análise de dados Índice O conjunto de dados da Northwind Store 1. Inspeção do banco de dados Inspeção de nomes de tabelas Inspeção das informações da coluna Como encontrar chaves primárias e estrangeiras Entendendo os relacionamentos entre as tabelas 2. Exploração de dados Explorando a tabela de pedidos Contagem de linhas Valores mínimos e máximos Listagem de categorias Contagem de valores distintos em cada categoria Contagem de linhas por valor de categoria Soma e valores médios Exploração de dados de bônus Número de produtos Receita por ano Segmentação de clientes por país 3. Análise avançada de dados e percepções práticas Vendas por canal Distribuição do valor do pedido Clientes de alto e baixo valor por canal Pronto para seu próprio projeto de portfólio SQL? Os projetos de portfólio SQL são uma parte importante da formação de um analista de dados. Como você inicia um projeto e para onde vai com os insights que descobre? Usaremos o banco de dados da loja Northwind para responder a essa pergunta. Realizar um projeto SQL sólido é um elemento essencial do processo de aprendizado de qualquer analista de dados. Um projeto de SQL é uma ótima ferramenta de aprendizado, pois faz com que você use o SQL em um conjunto de dados do mundo real. Isso é especialmente importante se você for um iniciante que não tem oportunidades de trabalhar com conjuntos de dados do mundo real. Ao trabalhar com vários conjuntos de dados do mundo real, você aprende os problemas que pode encontrar no mundo real. Como bônus, a realização de vários projetos de SQL para o seu portfólio é sempre uma coisa boa de se ver em um currículo. A questão é: como preparar um portfólio de projetos SQL? Quando você encontra um conjunto de dados on-line gratuito que deseja analisar, o que fazer em seguida? Responderemos a essas perguntas usando o banco de dados do Northwind Store. Você pode encontrar esse conjunto de dados no curso Bancos de dados SQL para prática. Ele é um dos seis conjuntos de dados que incluem dados de uma universidade, tráfego de blogs, resultados esportivos, uma loja de música e o Museu de Arte Moderna (MoMA). Esse curso faz parte da trilhaTrilha de Práticas em SQL , onde você pode praticar agregação, JOINs, subconsultas, CTEs, CASE WHEN e outros tópicos importantes de SQL. Se você precisar atualizar seus conhecimentos sobre algumas dessas áreas importantes para a geração de relatórios, experimente nosso Como Criar Relatórios Básicos em SQL curso. Vamos agora pegar o conjunto de dados da loja Northwind e usá-lo em um projeto SQL para um portfólio de analistas de dados. Farei esse projeto no PostgreSQL, mas tudo o que fizer poderá ser transferido para outros bancos de dados com pequenos ajustes de sintaxe. Durante o processo, fique à vontade para consultar nossa Folha de consulta gratuita sobre SQL para análise de dados. O conjunto de dados da Northwind Store A única informação que lhe darei sobre esse banco de dados é que ele consiste em seis tabelas: categories - Uma lista de categorias de produtos. channels - Uma lista de fontes por meio das quais a loja adquire clientes. customers - Uma lista dos clientes da loja. order_items - Uma lista dos produtos incluídos em cada pedido. orders - Uma lista de pedidos feitos pelos clientes. products - Uma lista dos produtos que a loja oferece. Reuniremos o restante das informações ao realizar nosso projeto. Na verdade, essa inspeção do banco de dados deve ser o estágio inicial de todo projeto, antes de começar a fazer a análise. 1. Inspeção do banco de dados Essa etapa da preparação de um projeto SQL envolve conhecer seus dados, como nomes de tabelas e colunas, chaves primárias e estrangeiras, as relações entre as tabelas e os tipos de dados em cada tabela. Inspeção de nomes de tabelas Uma forma primitiva de aprender sobre as tabelas no conjunto de dados é encontrá-las no navegador do RDBMS para o qual você importou o conjunto de dados, por exemplo, PostgreSQL, SQL Server ou MySQL. No PostgreSQL, você pode escrever esta consulta para obter uma lista de todas as tabelas em um banco de dados: SELECT table_name FROM information_schema.tables WHERE table_schema NOT IN ('information_schema', 'pg_catalog') AND table_type = 'BASE TABLE' ORDER BY table_schema, table_name; Ela procura dados na tabela information_schemaonde você pode encontrar metadados do banco de dados. Como eu quero o nome de cada tabela, uso a visualização tables depois de fazer referência ao esquema e colocar table_name no SELECT. A primeira condição em WHERE filtra os esquemas do sistema e deixa apenas as tabelas definidas pelo usuário. A segunda condição garante que somente as tabelas básicas sejam listadas, sem exibições e outras tabelas. Aqui está a lista de tabelas no banco de dados Northwind: table_name categories channels customers order_items orders products Inspeção das informações da coluna Agora queremos entender melhor os detalhes de cada tabela. Conhecer suas colunas é um bom começo. Podemos consultar novamente information_schema para obter informações importantes sobre as colunas: SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema NOT IN ('information_schema', 'pg_catalog') ORDER BY table_name, ordinal_position; Podemos encontrar nomes de tabelas, nomes de colunas e o tipo de dados de cada coluna listando table_name, column_name e data_type no SELECT. A coluna is_nullable nos fornecerá informações sobre se a coluna aceita valores NULL. table_namecolumn_namedata_typeis_nullable categoriescategory_idintegerNO categoriescategory_namecharacter varyingNO categoriesdescriptiontextYES channelsidintegerNO channelschannel_namecharacter varyingNO customerscustomer_idintegerNO customersemailcharacter varyingNO customersfull_namecharacter varyingNO customersaddresscharacter varyingYES customerscitycharacter varyingYES customersregioncharacter varyingYES customerspostal_codecharacter varyingYES customerscountrycharacter varyingYES customersphonecharacter varyingYES customersregistration_datetimestamp without time zoneNO customerschannel_idintegerNO customersfirst_order_idintegerYES customersfirst_order_datetimestamp without time zoneYES customerslast_order_idintegerYES customerslast_order_datetimestamp without time zoneYES order_itemsorder_idintegerNO order_itemsproduct_idintegerNO order_itemsunit_pricenumericNO order_itemsquantitysmallintNO order_itemsdiscountnumericNO ordersorder_idintegerNO orderscustomer_idintegerNO ordersorder_datetimestamp without time zoneYES orderstotal_amountnumericNO ordersship_namecharacter varyingYES ordersship_addresscharacter varyingYES ordersship_citycharacter varyingYES ordersship_regioncharacter varyingYES ordersship_postalcodecharacter varyingYES ordersship_countrycharacter varyingYES ordersshipped_datetimestamp without time zoneYES productsproduct_idintegerNO productsproduct_namecharacter varyingNO productscategory_idintegerNO productsunit_pricenumericYES productsdiscontinuedbooleanNO Dessa forma, temos todas as informações em um só lugar, o que facilita a investigação. Primeiro, podemos entender quais dados cada tabela mostra vendo os nomes das colunas. Todos os tipos de dados parecem lógicos. Por exemplo, não há IDs definidos como character varying. Portanto, não haverá necessidade de converter os dados em um formato adequado nesta etapa. Com relação a NULLs, obtemos algumas informações valiosas sobre as tabelas: A tabela categories permite que a descrição da categoria seja NULL. A tabela channels não permite nenhum NULLs. A tabela customers permite NULLs em muitas colunas, inclusive no país. Se quisermos criar uma segmentação geográfica de clientes, esses NULLs poderão nos causar problemas. A saída acima mostra que nenhuma coluna da tabela order_items são anuláveis. Para a tabela products é interessante observar que o preço unitário pode ser NULL. Parece um pouco estranho que a tabela orders tenha tantas colunas anuláveis. Basicamente, isso possibilita ter um ID de pedido e nenhuma outra informação sobre o pedido. Temos que ter isso em mente ao analisar essa tabela. Como encontrar chaves primárias e estrangeiras A próxima etapa da inspeção de dados é entender como as tabelas funcionam juntas. Para fazer isso, primeiro precisamos encontrar suas chaves primárias (PKs) e chaves estrangeiras (FKs). As PKs nos mostrarão quais colunas uma tabela usa para identificar os dados de forma exclusiva. Ao fazer isso, você entenderá melhor a estrutura da tabela. As FKs nos mostrarão qual coluna está vinculada a uma chave primária de outra tabela. Essa é a base para entender as relações entre as tabelas. Você pode listar PKs e FKs usando a consulta abaixo: SELECT kcu.table_name, kcu.column_name, tc.constraint_type FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON kcu.constraint_name = tc.constraint_name AND kcu.constraint_schema = tc.constraint_schema WHERE tc.constraint_type IN ('PRIMARY KEY', 'FOREIGN KEY') ORDER BY kcu.table_name ASC, tc.constraint_type DESC; Desta vez, precisamos usar duas exibições da tabela information_schemaou seja table_constraints e key_column_usage. A visualização table_constraints contém informações sobre as restrições de cada tabela. A visualização key_column_usage serve para obter informações sobre as colunas com as restrições de chave. Essas duas exibições são unidas por duas colunas comuns: constraint_name (o nome da restrição) e constraint_schema (o nome do esquema que contém a restrição). Uso a cláusula WHERE para gerar apenas as restrições de chave primária e chave estrangeira. Como etapa final, ordeno a saída em ordem alfabética pelo nome da tabela e, em seguida, inverto a ordem alfabética pelo tipo de restrição. O código gera essa tabela: table_namecolumn_nameconstraint_type categoriescategory_idPRIMARY KEY channelsidPRIMARY KEY customerscustomer_idPRIMARY KEY customerslast_order_idFOREIGN KEY customersfirst_order_idFOREIGN KEY customerschannel_idFOREIGN KEY order_itemsorder_idPRIMARY KEY order_itemsproduct_idPRIMARY KEY order_itemsorder_idFOREIGN KEY order_itemsproduct_idFOREIGN KEY ordersorder_idPRIMARY KEY orderscustomer_idFOREIGN KEY productsproduct_idPRIMARY KEY productscategory_idFOREIGN KEY As tabelas categories e channels são as únicas tabelas que têm PK, mas não têm FK. Todas as outras tabelas têm uma PK e pelo menos uma FK. A única exceção é a tabela order_itemsque tem a restrição PK em duas colunas: order_id e product_id. Em outras palavras, a tabela tem uma chave primária composta, o que significa que o identificador exclusivo dos dados é a combinação exclusiva de duas colunas. Nesse caso, o objetivo é mostrar apenas um ID de produto por pedido individual. Uma análise mais detalhada revela que as mesmas colunas também são uma chave estrangeira para a mesma tabela. Isso significa que as chaves primárias também são estrangeiras, pois vêm de outras tabelas. Entendendo os relacionamentos entre as tabelas A listagem de PKs e FKs já nos dá uma ideia dos relacionamentos entre as tabelas. No entanto, podemos entendê-los melhor mostrando as tabelas e colunas pai e filho das chaves estrangeiras. Por que faríamos isso? Uma chave estrangeira é sempre a chave primária de outra tabela. A listagem de todas as tabelas e colunas pai e filho é uma atualização da consulta anterior porque veremos facilmente as colunas compartilhadas entre as tabelas. Essas informações são úteis para entender as relações entre as tabelas, saber quais colunas você pode usar para unir as tabelas e se elas podem ser unidas diretamente ou por meio de outra tabela. Aqui está o código: SELECT ccu.table_name AS parent_table, ccu.column_name AS parent_column, kcu.table_name AS child_table, kcu.column_name AS child_column FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' ORDER BY kcu.table_name; Na consulta, usamos informações das colunas table_constraints, key_column_usage, e constraint_column_usage exibições. A visualização constraint_column_usage mostra as colunas usadas por uma restrição. Nós a usaremos para mostrar a tabela e a coluna pai da FK, ou seja, a tabela e a coluna em que essa FK é de fato uma PK. Os dados de key_column_usage mostrarão a tabela e a coluna de uma FK. parent_tableparent_columnchild_tablechild_column ordersorder_idcustomerslast_order_id ordersorder_idcustomersfirst_order_id channelsidcustomerschannel_id ordersorder_idorder_itemsorder_id productsproduct_idorder_itemsproduct_id customerscustomer_idorderscustomer_id categoriescategory_idproductscategory_id Na saída acima, vemos que a tabela orders está diretamente conectada à tabela customers por meio das colunas order_id (PK) e customer_id (FK). Ela está conectada à tabela order_items por meio de order_id (PK). A tabela channels está diretamente conectada apenas com a tabela customers. Já estabelecemos que as chaves primárias da tabela order_items também são chaves estrangeiras. Na tabela acima, podemos ver que elas vêm das tabelas orders e products. A tabela categories está conectada somente com a tabela products. 2. Exploração de dados Quando você começa a trabalhar com um novo banco de dados, a primeira etapa deve ser a exploração dos dados para obter algumas informações básicas sobre cada tabela em um banco de dados. É uma boa prática observar o seguinte: Contagem de linhas. Encontrar valores mínimos e máximos (especialmente para datas). Listar categorias distintas. Contagem de valores distintos em cada categoria. Contagem de linhas por valor de categoria. Somas e médias de valores numéricos importantes. Essa não é uma lista exaustiva e pode mudar de acordo com a tabela que você está explorando. Entretanto, é um mínimo e deve funcionar na maioria das tabelas. Em geral, essa etapa se resume a uma agregação e um agrupamento de dados fundamentais. Mostrarei como isso funciona em uma tabela do nosso banco de dados e, em seguida, você poderá explorar outras tabelas da mesma forma. Explorando a tabela de pedidos Contagem de linhas Como esperado, usamos a função de agregação COUNT() aqui: SELECT COUNT(*) AS number_of_rows FROM orders; Eu uso a função COUNT() com um asterisco; isso contará todas as linhas, inclusive NULLs. number_of_rows 11,618 A tabela orders tem 11.618 linhas. Em outras palavras, há 11.618 pedidos. Valores mínimos e máximos Vamos ver quais colunas são adequadas para uso com as funções MIN() e MAX(). Perfect (Perfeito) - podemos encontrar a data mais antiga e a mais recente em que um pedido foi feito e enviado: SELECT MIN(order_date) AS oldest_order_date, MAX(order_date) AS latest_order_date, MIN(shipped_date) AS oldest_shipped_date, MAX(shipped_date) AS latest_shipped_date FROM orders; Na saída, podemos ver que o primeiro pedido foi feito em 2017-01-01 e o último em 2024-06-24. Quanto às datas de envio, a primeira está em 2017-01-01 e a mais recente em 2024-06-23. oldest_order_datelatest_order_dateoldest_shipped_datelatest_shipped_date 2017-01-01 0:00:002024-06-24 0:00:002017-01-06 0:00:002024-06-23 0:00:00 Listagem de categorias Não precisamos de nenhuma função de agregação para fazer isso: SELECT DISTINCT ship_country FROM orders ORDER BY ship_country; Na consulta, usamos a cláusula DISTINCT para mostrar cada país apenas uma vez. Aqui está uma lista dos países nessa tabela: ship_country Argentina Austria Belgium Brazil Canada Denmark Finland France Germany Ireland Italy Mexico Norway Poland Portugal Spain Sweden Switzerland UK USA Venezuela Também será útil ver uma lista de cidades: SELECT DISTINCT ship_city FROM orders ORDER BY ship_city; Como há muitas cidades, aqui está uma lista parcial: ship_city Aachen Abilene Achille Adelphi Adrian Akron Albany Alberton Albuquerque … Yucca Valley Contagem de valores distintos em cada categoria Vamos ver agora o número de países para os quais a Northwind entregou remessas: SELECT COUNT(DISTINCT ship_country) AS number_of_countries FROM orders; Usamos novamente COUNT(), mas desta vez adicionamos DISTINCT e fazemos referência à coluna que queremos contar. O resultado mostra que há 21 países diferentes onde a empresa faz entregas: number_of_countries 21 Poderíamos fazer o mesmo com as cidades: SELECT COUNT(DISTINCT ship_city) AS number_of_cities FROM orders; Há 494 cidades exclusivas: number_of_cities 494 Mesmo que não as tenhamos listado como uma categoria separada, saber quantos clientes fizeram pedidos será útil: SELECT COUNT(DISTINCT customer_id) AS number_of_customers_with_orders FROM orders; Usamos o site COUNT() para contar os IDs dos clientes. É claro que precisamos de DISTINCT, pois é de se esperar que alguns clientes apareçam muitas vezes, ou seja, toda vez que fizerem um pedido. Há 761 clientes exclusivos que fizeram pelo menos um pedido à empresa. number_of_customers_with_orders 761 Contagem de linhas por valor de categoria Para isso, queremos listar cada valor de categoria (fizemos isso anteriormente) e contar o número de linhas para cada valor. Por exemplo, isso retornará o número de pedidos por país: SELECT ship_country, COUNT(*) AS number_of_orders FROM orders GROUP BY ship_country ORDER BY number_of_orders DESC; Seleciono a coluna ship_country e, em seguida, uso COUNT(*) para contar todas as linhas. Para que a contagem seja feita por cada país, preciso introduzir a cláusula GROUP BY ship_country. O que obtemos com isso é o número de pedidos por país. O resultado mostra que os EUA são o maior mercado, com 9.789 pedidos: ship_countrynumber_of_orders USA9,789 France236 Brazil233 Germany230 UK144 Spain123 Mexico118 Venezuela103 Argentina87 Canada82 Italy64 Austria58 Portugal58 Belgium56 Denmark56 Finland47 Norway30 Sweden28 Poland27 Ireland26 Switzerland23 Soma e valores médios Se seus dados forem adequados, você deverá encontrar médias e totais de valores numéricos. Podemos fazer isso para a coluna total_amount, que é o valor de um pedido: SELECT SUM(total_amount) AS orders_total_value, AVG(total_amount) AS average_order_value FROM orders; Para mostrar esses dois cálculos, use as funções de agregação SUM() e AVG(). Podemos ver que a receita total da loja é de pouco mais de 19 milhões. O valor médio do pedido é de 1.636,15: orders_total_valueaverage_order_value 19,008,819.691,636.15 Exploração de dados de bônus Como mencionei, as agregações anteriores devem ser o mínimo que você deve fazer para cada tabela no banco de dados. Essa exploração de dados vai um pouco além (mas não muito longe) das agregações simples e GROUP BY. Embora essa ainda seja a base, você também pode empregar outros conceitos, como filtragem de dados (usando WHERE e/ou HAVING), extração de partes da data ou da hora, uso de CASE WHEN para rotular dados e assim por diante. Vamos examinar vários exemplos. Número de produtos Podemos empregar a função COUNT() para encontrar o número total de produtos que a loja vende. SELECT COUNT(*) AS number_of_active_products FROM products WHERE discontinued IS FALSE; Há uma condição na cláusula WHERE para mostrar somente os produtos que não foram descontinuados, ou seja, esses são os produtos que a loja está vendendo no momento. Na seção anterior, sabemos que a coluna discontinued é do tipo booleano. Portanto, temos de usar o operador IS FALSE para incluir na contagem apenas os produtos não descontinuados. A contagem é 69: number_of_active_products 69 Receita por ano Um relatório simples que mostra a receita por ano pode ser criado usando SUM(): SELECT EXTRACT(YEAR FROM order_date) AS revenue_year, SUM(total_amount) AS revenue FROM orders GROUP BY revenue_year; Usamos a função EXTRACT() para obter apenas os anos das datas dos pedidos. Em seguida, somamos os valores totais de todos os pedidos e agrupamos por ano para mostrar os valores de cada ano separadamente. Podemos ver nos resultados que o ano de pico da empresa foi 2018. O ano com a menor receita é 2024, mas isso pode ser devido ao fato de o ano ainda não ter terminado (no momento da análise). Outra coisa interessante é que não há dados de receita para os anos de 2019 a 2022. Isso deve ser verificado para saber se os dados estão faltando por algum motivo ou se isso é um erro. revenue_yearrevenue 20173,088,759.84 20189,368,330.91 20234,646,048.11 20241,905,680.83 Segmentação de clientes por país Temos dados sobre os países dos clientes, portanto, uma visão geral do número de clientes em cada país seria informativa. Além disso, não estamos interessados em clientes que não tenham feito um pedido, pois isso pode aumentar artificialmente o número de clientes. Não, queremos apenas clientes que fazem pedidos conosco. Aqui está a consulta: SELECT country, COUNT(*) AS number_of_customers FROM customers WHERE first_order_id IS NOT NULL GROUP BY country ORDER BY number_of_customers DESC; Selecionamos e agrupamos por país e usamos a função de agregação COUNT() para encontrar o número de clientes. Se os dados da coluna first_order não forem nulos, então esse cliente fez pelo menos um pedido; essa é a condição que temos de usar em WHERE. Os dados são ordenados do maior para o menor número de clientes. O resultado mostra que o maior mercado da Northwind em termos de clientes são os EUA. Também é seguro concluir que esse é o maior mercado em termos de receita. countrynumber_of_customers USA697 Germany8 France8 Brazil8 UK5 Venezuela4 Spain4 Mexico4 Argentina3 Canada3 Belgium2 Denmark2 Portugal2 Finland2 Italy2 Austria2 Sweden1 Poland1 Ireland1 Switzerland1 Norway1 3. Análise avançada de dados e percepções práticas O que fizemos até agora é um bom começo. No entanto, a análise de dados em um projeto SQL para um portfólio não deve se limitar à simples agregação e exploração de cada tabela. Agora, iremos além disso e escreveremos consultas mais complexas que nos permitirão agir e melhorar os negócios da Northwind. Por exemplo, talvez queiramos ver como as vendas mudam dependendo do canal. Vamos ver o que conseguimos e depois decidir quais serão nossas próximas etapas. Vendas por canal Para cada canal, queremos mostrar o total de vendas, o valor médio do pedido, o número total de pedidos e o número de clientes exclusivos. Também queremos classificar os canais por total de vendas. Aqui está a consulta: SELECT ch.channel_name, SUM(o.total_amount) AS total_sales, AVG(o.total_amount) AS average_order_value, COUNT(o.order_id) AS total_orders, COUNT(DISTINCT o.customer_id) AS unique_customers, RANK() OVER (ORDER BY SUM(o.total_amount) DESC) AS sales_rank FROM channels ch LEFT JOIN customers c ON ch.id = c.channel_id LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY ch.channel_name ORDER BY sales_rank; Essa consulta usa várias agregações: SUM() para calcular o total de vendas, AVG() para o valor médio do pedido e COUNT() para o número total de pedidos e (com DISTINCT) para clientes exclusivos. Em seguida, usamos a função de janela RANK() para classificar os canais pelo total de vendas. Como as funções de janela são executadas antes da agregação, não podemos simplesmente usar a coluna total_sales na função de janela. Em vez disso, preciso replicar todo o cálculo - uma tarefa simples que pode ser concluída copiando e colando. Usamos os dados das tabelas channels e orders. No entanto, não posso unir diretamente essas duas tabelas, pois elas não têm uma coluna compartilhada. Em vez disso, temos que uni-las por meio da tabela customers. Aqui está o resultado: channel_nametotal_salesaverage_order_valuetotal_ordersunique_customerssales_rank Organic Search14,003,046.951,603.108,7355651 Direct2,436,649.061,799.591,354912 Referral1,034,734.451,645.05629463 Social837,378.771,824.35459324 Paid Search483,824.241,645.66294195 Email213,186.221,450.2514786 Vemos que a maior parte das vendas da Northwind vem da pesquisa orgânica. Não há contradições nos dados - a classificação por vendas totais também reflete o número de pedidos e de clientes únicos. É interessante observar que o canal de pesquisa orgânica não tem o valor médio de pedido mais alto. Isso exige algumas melhorias! Vamos imaginar uma estratégia de aumento de receita que se concentre em aumentar o valor médio dos pedidos de todos os canais em vez de novas aquisições. Provavelmente não conseguiremos adotar uma abordagem uniforme baseada apenas no canal de marketing. Em cada segmento, pode haver clientes com hábitos de gastos opostos. Podemos supor que isso se aplica especialmente à Pesquisa orgânica, que é um canal tão grande. Portanto, precisamos saber mais sobre a distribuição do valor do pedido para cada canal. Distribuição do valor do pedido Vamos calcular o valor médio do pedido, a mediana, o quartil superior, o quartil inferior e o desvio padrão de cada canal: SELECT ch.channel_name, AVG(total_amount) AS average_order_value, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total_amount) AS median_order_value, PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY total_amount) AS upper_quartile_order_value, PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY total_amount) AS lower_quartile_order_value, STDDEV(total_amount) AS order_value_stddev FROM channels ch LEFT JOIN customers c ON ch.id = c.channel_id LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY ch.channel_name; O valor médio do pedido é calculado por AVG(). A mediana (ou percentil 50), o quartil superior e o quartil inferior são calculados usando a função de agregação de conjunto ordenado PERCENTILE_CONT(), com o percentil especificado entre parênteses. Para o cálculo do percentil, os dados precisam ser classificados em ordem crescente; fazemos isso usando a cláusula WITHIN GROUP. Após o cálculo do percentil, STDDEV() é usado para calcular o desvio padrão. Aqui está o resultado: channel_nameaverage_order_valuemedian_order_valueupper_quartile_order_valuelower_quartile_order_valueorder_value_stddev Direct1,799.591,005.902,166.80517.402,323.63 Email1,450.25960.001,970.41492.501,655.97 Organic Search1,603.101,007.702,018.20480.001,916.39 Paid Search1,645.661,083.002,104.40486.551,813.22 Referral1,645.051,064.002,034.50482.901,969.01 Social1,824.351,122.802,123.20559.902,319.10 Vamos analisar as métricas do primeiro canal. O desvio padrão do canal Direct é 2.323,63, que é maior do que a média ou o valor médio. Isso indica alta variabilidade, ou seja, provavelmente há outliers ou uma ampla gama de valores de pedidos. Os quartis inferior e superior mostram que 50% dos pedidos estão entre 517,40 e 2.166,80. Entretanto, um alto desvio padrão significa que muitas ordens estão fora desse intervalo. A mediana é significativamente menor que a média, o que indica que a distribuição é inclinada para a direita, ou seja, um número baixo de valores de pedidos altos que aumentam a média. Podemos analisar cada canal da mesma forma. Isso nos dá outra ideia. Para criar estratégias personalizadas para aumentar a receita, podemos segmentar ainda mais os clientes por quartil superior e inferior. Consideraremos todos os clientes acima do quartil superior como gastadores altos; os gastadores baixos serão os clientes abaixo do quartil inferior. Dessa forma, podemos adaptar nossa estratégia a cada canal e aos hábitos de consumo de cada grupo. Clientes de alto e baixo valor por canal Farei esse cálculo para apenas um canal de marketing. Você pode fazer isso para todos os outros, pois o código permanecerá o mesmo; você só mudará o canal em WHERE. Os clientes no quartil superior são clientes de alto valor e queremos colocá-los em uma lista separada: WITH customer_order_totals AS ( SELECT c.customer_id, c.full_name, SUM(o.total_amount) AS total_order_value, ch.channel_name FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN channels ch ON c.channel_id = ch.id WHERE ch.channel_name = 'Direct' GROUP BY c.customer_id, c.full_name, ch.channel_name ), upper_quartile_value AS ( SELECT PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY total_order_value) AS upper_quartile FROM customer_order_totals ) SELECT cot.customer_id, cot.full_name, cot.total_order_value, cot.channel_name FROM customer_order_totals cot JOIN upper_quartile_value uqv ON cot.total_order_value >= uqv.upper_quartile ORDER BY cot.total_order_value DESC; O primeiro CTE calcula o valor total do pedido de cada cliente usando SUM() e unindo as tabelas customers e pedidos. Em WHERE, filtramos todos os canais, exceto o Direct. O segundo CTE calcula o quartil superior de maneira familiar. O terceiro SELECT então CROSS JOINs os dois CTEs para encontrar todos os clientes cujo valor do pedido está acima do quartil superior. Aqui estão as primeiras dez linhas do resultado: customer_idfull_nametotal_order_valuechannel_name 134Barry Michael79,371.50Direct 152Carolann Williams64,365.21Direct 7Frédérique Citeaux61,865.74Direct 17Sven Ottlieb57,251.14Direct 64Sergio Gutiérrez55,140.75Direct 490Alice Blevins54,736.24Direct 8Martín Sommer54,499.55Direct 303Gregory Mack52,554.20Direct 316Jeff Heard51,976.31Direct 129Stephan Bufford50,868.70Direct No total, há 23 clientes no resultado. Podemos usar essas informações para atingir esses clientes de alto valor. Por exemplo, podemos criar programas de fidelidade em que esses clientes possam obter descontos especiais, serviço personalizado, pontos resgatáveis para compras, programas de associação VIP e assim por diante. Da mesma forma, podemos listar os clientes cujos pedidos estão abaixo do quartil inferior: WITH customer_order_totals AS ( SELECT c.customer_id, c.full_name, SUM(o.total_amount) AS total_order_value, ch.channel_name FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN channels ch ON c.channel_id = ch.id WHERE ch.channel_name = 'Direct' GROUP BY c.customer_id, c.full_name, ch.channel_name ), lower_quartile_value AS ( SELECT PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY total_order_value) AS lower_quartile FROM customer_order_totals ) SELECT cot.customer_id, cot.full_name, cot.total_order_value, cot.channel_name FROM customer_order_totals cot, lower_quartile_value lqv WHERE cot.total_order_value <= lqv.lower_quartile ORDER BY cot.total_order_value ASC; Essa consulta é quase igual à anterior, só que desta vez calculamos o quartil inferior. Além disso, pesquisamos os clientes com pedidos abaixo desse valor. Mais uma vez, a consulta retorna 191 clientes. Aqui estão as dez primeiras linhas: customer_idfull_nametotal_order_valuechannel_name 939Shannon Aguilar98.40Direct 997Barbra Armstrong251.50Direct 687Andrew Scott452.90Direct 787Dennis Myer912.00Direct 917Les Allen991.40Direct 921Shelby Turgeon1,162.25Direct 560Nancy Wiggins1,425.80Direct 678Tracey Thomas2,555.20Direct 756Dora Rowlands2,713.50Direct 715George Scott2,906.50Direct Esses clientes precisam ser abordados de forma diferente, pois precisam ser motivados a gastar mais. As estratégias para aumentar a receita com eles podem incluir pacotes de produtos personalizados, frete grátis acima de um determinado valor, aumento dos descontos para limites de pedidos mais altos ou oferta de um brinde acima de um determinado valor de pedido. Agora, faça a mesma análise para todos os outros canais e pense em quais táticas você poderia usar para aumentar os valores dos pedidos desse canal. Pronto para seu próprio projeto de portfólio SQL? Neste artigo, mostramos como preparar um projeto de portfólio SQL usando o banco de dados da loja Northwind. É claro que, se você for criativo o suficiente, também poderá criar seu próprio conjunto de dados. A conclusão de um projeto SQL para seu portfólio é uma parte importante da preparação para o processo de contratação. Os portfólios são uma excelente demonstração de suas habilidades práticas de análise de dados em SQL. O projeto acima é apenas um exemplo. Há muitas outras ideias de análise que você pode experimentar em nosso curso Bancos de dados SQL para a prática, que é altamente recomendável! Tags: aprender sql análise de dados