13 KiB
Query Implementation Plan for Killmail Analytics
Objective
Build an interactive analytics interface where every aggregation table can be filtered by clicking rows, progressively narrowing down the dataset.
Core Concept: Progressive Filtering
- Start with ALL killmails displayed
- Each table shows a different dimension breakdown
- Clicking ANY row in ANY table adds it as a filter
- All tables re-aggregate with the new filter set
- Filters are cumulative and removable
Tables to Display
1. Time Aggregations
Table A: By Hour of Day
SELECT
kill_hour,
count() as kill_count,
count(DISTINCT victim_character_id) as unique_victims,
count(DISTINCT solar_system_id) as systems_hit
FROM killmails
WHERE <active_filters>
GROUP BY kill_hour
ORDER BY kill_hour
Clickable column: kill_hour → adds filter kill_hour = X
Table B: By Day of Week
SELECT
kill_day_of_week,
CASE kill_day_of_week
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
WHEN 7 THEN 'Sunday'
END as day_name,
count() as kill_count,
count(DISTINCT victim_character_id) as unique_victims
FROM killmails
WHERE <active_filters>
GROUP BY kill_day_of_week
ORDER BY kill_day_of_week
Clickable column: kill_day_of_week → adds filter kill_day_of_week = X
Table C: By Date
SELECT
kill_date,
count() as kill_count,
count(DISTINCT victim_character_id) as unique_victims,
count(DISTINCT solar_system_id) as systems_hit
FROM killmails
WHERE <active_filters>
GROUP BY kill_date
ORDER BY kill_date DESC
Clickable column: kill_date → adds filter kill_date = 'YYYY-MM-DD'
Table D: By Month
SELECT
toYYYYMM(killmail_time) as month,
count() as kill_count,
count(DISTINCT victim_character_id) as unique_victims
FROM killmails
WHERE <active_filters>
GROUP BY month
ORDER BY month DESC
Clickable column: month → adds filter toYYYYMM(killmail_time) = YYYYMM
2. Location Aggregations
Table E: By Solar System
SELECT
solar_system_name,
region_name,
security,
count() as kill_count,
count(DISTINCT victim_character_id) as unique_victims,
count(DISTINCT victim_ship_type_name) as ship_variety
FROM killmails
WHERE <active_filters>
GROUP BY solar_system_name, region_name, security
ORDER BY kill_count DESC
LIMIT 100
Clickable columns:
solar_system_name→ adds filtersolar_system_name = 'X'region_name→ adds filterregion_name = 'X'
Table F: By Region
SELECT
region_name,
count() as kill_count,
count(DISTINCT solar_system_name) as systems_with_kills,
count(DISTINCT victim_character_id) as unique_victims
FROM killmails
WHERE <active_filters>
GROUP BY region_name
ORDER BY kill_count DESC
Clickable column: region_name → adds filter region_name = 'X'
Table G: By Constellation
SELECT
constellation_name,
region_name,
count() as kill_count,
count(DISTINCT solar_system_name) as systems_in_constellation
FROM killmails
WHERE <active_filters>
GROUP BY constellation_name, region_name
ORDER BY kill_count DESC
Clickable column: constellation_name → adds filter constellation_name = 'X'
Table H: By Security Status
SELECT
CASE
WHEN security >= 0.5 THEN 'High Sec'
WHEN security > 0.0 THEN 'Low Sec'
ELSE 'Null Sec'
END as sec_status,
count() as kill_count,
count(DISTINCT solar_system_name) as systems
FROM killmails
WHERE <active_filters>
GROUP BY sec_status
ORDER BY kill_count DESC
Clickable column: sec_status → adds filter based on security range
3. Ship Aggregations
Table I: By Victim Ship
SELECT
victim_ship_type_name,
victim_ship_group_name,
victim_ship_category_name,
count() as kill_count,
count(DISTINCT victim_character_id) as unique_pilots_killed
FROM killmails
WHERE <active_filters>
GROUP BY victim_ship_type_name, victim_ship_group_name, victim_ship_category_name
ORDER BY kill_count DESC
LIMIT 100
Clickable columns:
victim_ship_type_name→ adds filtervictim_ship_type_name = 'X'victim_ship_group_name→ adds filtervictim_ship_group_name = 'X'victim_ship_category_name→ adds filtervictim_ship_category_name = 'X'
Table J: By Attacker Ship
SELECT
ship_type_name,
ship_group_name,
count() as times_used,
count(DISTINCT killmail_id) as kills_participated,
sum(damage_done) as total_damage,
countIf(final_blow) as final_blows
FROM killmail_attackers
WHERE killmail_id IN (
SELECT killmail_id FROM killmails WHERE <active_filters>
)
GROUP BY ship_type_name, ship_group_name
ORDER BY kills_participated DESC
LIMIT 100
Clickable columns:
ship_type_name→ adds filter on attacker shipship_group_name→ adds filter on attacker ship group
4. Player/Entity Aggregations
Table K: By Victim Character
SELECT
victim_character_name,
victim_corporation_name,
victim_alliance_name,
count() as deaths,
count(DISTINCT victim_ship_type_name) as ships_lost
FROM killmails
WHERE <active_filters>
GROUP BY victim_character_name, victim_corporation_name, victim_alliance_name
ORDER BY deaths DESC
LIMIT 100
Clickable columns:
victim_character_name→ filter by victimvictim_corporation_name→ filter by victim corpvictim_alliance_name→ filter by victim alliance
Table L: By Victim Corporation
SELECT
victim_corporation_name,
victim_alliance_name,
count() as losses,
count(DISTINCT victim_character_name) as members_killed
FROM killmails
WHERE <active_filters>
GROUP BY victim_corporation_name, victim_alliance_name
ORDER BY losses DESC
LIMIT 100
Clickable columns:
victim_corporation_name→ filter by victim corpvictim_alliance_name→ filter by victim alliance
Table M: By Victim Alliance
SELECT
victim_alliance_name,
count() as losses,
count(DISTINCT victim_corporation_name) as corps_hit,
count(DISTINCT victim_character_name) as members_killed
FROM killmails
WHERE <active_filters> AND victim_alliance_name != ''
GROUP BY victim_alliance_name
ORDER BY losses DESC
LIMIT 100
Clickable column: victim_alliance_name → filter by victim alliance
Table N: By Attacker Character
SELECT
character_name,
corporation_name,
alliance_name,
count(DISTINCT killmail_id) as kills_participated,
countIf(final_blow) as final_blows,
sum(damage_done) as total_damage
FROM killmail_attackers
WHERE killmail_id IN (
SELECT killmail_id FROM killmails WHERE <active_filters>
)
GROUP BY character_name, corporation_name, alliance_name
ORDER BY kills_participated DESC
LIMIT 100
Clickable columns:
character_name→ filter by attackercorporation_name→ filter by attacker corpalliance_name→ filter by attacker alliance
Table O: By Attacker Corporation
SELECT
corporation_name,
alliance_name,
count(DISTINCT killmail_id) as kills_participated,
count(DISTINCT character_name) as members_involved
FROM killmail_attackers
WHERE killmail_id IN (
SELECT killmail_id FROM killmails WHERE <active_filters>
)
GROUP BY corporation_name, alliance_name
ORDER BY kills_participated DESC
LIMIT 100
Clickable columns:
corporation_name→ filter by attacker corpalliance_name→ filter by attacker alliance
Table P: By Attacker Alliance
SELECT
alliance_name,
count(DISTINCT killmail_id) as kills_participated,
count(DISTINCT corporation_name) as corps_involved,
count(DISTINCT character_name) as members_involved
FROM killmail_attackers
WHERE killmail_id IN (
SELECT killmail_id FROM killmails WHERE <active_filters>
) AND alliance_name != ''
GROUP BY alliance_name
ORDER BY kills_participated DESC
LIMIT 100
Clickable column: alliance_name → filter by attacker alliance
5. Module/Item Aggregations (THE COMPLEX ONES)
Table Q: By Module Slot Type
SELECT
slot_type,
count() as items_fitted,
count(DISTINCT killmail_id) as ships_with_slot,
count(DISTINCT item_type_name) as module_variety
FROM killmail_items
WHERE killmail_id IN (
SELECT killmail_id FROM killmails WHERE <active_filters>
)
GROUP BY slot_type
ORDER BY items_fitted DESC
Clickable column: slot_type → filter to show only items in this slot type
Table R: By Module (within selected slot type)
This table only appears after slot_type is filtered
SELECT
item_type_name,
item_group_name,
item_category_name,
count() as times_fitted,
count(DISTINCT killmail_id) as ships_with_module
FROM killmail_items
WHERE killmail_id IN (
SELECT killmail_id FROM killmails WHERE <active_filters>
)
AND slot_type = <selected_slot_type>
GROUP BY item_type_name, item_group_name, item_category_name
ORDER BY times_fitted DESC
LIMIT 100
Clickable columns:
item_type_name→ adds filterhas this module in this slotitem_group_name→ filter by module groupitem_category_name→ filter by module category
Table S: Module Co-occurrence (Advanced)
Shows what modules appear together with a selected module
WITH selected_ships AS (
SELECT DISTINCT killmail_id
FROM killmail_items
WHERE item_type_name = <selected_module>
AND slot_type = <selected_slot_type>
AND killmail_id IN (
SELECT killmail_id FROM killmails WHERE <active_filters>
)
)
SELECT
item_type_name,
slot_type,
count() as times_fitted_together,
count() * 100.0 / (SELECT count() FROM selected_ships) as percentage
FROM killmail_items
WHERE killmail_id IN selected_ships
AND NOT (item_type_name = <selected_module> AND slot_type = <selected_slot_type>)
GROUP BY item_type_name, slot_type
ORDER BY times_fitted_together DESC
LIMIT 50
Filter Implementation Logic
Filter State Structure
{
"kill_hour": [13, 14],
"solar_system_name": ["Amamake", "Dal"],
"victim_ship_type_name": ["Algos"],
"region_name": ["Heimatar"],
"slot_type": ["mid"],
"has_module": {
"slot": "mid",
"module_name": "Stasis Webifier II"
}
}
Building WHERE Clause
WHERE 1=1
AND kill_hour IN (13, 14)
AND solar_system_name IN ('Amamake', 'Dal')
AND victim_ship_type_name = 'Algos'
AND region_name = 'Heimatar'
AND killmail_id IN (
SELECT DISTINCT killmail_id
FROM killmail_items
WHERE slot_type = 'mid'
AND item_type_name = 'Stasis Webifier II'
)
Special Case: Module Filters
When filtering by "has module X in slot Y":
AND killmail_id IN (
SELECT killmail_id
FROM killmail_items
WHERE slot_type = 'Y'
AND item_type_name = 'X'
)
For multiple modules in same slot (AND condition):
AND killmail_id IN (
SELECT killmail_id
FROM killmail_items
WHERE slot_type = 'mid'
AND item_type_name IN ('Module A', 'Module B')
GROUP BY killmail_id
HAVING count(DISTINCT item_type_name) = 2
)
Additional Aggregatable Columns
We CAN aggregate on:
- ✅
kill_hour,kill_day_of_week,kill_date,month - ✅
solar_system_name,constellation_name,region_name,security - ✅
victim_ship_type_name,victim_ship_group_name,victim_ship_category_name - ✅
victim_character_name,victim_corporation_name,victim_alliance_name - ✅ Attacker ships (from
killmail_attackers) - ✅ Attacker characters/corps/alliances (from
killmail_attackers) - ✅
slot_type,item_type_name,item_group_name,item_category_name(fromkillmail_items) - ✅ Weapon types (from
killmail_attackers.weapon_type_name) - ✅
final_blow(bool - can group by attacker ships that got final blow)
Performance Notes (ignore for now):
- These queries will be FAST on 10GB
- ClickHouse will handle millions of rows in milliseconds
- The subquery filters (
killmail_id IN (...)) are fine - ClickHouse optimizes these well - LIMIT 100 keeps UI snappy even with huge result sets
Implementation Checklist for Clanker
- Query Template System: Build WHERE clause dynamically from filter state
- Execute ALL aggregation queries on every filter change
- Display tables with clickable rows/columns
- Update filter state on click
- Re-run all queries with new filters
- Show active filters with remove buttons
- Handle module filtering specially (slot + type combo)
That's it. Every table. Every clickable dimension. Maximum analytics power.