Genel Bakış

Hardal Analytics, işletme ihtiyaçlarınıza özel yollarla verilerinizi analiz etmek için SQL sorguları kullanarak özel metrikler oluşturmanıza olanak tanır. Bu rehber, toplanan verilerinizden içgörüler elde etmek için Hardal’ın Analytics Dashboard’unda etkili SQL sorguları yazmanızı anlamanıza yardımcı olacaktır.

Hardal, analitik iş yükleri için yüksek performanslı SQL sorgulama yetenekleri sunan ClickHouse’u temel veritabanı olarak kullanır.

Ön Koşullar

Hardal Analytics’te SQL sorguları yazmadan önce şunlara sahip olmalısınız:

  • Hardal Analytics Dashboard’una erişim
  • SQL syntax’ı hakkında temel bilgi
  • JSON veri yapıları hakkında bilgi (Hardal’ın verilerinin çoğu JSON formatında saklanır)
  • Veri toplama kurulumunuz hakkında bilgi

Veri Yapısı

Hardal Veritabanı Yapısı hakkında detaylı bilgi edinin

Veritabanı Genel Bakış

Analitik veriler, birden fazla tablo içeren default adlı bir veritabanında saklanır. Bu dokümantasyonda, default.analytics formatıyla erişilebilen analytics tablosuna odaklanacağız.

Tablo Yapısı

analytics tablosu aşağıdaki sütunları içerir:

Sütun AdıTürAçıklama
idUUIDHer kayıt için benzersiz tanımlayıcı
event_nameLowCardinality(String)İzlenen olayın adı
distinct_idLowCardinality(String)İzleme için benzersiz tanımlayıcı
propertiesStringDetaylı olay verilerini içeren JSON nesnesi
created_atDateTimeOlayın oluşturulma zaman damgası

Properties Yapısı

properties sütunu, zengin olay verileri içeren bir JSON nesnesi içerir. İşte standart yapı:

Üst Düzey Properties

  • distinct: Kullanıcı kimlik bilgilerini içerir
  • page: Sayfa ile ilgili bilgiler
  • screen: Cihaz ekran özellikleri
  • browser: Tarayıcı detayları
  • device_type: Cihaz tipi (örn., desktop, mobile)
  • timezone: Kullanıcının zaman dilimi
  • timestamp: Olay zaman damgası
  • query_params: URL sorgu parametreleri
  • events: Detaylı olay bilgileri dizisi
  • batch_size: Batch’teki olay sayısı
  • batch_timestamp: Batch’in zaman damgası

Properties Detayı

Sayfa Bilgileri
"page": {
  "url": "Tam URL",
  "path": "URL yolu",
  "title": "Sayfa başlığı",
  "protocol": "Kullanılan protokol",
  "hostname": "Alan adı",
  "hash": "URL hash'i",
  "referrer": "Referans veren URL"
}
Ekran Bilgileri
"screen": {
  "resolution": "Ekran çözünürlüğü",
  "color_depth": "Bit cinsinden renk derinliği",
  "pixel_depth": "Piksel derinliği",
  "viewport_size": "Görüntü alanı boyutları",
  "device_pixel_ratio": "Cihaz piksel oranı"
}
Tarayıcı Bilgileri
"browser": {
  "name": "Tarayıcı adı",
  "version": "Tarayıcı versiyonu",
  "language": "Tarayıcı dili",
  "platform": "İşletim platformu",
  "vendor": "Tarayıcı sağlayıcısı",
  "user_agent": "User agent dizisi"
}

SQL Sorguları Yazma

Özel bir SQL sorgusu oluşturmak için:

  1. Analytics Dashboard’a gidin
  2. “Create Metric” butonuna tıklayın
  3. Sorgu editöründe SQL sorgunuzu yazın
  4. Sorguyu çalıştırmak ve sonuçları görselleştirmek için “Execute Query” butonuna tıklayın
  5. Tercih ettiğiniz görselleştirme tipini seçin (Tablo, Çizgi Grafiği, Çubuk Grafiği veya Skor Kartı)

Temel Sorgu Yapısı

Hardal’dan veri almak için temel bir sorgu şu yapıya sahiptir:

SELECT 
    [columns]
FROM 
    default.analytics
WHERE 
    [conditions]
GROUP BY 
    [grouping]
ORDER BY 
    [ordering]
LIMIT 
    [limit]

JSON Verilerle Çalışma

Hardal’ın verilerinin çoğu properties sütununda JSON formatında saklandığından, değerleri çıkarmak için özel fonksiyonlar kullanmanız gerekecektir:

String Çıkarma

-- Temel property çıkarma
JSONExtractString(properties, 'device_type')
JSONExtractString(properties, 'timezone')

-- İç içe property çıkarma
JSONExtractString(properties, 'page', 'url')
JSONExtractString(properties, 'browser', 'name')
JSONExtractString(properties, 'screen', 'resolution')

Sayısal Çıkarma

-- Float değerler
JSONExtractFloat(properties, 'screen', 'device_pixel_ratio')
JSONExtractFloat(properties, 'screen', 'color_depth')

-- Integer değerler
JSONExtractInt(properties, 'batch_size')

Dizi Çıkarma

JSON’daki dizilerle çalışırken (örneğin events):

-- İlk olaydan event name çıkarma (dizi indeksi 1'den başlar)
JSONExtractString(JSONExtractString(properties, 'events')[1], 'event_name')

-- İlk olaydan source çıkarma
JSONExtractString(JSONExtractString(properties, 'events')[1], 'source')

-- İlk olaydan timestamp çıkarma
JSONExtractString(JSONExtractString(properties, 'events')[1], 'timestamp')

-- ARRAY JOIN kullanarak dizilerle çalışma
FROM default.analytics
ARRAY JOIN JSONExtractArrayRaw(properties, 'events') as event

-- İlk olaydan iç içe query parametrelerini çıkarma
JSONExtractString(
  JSONExtractString(
    JSONExtractString(properties, 'events')[1], 
    'query_params'
  ), 
  'tid'
)

Tarih ve Zaman Filtreleme

Önemli: Hardal, çok fazla veriyi tarayıp performansı etkilemesini önlemek için tüm sorguların BETWEEN toDateTime(?) AND toDateTime(?) sözdizimini kullanarak bir tarih aralığı filtresi içermesini gerektirir. Bu zorunludur.

-- Gerekli tarih aralığı formatı (? yerine gerçek tarihleri koyun)
WHERE created_at BETWEEN toDateTime(?) AND toDateTime(?)

-- Belirli tarihlerle örnek
WHERE created_at BETWEEN toDateTime('2025-02-10 00:00:00') AND toDateTime('2025-02-17 23:59:59')

-- Ek zaman filtreleri ekleyebilirsiniz
WHERE created_at BETWEEN toDateTime('2025-02-17 00:00:00') AND toDateTime('2025-02-18 00:00:00')
  AND JSONExtractString(properties, 'timestamp') LIKE '%18T%'

Son 7 günü filtrelemek istiyorsanız, tarih aralığını açıkça hesaplamanız gerekecektir:

WHERE created_at BETWEEN toDateTime(date_sub(DAY, 7, now())) AND toDateTime(now())

Örnek Sorgular

İşte Hardal Analytics’te kullanabileceğiniz bazı pratik SQL sorgu örnekleri:

Temel Property Çıkarma

SELECT
    id,
    event_name,
    JSONExtractString(properties, 'device_type') AS device,
    JSONExtractString(properties, 'page', 'url') AS page_url,
    JSONExtractString(properties, 'browser', 'name') AS browser_name,
    JSONExtractString(properties, 'screen', 'resolution') AS screen_resolution,
    JSONExtractFloat(properties, 'screen', 'device_pixel_ratio') AS pixel_ratio,
    created_at
FROM default.analytics
WHERE created_at BETWEEN toDateTime('2025-02-10 00:00:00') AND toDateTime('2025-02-17 23:59:59')
LIMIT 10;

Temel Sayfa Görüntüleme Analizi

SELECT
  toDate(created_at) as date,
  JSONExtractString(JSONExtractRaw(properties, 'page'), 'path') as page_path,
  COUNT(*) as view_count
FROM default.analytics
WHERE event_name = 'page_view'
  AND created_at BETWEEN toDateTime(date_sub(DAY, 7, now())) AND toDateTime(now())
GROUP BY
  date,
  page_path
ORDER BY date DESC, view_count DESC
FORMAT TabSeparatedWithNames

Şehre Göre Satın Alma Analizi

WITH CityPurchases AS (
    SELECT 
        JSONExtractString(properties, 'city') AS city,
        COUNT(DISTINCT JSONExtractString(properties, 'crmId')) AS unique_customers,
        COUNT(*) AS total_purchases,
        SUM(toFloat64OrNull(JSONExtractString(item, 'price'))) AS total_revenue,
        AVG(toFloat64OrNull(JSONExtractString(item, 'price'))) AS avg_basket_size,
        COUNT(*) / COUNT(DISTINCT JSONExtractString(properties, 'crmId')) AS purchases_per_customer
    FROM default.analytics
    ARRAY JOIN JSONExtractArrayRaw(properties, 'items') AS item
    WHERE created_at BETWEEN toDateTime(date_sub(DAY, 30, now())) AND toDateTime(now())
        AND event_name = 'purchase'
        AND JSONExtractString(properties, 'country') = 'TR'
        AND JSONExtractString(properties, 'city') != ''
    GROUP BY city
    ORDER BY total_purchases DESC
    LIMIT 20
)
SELECT 
    city AS "City",
    unique_customers AS "Unique Customers",
    total_purchases AS "Total Purchases",
    ROUND(total_revenue, 2) AS "Total Revenue",
    ROUND(avg_basket_size, 2) AS "Average Basket Size",
    ROUND(purchases_per_customer, 2) AS "Purchases per Customer"
FROM CityPurchases
ORDER BY "Total Purchases" DESC

Platforma Göre Satın Almalar

SELECT
   toDate(created_at) AS PurchaseDate,
   CASE
       WHEN JSONExtractString(properties, 'user_agent') LIKE '%iPhone%' OR JSONExtractString(properties, 'user_agent') LIKE '%iPad%' THEN 'iOS'
       WHEN JSONExtractString(properties, 'user_agent') LIKE '%Android%' THEN 'Android'
       WHEN JSONExtractString(properties, 'user_agent') LIKE '%Windows%' OR JSONExtractString(properties, 'user_agent') LIKE '%Macintosh%' THEN 'Web'
       ELSE 'Unknown'
   END AS Platform,
   COUNT(*) AS TotalPurchases,
   SUM(toFloat64OrNull(JSONExtractString(item, 'price'))) AS TotalRevenue
FROM default.analytics
ARRAY JOIN JSONExtractArrayRaw(properties, 'items') AS item
WHERE
   created_at BETWEEN toDateTime(date_sub(DAY, 7, now())) AND toDateTime(now())
   AND event_name = 'purchase'
GROUP BY
   PurchaseDate,
   Platform
ORDER BY
   PurchaseDate DESC,
   TotalPurchases DESC

Kullanıcı Yolculuğu Analizi

WITH 
base_data AS (
    SELECT
        created_at,
        event_name,
        JSONExtractString(properties, 'distinct', 'server_distinct_id') AS server_distinct_id,
        JSONExtractString(properties, 'page', 'url') AS page_url,
        JSONExtractString(properties, 'page', 'path') AS page_path,
        JSONExtractString(properties, 'page', 'referrer') AS page_referrer,
        JSONExtractString(properties, 'device_type') AS device_type
    FROM default.analytics
    WHERE created_at BETWEEN toDateTime(date_sub(DAY, 2, now())) AND toDateTime(now())
),

session_calcs AS (
    SELECT 
        *,
        MIN(created_at) OVER (PARTITION BY server_distinct_id) as first_seen,
        MAX(created_at) OVER (PARTITION BY server_distinct_id) as last_seen,
        if(dateDiff('minute',
            lagInFrame(created_at) OVER (PARTITION BY server_distinct_id ORDER BY created_at),
            created_at) > 30 OR
            lagInFrame(created_at) OVER (PARTITION BY server_distinct_id ORDER BY created_at) IS NULL,
            1, 0) AS is_new_session
    FROM base_data
)

SELECT 
    server_distinct_id,
    event_name,
    page_url,
    page_path,
    page_referrer,
    device_type,
    first_seen,
    last_seen,
    COUNT(*) as event_count
FROM session_calcs
GROUP BY 
    server_distinct_id,
    event_name,
    page_url,
    page_path,
    page_referrer,
    device_type,
    first_seen,
    last_seen
ORDER BY last_seen DESC
LIMIT 100

GA4 Event’lerini Analiz Etme

SELECT 
    toDate(created_at) as date,
    COUNT(*) as total_purchases,
    JSONExtractString(JSONExtractRaw(e, 'query_params'), 'ep.transaction_id') as transaction_id,
    SUM(CAST(JSONExtractString(JSONExtractRaw(e, 'query_params'), 'epn.value') AS Float64)) as total_value
FROM default.analytics
ARRAY JOIN JSONExtractArrayRaw(properties, 'events') as e
WHERE created_at >= toDateTime('2025-02-17 00:00:00')
    AND created_at < toDateTime('2025-02-19 00:00:00')
    AND event_name = 'network_batch'
    AND JSONExtractString(e, 'source') = 'ga4'
    AND JSONExtractString(e, 'event_name') = 'purchase'
GROUP BY 
    date,
    transaction_id
ORDER BY total_value DESC

Görselleştirme Seçenekleri

Sorgunuzu çalıştırdıktan sonra, sonuçları çeşitli formatlarda görselleştirebilirsiniz:

Tablo Görünümü

Ham verileri tablo formatında görüntüler. Detaylı veri keşfi veya bireysel kayıtları görmeniz gerektiğinde en iyisidir.

Çubuk Grafiği

Kategoriler arasında değerleri karşılaştırmak için kullanışlıdır. Verileri sayfa, ülke veya cihaz tipi gibi boyutlara göre gruplandıran sorgular için iyidir.

Çizgi Grafiği

Zaman içindeki trendleri göstermek için idealdir. Tarih veya zaman damgası sütunları içeren sorgular için en iyisidir.

Skor Kartı

Toplam satın alma, ortalama gelir veya kullanıcı sayısı gibi genellikle bir anahtar metrik olan tek bir değer gösterir. Dashboard özet metrikleri için iyidir.

Sorun Giderme ve Optimizasyon

Yaygın Sorunlar

  1. Gerekli Tarih Aralığı Eksik: Tüm sorgular bir tarih aralığı filtresi içermelidir:

    • WHERE created_at BETWEEN toDateTime(?) AND toDateTime(?) eklediğinizden emin olun
    • ? yer tutucularını gerçek tarih değerleriyle değiştirin
  2. Sorgu Zaman Aşımı: Sorgunuz çalıştırılmak için çok uzun sürerse:

    • Daha spesifik filtreler ekleyin
    • Tarih aralığını daraltın
    • LIMIT cümlesi ekleyin
    • Daha verimli JSON çıkarma kullanın
  3. Sonuç Yok: Sorgunuz hiç veri döndürmüyorsa:

    • Tarih aralığınızı kontrol edin - dönem için veri olduğundan emin olun
    • Event adlarını ve property yollarını doğrulayın
    • Herhangi bir veri olup olmadığını görmek için filtreleri basitleştirin
  4. JSON Çıkarma Hataları: İç içe verilere erişirken sorun yaşıyorsanız:

    • Veri tipi için doğru çıkarma fonksiyonunu kullanın
    • Yolu tam olarak kontrol edin
    • ClickHouse’da dizi indekslerinin 0 değil 1’den başladığını unutmayın

Performans İpuçları

  1. Belirli Tarih Aralıkları Kullanın: Taranan veri miktarını sınırlamak için her zaman bir tarih filtresi ekleyin.

  2. Erken Filtreleyin: WHERE ifadelerini sorgu mantığınızda mümkün olduğunca erken uygulayın.

  3. Sonuç Kümelerini Sınırlayın: Sadece ihtiyacınız olan verileri döndürmek için LIMIT veya TOP kullanın.

  4. Uygun JSON Çıkarıcıları Kullanın: Çıkarıcıyı veri tipiyle eşleştirin (String, Int, Float).

  5. Karmaşık Alt Sorgulardan Kaçının: Daha iyi okunabilirlik ve performans için CTE’ler (WITH cümleleri) kullanın.

En İyi Uygulamalar

  1. Her Zaman Tarih Aralığı Ekleyin: Her sorgu, BETWEEN toDateTime(?) AND toDateTime(?) kullanarak gerekli tarih aralığı filtresini içermelidir.

  2. Sorgularınızı Belgelendirin: Karmaşık mantığı açıklamak için yorumlar ekleyin.

  3. Anlamlı Takma Adlar Kullanın: Sütunlara, verilerin neyi temsil ettiğini yansıtan açık isimler verin.

  4. Sorgularınızı Biçimlendirin: Okunabilirlik için tutarlı girinti ve satır sonları kullanın.

  5. Aşamalı Test Edin: Karmaşık sorguları adım adım oluşturun, her aşamada sonuçları doğrulayın.

  6. Test Edilmiş Modelleri Yeniden Kullanın: Gelecekte kullanmak üzere çalışan sorgu modellerini kaydedin.

  7. NULL Değerleri İşleyin: JSON çıkarma veya hesaplamalarda potansiyel NULL değerlerini her zaman işleyin.

  8. WITH Cümleleri Kullanın: Karmaşık sorgular için mantığınızı daha açık hale getirmek üzere CTE’ler (WITH cümleleri) kullanın.

  9. Uygun JSON Çıkarma Fonksiyonları Kullanın:

    • Metin için JSONExtractString
    • Ondalık sayılar için JSONExtractFloat
    • Tam sayılar için JSONExtractInt
    • Boolean değerler için JSONExtractBool
  10. Dizi İndekslemeyi Unutmayın: Dizilerle çalışırken, indekslemenin 0 değil 1’den başladığını unutmayın

  11. Çıkarmaları Doğru Şekilde Zincirleyin: İç içe özellikler için, çıkarma fonksiyonlarını dış elementlerden iç elementlere doğru zincirleyin

  12. Geliştirme Sırasında LIMIT Kullanın: Geliştirme sırasında büyük miktarda veri işlemekten kaçınmak için LIMIT cümleleri eklemeyi düşünün