SQL data exploration

This commit is contained in:
Pawel Sarkowicz
2026-03-22 09:32:47 -04:00
parent 3d38d13889
commit 9c12605ea0

847
sql/01_data_exploration.sql Normal file
View File

@@ -0,0 +1,847 @@
/*
* TB2 Data exploration
*
* We will set out to the understand the database structure,
* and to understand how this data actually produces climbs on a Tension Board.
*
*
* This data was downloaded via boardlib (https://github.com/lemeryfertitta/BoardLib) on 2026-03-14.
* It is clear from the `kits` table that it was updated on 2026-01-22 (well, most of it).
*/
--------------------------------------------------------------------------------
/*
* Understanding the board
*
* Goal:
* 1. Shallow dive into the database structure
* 2. Identify tha main tables
* 3. Understand how the tension board data works, and how frames are mapped to board
*/
SELECT 'android_metadata' AS table_name, COUNT(*) AS rows FROM android_metadata
UNION ALL SELECT 'ascents', COUNT(*) FROM ascents
UNION ALL SELECT 'attempts', COUNT(*) FROM attempts
UNION ALL SELECT 'beta_links', COUNT(*) FROM beta_links
UNION ALL SELECT 'bids', COUNT(*) FROM bids
UNION ALL SELECT 'circuits', COUNT(*) FROM circuits
UNION ALL SELECT 'circuits_climbs', COUNT(*) FROM circuits_climbs
UNION ALL SELECT 'climb_cache_fields', COUNT(*) FROM climb_cache_fields
UNION ALL SELECT 'climb_random_positions', COUNT(*) FROM climb_random_positions
UNION ALL SELECT 'climb_stats', COUNT(*) FROM climb_stats
UNION ALL SELECT 'climbs', COUNT(*) FROM climbs
UNION ALL SELECT 'difficulty_grades', COUNT(*) FROM difficulty_grades
UNION ALL SELECT 'holes', COUNT(*) FROM holes
UNION ALL SELECT 'kits', COUNT(*) FROM kits
UNION ALL SELECT 'layouts', COUNT(*) FROM layouts
UNION ALL SELECT 'leds', COUNT(*) FROM leds
UNION ALL SELECT 'placement_roles', COUNT(*) FROM placement_roles
UNION ALL SELECT 'placements', COUNT(*) FROM placements
UNION ALL SELECT 'product_sizes', COUNT(*) FROM product_sizes
UNION ALL SELECT 'product_sizes_layouts_sets', COUNT(*) FROM product_sizes_layouts_sets
UNION ALL SELECT 'products', COUNT(*) FROM products
UNION ALL SELECT 'products_angles', COUNT(*) FROM products_angles
UNION ALL SELECT 'sets', COUNT(*) FROM sets
UNION ALL SELECT 'shared_syncs', COUNT(*) FROM shared_syncs
UNION ALL SELECT 'tags', COUNT(*) FROM tags
UNION ALL SELECT 'user_permissions', COUNT(*) FROM user_permissions
UNION ALL SELECT 'user_syncs', COUNT(*) FROM user_syncs
UNION ALL SELECT 'users', COUNT(*) FROM users
UNION ALL SELECT 'walls', COUNT(*) FROM walls
UNION ALL SELECT 'walls_sets', COUNT(*) FROM walls_sets
ORDER BY rows DESC;
/*
table_name |rows |
--------------------------+------+
climb_stats |147046|
climbs |128762|
climb_cache_fields | 90670|
beta_links | 9500|
leds | 3388|
placements | 1299|
holes | 967|
difficulty_grades | 39|
attempts | 38|
product_sizes_layouts_sets| 36|
kits | 25|
products_angles | 25|
shared_syncs | 15|
product_sizes | 9|
placement_roles | 8|
sets | 6|
layouts | 3|
products | 2|
android_metadata | 1|
ascents | 0|
bids | 0|
circuits | 0|
circuits_climbs | 0|
climb_random_positions | 0|
tags | 0|
user_permissions | 0|
user_syncs | 0|
users | 0|
walls | 0|
walls_sets | 0|
*/
/*
* It is clear that climb_stats (147k), climbs (128k), and climb_cache_fields (90k) are the most important.
* beta_links just includes instagram links showing beta.
*
* Some Obesrvations:
* - climb_stats has more entries than climbs -- potentially multiple entries per climb? Maybe same climb at different angles?
* - placements/holes are likely reference tables for the physical board --hole positions, holds, etc.
* - placement roles might be start/finish/middle/foot hold, etc. Or hold type?
* - plenty of empty tables. Some should correspond to a specific user if you download the DB using broadlib with the -u flag. I couldn't get it working though.
*
* Let's start by looking at some sample data.
*/
--------------------------------------------------------
/*
* CLIMBS
*/
SELECT * FROM climbs;
/*
uuid |layout_id|setter_id|setter_username|name |description |hsm|edge_left|edge_right|edge_bottom|edge_top|angle|frames_count|frames_pace|frames |is_draft|is_listed|created_at |is_nomatch|
--------------------------------+---------+---------+---------------+--------------+------------+---+---------+----------+-----------+--------+-----+------------+-----------+-----------------------------------------+--------+---------+--------------------------+----------+
00163801596af1064d549ad75b684539| 9| 33802|vinsewah |Duroxmanie 2.0|No matching | 3| 8| 88| 32| 128| | 1| 0|p3r4p29r2p59r1p65r2p75r3p89r2p157r4p158r4| 0| 1|2021-02-16 09:13:28.000000| 1|
001945feb7509ce231c9d8b237082a39| 9| 30521|ssssss |1 am | | 3| 24| 72| 56| 128| | 1| 0|p18r1p22r1p57r2p70r2p149r3p161r4p162r4 | 0| 1|2021-02-13 01:53:03.000000| 0|
002078ce5b07166d80e87d2cafc94dab| 9| 61740|rockindude |test69 |No matching.| 7| 24| 64| 64| 152| | 1| 0|p16r2p49r1p70r2p83r3p127r2p140r2p191r1 | 0| 1|2021-12-22 03:41:04.000000| 1|
0027cc6eea485099809f5336a0452564| 9| 56399|memphisben |Pre Game |No matching.| 1| 8| 40| 40| 128| | 1| 0|p22r1p49r1p74r3p76r4p78r2p80r2 | 0| 1|2021-02-13 01:52:54.000000| 1|
002e2db25b124ff5719afdb2c6732b2c| 9| 33924|jfisch040 |Yoooooooooo | | 9| 16| 48| 4| 152| | 1| 0|p1r3p14r2p67r1p73r2p80r2p279r4 | 0| 1|2021-02-13 01:52:57.000000| 0|
* The frams column is what actually determines the holds on the climb, and what role they are.
* There are some climb characteristics (name, desceription, whether or not matching is allowed, setter info, edges, whether it is listed).
* The UUID is how we link the specifc climb to the other tables.
* What is hsm?
*/
SELECT * FROM climb_cache_fields;
/*
climb_uuid |ascensionist_count|display_difficulty|quality_average|
--------------------------------+------------------+------------------+---------------+
0004edf6aeac9618d96a3b949cd9a724| 2| 24.0| 3.0|
00072fbd8c22711ef3532a5017c1a5c2| 4| 19.25| 3.0|
00178ae931e482c3e6337d86d761936b| 1| 27.0| 3.0|
0020974d7ee7f1b6d78b44a70f3fa27b| 1| 24.0| 3.0|
0024b68ebc5cbbcfbe653ec4ed224271| 1| 23.0| 3.0|
*
* climb_uuid, ascentionist_count, display difficulty, and quality_average.
*/
SELECT * FROM climb_stats;
/*
climb_uuid |angle|display_difficulty|benchmark_difficulty|ascensionist_count|difficulty_average|quality_average|fa_username |fa_at |
--------------------------------+-----+------------------+--------------------+------------------+------------------+---------------+--------------------+-------------------+
0004edf6aeac9618d96a3b949cd9a724| 40| 24.0| | 2| 24.0| 3.0|david.p.kunz |2020-03-23 23:52:37|
00072fbd8c22711ef3532a5017c1a5c2| 25| 19.25| | 4| 19.25| 3.0|free.and.independent|2019-10-05 01:55:14|
0008d8af4649234054bea434aaeabaab| 45| 20.0| | 2| 20.0| 2.0|judemandudeman |2018-01-30 03:18:13|
000eb831d3a1e92ea8fdec2518fd77d3| 20| 18.0| | 1| 18.0| 3.0|dasruch17 |2019-03-15 15:46:06|
000eb831d3a1e92ea8fdec2518fd77d3| 40| 23.0| | 1| 23.0| 3.0|hunter.tension |2021-06-27 22:41:10|
*
* So per UUID, we have a lot of the same info as climb_cache_fields.
* We also have angle and first ascent information
*
*
*
* Why are there more climb_stats than climbs?
* Let's see if it is due to multiple angles per climb.
*/
SELECT
cs.climb_uuid,
COUNT(*) AS angle_count
FROM climb_stats cs
GROUP BY cs.climb_uuid
ORDER BY angle_count DESC;
/*
climb_uuid |angle_count|
--------------------------------+-----------+
0227943857CA4D55849E8D351775B10A| 14|
18E0834CBBB64952AE12BB7DD7F56E28| 14|
197A52F20F424C3DB935993E2385758D| 14|
2048D3DB80DD443BA4BB37F263984929| 14|
2A740F4239AD4D498F65780626D7CECA| 14|
*
*
* Yep, some UUIDs correspond to multiple angles.
*
* How many climbs are there if we don't take angle into account?
*
*/
SELECT COUNT(DISTINCT climb_uuid) FROM climb_stats;
/*
*
COUNT(DISTINCT climb_uuid)|
--------------------------+
90494|
* So 90k climbs in total.
*
* Let's take a look at difficulty_grades.
*/
SELECT * FROM difficulty_grades;
/*
difficulty|boulder_name|route_name|is_listed|
----------+------------+----------+---------+
1|1a/V0 |2b/5.1 | 0|
2|1b/V0 |2c/5.2 | 0|
3|1c/V0 |3a/5.3 | 0|
4|2a/V0 |3b/5.3 | 0|
5|2b/V0 |3c/5.4 | 0|
6|2c/V0 |4a/5.5 | 0|
7|3a/V0 |4b/5.6 | 0|
8|3b/V0 |4c/5.7 | 0|
9|3c/V0 |5a/5.8 | 0|
10|4a/V0 |5b/5.9 | 1|
11|4b/V0 |5c/5.10a | 1|
12|4c/V0 |6a/5.10b | 1|
13|5a/V1 |6a+/5.10c | 1|
14|5b/V1 |6b/5.10d | 1|
15|5c/V2 |6b+/5.11a | 1|
16|6a/V3 |6c/5.11b | 1|
17|6a+/V3 |6c+/5.11c | 1|
18|6b/V4 |7a/5.11d | 1|
19|6b+/V4 |7a+/5.12a | 1|
20|6c/V5 |7b/5.12b | 1|
21|6c+/V5 |7b+/5.12c | 1|
22|7a/V6 |7c/5.12d | 1|
23|7a+/V7 |7c+/5.13a | 1|
24|7b/V8 |8a/5.13b | 1|
25|7b+/V8 |8a+/5.13c | 1|
26|7c/V9 |8b/5.13d | 1|
27|7c+/V10 |8b+/5.14a | 1|
28|8a/V11 |8c/5.14b | 1|
29|8a+/V12 |8c+/5.14c | 1|
30|8b/V13 |9a/5.14d | 1|
31|8b+/V14 |9a+/5.15a | 1|
32|8c/V15 |9b/5.15b | 1|
33|8c+/V16 |9b+/5.15c | 1|
34|9a/V17 |9c/5.15d | 0|
35|9a+/V18 |9c+/5.16a | 0|
36|9b/V19 |10a/5.16b | 0|
37|9b+/V20 |10a+/5.16c| 0|
38|9c/V21 |10b/5.16d | 0|
39|9c+/V22 |10b+/5.17a| 0|
*
* So this just tells us what the numeric value corresponds to in terms of a boulder grade or a route grade.
*/
--------------------------------------------------------
/*
* BOARDS, PLACEMENTS and HOLDS
*/
SELECT * FROM placements;
/*
id|layout_id|hole_id|set_id|default_placement_role_id|
--+---------+-------+------+-------------------------+
1| 9| 2| 8| 3|
2| 9| 10| 8| 3|
3| 9| 317| 8| 1|
4| 9| 325| 8| 1|
5| 9| 320| 8| 1|
*
* So we have specific layouts, hole_id, set_id, and default_palcement_role_id.
* Let's examine each of these, starting with layout.
*/
SELECT * FROM layouts;
/*
id|product_id|name |instagram_caption|is_mirrored|is_listed|password|created_at |
--+----------+----------------------+-----------------+-----------+---------+--------+--------------------------+
10| 5|Tension Board 2 Mirror| | 1| 1| |2022-08-19 14:52:19.570731|
11| 5|Tension Board 2 Spray | | 0| 1| |2022-10-26 03:42:45.736011|
9| 4|Original Layout | | 1| 1| |2017-01-01 00:45:51.000000|
*
* So this tells us which specific board, along with whether or not it is mirrored.
* So both TB1 and TB2 Mirror are, while TB2 Spray is not.
*
* There is a distinction between product_id. I imagine it is just TB1 vs TB2.
*
*/
SELECT * FROM products;
/*
id|name |is_listed|password|min_count_in_frame|max_count_in_frame|
--+---------------+---------+--------+------------------+------------------+
4|Tension Board | 1| | 2| 35|
5|Tension Board 2| 1| | 2| 35|
*
* Yep, product ID just tells us which board we're working with.
*
* May as well see product_sizes, produce_sizes_layouts_sets, and products_angles while we're at it.
* Let's start with the latter, since it's self-explanatory.
*/
SELECT * FROM products_angles;
/*
product_id|angle|
----------+-----+
4| 20|
4| 25|
4| 30|
4| 35|
4| 40|
4| 45|
4| 50|
5| 20|
5| 25|
5| 30|
5| 35|
5| 40|
5| 45|
5| 50|
5| 55|
4| 0|
4| 5|
4| 10|
4| 15|
5| 0|
5| 5|
5| 10|
5| 15|
5| 60|
5| 65|
*
* Yep, just tells us the angles that the TB1 and TB2 can go.
* Let's look at the sizes.
*/
SELECT * FROM product_sizes;
/*
id|product_id|edge_left|edge_right|edge_bottom|edge_top|name |description |image_filename |position|is_listed|
--+----------+---------+----------+-----------+--------+-----------------+---------------------------------+----------------------+--------+---------+
1| 4| 0| 96| 0| 156|Full Wall |Rows: KB1, KB2, 1-18¶Columns: A-K|product_sizes/1.png | 0| 1|
2| 4| 0| 96| 4| 156|Half Kickboard |Rows: KB2, 1-18¶Columns: A-K |product_sizes/2.png | 1| 1|
3| 4| 0| 96| 8| 156|No Kickboard |Rows: 1-18¶Columns: A-K |product_sizes/3-v2.png| 2| 1|
4| 4| 0| 96| 8| 132|Short |Rows: 1-15¶Columns: A-K |product_sizes/4-v2.png| 3| 1|
5| 4| 16| 80| 8| 132|Short & Narrow |Rows: 1-15¶Columns: B.5-I.5 |product_sizes/5-v3.png| 4| 1|
6| 5| -68| 68| 0| 144|12 high x 12 wide| |product_sizes/6.png | 1| 1|
7| 5| -68| 68| 0| 120|10 high x 12 wide| |product_sizes/7.png | 2| 1|
8| 5| -44| 44| 0| 144|12 high x 8 wide | |product_sizes/8.png | 3| 1|
9| 5| -44| 44| 0| 120|10 high x 8 wide | |product_sizes/9.png | 4| 1|
*
* This just gives us product_size_id, and some info.
* We'll be mainly interested in the TB2 Mirror 12x12, so we want product_size_id=6.
*/
SELECT * FROM product_sizes_layouts_sets;
/*
id|product_size_id|layout_id|set_id|image_filename |is_listed|
--+---------------+---------+------+------------------------------------------------+---------+
1| 1| 9| 8|product_sizes_layouts_sets/1.png | 1|
2| 1| 9| 9|product_sizes_layouts_sets/2.png | 1|
3| 1| 9| 10|product_sizes_layouts_sets/3.png | 1|
4| 1| 9| 11|product_sizes_layouts_sets/4.png | 1|
5| 2| 9| 8|product_sizes_layouts_sets/5.png | 1|
6| 2| 9| 9|product_sizes_layouts_sets/6.png | 1|
7| 2| 9| 10|product_sizes_layouts_sets/7.png | 1|
8| 2| 9| 11|product_sizes_layouts_sets/8.png | 1|
9| 3| 9| 8|product_sizes_layouts_sets/9.png | 1|
10| 3| 9| 9|product_sizes_layouts_sets/10.png | 1|
11| 3| 9| 10|product_sizes_layouts_sets/11.png | 1|
12| 3| 9| 11|product_sizes_layouts_sets/12.png | 1|
13| 4| 9| 8|product_sizes_layouts_sets/13.png | 1|
14| 4| 9| 9|product_sizes_layouts_sets/14.png | 1|
15| 4| 9| 10|product_sizes_layouts_sets/15.png | 1|
16| 4| 9| 11|product_sizes_layouts_sets/16.png | 1|
17| 5| 9| 8|product_sizes_layouts_sets/17.png | 1|
18| 5| 9| 9|product_sizes_layouts_sets/18.png | 1|
19| 5| 9| 10|product_sizes_layouts_sets/19.png | 1|
20| 5| 9| 11|product_sizes_layouts_sets/20.png | 1|
23| 7| 10| 12|product_sizes_layouts_sets/23.png | 1|
25| 8| 10| 12|product_sizes_layouts_sets/25.png | 1|
26| 8| 10| 13|product_sizes_layouts_sets/26.png | 1|
27| 9| 10| 12|product_sizes_layouts_sets/27.png | 1|
28| 9| 10| 13|product_sizes_layouts_sets/28.png | 1|
29| 6| 11| 12|product_sizes_layouts_sets/12x12-tb2-wood.png | 1|
30| 6| 11| 13|product_sizes_layouts_sets/12x12-tb2-plastic.png| 1|
31| 7| 11| 12|product_sizes_layouts_sets/12x10-tb2-wood.png | 1|
32| 7| 11| 13|product_sizes_layouts_sets/12x10-tb2-plastic.png| 1|
33| 8| 11| 12|product_sizes_layouts_sets/8x12-tb2-wood.png | 1|
34| 8| 11| 13|product_sizes_layouts_sets/8x12-tb2-plastic.png | 1|
35| 9| 11| 12|product_sizes_layouts_sets/8x10-tb2-wood.png | 1|
36| 9| 11| 13|product_sizes_layouts_sets/8x10-tb2-plastic.png | 1|
21| 6| 10| 12|product_sizes_layouts_sets/21-2.png | 1|
22| 6| 10| 13|product_sizes_layouts_sets/22-2.png | 1|
24| 7| 10| 13|product_sizes_layouts_sets/24-2.png | 1|
*
* These tell the product_size_id, which might be useful later.
* We'll mostly be interested in the TB2 12x12 Mirror, so thats product_id=6, layout_id=10.
* This tells us which images we'll want to look at for later. We'll make some heat maps, so 21-2.png and 22-2.png are out pictures.
* We'll combine them into one in GIMP and call it tb2_board_12x12_composite.png
*
* Back to understanding the placements. We'll look at holes next.
*/
SELECT * FROM holes;
/*
id|product_id|name|x |y |mirrored_hole_id|mirror_group|
--+----------+----+--+---+----------------+------------+
1| 4|A,18| 8|152| 11| 0|
2| 4|B,18|16|152| 10| 0|
3| 4|C,18|24|152| 9| 0|
4| 4|D,18|32|152| 8| 0|
5| 4|E,18|40|152| 7| 0|
*
* The coordinates must be the position on the board.
* These make sense with the product sizes above -- it is clear what the boundaries are (from the edge features).
*
* With the TB1 and TB2 Mirror, you can mirror climbs. So the mirror_hole_id must be where the associated mirror hole is.
* Not sure about the mirror_group.
*
* Let's look at sets next.
*/
SELECT * FROM sets;
/*
id|name |hsm|
--+--------+---+
8|Set A | 1|
9|Set B | 2|
10|Set C | 4|
11|Foot Set| 8|
12|Wood | 1|
13|Plastic | 2|
*
* So these tell us the corresponding set of the board.
* Any board will often use a combination: for example, the TB2 Mirror uses both wood and plastic.
* No idea what hsm means. Probably something to do with "hold set ____"
*
* Next let's understand this default_placement_id. We'll look at placement_roles.
*/
SELECT * FROM placement_roles;
/*
id|product_id|position|name |full_name|led_color|screen_color|
--+----------+--------+------+---------+---------+------------+
1| 4| 1|start |Start |00FF00 |00DD00 |
3| 4| 3|finish|Finish |FF0000 |FF0000 |
4| 4| 4|foot |Foot Only|FF00FF |FF00FF |
5| 5| 1|start |Start |00FF00 |00DD00 |
7| 5| 3|finish|Finish |FF0000 |FF0000 |
8| 5| 4|foot |Foot Only|FF00FF |FF00FF |
2| 4| 2|middle|Middle |0000FF |0066FF |
6| 5| 2|middle|Middle |0000FF |0066FF |
*
* These are indeed start/finish/middle/foot, but with 4 being for the TB1 and 4 being for the TB2.
* So r5 = start, r6 = middle, r7 = finish, r8 = foot only on TB2. Similarly with r1-r4 on TB1.
*
* Also tells us which colours are used by the board and the app.
*
* Lastly, let's look at the LEDs table since it is one of the bigger ones, and the LEDs relate to the placements/holes.
*/
SELECT * FROM leds;
/*
id|product_size_id|hole_id|position|
--+---------------+-------+--------+
1| 1| 379| 0|
2| 1| 389| 1|
3| 1| 378| 2|
4| 1| 388| 3|
5| 1| 377| 4|
*
* product_size_id tells us which size of board this led belongs to, and the hole_id tells us the corresponding hole.
*/
--------------------------
/*
* STRAGGLERS
*
* Let's take a look at some of the non-empty tables that are left over.
*
*/
SELECT * FROM beta_links;
/*
* yep, just instagram links of people doing the climb..'
*
* what about attemps?
*/
SELECT * FROM attempts;
/*
id|position|name |
--+--------+---------+
1| 1|Flash |
2| 2|2 tries |
3| 3|3 tries |
4| 4|4 tries |
5| 5|5 tries |
6| 6|6 tries |
7| 7|7 tries |
8| 8|8 tries |
9| 9|9 tries |
10| 10|10 tries |
11| 100|1 day |
12| 200|2 days |
13| 300|3 days |
14| 400|4 days |
15| 500|5 days |
16| 600|6 days |
17| 700|7 days |
18| 800|8 days |
19| 900|9 days |
20| 1000|10 days |
21| 10000|1 month |
22| 20000|2 months |
23| 30000|3 months |
24| 40000|4 months |
25| 50000|5 months |
26| 60000|6 months |
27| 70000|7 months |
28| 80000|8 months |
29| 90000|9 months |
30| 100000|10 months|
31| 110000|11 months|
32| 120000|12 months|
33| 1000000|1 year |
34| 2000000|2 years |
35| 3000000|3 years |
36| 4000000|4 years |
37| 5000000|5 years |
0| 0|Unknown |
*
* Not really sure what to make of this table.
*
* Let's do kits next.
*/
SELECT * FROM kits;
/*
serial_number|name |is_autoconnect|is_listed|created_at |updated_at |
-------------+--------------------------+--------------+---------+--------------------------+--------------------------+
84668 |TB2 Spray | 0| 1|2022-10-06 18:37:44.021720|2022-10-06 18:37:44.021720|
84669 |TB2 Mirror | 0| 1|2022-10-06 18:37:33.495216|2022-10-06 18:37:33.495216|
84670 |TB1 | 0| 1|2022-10-06 18:37:22.548024|2022-10-06 18:37:22.548024|
84685 |Tension Board | 1| 1|2023-09-26 01:41:54.577139|2023-09-26 01:41:54.577139|
81114 |Tension Board 20 degrees | 0| 1|2023-12-01 20:48:43.013655|2023-12-13 01:17:00.160708|
84771 |Tension Board 2 40 degrees| 0| 1|2023-12-01 20:48:22.110324|2023-12-13 01:16:36.884910|
84863 |Tension Board 1 | 0| 1|2024-04-05 18:15:05.684248|2024-04-05 18:15:05.684248|
84783 |Tension Board 2 | 0| 1|2024-04-05 18:15:20.260241|2024-04-05 18:15:20.260241|
81106 |Tension Board | 0| 1|2024-07-24 18:18:43.531949|2024-07-24 18:18:43.531949|
84818 |Tension Board 2 | 0| 1|2024-07-24 18:19:00.103068|2024-07-24 18:19:00.103068|
84562 |Tension Board 1 | 0| 1|2025-01-22 01:20:51.718259|2025-01-22 01:20:51.718259|
84911 |Tension Board 2 | 0| 1|2025-01-22 01:21:06.579570|2025-01-22 01:21:06.579570|
84776 |Tension Board 2 | 0| 1|2025-02-26 20:15:08.355773|2025-02-26 20:15:08.355773|
84439 |Tension Board 20 degrees | 0| 1|2023-12-01 20:49:00.365305|2025-04-17 03:00:37.331399|
81199 |Tension Board 1 | 0| 1|2025-05-12 23:08:24.829370|2025-05-12 23:08:24.829370|
841062 |Tension Board 2 | 0| 1|2025-05-12 23:08:41.275933|2025-05-12 23:08:41.275933|
84938 |Tension Board Mirror | 0| 1|2025-09-18 23:12:12.029864|2025-09-18 23:12:12.029864|
84937 |Tension Board Spray | 0| 1|2025-09-18 23:12:24.734650|2025-09-18 23:12:24.734650|
91396 |Tension Board Full | 0| 1|2025-09-22 03:51:35.982550|2025-09-22 03:51:35.982550|
841139 |Tension Board 2 | 0| 1|2025-09-22 03:51:52.379943|2025-09-22 03:51:52.379943|
841066 |Tension Board Mirror | 0| 1|2025-11-30 19:58:24.143556|2025-11-30 19:58:24.143556|
84983 |Tension Board Spray | 0| 1|2025-11-30 19:58:40.803397|2025-11-30 19:58:40.803397|
84870 |Tension Board 2 | 0| 1|2025-12-12 17:52:43.951207|2025-12-12 17:52:43.951207|
841240 |Tension Board Left | 0| 1|2026-01-22 20:08:37.220459|2026-01-22 20:08:37.220459|
91744 |Tension Board Right | 0| 1|2026-01-22 20:08:48.978201|2026-01-22 20:08:48.978201|
*
* Not sure what to make of this table either. I guess just products they have? Some are fixed, some are not, and some are parts of the full board?
*
* Shared syncs next.
*/
SELECT * FROM shared_syncs;
/*
table_name |last_synchronized_at |
--------------------------+--------------------------+
attempts |2024-06-22 23:43:48.952599|
products |2024-06-22 23:43:48.952599|
product_sizes |2024-06-22 23:43:48.952599|
holes |2024-06-22 23:43:48.952599|
leds |2024-06-22 23:43:48.952599|
sets |2024-06-22 23:43:48.952599|
products_angles |2024-06-22 23:43:48.952599|
placements |2024-06-22 23:43:48.952599|
product_sizes_layouts_sets|2024-06-25 18:11:42.775946|
layouts |2025-08-22 00:38:52.971578|
placement_roles |2025-08-23 05:22:16.042123|
climbs |2026-01-31 01:07:23.833934|
climb_stats |2026-01-31 01:22:10.306067|
beta_links |2026-01-09 02:29:20.891517|
kits |2026-01-22 20:08:48.978201|
*
* Possibly when each table in this DB was synced?
*/
SELECT fa_at FROM climb_stats ORDER BY fa_at DESC;
/*
fa_at |
-------------------+
2026-01-31 01:20:12|
2026-01-31 01:11:42|
2026-01-31 01:07:46|
*
* Yep, last logged first ascent agrees with this.
*
* This leaves android_meta.
*/
SELECT * FROM android_metadata;
/*
locale|
------+
en_US |
*
* Nothing of value here.
*/
-------------------------------------------------------
/*
* Trying to understand some more about the data.
*
* Let's start with udnerstanding more about layouts and placements.
*/
-- How many climbs per layout? What about stats?
SELECT
c.layout_id,
COUNT(DISTINCT c.uuid) AS climbs,
COUNT(cs.climb_uuid) AS stats_rows
FROM climbs c
LEFT JOIN climb_stats cs ON c.uuid = cs.climb_uuid
GROUP BY c.layout_id;
/*
layout_id|climbs|stats_rows|
---------+------+----------+
9| 68511| 76256|
10| 39396| 44986|
11| 20855| 25804|
*
* So 68k for the TB1, 39k for TB2 Mirror, and 20k for TB2 spray.
*/
-- How many placements per layout?
SELECT
layout_id,
COUNT(*) AS placement_count
FROM placements
GROUP BY layout_id;
/*
layout_id|placement_count|
---------+---------------+
9| 303|
10| 498|
11| 498|
*
* This makes sense. I know the TB2 Mirror 12x12 is supposed to have 498 holds.
*
* How do the sets relate to the layout?
*/
SELECT
l.id AS layout_id,
l.name AS layout_name,
p.set_id,
s.name AS set_name,
COUNT(p.id) AS placement_count
FROM layouts l
JOIN placements p ON l.id = p.layout_id
JOIN sets s ON p.set_id = s.id
GROUP BY l.id, l.name, p.set_id, s.name
ORDER BY l.id, p.set_id;
/*
layout_id|layout_name |set_id|set_name|placement_count|
---------+----------------------+------+--------+---------------+
9|Original Layout | 8|Set A | 82|
9|Original Layout | 9|Set B | 83|
9|Original Layout | 10|Set C | 84|
9|Original Layout | 11|Foot Set| 54|
10|Tension Board 2 Mirror| 12|Wood | 242|
10|Tension Board 2 Mirror| 13|Plastic | 256|
11|Tension Board 2 Spray | 12|Wood | 242|
11|Tension Board 2 Spray | 13|Plastic | 256|
*
* So this tells us which sets belong to which board.
* With the TB2 Mirror, all the wood (242) and plastic (256) add up to 498, as expected.
*/
-- Where do the placements go?
SELECT
p.id AS placement_id,
p.layout_id,
l.name AS layout_name,
p.hole_id,
h.name AS hole_name,
h.x,
h.y,
s.name AS set_name
FROM placements p
JOIN holes h ON p.hole_id = h.id
JOIN sets s ON p.set_id = s.id
JOIN layouts l ON p.layout_id = l.id
ORDER BY p.layout_id, p.id;
/*
placement_id|layout_id|layout_name |hole_id|hole_name|x |y |set_name|
------------+---------+---------------+-------+---------+--+---+--------+
1| 9|Original Layout| 2|B,18 |16|152|Set A |
2| 9|Original Layout| 10|J,18 |80|152|Set A |
3| 9|Original Layout| 317|B,3 |16| 32|Set A |
4| 9|Original Layout| 325|J,3 |80| 32|Set A |
5| 9|Original Layout| 320|E,3 |40| 32|Set A |
* Okay, so we can figure out from this table a) which board (sepcific layout) b) which hole c) the specific (x,y)-cordinate that the placement goes.
* Looking at the product_sizes table, we have our coordinate system (x,y). For the TB2, x ranges from -64 to 64 and y from 0 to 144.
* Note that there are a few inches on the left and right of the board, so this makes sense.
*/
-- How many LEDs total, and by product_size?
SELECT
ps.id AS product_size_id,
ps.name AS size_name,
p.name AS product_name,
COUNT(l.id) AS led_count
FROM leds l
JOIN product_sizes ps ON l.product_size_id = ps.id
JOIN products p ON ps.product_id = p.id
GROUP BY ps.id, ps.name, p.name
ORDER BY ps.id;
/*
* There seems to be a discrepency as there are 578 LEDs on the 12x12 TB2, but only 498 holds.
*
product_size_id|size_name |product_name |led_count|
---------------+-----------------+---------------+---------+
1|Full Wall |Tension Board | 389|
2|Half Kickboard |Tension Board | 379|
3|No Kickboard |Tension Board | 368|
4|Short |Tension Board | 305|
5|Short & Narrow |Tension Board | 217|
6|12 high x 12 wide|Tension Board 2| 578|
7|10 high x 12 wide|Tension Board 2| 479|
8|12 high x 8 wide |Tension Board 2| 368|
9|10 high x 8 wide |Tension Board 2| 305|
*
* Looking at the installation guide (https://artrock.at/wp-content/uploads/2026/03/MIRROR_2024_TensionBoard2_InstallGuide_1_22_26.pdf),
* the main grid (17 x 18 = 306) + the subgrid (16 x 17 = 272) adds up to 578.
*
* It seems as though we're not using all the holes.
*
*
* Let's dive deeper into the data to unravel this.
*
*/
-- Check if every hole has an LED
SELECT
COUNT(DISTINCT h.id) AS total_holes,
COUNT(DISTINCT l.hole_id) AS holes_with_leds,
COUNT(DISTINCT h.id) - COUNT(DISTINCT l.hole_id) AS holes_without_leds
FROM holes h
LEFT JOIN leds l ON h.id = l.hole_id;
/*
*
total_holes|holes_with_leds|holes_without_leds|
-----------+---------------+------------------+
967| 967| 0|
*/
-- For TB2 (product_id=5), which holes are used by layouts vs LEDs?
-- First, what holes does layout 10 use?
SELECT COUNT(DISTINCT p.hole_id) AS holes_in_layout_10
FROM placements p
WHERE p.layout_id = 10;
/*
holes_in_layout_10|
------------------+
498|
*/
-- What's the hole range for each product?
SELECT
h.product_id,
MIN(h.id) AS min_hole_id,
MAX(h.id) AS max_hole_id,
COUNT(*) AS total_holes
FROM holes h
GROUP BY h.product_id;
/*
product_id|min_hole_id|max_hole_id|total_holes|
----------+-----------+-----------+-----------+
4| 1| 389| 389|
5| 390| 967| 578|
*/
-- Do LED positions overlap or are they sequential per size?
SELECT
product_size_id,
MIN(position) AS min_pos,
MAX(position) AS max_pos,
COUNT(*) AS led_count
FROM leds
GROUP BY product_size_id
ORDER BY product_size_id;
/*
product_size_id|min_pos|max_pos|led_count|
---------------+-------+-------+---------+
1| 0| 389| 389|
2| 0| 389| 379|
3| 22| 389| 368|
4| 0| 304| 305|
5| 0| 216| 217|
6| 0| 577| 578|
7| 0| 478| 479|
8| 0| 367| 368|
9| 0| 304| 305|
*/
-- Which holes in the TB2 range (390-967) are NOT used by layout 10?
SELECT *
FROM holes h
WHERE h.product_id = 5
AND h.id NOT IN (
SELECT DISTINCT p.hole_id
FROM placements p
WHERE p.layout_id = 10
);
/*
id |product_id|name |x |y |mirrored_hole_id|mirror_group|
---+----------+------+---+--+----------------+------------+
391| 5|-64,12|-64|12| 951| 0|
424| 5|-60,8 |-60| 8| 949| 0|
423| 5|-60,16|-60|16| 948| 0|
422| 5|-60,24|-60|24| 947| 0|
416| 5|-60,72|-60|72| 941| 0|
*/
---------------------------------------------------------------
/*
* So we understand HOW the board works pretty well now. Let's summarize.
* - There are about 128k climbs, across 3 layouts -- the TB1, TB2 (Mirror) and TB2 (Spray).
* - There are about 147k statistcs for climbs. This includes multiple angles for each climb.
* - Some key features are the frames, the angle, and the layout_id (the latter determins the board, the former the actual climb on the board)
* - Hold positions are decoded via mapping placements to (x,y) coordinates (from the holes tables)
* - There are four hold types: start, middle, finish, foot. 498 holds on the TB2.
* - There are different hold sets (e.g., Wood/Plastic on TB2).
* - LEDs just map on to holes, and light up depending on our frames. There are 80 unused LEDs on the TB2.
*/