feat(sql): update queries to support individual items and fix schema drop order

This commit is contained in:
2026-01-19 17:23:01 +01:00
parent fa053055a3
commit be3ac284f4
23 changed files with 1140 additions and 341 deletions
+3 -3
View File
@@ -17,10 +17,10 @@ SELECT
{% block total_sets %}
NULL AS "total_sets" -- dummy for order: total_sets
{% endblock %}
FROM "bricktracker_minifigures"
FROM "rebrickable_minifigures"
INNER JOIN "rebrickable_minifigures"
ON "bricktracker_minifigures"."figure" IS NOT DISTINCT FROM "rebrickable_minifigures"."figure"
LEFT JOIN "bricktracker_minifigures"
ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
{% block join %}{% endblock %}
+12 -1
View File
@@ -9,7 +9,7 @@ IFNULL("problem_join"."total_damaged", 0) AS "total_damaged",
{% endblock %}
{% block total_quantity %}
SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_quantity",
SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) + SUM(IFNULL("individual_minifigures_join"."quantity", 0)) AS "total_quantity",
{% endblock %}
{% block total_sets %}
@@ -28,6 +28,17 @@ LEFT JOIN (
GROUP BY "bricktracker_parts"."figure"
) "problem_join"
ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "problem_join"."figure"
-- LEFT JOIN to include individual minifigure instances (not in sets)
LEFT JOIN (
SELECT
"bricktracker_individual_minifigures"."figure",
SUM("bricktracker_individual_minifigures"."quantity") AS "quantity"
FROM "bricktracker_individual_minifigures"
WHERE "bricktracker_individual_minifigures"."figure" IS NOT DISTINCT FROM :figure
GROUP BY "bricktracker_individual_minifigures"."figure"
) "individual_minifigures_join"
ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "individual_minifigures_join"."figure"
{% endblock %}
{% block where %}
+64 -17
View File
@@ -1,17 +1,14 @@
SELECT
"bricktracker_parts"."id",
"bricktracker_parts"."figure",
"bricktracker_parts"."part",
"bricktracker_parts"."color",
"bricktracker_parts"."spare",
"bricktracker_parts"."quantity",
"bricktracker_parts"."element",
--"bricktracker_parts"."rebrickable_inventory",
"bricktracker_parts"."missing",
"bricktracker_parts"."damaged",
"bricktracker_parts"."checked",
--"rebrickable_parts"."part",
--"rebrickable_parts"."color_id",
"combined"."id",
"combined"."figure",
"combined"."part",
"combined"."color",
"combined"."spare",
"combined"."quantity",
"combined"."element",
"combined"."missing",
"combined"."damaged",
"combined"."checked",
"rebrickable_parts"."color_name",
"rebrickable_parts"."color_rgb",
"rebrickable_parts"."color_transparent",
@@ -19,7 +16,6 @@ SELECT
"rebrickable_parts"."bricklink_color_name",
"rebrickable_parts"."bricklink_part_num",
"rebrickable_parts"."name",
--"rebrickable_parts"."category",
"rebrickable_parts"."image",
"rebrickable_parts"."image_id",
"rebrickable_parts"."url",
@@ -42,11 +38,62 @@ SELECT
{% block total_minifigures %}
NULL AS "total_minifigures" -- dummy for order: total_minifigures
{% endblock %}
FROM "bricktracker_parts"
FROM (
-- Parts from set-based minifigures
SELECT
"bricktracker_parts"."id",
"bricktracker_parts"."figure",
"bricktracker_parts"."part",
"bricktracker_parts"."color",
"bricktracker_parts"."spare",
"bricktracker_parts"."quantity",
"bricktracker_parts"."element",
"bricktracker_parts"."missing",
"bricktracker_parts"."damaged",
"bricktracker_parts"."checked",
'set' AS "source_type"
FROM "bricktracker_parts"
UNION ALL
-- Parts from individual minifigures
SELECT
"bricktracker_individual_minifigure_parts"."id",
"bricktracker_individual_minifigures"."figure",
"bricktracker_individual_minifigure_parts"."part",
"bricktracker_individual_minifigure_parts"."color",
"bricktracker_individual_minifigure_parts"."spare",
"bricktracker_individual_minifigure_parts"."quantity",
"bricktracker_individual_minifigure_parts"."element",
"bricktracker_individual_minifigure_parts"."missing",
"bricktracker_individual_minifigure_parts"."damaged",
"bricktracker_individual_minifigure_parts"."checked",
'individual_minifigure' AS "source_type"
FROM "bricktracker_individual_minifigure_parts"
INNER JOIN "bricktracker_individual_minifigures"
ON "bricktracker_individual_minifigure_parts"."id" = "bricktracker_individual_minifigures"."id"
UNION ALL
-- Individual/standalone parts (not from any set or minifigure)
SELECT
"bricktracker_individual_parts"."id",
NULL AS "figure",
"bricktracker_individual_parts"."part",
"bricktracker_individual_parts"."color",
0 AS "spare",
"bricktracker_individual_parts"."quantity",
NULL AS "element",
"bricktracker_individual_parts"."missing",
"bricktracker_individual_parts"."damaged",
"bricktracker_individual_parts"."checked",
'individual_part' AS "source_type"
FROM "bricktracker_individual_parts"
) AS "combined"
INNER JOIN "rebrickable_parts"
ON "bricktracker_parts"."part" IS NOT DISTINCT FROM "rebrickable_parts"."part"
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM "rebrickable_parts"."color_id"
ON "combined"."part" IS NOT DISTINCT FROM "rebrickable_parts"."part"
AND "combined"."color" IS NOT DISTINCT FROM "rebrickable_parts"."color_id"
{% block join %}{% endblock %}
+39 -15
View File
@@ -1,16 +1,40 @@
SELECT DISTINCT
"rebrickable_parts"."color_id" AS "color_id",
"rebrickable_parts"."color_name" AS "color_name",
"rebrickable_parts"."color_rgb" AS "color_rgb"
FROM "rebrickable_parts"
INNER JOIN "bricktracker_parts"
ON "bricktracker_parts"."part" IS NOT DISTINCT FROM "rebrickable_parts"."part"
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM "rebrickable_parts"."color_id"
{% if owner_id and owner_id != 'all' %}
INNER JOIN "bricktracker_sets"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
INNER JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
ORDER BY "rebrickable_parts"."color_name" ASC
"color_id",
"color_name",
"color_rgb"
FROM (
-- Colors from set-based parts
SELECT DISTINCT
"rebrickable_parts"."color_id" AS "color_id",
"rebrickable_parts"."color_name" AS "color_name",
"rebrickable_parts"."color_rgb" AS "color_rgb"
FROM "rebrickable_parts"
INNER JOIN "bricktracker_parts"
ON "bricktracker_parts"."part" IS NOT DISTINCT FROM "rebrickable_parts"."part"
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM "rebrickable_parts"."color_id"
{% if owner_id and owner_id != 'all' %}
INNER JOIN "bricktracker_sets"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
INNER JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
UNION
-- Colors from individual parts
SELECT DISTINCT
"rebrickable_parts"."color_id" AS "color_id",
"rebrickable_parts"."color_name" AS "color_name",
"rebrickable_parts"."color_rgb" AS "color_rgb"
FROM "rebrickable_parts"
INNER JOIN "bricktracker_individual_parts"
ON "bricktracker_individual_parts"."part" IS NOT DISTINCT FROM "rebrickable_parts"."part"
AND "bricktracker_individual_parts"."color" IS NOT DISTINCT FROM "rebrickable_parts"."color_id"
{% if owner_id and owner_id != 'all' %}
INNER JOIN "bricktracker_set_owners"
ON "bricktracker_individual_parts"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
)
ORDER BY "color_name" ASC
+31 -26
View File
@@ -1,55 +1,60 @@
{% extends 'part/base/base.sql' %}
{% block total_missing %}
SUM("bricktracker_parts"."missing") AS "total_missing",
SUM("combined"."missing") AS "total_missing",
{% endblock %}
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
SUM("combined"."damaged") AS "total_damaged",
{% endblock %}
{% block total_quantity %}
SUM("bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity",
SUM("combined"."quantity" * IFNULL("minifigure_quantities"."quantity", 1)) AS "total_quantity",
{% endblock %}
{% block total_sets %}
IFNULL(COUNT(DISTINCT "bricktracker_parts"."id"), 0) AS "total_sets",
IFNULL(COUNT(DISTINCT CASE WHEN "combined"."source_type" = 'set' THEN "combined"."id" ELSE NULL END), 0) AS "total_sets",
{% endblock %}
{% block total_minifigures %}
SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures"
SUM(IFNULL("minifigure_quantities"."quantity", 0)) AS "total_minifigures"
{% endblock %}
{% block join %}
LEFT JOIN "bricktracker_minifigures"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id"
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
-- Join to get minifigure quantities from both set-based and individual minifigures
LEFT JOIN (
SELECT
"bricktracker_minifigures"."id",
"bricktracker_minifigures"."figure",
"bricktracker_minifigures"."quantity"
FROM "bricktracker_minifigures"
{% if theme_id or year %}
INNER JOIN "bricktracker_sets" AS "filter_sets"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "filter_sets"."id"
INNER JOIN "rebrickable_sets" AS "filter_rs"
ON "filter_sets"."set" IS NOT DISTINCT FROM "filter_rs"."set"
{% endif %}
UNION ALL
SELECT
"bricktracker_individual_minifigures"."id",
"bricktracker_individual_minifigures"."figure",
"bricktracker_individual_minifigures"."quantity"
FROM "bricktracker_individual_minifigures"
) AS "minifigure_quantities"
ON "combined"."id" IS NOT DISTINCT FROM "minifigure_quantities"."id"
AND "combined"."figure" IS NOT DISTINCT FROM "minifigure_quantities"."figure"
{% endblock %}
{% block where %}
{% set conditions = [] %}
{% if color_id and color_id != 'all' %}
{% set _ = conditions.append('"bricktracker_parts"."color" = ' ~ color_id) %}
{% endif %}
{% if theme_id and theme_id != 'all' %}
{% set _ = conditions.append('"filter_rs"."theme_id" = ' ~ theme_id) %}
{% endif %}
{% if year and year != 'all' %}
{% set _ = conditions.append('"filter_rs"."year" = ' ~ year) %}
{% set _ = conditions.append('"combined"."color" = ' ~ color_id) %}
{% endif %}
{% if search_query %}
{% set search_condition = '(LOWER("rebrickable_parts"."name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("rebrickable_parts"."color_name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("bricktracker_parts"."part") LIKE LOWER(\'%' ~ search_query ~ '%\'))' %}
{% set search_condition = '(LOWER("rebrickable_parts"."name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("rebrickable_parts"."color_name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("combined"."part") LIKE LOWER(\'%' ~ search_query ~ '%\'))' %}
{% set _ = conditions.append(search_condition) %}
{% endif %}
{% if skip_spare_parts %}
{% set _ = conditions.append('"bricktracker_parts"."spare" = 0') %}
{% set _ = conditions.append('"combined"."spare" = 0') %}
{% endif %}
{% if individuals_filter %}
{% set _ = conditions.append('"combined"."source_type" = \'individual_part\'') %}
{% endif %}
{% if conditions %}
WHERE {{ conditions | join(' AND ') }}
@@ -58,7 +63,7 @@ WHERE {{ conditions | join(' AND ') }}
{% block group %}
GROUP BY
"bricktracker_parts"."part",
"bricktracker_parts"."color",
"bricktracker_parts"."spare"
"combined"."part",
"combined"."color",
"combined"."spare"
{% endblock %}
+87 -24
View File
@@ -2,73 +2,136 @@
{% block total_missing %}
{% if owner_id and owner_id != 'all' %}
SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."missing" ELSE 0 END) AS "total_missing",
SUM(CASE
WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."missing"
WHEN "combined"."source_type" = 'individual_minifigure' AND "individual_minifigure_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."missing"
WHEN "combined"."source_type" = 'individual_part' AND ("individual_part_owners"."owner_{{ owner_id }}" = 1 OR "individual_part_lot_owners"."owner_{{ owner_id }}" = 1) THEN "combined"."missing"
ELSE 0
END) AS "total_missing",
{% else %}
SUM("bricktracker_parts"."missing") AS "total_missing",
SUM("combined"."missing") AS "total_missing",
{% endif %}
{% endblock %}
{% block total_damaged %}
{% if owner_id and owner_id != 'all' %}
SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."damaged" ELSE 0 END) AS "total_damaged",
SUM(CASE
WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."damaged"
WHEN "combined"."source_type" = 'individual_minifigure' AND "individual_minifigure_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."damaged"
WHEN "combined"."source_type" = 'individual_part' AND ("individual_part_owners"."owner_{{ owner_id }}" = 1 OR "individual_part_lot_owners"."owner_{{ owner_id }}" = 1) THEN "combined"."damaged"
ELSE 0
END) AS "total_damaged",
{% else %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
SUM("combined"."damaged") AS "total_damaged",
{% endif %}
{% endblock %}
{% block total_quantity %}
{% if owner_id and owner_id != 'all' %}
SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1) ELSE 0 END) AS "total_quantity",
SUM(CASE
WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)
WHEN "combined"."source_type" = 'individual_minifigure' AND "individual_minifigure_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."quantity"
WHEN "combined"."source_type" = 'individual_part' AND ("individual_part_owners"."owner_{{ owner_id }}" = 1 OR "individual_part_lot_owners"."owner_{{ owner_id }}" = 1) THEN "combined"."quantity"
ELSE 0
END) AS "total_quantity",
{% else %}
SUM("bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity",
SUM(CASE
WHEN "combined"."source_type" = 'set' THEN "combined"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)
ELSE "combined"."quantity"
END) AS "total_quantity",
{% endif %}
{% endblock %}
{% block total_sets %}
{% if owner_id and owner_id != 'all' %}
COUNT(DISTINCT CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."id" ELSE NULL END) AS "total_sets",
COUNT(DISTINCT CASE WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."id" ELSE NULL END) AS "total_sets",
{% else %}
COUNT(DISTINCT "bricktracker_parts"."id") AS "total_sets",
COUNT(DISTINCT CASE WHEN "combined"."source_type" = 'set' THEN "combined"."id" ELSE NULL END) AS "total_sets",
{% endif %}
{% endblock %}
{% block total_minifigures %}
{% if owner_id and owner_id != 'all' %}
SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN IFNULL("bricktracker_minifigures"."quantity", 0) ELSE 0 END) AS "total_minifigures"
SUM(CASE
WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN IFNULL("bricktracker_minifigures"."quantity", 0)
WHEN "combined"."source_type" = 'individual_minifigure' AND "individual_minifigure_owners"."owner_{{ owner_id }}" = 1 THEN 1
ELSE 0
END) AS "total_minifigures"
{% else %}
SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures"
SUM(CASE
WHEN "combined"."source_type" = 'set' THEN IFNULL("bricktracker_minifigures"."quantity", 0)
WHEN "combined"."source_type" = 'individual_minifigure' THEN 1
ELSE 0
END) AS "total_minifigures"
{% endif %}
{% endblock %}
{% block join %}
-- Join with sets to get owner information
INNER JOIN "bricktracker_sets"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
-- Left join with sets (for set-based parts)
LEFT JOIN "bricktracker_sets"
ON "combined"."source_type" = 'set'
AND "combined"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
-- Left join with set owners (using dynamic columns)
LEFT JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
ON "combined"."source_type" = 'set'
AND "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
-- Left join with minifigures
-- Left join with set-based minifigures
LEFT JOIN "bricktracker_minifigures"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id"
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
ON "combined"."source_type" = 'set'
AND "combined"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id"
AND "combined"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
-- Left join with individual minifigures (for individual minifigure parts)
LEFT JOIN "bricktracker_individual_minifigures"
ON "combined"."source_type" = 'individual_minifigure'
AND "combined"."id" IS NOT DISTINCT FROM "bricktracker_individual_minifigures"."id"
-- Left join with set owners for individual minifigures (using dynamic columns) - reuse set_owners table
LEFT JOIN "bricktracker_set_owners" AS "individual_minifigure_owners"
ON "combined"."source_type" = 'individual_minifigure'
AND "bricktracker_individual_minifigures"."id" IS NOT DISTINCT FROM "individual_minifigure_owners"."id"
-- Left join with individual parts (for standalone parts and lot parts)
LEFT JOIN "bricktracker_individual_parts"
ON "combined"."source_type" = 'individual_part'
AND "combined"."id" IS NOT DISTINCT FROM "bricktracker_individual_parts"."id"
-- Left join with set owners for individual parts (using dynamic columns) - for standalone parts
LEFT JOIN "bricktracker_set_owners" AS "individual_part_owners"
ON "combined"."source_type" = 'individual_part'
AND "bricktracker_individual_parts"."id" IS NOT DISTINCT FROM "individual_part_owners"."id"
-- Left join with individual part lots (for parts belonging to a lot)
LEFT JOIN "bricktracker_individual_part_lots"
ON "combined"."source_type" = 'individual_part'
AND "bricktracker_individual_parts"."lot_id" IS NOT DISTINCT FROM "bricktracker_individual_part_lots"."id"
-- Left join with set owners for individual part lots (using dynamic columns)
LEFT JOIN "bricktracker_set_owners" AS "individual_part_lot_owners"
ON "combined"."source_type" = 'individual_part'
AND "bricktracker_individual_part_lots"."id" IS NOT DISTINCT FROM "individual_part_lot_owners"."id"
{% endblock %}
{% block where %}
{% set conditions = [] %}
{% if owner_id and owner_id != 'all' %}
{% set _ = conditions.append('"bricktracker_set_owners"."owner_' ~ owner_id ~ '" = 1') %}
{% set owner_condition = '(("combined"."source_type" = \'set\' AND "bricktracker_set_owners"."owner_' ~ owner_id ~ '" = 1) OR ("combined"."source_type" = \'individual_minifigure\' AND "individual_minifigure_owners"."owner_' ~ owner_id ~ '" = 1) OR ("combined"."source_type" = \'individual_part\' AND ("individual_part_owners"."owner_' ~ owner_id ~ '" = 1 OR "individual_part_lot_owners"."owner_' ~ owner_id ~ '" = 1)))' %}
{% set _ = conditions.append(owner_condition) %}
{% endif %}
{% if color_id and color_id != 'all' %}
{% set _ = conditions.append('"bricktracker_parts"."color" = ' ~ color_id) %}
{% set _ = conditions.append('"combined"."color" = ' ~ color_id) %}
{% endif %}
{% if search_query %}
{% set search_condition = '(LOWER("rebrickable_parts"."name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("rebrickable_parts"."color_name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("bricktracker_parts"."part") LIKE LOWER(\'%' ~ search_query ~ '%\'))' %}
{% set search_condition = '(LOWER("rebrickable_parts"."name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("rebrickable_parts"."color_name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("combined"."part") LIKE LOWER(\'%' ~ search_query ~ '%\'))' %}
{% set _ = conditions.append(search_condition) %}
{% endif %}
{% if skip_spare_parts %}
{% set _ = conditions.append('"bricktracker_parts"."spare" = 0') %}
{% set _ = conditions.append('"combined"."spare" = 0') %}
{% endif %}
{% if individuals_filter %}
{% set _ = conditions.append('"combined"."source_type" = \'individual_part\'') %}
{% endif %}
{% if conditions %}
WHERE {{ conditions | join(' AND ') }}
@@ -77,7 +140,7 @@ WHERE {{ conditions | join(' AND ') }}
{% block group %}
GROUP BY
"bricktracker_parts"."part",
"bricktracker_parts"."color",
"bricktracker_parts"."spare"
"combined"."part",
"combined"."color",
"combined"."spare"
{% endblock %}
+83 -21
View File
@@ -1,26 +1,88 @@
-- Query parts from both set-based and individual minifigures
SELECT
"parts_combined"."id",
"parts_combined"."figure",
"parts_combined"."part",
"parts_combined"."color",
"parts_combined"."spare",
SUM("parts_combined"."quantity") AS "quantity",
"parts_combined"."element",
SUM("parts_combined"."missing") AS "total_missing",
SUM("parts_combined"."damaged") AS "total_damaged",
MAX("parts_combined"."checked") AS "checked",
"rebrickable_parts"."color_name",
"rebrickable_parts"."color_rgb",
"rebrickable_parts"."color_transparent",
"rebrickable_parts"."bricklink_color_id",
"rebrickable_parts"."bricklink_color_name",
"rebrickable_parts"."bricklink_part_num",
"rebrickable_parts"."name",
"rebrickable_parts"."image",
"rebrickable_parts"."image_id",
"rebrickable_parts"."url",
"rebrickable_parts"."print",
NULL AS "total_quantity",
NULL AS "total_spare",
NULL AS "total_sets",
NULL AS "total_minifigures"
FROM (
-- Set-based minifigure parts
SELECT
"bricktracker_parts"."id",
"bricktracker_parts"."figure",
"bricktracker_parts"."part",
"bricktracker_parts"."color",
"bricktracker_parts"."spare",
"bricktracker_parts"."quantity",
"bricktracker_parts"."element",
"bricktracker_parts"."missing",
"bricktracker_parts"."damaged",
"bricktracker_parts"."checked"
FROM "bricktracker_parts"
WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
{% extends 'part/base/base.sql' %}
UNION ALL
{% block total_missing %}
SUM("bricktracker_parts"."missing") AS "total_missing",
{% endblock %}
-- Individual minifigure parts
SELECT
"bricktracker_individual_minifigure_parts"."id",
"bricktracker_individual_minifigures"."figure",
"bricktracker_individual_minifigure_parts"."part",
"bricktracker_individual_minifigure_parts"."color",
"bricktracker_individual_minifigure_parts"."spare",
"bricktracker_individual_minifigure_parts"."quantity",
"bricktracker_individual_minifigure_parts"."element",
"bricktracker_individual_minifigure_parts"."missing",
"bricktracker_individual_minifigure_parts"."damaged",
"bricktracker_individual_minifigure_parts"."checked"
FROM "bricktracker_individual_minifigure_parts"
INNER JOIN "bricktracker_individual_minifigures"
ON "bricktracker_individual_minifigure_parts"."id" = "bricktracker_individual_minifigures"."id"
WHERE "bricktracker_individual_minifigures"."figure" IS NOT DISTINCT FROM :figure
) AS "parts_combined"
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
{% endblock %}
INNER JOIN "rebrickable_parts"
ON "parts_combined"."part" = "rebrickable_parts"."part"
AND "parts_combined"."color" = "rebrickable_parts"."color_id"
{% block where %}
{% set conditions = [] %}
{% set _ = conditions.append('"bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure') %}
{% if skip_spare_parts %}
{% set _ = conditions.append('"bricktracker_parts"."spare" = 0') %}
{% endif %}
WHERE {{ conditions | join(' AND ') }}
{% endblock %}
{% block group %}
GROUP BY
"bricktracker_parts"."part",
"bricktracker_parts"."color",
"bricktracker_parts"."spare"
{% endblock %}
"parts_combined"."part",
"parts_combined"."color",
"parts_combined"."spare",
"parts_combined"."element",
"rebrickable_parts"."color_name",
"rebrickable_parts"."color_rgb",
"rebrickable_parts"."color_transparent",
"rebrickable_parts"."bricklink_color_id",
"rebrickable_parts"."bricklink_color_name",
"rebrickable_parts"."bricklink_part_num",
"rebrickable_parts"."name",
"rebrickable_parts"."image",
"rebrickable_parts"."image_id",
"rebrickable_parts"."url",
"rebrickable_parts"."print"
{% if order %}
-- Replace combined/bricktracker_parts references with parts_combined for this query
ORDER BY {{ order | replace('"combined"', '"parts_combined"') | replace('"bricktracker_parts"', '"parts_combined"') }}
{% endif %}
+4 -4
View File
@@ -7,12 +7,12 @@
{% block where %}
WHERE "rebrickable_parts"."print" IS NOT DISTINCT FROM :print
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM :color
AND "bricktracker_parts"."part" IS DISTINCT FROM :part
AND "combined"."color" IS NOT DISTINCT FROM :color
AND "combined"."part" IS DISTINCT FROM :part
{% endblock %}
{% block group %}
GROUP BY
"bricktracker_parts"."part",
"bricktracker_parts"."color"
"combined"."part",
"combined"."color"
{% endblock %}
+6 -6
View File
@@ -1,19 +1,19 @@
{% extends 'part/base/base.sql' %}
{% block total_missing %}
"bricktracker_parts"."missing" AS "total_missing",
"combined"."missing" AS "total_missing",
{% endblock %}
{% block total_damaged %}
"bricktracker_parts"."damaged" AS "total_damaged",
"combined"."damaged" AS "total_damaged",
{% endblock %}
{% block total_quantity %}
"bricktracker_parts"."quantity" AS "total_quantity",
"combined"."quantity" AS "total_quantity",
{% endblock %}
{% block total_spare %}
"bricktracker_parts"."spare" AS "total_spare",
"combined"."spare" AS "total_spare",
{% endblock %}
{% block total_sets %}
@@ -21,13 +21,13 @@
{% endblock %}
{% block total_minifigures %}
CASE WHEN "bricktracker_parts"."figure" IS NOT NULL THEN 1 ELSE 0 END AS "total_minifigures"
CASE WHEN "combined"."figure" IS NOT NULL THEN 1 ELSE 0 END AS "total_minifigures"
{% endblock %}
{% block where %}
{% set conditions = [] %}
{% if skip_spare_parts %}
{% set _ = conditions.append('"bricktracker_parts"."spare" = 0') %}
{% set _ = conditions.append('"combined"."spare" = 0') %}
{% endif %}
{% if conditions %}
WHERE {{ conditions | join(' AND ') }}
+60 -46
View File
@@ -2,104 +2,118 @@
{% block total_missing %}
{% if owner_id and owner_id != 'all' %}
SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."missing" ELSE 0 END) AS "total_missing",
SUM(CASE
WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."missing"
WHEN "combined"."source_type" = 'individual' AND "ind_minifig_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."missing"
ELSE 0
END) AS "total_missing",
{% else %}
SUM("bricktracker_parts"."missing") AS "total_missing",
SUM("combined"."missing") AS "total_missing",
{% endif %}
{% endblock %}
{% block total_damaged %}
{% if owner_id and owner_id != 'all' %}
SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."damaged" ELSE 0 END) AS "total_damaged",
SUM(CASE
WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."damaged"
WHEN "combined"."source_type" = 'individual' AND "ind_minifig_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."damaged"
ELSE 0
END) AS "total_damaged",
{% else %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
SUM("combined"."damaged") AS "total_damaged",
{% endif %}
{% endblock %}
{% block total_quantity %}
{% if owner_id and owner_id != 'all' %}
SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1) ELSE 0 END) AS "total_quantity",
SUM(CASE
WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)
WHEN "combined"."source_type" = 'individual' AND "ind_minifig_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."quantity" * IFNULL("bricktracker_individual_minifigures"."quantity", 1)
ELSE 0
END) AS "total_quantity",
{% else %}
SUM("bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity",
SUM(CASE
WHEN "combined"."source_type" = 'set' THEN "combined"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)
WHEN "combined"."source_type" = 'individual' THEN "combined"."quantity" * IFNULL("bricktracker_individual_minifigures"."quantity", 1)
ELSE "combined"."quantity"
END) AS "total_quantity",
{% endif %}
{% endblock %}
{% block total_sets %}
{% if owner_id and owner_id != 'all' %}
COUNT(DISTINCT CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."id" ELSE NULL END) AS "total_sets",
COUNT(DISTINCT CASE WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "combined"."id" ELSE NULL END) AS "total_sets",
{% else %}
COUNT(DISTINCT "bricktracker_parts"."id") AS "total_sets",
COUNT(DISTINCT CASE WHEN "combined"."source_type" = 'set' THEN "combined"."id" ELSE NULL END) AS "total_sets",
{% endif %}
{% endblock %}
{% block total_minifigures %}
{% if owner_id and owner_id != 'all' %}
SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN IFNULL("bricktracker_minifigures"."quantity", 0) ELSE 0 END) AS "total_minifigures"
SUM(CASE
WHEN "combined"."source_type" = 'set' AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN IFNULL("bricktracker_minifigures"."quantity", 0)
WHEN "combined"."source_type" = 'individual' AND "ind_minifig_owners"."owner_{{ owner_id }}" = 1 THEN IFNULL("bricktracker_individual_minifigures"."quantity", 0)
ELSE 0
END) AS "total_minifigures"
{% else %}
SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures"
SUM(CASE
WHEN "combined"."source_type" = 'set' THEN IFNULL("bricktracker_minifigures"."quantity", 0)
WHEN "combined"."source_type" = 'individual' THEN IFNULL("bricktracker_individual_minifigures"."quantity", 0)
ELSE 0
END) AS "total_minifigures"
{% endif %}
{% endblock %}
{% block join %}
-- Join with sets to get owner information
INNER JOIN "bricktracker_sets"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
-- Join with rebrickable sets for theme/year filtering
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
-- Left join with sets for set-based parts
LEFT JOIN "bricktracker_sets"
ON "combined"."source_type" = 'set'
AND "combined"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
-- Left join with set owners (using dynamic columns)
LEFT JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
-- Left join with set tags (for tag filtering)
{% if tag_id and tag_id != 'all' %}
LEFT JOIN "bricktracker_set_tags"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_tags"."id"
{% endif %}
-- Left join with minifigures
-- Left join with set-based minifigures
LEFT JOIN "bricktracker_minifigures"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id"
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
ON "combined"."source_type" = 'set'
AND "combined"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id"
AND "combined"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
-- Left join with individual minifigures
LEFT JOIN "bricktracker_individual_minifigures"
ON "combined"."source_type" = 'individual'
AND "combined"."id" IS NOT DISTINCT FROM "bricktracker_individual_minifigures"."id"
-- Left join with individual minifigure owners (using consolidated metadata table)
LEFT JOIN "bricktracker_set_owners" AS "ind_minifig_owners"
ON "bricktracker_individual_minifigures"."id" IS NOT DISTINCT FROM "ind_minifig_owners"."id"
{% endblock %}
{% block where %}
{% set conditions = [] %}
-- Always filter for problematic parts
{% set _ = conditions.append('("bricktracker_parts"."missing" > 0 OR "bricktracker_parts"."damaged" > 0)') %}
{% set _ = conditions.append('("combined"."missing" > 0 OR "combined"."damaged" > 0)') %}
{% if owner_id and owner_id != 'all' %}
{% set _ = conditions.append('"bricktracker_set_owners"."owner_' ~ owner_id ~ '" = 1') %}
{% set owner_condition = '(("combined"."source_type" = \'set\' AND "bricktracker_set_owners"."owner_' ~ owner_id ~ '" = 1) OR ("combined"."source_type" = \'individual\' AND "ind_minifig_owners"."owner_' ~ owner_id ~ '" = 1))' %}
{% set _ = conditions.append(owner_condition) %}
{% endif %}
{% if color_id and color_id != 'all' %}
{% set _ = conditions.append('"bricktracker_parts"."color" = ' ~ color_id) %}
{% endif %}
{% if theme_id and theme_id != 'all' %}
{% set _ = conditions.append('"rebrickable_sets"."theme_id" = ' ~ theme_id) %}
{% endif %}
{% if year and year != 'all' %}
{% set _ = conditions.append('"rebrickable_sets"."year" = ' ~ year) %}
{% endif %}
{% if storage_id and storage_id != 'all' %}
{% set _ = conditions.append('"bricktracker_sets"."storage" = \'' ~ storage_id ~ '\'') %}
{% endif %}
{% if tag_id and tag_id != 'all' %}
{% set _ = conditions.append('"bricktracker_set_tags"."tag_' ~ tag_id ~ '" = 1') %}
{% set _ = conditions.append('"combined"."color" = ' ~ color_id) %}
{% endif %}
{% if search_query %}
{% set search_condition = '(LOWER("rebrickable_parts"."name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("rebrickable_parts"."color_name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("bricktracker_parts"."part") LIKE LOWER(\'%' ~ search_query ~ '%\'))' %}
{% set search_condition = '(LOWER("rebrickable_parts"."name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("rebrickable_parts"."color_name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("combined"."part") LIKE LOWER(\'%' ~ search_query ~ '%\'))' %}
{% set _ = conditions.append(search_condition) %}
{% endif %}
{% if skip_spare_parts %}
{% set _ = conditions.append('"bricktracker_parts"."spare" = 0') %}
{% set _ = conditions.append('"combined"."spare" = 0') %}
{% endif %}
WHERE {{ conditions | join(' AND ') }}
{% endblock %}
{% block group %}
GROUP BY
"bricktracker_parts"."part",
"bricktracker_parts"."color",
"bricktracker_parts"."spare"
"combined"."part",
"combined"."color",
"combined"."spare"
{% endblock %}
+4 -9
View File
@@ -2,19 +2,14 @@
{% extends 'part/base/base.sql' %}
{% block total_missing %}
IFNULL("bricktracker_parts"."missing", 0) AS "total_missing",
IFNULL("combined"."missing", 0) AS "total_missing",
{% endblock %}
{% block total_damaged %}
IFNULL("bricktracker_parts"."damaged", 0) AS "total_damaged",
IFNULL("combined"."damaged", 0) AS "total_damaged",
{% endblock %}
{% block where %}
{% set conditions = [] %}
{% set _ = conditions.append('"bricktracker_parts"."id" IS NOT DISTINCT FROM :id') %}
{% set _ = conditions.append('"bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure') %}
{% if skip_spare_parts %}
{% set _ = conditions.append('"bricktracker_parts"."spare" = 0') %}
{% endif %}
WHERE {{ conditions | join(' AND ') }}
WHERE "combined"."id" IS NOT DISTINCT FROM :id
AND "combined"."figure" IS NOT DISTINCT FROM :figure
{% endblock %}
@@ -6,12 +6,12 @@
{% block total_damaged %}{% endblock %}
{% block where %}
WHERE "bricktracker_parts"."color" IS DISTINCT FROM :color
AND "bricktracker_parts"."part" IS NOT DISTINCT FROM :part
WHERE "combined"."color" IS DISTINCT FROM :color
AND "combined"."part" IS NOT DISTINCT FROM :part
{% endblock %}
{% block group %}
GROUP BY
"bricktracker_parts"."part",
"bricktracker_parts"."color"
"combined"."part",
"combined"."color"
{% endblock %}
+28 -11
View File
@@ -1,34 +1,51 @@
{% extends 'part/base/base.sql' %}
{% block total_missing %}
SUM("bricktracker_parts"."missing") AS "total_missing",
SUM("combined"."missing") AS "total_missing",
{% endblock %}
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
SUM("combined"."damaged") AS "total_damaged",
{% endblock %}
{% block total_quantity %}
SUM((NOT "bricktracker_parts"."spare") * "bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity",
SUM((NOT "combined"."spare") * "combined"."quantity" * IFNULL("minifigure_quantities"."quantity", 1)) AS "total_quantity",
{% endblock %}
{% block total_spare %}
SUM("bricktracker_parts"."spare" * "bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_spare",
SUM("combined"."spare" * "combined"."quantity" * IFNULL("minifigure_quantities"."quantity", 1)) AS "total_spare",
{% endblock %}
{% block join %}
LEFT JOIN "bricktracker_minifigures"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id"
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
-- Join to get minifigure quantities from both set-based and individual minifigures
LEFT JOIN (
-- Set-based minifigure quantities
SELECT
"bricktracker_minifigures"."id",
"bricktracker_minifigures"."figure",
"bricktracker_minifigures"."quantity"
FROM "bricktracker_minifigures"
UNION ALL
-- Individual minifigure quantities
SELECT
"bricktracker_individual_minifigures"."id",
"bricktracker_individual_minifigures"."figure",
"bricktracker_individual_minifigures"."quantity"
FROM "bricktracker_individual_minifigures"
) AS "minifigure_quantities"
ON "combined"."id" IS NOT DISTINCT FROM "minifigure_quantities"."id"
AND "combined"."figure" IS NOT DISTINCT FROM "minifigure_quantities"."figure"
{% endblock %}
{% block where %}
WHERE "bricktracker_parts"."part" IS NOT DISTINCT FROM :part
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM :color
WHERE "combined"."part" IS NOT DISTINCT FROM :part
AND "combined"."color" IS NOT DISTINCT FROM :color
{% endblock %}
{% block group %}
GROUP BY
"bricktracker_parts"."part",
"bricktracker_parts"."color"
"combined"."part",
"combined"."color"
{% endblock %}
+10 -10
View File
@@ -1,18 +1,18 @@
{% extends 'part/base/base.sql' %}
{% block where %}
WHERE "bricktracker_parts"."id" IS NOT DISTINCT FROM :id
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
AND "bricktracker_parts"."part" IS NOT DISTINCT FROM :part
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM :color
AND "bricktracker_parts"."spare" IS NOT DISTINCT FROM :spare
WHERE "combined"."id" IS NOT DISTINCT FROM :id
AND "combined"."figure" IS NOT DISTINCT FROM :figure
AND "combined"."part" IS NOT DISTINCT FROM :part
AND "combined"."color" IS NOT DISTINCT FROM :color
AND "combined"."spare" IS NOT DISTINCT FROM :spare
{% endblock %}
{% block group %}
GROUP BY
"bricktracker_parts"."id",
"bricktracker_parts"."figure",
"bricktracker_parts"."part",
"bricktracker_parts"."color",
"bricktracker_parts"."spare"
"combined"."id",
"combined"."figure",
"combined"."part",
"combined"."color",
"combined"."spare"
{% endblock %}
+46 -7
View File
@@ -1,31 +1,70 @@
BEGIN transaction;
DROP TABLE IF EXISTS "bricktracker_metadata_owners";
DROP TABLE IF EXISTS "bricktracker_metadata_statuses";
DROP TABLE IF EXISTS "bricktracker_metadata_tags";
-- Disable foreign key checks during drop to avoid constraint errors
PRAGMA foreign_keys = OFF;
-- Drop child tables first (those with foreign key references)
-- Individual minifigure parts (references individual_minifigures)
DROP TABLE IF EXISTS "bricktracker_individual_minifigure_parts";
-- Individual parts (references individual_part_lots)
-- Drop before lots since lot_id is a foreign key
DROP TABLE IF EXISTS "bricktracker_individual_parts";
-- Individual minifigures (references rebrickable_minifigures, metadata tables)
DROP TABLE IF EXISTS "bricktracker_individual_minifigures";
-- Individual part lots (references metadata tables)
DROP TABLE IF EXISTS "bricktracker_individual_part_lots";
-- Set-based parts and minifigures (references sets)
DROP TABLE IF EXISTS "bricktracker_minifigures";
DROP TABLE IF EXISTS "bricktracker_parts";
DROP TABLE IF EXISTS "bricktracker_sets";
-- Set metadata junction tables (reference sets and metadata)
DROP TABLE IF EXISTS "bricktracker_set_checkboxes";
DROP TABLE IF EXISTS "bricktracker_set_owners";
DROP TABLE IF EXISTS "bricktracker_set_statuses";
DROP TABLE IF EXISTS "bricktracker_set_storages";
DROP TABLE IF EXISTS "bricktracker_set_tags";
-- Wish metadata junction tables
DROP TABLE IF EXISTS "bricktracker_wish_owners";
-- Main sets and wishes tables
DROP TABLE IF EXISTS "bricktracker_sets";
DROP TABLE IF EXISTS "bricktracker_wishes";
-- Metadata definition tables
DROP TABLE IF EXISTS "bricktracker_metadata_owners";
DROP TABLE IF EXISTS "bricktracker_metadata_statuses";
DROP TABLE IF EXISTS "bricktracker_metadata_tags";
DROP TABLE IF EXISTS "bricktracker_metadata_storages";
DROP TABLE IF EXISTS "bricktracker_metadata_purchase_locations";
-- Rebrickable reference tables
DROP TABLE IF EXISTS "rebrickable_colors";
DROP TABLE IF EXISTS "rebrickable_minifigures";
DROP TABLE IF EXISTS "rebrickable_parts";
DROP TABLE IF EXISTS "rebrickable_sets";
DROP TABLE IF EXISTS "rebrickable_sets_new";
-- Legacy/migration tables
DROP TABLE IF EXISTS "inventory";
DROP TABLE IF EXISTS "inventory_old";
DROP TABLE IF EXISTS "minifigures";
DROP TABLE IF EXISTS "minifigures_old";
DROP TABLE IF EXISTS "missing";
DROP TABLE IF EXISTS "missing_old";
DROP TABLE IF EXISTS "rebrickable_minifigures";
DROP TABLE IF EXISTS "rebrickable_parts";
DROP TABLE IF EXISTS "rebrickable_sets";
DROP TABLE IF EXISTS "sets";
DROP TABLE IF EXISTS "sets_old";
DROP TABLE IF EXISTS "wishlist";
DROP TABLE IF EXISTS "wishlist_old";
-- Re-enable foreign key checks
PRAGMA foreign_keys = ON;
COMMIT;
PRAGMA user_version = 0;
@@ -1,9 +1,10 @@
BEGIN TRANSACTION;
-- Add owner column to set_owners table (used by all entities: sets, individual parts, individual minifigures, individual part lots)
ALTER TABLE "bricktracker_set_owners"
ADD COLUMN "owner_{{ id }}" BOOLEAN NOT NULL DEFAULT 0;
-- Also inject into wishes
-- Also inject into wishes (wishes use their own table)
ALTER TABLE "bricktracker_wish_owners"
ADD COLUMN "owner_{{ id }}" BOOLEAN NOT NULL DEFAULT 0;
@@ -1,10 +1,17 @@
SELECT
"bricktracker_metadata_purchase_locations"."id",
"bricktracker_metadata_purchase_locations"."name"
"bricktracker_metadata_purchase_locations"."name",
{% block total_sets %}
NULL as "total_sets" -- dummy for order: total_sets
{% endblock %}
FROM "bricktracker_metadata_purchase_locations"
{% block join %}{% endblock %}
{% block where %}{% endblock %}
{% block group %}{% endblock %}
{% if order %}
ORDER BY {{ order }}
{% endif %}
@@ -1,5 +1,7 @@
BEGIN TRANSACTION;
-- Add status column to set_statuses table (used by all entities: sets, individual parts, individual minifigures)
-- Note: Individual part lots don't have statuses
ALTER TABLE "bricktracker_set_statuses"
ADD COLUMN "status_{{ id }}" BOOLEAN NOT NULL DEFAULT 0;
@@ -1,5 +1,6 @@
BEGIN TRANSACTION;
-- Add tag column to set_tags table (used by all entities: sets, individual parts, individual minifigures, individual part lots)
ALTER TABLE "bricktracker_set_tags"
ADD COLUMN "tag_{{ id }}" BOOLEAN NOT NULL DEFAULT 0;
+135 -14
View File
@@ -1,7 +1,5 @@
-- Statistics Overview Query (Optimized with CTEs)
-- Provides comprehensive statistics for BrickTracker dashboard
-- Performance improved by consolidating subqueries into CTEs
-- Expected impact: 60-80% performance improvement for dashboard loading
-- Statistics Overview Query
-- Provides statistics for BrickTracker dashboard
WITH
-- Set statistics aggregation
@@ -21,31 +19,147 @@ set_stats AS (
FROM "bricktracker_sets"
),
-- Part statistics aggregation
part_stats AS (
-- Part statistics aggregation (set-based parts)
set_part_stats AS (
SELECT
COUNT(*) AS total_part_instances,
SUM("quantity") AS total_parts_count,
COALESCE(SUM("quantity"), 0) AS total_parts_count,
COUNT(DISTINCT "part") AS unique_parts,
SUM("missing") AS total_missing_parts,
SUM("damaged") AS total_damaged_parts
COALESCE(SUM("missing"), 0) AS total_missing_parts,
COALESCE(SUM("damaged"), 0) AS total_damaged_parts
FROM "bricktracker_parts"
),
-- Minifigure statistics aggregation
minifig_stats AS (
-- Individual part statistics aggregation
individual_part_stats AS (
SELECT
COUNT(*) AS total_individual_parts,
COALESCE(SUM("quantity"), 0) AS total_individual_parts_count,
COUNT(DISTINCT "part") AS unique_individual_parts,
COALESCE(SUM("missing"), 0) AS total_missing_individual_parts,
COALESCE(SUM("damaged"), 0) AS total_damaged_individual_parts,
COUNT(CASE WHEN "purchase_price" IS NOT NULL AND "lot_id" IS NULL THEN 1 END) AS individual_parts_with_price,
COALESCE(ROUND(SUM(CASE WHEN "lot_id" IS NULL THEN "purchase_price" END), 2), 0) AS individual_parts_total_cost
FROM "bricktracker_individual_parts"
),
-- Combined part statistics
part_stats AS (
SELECT
set_part_stats.total_part_instances + COALESCE(individual_part_stats.total_individual_parts, 0) AS total_part_instances,
set_part_stats.total_parts_count + COALESCE(individual_part_stats.total_individual_parts_count, 0) AS total_parts_count,
(SELECT COUNT(DISTINCT "part") FROM (
SELECT "part" FROM "bricktracker_parts"
UNION
SELECT "part" FROM "bricktracker_individual_parts"
)) AS unique_parts,
set_part_stats.total_missing_parts + COALESCE(individual_part_stats.total_missing_individual_parts, 0) AS total_missing_parts,
set_part_stats.total_damaged_parts + COALESCE(individual_part_stats.total_damaged_individual_parts, 0) AS total_damaged_parts
FROM set_part_stats, individual_part_stats
),
-- Minifigure statistics aggregation (set-based minifigures)
set_minifig_stats AS (
SELECT
COUNT(*) AS total_minifigure_instances,
SUM("quantity") AS total_minifigures_count,
COALESCE(SUM("quantity"), 0) AS total_minifigures_count,
COUNT(DISTINCT "figure") AS unique_minifigures
FROM "bricktracker_minifigures"
),
-- Individual minifigure statistics aggregation
individual_minifig_stats AS (
SELECT
COUNT(*) AS total_individual_minifigures,
COALESCE(SUM("quantity"), 0) AS total_individual_minifigures_count,
COUNT(DISTINCT "figure") AS unique_individual_minifigures,
COUNT(CASE WHEN "purchase_price" IS NOT NULL THEN 1 END) AS individual_minifigs_with_price,
COALESCE(ROUND(SUM("purchase_price"), 2), 0) AS individual_minifigs_total_cost
FROM "bricktracker_individual_minifigures"
),
-- Combined minifigure statistics
minifig_stats AS (
SELECT
set_minifig_stats.total_minifigure_instances + COALESCE(individual_minifig_stats.total_individual_minifigures, 0) AS total_minifigure_instances,
set_minifig_stats.total_minifigures_count + COALESCE(individual_minifig_stats.total_individual_minifigures_count, 0) AS total_minifigures_count,
(SELECT COUNT(DISTINCT "figure") FROM (
SELECT "figure" FROM "bricktracker_minifigures"
UNION
SELECT "figure" FROM "bricktracker_individual_minifigures"
)) AS unique_minifigures
FROM set_minifig_stats, individual_minifig_stats
),
-- Part lot statistics aggregation
part_lot_stats AS (
SELECT
COUNT(*) AS total_part_lots,
COUNT(CASE WHEN "purchase_price" IS NOT NULL THEN 1 END) AS part_lots_with_price,
ROUND(SUM("purchase_price"), 2) AS part_lots_total_cost
FROM "bricktracker_individual_part_lots"
),
-- Rebrickable sets count (for sets we actually own)
rebrickable_stats AS (
SELECT COUNT(*) AS unique_rebrickable_sets
FROM "rebrickable_sets"
WHERE "set" IN (SELECT DISTINCT "set" FROM "bricktracker_sets")
),
-- Combined financial statistics
financial_stats AS (
SELECT
-- Items with price
set_stats.sets_with_price +
COALESCE(individual_part_stats.individual_parts_with_price, 0) +
COALESCE(individual_minifig_stats.individual_minifigs_with_price, 0) +
COALESCE(part_lot_stats.part_lots_with_price, 0) AS total_items_with_price,
-- Total cost across all item types
ROUND(COALESCE(set_stats.total_cost, 0) +
COALESCE(individual_part_stats.individual_parts_total_cost, 0) +
COALESCE(individual_minifig_stats.individual_minifigs_total_cost, 0) +
COALESCE(part_lot_stats.part_lots_total_cost, 0), 2) AS combined_total_cost,
-- Average cost across all items with price
CASE
WHEN (set_stats.sets_with_price +
COALESCE(individual_part_stats.individual_parts_with_price, 0) +
COALESCE(individual_minifig_stats.individual_minifigs_with_price, 0) +
COALESCE(part_lot_stats.part_lots_with_price, 0)) > 0
THEN ROUND((COALESCE(set_stats.total_cost, 0) +
COALESCE(individual_part_stats.individual_parts_total_cost, 0) +
COALESCE(individual_minifig_stats.individual_minifigs_total_cost, 0) +
COALESCE(part_lot_stats.part_lots_total_cost, 0)) /
(set_stats.sets_with_price +
COALESCE(individual_part_stats.individual_parts_with_price, 0) +
COALESCE(individual_minifig_stats.individual_minifigs_with_price, 0) +
COALESCE(part_lot_stats.part_lots_with_price, 0)), 2)
ELSE 0
END AS combined_average_cost,
-- Min/Max price across all item types
(SELECT MIN(price) FROM (
SELECT "purchase_price" AS price FROM "bricktracker_sets" WHERE "purchase_price" IS NOT NULL
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_parts" WHERE "purchase_price" IS NOT NULL AND "lot_id" IS NULL
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_minifigures" WHERE "purchase_price" IS NOT NULL
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_part_lots" WHERE "purchase_price" IS NOT NULL
)) AS combined_minimum_cost,
(SELECT MAX(price) FROM (
SELECT "purchase_price" AS price FROM "bricktracker_sets" WHERE "purchase_price" IS NOT NULL
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_parts" WHERE "purchase_price" IS NOT NULL AND "lot_id" IS NULL
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_minifigures" WHERE "purchase_price" IS NOT NULL
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_part_lots" WHERE "purchase_price" IS NOT NULL
)) AS combined_maximum_cost
FROM set_stats, individual_part_stats, individual_minifig_stats, part_lot_stats
)
-- Final select combining all statistics
@@ -67,17 +181,24 @@ SELECT
minifig_stats.total_minifigures_count,
minifig_stats.unique_minifigures,
-- Financial statistics
-- Financial statistics (set-only for backwards compatibility)
set_stats.sets_with_price,
set_stats.total_cost,
set_stats.average_cost,
set_stats.minimum_cost,
set_stats.maximum_cost,
-- Combined financial statistics (all item types)
financial_stats.total_items_with_price,
financial_stats.combined_total_cost,
financial_stats.combined_average_cost,
financial_stats.combined_minimum_cost,
financial_stats.combined_maximum_cost,
-- Storage and location statistics
set_stats.storage_locations_used,
set_stats.purchase_locations_used,
set_stats.sets_with_storage,
set_stats.sets_with_purchase_location
FROM set_stats, part_stats, minifig_stats, rebrickable_stats
FROM set_stats, part_stats, minifig_stats, rebrickable_stats, financial_stats
@@ -1,45 +1,174 @@
-- Purchase Location Statistics
-- Shows statistics grouped by purchase location
-- Includes sets, individual parts, individual minifigures, and part lots
WITH
-- Set statistics by purchase location
set_purchase_stats AS (
SELECT
"bricktracker_sets"."purchase_location" AS "location_id",
COUNT("bricktracker_sets"."id") AS "set_count",
COUNT(DISTINCT "bricktracker_sets"."set") AS "unique_set_count",
SUM("rebrickable_sets"."number_of_parts") AS "total_parts",
COUNT(CASE WHEN "bricktracker_sets"."purchase_price" IS NOT NULL THEN 1 END) AS "sets_with_price",
ROUND(SUM("bricktracker_sets"."purchase_price"), 2) AS "total_spent",
MIN("bricktracker_sets"."purchase_date") AS "first_purchase",
MAX("bricktracker_sets"."purchase_date") AS "latest_purchase",
COALESCE(SUM("problem_stats"."missing_parts"), 0) AS "missing_parts",
COALESCE(SUM("problem_stats"."damaged_parts"), 0) AS "damaged_parts",
COALESCE(SUM("minifigure_stats"."minifigure_count"), 0) AS "total_minifigures"
FROM "bricktracker_sets"
INNER JOIN "rebrickable_sets" ON "bricktracker_sets"."set" = "rebrickable_sets"."set"
LEFT JOIN (
SELECT
"bricktracker_parts"."id",
SUM("bricktracker_parts"."missing") AS "missing_parts",
SUM("bricktracker_parts"."damaged") AS "damaged_parts"
FROM "bricktracker_parts"
GROUP BY "bricktracker_parts"."id"
) "problem_stats" ON "bricktracker_sets"."id" = "problem_stats"."id"
LEFT JOIN (
SELECT
"bricktracker_minifigures"."id",
SUM("bricktracker_minifigures"."quantity") AS "minifigure_count"
FROM "bricktracker_minifigures"
GROUP BY "bricktracker_minifigures"."id"
) "minifigure_stats" ON "bricktracker_sets"."id" = "minifigure_stats"."id"
WHERE "bricktracker_sets"."purchase_location" IS NOT NULL
GROUP BY "bricktracker_sets"."purchase_location"
),
-- Individual part statistics by purchase location
individual_part_purchase_stats AS (
SELECT
"purchase_location" AS "location_id",
COUNT(*) AS "individual_part_count",
SUM("quantity") AS "individual_part_quantity",
SUM("missing") AS "individual_missing_parts",
SUM("damaged") AS "individual_damaged_parts",
COUNT(CASE WHEN "purchase_price" IS NOT NULL THEN 1 END) AS "individual_parts_with_price",
ROUND(SUM("purchase_price"), 2) AS "individual_total_spent",
MIN("purchase_date") AS "individual_first_purchase",
MAX("purchase_date") AS "individual_latest_purchase"
FROM "bricktracker_individual_parts"
WHERE "purchase_location" IS NOT NULL AND "lot_id" IS NULL
GROUP BY "purchase_location"
),
-- Individual minifigure statistics by purchase location
individual_minifig_purchase_stats AS (
SELECT
"purchase_location" AS "location_id",
COUNT(*) AS "individual_minifig_count",
SUM("quantity") AS "individual_minifig_quantity",
COUNT(CASE WHEN "purchase_price" IS NOT NULL THEN 1 END) AS "individual_minifigs_with_price",
ROUND(SUM("purchase_price"), 2) AS "individual_minifig_total_spent",
MIN("purchase_date") AS "individual_minifig_first_purchase",
MAX("purchase_date") AS "individual_minifig_latest_purchase"
FROM "bricktracker_individual_minifigures"
WHERE "purchase_location" IS NOT NULL
GROUP BY "purchase_location"
),
-- Part lot statistics by purchase location
part_lot_purchase_stats AS (
SELECT
"purchase_location" AS "location_id",
COUNT(*) AS "lot_count",
COUNT(CASE WHEN "purchase_price" IS NOT NULL THEN 1 END) AS "lots_with_price",
ROUND(SUM("purchase_price"), 2) AS "lot_total_spent",
MIN("purchase_date") AS "lot_first_purchase",
MAX("purchase_date") AS "lot_latest_purchase"
FROM "bricktracker_individual_part_lots"
WHERE "purchase_location" IS NOT NULL
GROUP BY "purchase_location"
),
-- Min/Max price calculations (across all item types)
price_stats AS (
SELECT
"purchase_location" AS "location_id",
MIN("purchase_price") AS "min_price",
MAX("purchase_price") AS "max_price"
FROM (
SELECT "purchase_location", "purchase_price" FROM "bricktracker_sets" WHERE "purchase_price" IS NOT NULL
UNION ALL
SELECT "purchase_location", "purchase_price" FROM "bricktracker_individual_parts" WHERE "purchase_price" IS NOT NULL AND "lot_id" IS NULL
UNION ALL
SELECT "purchase_location", "purchase_price" FROM "bricktracker_individual_minifigures" WHERE "purchase_price" IS NOT NULL
UNION ALL
SELECT "purchase_location", "purchase_price" FROM "bricktracker_individual_part_lots" WHERE "purchase_price" IS NOT NULL
)
WHERE "purchase_location" IS NOT NULL
GROUP BY "purchase_location"
)
-- Combine all statistics
SELECT
"bricktracker_sets"."purchase_location" AS "location_id",
COALESCE(sps.location_id, ipps.location_id, imps.location_id, plps.location_id) AS "location_id",
"bricktracker_metadata_purchase_locations"."name" AS "location_name",
COUNT("bricktracker_sets"."id") AS "set_count",
COUNT(DISTINCT "bricktracker_sets"."set") AS "unique_set_count",
SUM("rebrickable_sets"."number_of_parts") AS "total_parts",
ROUND(AVG("rebrickable_sets"."number_of_parts"), 0) AS "avg_parts_per_set",
-- Financial statistics per purchase location
COUNT(CASE WHEN "bricktracker_sets"."purchase_price" IS NOT NULL THEN 1 END) AS "sets_with_price",
ROUND(SUM("bricktracker_sets"."purchase_price"), 2) AS "total_spent",
ROUND(AVG("bricktracker_sets"."purchase_price"), 2) AS "avg_price",
ROUND(MIN("bricktracker_sets"."purchase_price"), 2) AS "min_price",
ROUND(MAX("bricktracker_sets"."purchase_price"), 2) AS "max_price",
-- Date range statistics
MIN("bricktracker_sets"."purchase_date") AS "first_purchase",
MAX("bricktracker_sets"."purchase_date") AS "latest_purchase",
-- Problem statistics per purchase location
COALESCE(SUM("problem_stats"."missing_parts"), 0) AS "missing_parts",
COALESCE(SUM("problem_stats"."damaged_parts"), 0) AS "damaged_parts",
-- Minifigure statistics per purchase location
COALESCE(SUM("minifigure_stats"."minifigure_count"), 0) AS "total_minifigures"
FROM "bricktracker_sets"
INNER JOIN "rebrickable_sets" ON "bricktracker_sets"."set" = "rebrickable_sets"."set"
LEFT JOIN "bricktracker_metadata_purchase_locations" ON "bricktracker_sets"."purchase_location" = "bricktracker_metadata_purchase_locations"."id"
LEFT JOIN (
SELECT
"bricktracker_parts"."id",
SUM("bricktracker_parts"."missing") AS "missing_parts",
SUM("bricktracker_parts"."damaged") AS "damaged_parts"
FROM "bricktracker_parts"
GROUP BY "bricktracker_parts"."id"
) "problem_stats" ON "bricktracker_sets"."id" = "problem_stats"."id"
LEFT JOIN (
SELECT
"bricktracker_minifigures"."id",
SUM("bricktracker_minifigures"."quantity") AS "minifigure_count"
FROM "bricktracker_minifigures"
GROUP BY "bricktracker_minifigures"."id"
) "minifigure_stats" ON "bricktracker_sets"."id" = "minifigure_stats"."id"
WHERE "bricktracker_sets"."purchase_location" IS NOT NULL
GROUP BY "bricktracker_sets"."purchase_location", "bricktracker_metadata_purchase_locations"."name"
ORDER BY "set_count" DESC, "location_name" ASC
-- Set counts
COALESCE(sps.set_count, 0) AS "set_count",
COALESCE(sps.unique_set_count, 0) AS "unique_set_count",
-- Individual item counts
COALESCE(ipps.individual_part_count, 0) AS "individual_part_count",
COALESCE(imps.individual_minifig_count, 0) AS "individual_minifig_count",
COALESCE(plps.lot_count, 0) AS "lot_count",
-- Part counts
COALESCE(sps.total_parts, 0) + COALESCE(ipps.individual_part_quantity, 0) AS "total_parts",
CASE
WHEN COALESCE(sps.set_count, 0) > 0
THEN ROUND(CAST(COALESCE(sps.total_parts, 0) AS FLOAT) / sps.set_count, 0)
ELSE 0
END AS "avg_parts_per_set",
-- Financial statistics
COALESCE(sps.sets_with_price, 0) + COALESCE(ipps.individual_parts_with_price, 0) +
COALESCE(imps.individual_minifigs_with_price, 0) + COALESCE(plps.lots_with_price, 0) AS "items_with_price",
ROUND(COALESCE(sps.total_spent, 0) + COALESCE(ipps.individual_total_spent, 0) +
COALESCE(imps.individual_minifig_total_spent, 0) + COALESCE(plps.lot_total_spent, 0), 2) AS "total_spent",
CASE
WHEN (COALESCE(sps.sets_with_price, 0) + COALESCE(ipps.individual_parts_with_price, 0) +
COALESCE(imps.individual_minifigs_with_price, 0) + COALESCE(plps.lots_with_price, 0)) > 0
THEN ROUND((COALESCE(sps.total_spent, 0) + COALESCE(ipps.individual_total_spent, 0) +
COALESCE(imps.individual_minifig_total_spent, 0) + COALESCE(plps.lot_total_spent, 0)) /
(COALESCE(sps.sets_with_price, 0) + COALESCE(ipps.individual_parts_with_price, 0) +
COALESCE(imps.individual_minifigs_with_price, 0) + COALESCE(plps.lots_with_price, 0)), 2)
ELSE 0
END AS "avg_price",
ROUND(COALESCE(ps.min_price, 0), 2) AS "min_price",
ROUND(COALESCE(ps.max_price, 0), 2) AS "max_price",
-- Date range statistics (earliest and latest purchases across all types)
(SELECT MIN(d) FROM (
SELECT sps.first_purchase AS d
UNION ALL SELECT ipps.individual_first_purchase
UNION ALL SELECT imps.individual_minifig_first_purchase
UNION ALL SELECT plps.lot_first_purchase
) WHERE d IS NOT NULL) AS "first_purchase",
(SELECT MAX(d) FROM (
SELECT sps.latest_purchase AS d
UNION ALL SELECT ipps.individual_latest_purchase
UNION ALL SELECT imps.individual_minifig_latest_purchase
UNION ALL SELECT plps.lot_latest_purchase
) WHERE d IS NOT NULL) AS "latest_purchase",
-- Problem statistics
COALESCE(sps.missing_parts, 0) + COALESCE(ipps.individual_missing_parts, 0) AS "missing_parts",
COALESCE(sps.damaged_parts, 0) + COALESCE(ipps.individual_damaged_parts, 0) AS "damaged_parts",
-- Minifigure counts
COALESCE(sps.total_minifigures, 0) + COALESCE(imps.individual_minifig_quantity, 0) AS "total_minifigures"
FROM set_purchase_stats sps
FULL OUTER JOIN individual_part_purchase_stats ipps ON sps.location_id = ipps.location_id
FULL OUTER JOIN individual_minifig_purchase_stats imps ON COALESCE(sps.location_id, ipps.location_id) = imps.location_id
FULL OUTER JOIN part_lot_purchase_stats plps ON COALESCE(sps.location_id, ipps.location_id, imps.location_id) = plps.location_id
LEFT JOIN price_stats ps ON COALESCE(sps.location_id, ipps.location_id, imps.location_id, plps.location_id) = ps.location_id
LEFT JOIN "bricktracker_metadata_purchase_locations" ON COALESCE(sps.location_id, ipps.location_id, imps.location_id, plps.location_id) = "bricktracker_metadata_purchase_locations"."id"
ORDER BY "set_count" DESC, "location_name" ASC
+217 -46
View File
@@ -1,49 +1,220 @@
-- Purchases by Year Statistics
-- Shows statistics grouped by purchase year (when you bought the sets)
-- Shows statistics grouped by purchase year (when you bought items)
-- Includes sets, individual parts, individual minifigures, and part lots
WITH
-- Set purchases by year
set_purchases AS (
SELECT
strftime('%Y', datetime("bricktracker_sets"."purchase_date", 'unixepoch')) AS "purchase_year",
COUNT("bricktracker_sets"."id") AS "total_sets",
COUNT(DISTINCT "bricktracker_sets"."set") AS "unique_sets",
SUM("rebrickable_sets"."number_of_parts") AS "total_parts",
ROUND(AVG("rebrickable_sets"."number_of_parts"), 0) AS "avg_parts_per_set",
COUNT(CASE WHEN "bricktracker_sets"."purchase_price" IS NOT NULL THEN 1 END) AS "sets_with_price",
ROUND(SUM("bricktracker_sets"."purchase_price"), 2) AS "sets_total_spent",
MIN("rebrickable_sets"."year") AS "oldest_set_year",
MAX("rebrickable_sets"."year") AS "newest_set_year",
ROUND(AVG("rebrickable_sets"."year"), 0) AS "avg_set_release_year",
COALESCE(SUM("problem_stats"."missing_parts"), 0) AS "set_missing_parts",
COALESCE(SUM("problem_stats"."damaged_parts"), 0) AS "set_damaged_parts",
COALESCE(SUM("minifigure_stats"."minifigure_count"), 0) AS "set_minifigures",
COALESCE(SUM("minifigure_stats"."unique_minifigures"), 0) AS "set_unique_minifigures",
COUNT(DISTINCT "rebrickable_sets"."theme_id") AS "unique_themes",
COUNT(DISTINCT "bricktracker_sets"."purchase_location") AS "set_unique_purchase_locations",
COUNT(DISTINCT strftime('%m', datetime("bricktracker_sets"."purchase_date", 'unixepoch'))) AS "months_with_purchases"
FROM "bricktracker_sets"
INNER JOIN "rebrickable_sets" ON "bricktracker_sets"."set" = "rebrickable_sets"."set"
LEFT JOIN (
SELECT
"bricktracker_parts"."id",
SUM("bricktracker_parts"."missing") AS "missing_parts",
SUM("bricktracker_parts"."damaged") AS "damaged_parts"
FROM "bricktracker_parts"
GROUP BY "bricktracker_parts"."id"
) "problem_stats" ON "bricktracker_sets"."id" = "problem_stats"."id"
LEFT JOIN (
SELECT
"bricktracker_minifigures"."id",
SUM("bricktracker_minifigures"."quantity") AS "minifigure_count",
COUNT(DISTINCT "bricktracker_minifigures"."figure") AS "unique_minifigures"
FROM "bricktracker_minifigures"
GROUP BY "bricktracker_minifigures"."id"
) "minifigure_stats" ON "bricktracker_sets"."id" = "minifigure_stats"."id"
WHERE "bricktracker_sets"."purchase_date" IS NOT NULL
GROUP BY strftime('%Y', datetime("bricktracker_sets"."purchase_date", 'unixepoch'))
),
-- Individual part purchases by year
individual_part_purchases AS (
SELECT
strftime('%Y', datetime("bricktracker_individual_parts"."purchase_date", 'unixepoch')) AS "purchase_year",
COUNT(*) AS "individual_part_count",
SUM("bricktracker_individual_parts"."quantity") AS "individual_part_quantity",
COUNT(DISTINCT "bricktracker_individual_parts"."part" || '-' || "bricktracker_individual_parts"."color") AS "unique_individual_parts",
SUM("bricktracker_individual_parts"."missing") AS "individual_missing_parts",
SUM("bricktracker_individual_parts"."damaged") AS "individual_damaged_parts",
COUNT(CASE WHEN "bricktracker_individual_parts"."purchase_price" IS NOT NULL AND "bricktracker_individual_parts"."lot_id" IS NULL THEN 1 END) AS "individual_parts_with_price",
ROUND(SUM(CASE WHEN "bricktracker_individual_parts"."lot_id" IS NULL THEN "bricktracker_individual_parts"."purchase_price" END), 2) AS "individual_parts_total_spent",
COUNT(DISTINCT "bricktracker_individual_parts"."purchase_location") AS "individual_part_unique_purchase_locations"
FROM "bricktracker_individual_parts"
WHERE "bricktracker_individual_parts"."purchase_date" IS NOT NULL
GROUP BY strftime('%Y', datetime("bricktracker_individual_parts"."purchase_date", 'unixepoch'))
),
-- Individual minifigure purchases by year
individual_minifig_purchases AS (
SELECT
strftime('%Y', datetime("bricktracker_individual_minifigures"."purchase_date", 'unixepoch')) AS "purchase_year",
COUNT(*) AS "individual_minifig_count",
SUM("bricktracker_individual_minifigures"."quantity") AS "individual_minifig_quantity",
COUNT(DISTINCT "bricktracker_individual_minifigures"."figure") AS "unique_individual_minifigures",
COUNT(CASE WHEN "bricktracker_individual_minifigures"."purchase_price" IS NOT NULL THEN 1 END) AS "individual_minifigs_with_price",
ROUND(SUM("bricktracker_individual_minifigures"."purchase_price"), 2) AS "individual_minifigs_total_spent",
COUNT(DISTINCT "bricktracker_individual_minifigures"."purchase_location") AS "individual_minifig_unique_purchase_locations"
FROM "bricktracker_individual_minifigures"
WHERE "bricktracker_individual_minifigures"."purchase_date" IS NOT NULL
GROUP BY strftime('%Y', datetime("bricktracker_individual_minifigures"."purchase_date", 'unixepoch'))
),
-- Part lot purchases by year
part_lot_purchases AS (
SELECT
strftime('%Y', datetime("bricktracker_individual_part_lots"."purchase_date", 'unixepoch')) AS "purchase_year",
COUNT(*) AS "lot_count",
COUNT(CASE WHEN "bricktracker_individual_part_lots"."purchase_price" IS NOT NULL THEN 1 END) AS "lots_with_price",
ROUND(SUM("bricktracker_individual_part_lots"."purchase_price"), 2) AS "lots_total_spent",
COUNT(DISTINCT "bricktracker_individual_part_lots"."purchase_location") AS "lot_unique_purchase_locations"
FROM "bricktracker_individual_part_lots"
WHERE "bricktracker_individual_part_lots"."purchase_date" IS NOT NULL
GROUP BY strftime('%Y', datetime("bricktracker_individual_part_lots"."purchase_date", 'unixepoch'))
),
-- All purchase years (union of all types)
all_years AS (
SELECT DISTINCT "purchase_year" FROM set_purchases WHERE "purchase_year" IS NOT NULL
UNION
SELECT DISTINCT "purchase_year" FROM individual_part_purchases WHERE "purchase_year" IS NOT NULL
UNION
SELECT DISTINCT "purchase_year" FROM individual_minifig_purchases WHERE "purchase_year" IS NOT NULL
UNION
SELECT DISTINCT "purchase_year" FROM part_lot_purchases WHERE "purchase_year" IS NOT NULL
)
-- Combine all statistics
SELECT
strftime('%Y', datetime("bricktracker_sets"."purchase_date", 'unixepoch')) AS "purchase_year",
COUNT("bricktracker_sets"."id") AS "total_sets",
COUNT(DISTINCT "bricktracker_sets"."set") AS "unique_sets",
SUM("rebrickable_sets"."number_of_parts") AS "total_parts",
ROUND(AVG("rebrickable_sets"."number_of_parts"), 0) AS "avg_parts_per_set",
-- Financial statistics per purchase year
COUNT(CASE WHEN "bricktracker_sets"."purchase_price" IS NOT NULL THEN 1 END) AS "sets_with_price",
ROUND(SUM("bricktracker_sets"."purchase_price"), 2) AS "total_spent",
ROUND(AVG("bricktracker_sets"."purchase_price"), 2) AS "avg_price_per_set",
ROUND(MIN("bricktracker_sets"."purchase_price"), 2) AS "min_price",
ROUND(MAX("bricktracker_sets"."purchase_price"), 2) AS "max_price",
-- Release year statistics for sets purchased in this year
MIN("rebrickable_sets"."year") AS "oldest_set_year",
MAX("rebrickable_sets"."year") AS "newest_set_year",
ROUND(AVG("rebrickable_sets"."year"), 0) AS "avg_set_release_year",
-- Problem statistics per purchase year
COALESCE(SUM("problem_stats"."missing_parts"), 0) AS "missing_parts",
COALESCE(SUM("problem_stats"."damaged_parts"), 0) AS "damaged_parts",
-- Minifigure statistics per purchase year
COALESCE(SUM("minifigure_stats"."minifigure_count"), 0) AS "total_minifigures",
-- Diversity statistics per purchase year
COUNT(DISTINCT "rebrickable_sets"."theme_id") AS "unique_themes",
COUNT(DISTINCT "bricktracker_sets"."purchase_location") AS "unique_purchase_locations",
-- Monthly statistics within the year
COUNT(DISTINCT strftime('%m', datetime("bricktracker_sets"."purchase_date", 'unixepoch'))) AS "months_with_purchases"
FROM "bricktracker_sets"
INNER JOIN "rebrickable_sets" ON "bricktracker_sets"."set" = "rebrickable_sets"."set"
LEFT JOIN (
SELECT
"bricktracker_parts"."id",
SUM("bricktracker_parts"."missing") AS "missing_parts",
SUM("bricktracker_parts"."damaged") AS "damaged_parts"
FROM "bricktracker_parts"
GROUP BY "bricktracker_parts"."id"
) "problem_stats" ON "bricktracker_sets"."id" = "problem_stats"."id"
LEFT JOIN (
SELECT
"bricktracker_minifigures"."id",
SUM("bricktracker_minifigures"."quantity") AS "minifigure_count"
FROM "bricktracker_minifigures"
GROUP BY "bricktracker_minifigures"."id"
) "minifigure_stats" ON "bricktracker_sets"."id" = "minifigure_stats"."id"
WHERE "bricktracker_sets"."purchase_date" IS NOT NULL
GROUP BY strftime('%Y', datetime("bricktracker_sets"."purchase_date", 'unixepoch'))
ORDER BY "purchase_year" DESC
ay.purchase_year,
-- Set counts
COALESCE(sp.total_sets, 0) AS "total_sets",
COALESCE(sp.unique_sets, 0) AS "unique_sets",
-- Individual item counts
COALESCE(ipp.individual_part_count, 0) AS "individual_part_count",
COALESCE(imp.individual_minifig_count, 0) AS "individual_minifig_count",
COALESCE(plp.lot_count, 0) AS "lot_count",
-- Part counts (unique and total)
COALESCE(ipp.unique_individual_parts, 0) AS "unique_parts",
COALESCE(sp.total_parts, 0) + COALESCE(ipp.individual_part_quantity, 0) AS "total_parts",
COALESCE(sp.avg_parts_per_set, 0) AS "avg_parts_per_set",
-- Minifigure counts (unique and total)
COALESCE(sp.set_unique_minifigures, 0) + COALESCE(imp.unique_individual_minifigures, 0) AS "unique_minifigures",
COALESCE(sp.set_minifigures, 0) + COALESCE(imp.individual_minifig_quantity, 0) AS "total_minifigures",
-- Financial statistics (combined)
COALESCE(sp.sets_with_price, 0) + COALESCE(ipp.individual_parts_with_price, 0) +
COALESCE(imp.individual_minifigs_with_price, 0) + COALESCE(plp.lots_with_price, 0) AS "items_with_price",
ROUND(COALESCE(sp.sets_total_spent, 0) + COALESCE(ipp.individual_parts_total_spent, 0) +
COALESCE(imp.individual_minifigs_total_spent, 0) + COALESCE(plp.lots_total_spent, 0), 2) AS "total_spent",
-- Average, min, max price across all item types for this year
CASE
WHEN (COALESCE(sp.sets_with_price, 0) + COALESCE(ipp.individual_parts_with_price, 0) +
COALESCE(imp.individual_minifigs_with_price, 0) + COALESCE(plp.lots_with_price, 0)) > 0
THEN ROUND((COALESCE(sp.sets_total_spent, 0) + COALESCE(ipp.individual_parts_total_spent, 0) +
COALESCE(imp.individual_minifigs_total_spent, 0) + COALESCE(plp.lots_total_spent, 0)) /
(COALESCE(sp.sets_with_price, 0) + COALESCE(ipp.individual_parts_with_price, 0) +
COALESCE(imp.individual_minifigs_with_price, 0) + COALESCE(plp.lots_with_price, 0)), 2)
ELSE 0
END AS "avg_price_per_item",
(SELECT MIN(price) FROM (
SELECT "purchase_price" AS price FROM "bricktracker_sets"
WHERE "purchase_price" IS NOT NULL
AND strftime('%Y', datetime("purchase_date", 'unixepoch')) = ay.purchase_year
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_parts"
WHERE "purchase_price" IS NOT NULL AND "lot_id" IS NULL
AND strftime('%Y', datetime("purchase_date", 'unixepoch')) = ay.purchase_year
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_minifigures"
WHERE "purchase_price" IS NOT NULL
AND strftime('%Y', datetime("purchase_date", 'unixepoch')) = ay.purchase_year
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_part_lots"
WHERE "purchase_price" IS NOT NULL
AND strftime('%Y', datetime("purchase_date", 'unixepoch')) = ay.purchase_year
)) AS "min_price",
(SELECT MAX(price) FROM (
SELECT "purchase_price" AS price FROM "bricktracker_sets"
WHERE "purchase_price" IS NOT NULL
AND strftime('%Y', datetime("purchase_date", 'unixepoch')) = ay.purchase_year
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_parts"
WHERE "purchase_price" IS NOT NULL AND "lot_id" IS NULL
AND strftime('%Y', datetime("purchase_date", 'unixepoch')) = ay.purchase_year
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_minifigures"
WHERE "purchase_price" IS NOT NULL
AND strftime('%Y', datetime("purchase_date", 'unixepoch')) = ay.purchase_year
UNION ALL
SELECT "purchase_price" FROM "bricktracker_individual_part_lots"
WHERE "purchase_price" IS NOT NULL
AND strftime('%Y', datetime("purchase_date", 'unixepoch')) = ay.purchase_year
)) AS "max_price",
-- Set-specific statistics (for backward compatibility, may be NULL if no sets purchased this year)
sp.oldest_set_year,
sp.newest_set_year,
sp.avg_set_release_year,
-- Backward compatibility: avg_price_per_set uses combined average (duplicate calculation)
CASE
WHEN (COALESCE(sp.sets_with_price, 0) + COALESCE(ipp.individual_parts_with_price, 0) +
COALESCE(imp.individual_minifigs_with_price, 0) + COALESCE(plp.lots_with_price, 0)) > 0
THEN ROUND((COALESCE(sp.sets_total_spent, 0) + COALESCE(ipp.individual_parts_total_spent, 0) +
COALESCE(imp.individual_minifigs_total_spent, 0) + COALESCE(plp.lots_total_spent, 0)) /
(COALESCE(sp.sets_with_price, 0) + COALESCE(ipp.individual_parts_with_price, 0) +
COALESCE(imp.individual_minifigs_with_price, 0) + COALESCE(plp.lots_with_price, 0)), 2)
ELSE 0
END AS "avg_price_per_set",
-- Problem statistics
COALESCE(sp.set_missing_parts, 0) + COALESCE(ipp.individual_missing_parts, 0) AS "missing_parts",
COALESCE(sp.set_damaged_parts, 0) + COALESCE(ipp.individual_damaged_parts, 0) AS "damaged_parts",
-- Diversity statistics
COALESCE(sp.unique_themes, 0) AS "unique_themes",
(SELECT COUNT(DISTINCT location) FROM (
SELECT COALESCE(sp.set_unique_purchase_locations, 0) AS location
UNION
SELECT COALESCE(ipp.individual_part_unique_purchase_locations, 0)
UNION
SELECT COALESCE(imp.individual_minifig_unique_purchase_locations, 0)
UNION
SELECT COALESCE(plp.lot_unique_purchase_locations, 0)
)) AS "unique_purchase_locations",
COALESCE(sp.months_with_purchases, 0) AS "months_with_purchases"
FROM all_years ay
LEFT JOIN set_purchases sp ON ay.purchase_year = sp.purchase_year
LEFT JOIN individual_part_purchases ipp ON ay.purchase_year = ipp.purchase_year
LEFT JOIN individual_minifig_purchases imp ON ay.purchase_year = imp.purchase_year
LEFT JOIN part_lot_purchases plp ON ay.purchase_year = plp.purchase_year
ORDER BY ay.purchase_year DESC
+125 -35
View File
@@ -1,40 +1,130 @@
-- Storage Location Statistics
-- Shows statistics grouped by storage location
-- Includes sets, individual parts, individual minifigures, and part lots
WITH
-- Set statistics by storage
set_storage_stats AS (
SELECT
"bricktracker_sets"."storage" AS "storage_id",
COUNT("bricktracker_sets"."id") AS "set_count",
COUNT(DISTINCT "bricktracker_sets"."set") AS "unique_set_count",
SUM("rebrickable_sets"."number_of_parts") AS "total_parts",
COUNT(CASE WHEN "bricktracker_sets"."purchase_price" IS NOT NULL THEN 1 END) AS "sets_with_price",
ROUND(SUM("bricktracker_sets"."purchase_price"), 2) AS "total_value",
COALESCE(SUM("problem_stats"."missing_parts"), 0) AS "missing_parts",
COALESCE(SUM("problem_stats"."damaged_parts"), 0) AS "damaged_parts",
COALESCE(SUM("minifigure_stats"."minifigure_count"), 0) AS "total_minifigures"
FROM "bricktracker_sets"
INNER JOIN "rebrickable_sets" ON "bricktracker_sets"."set" = "rebrickable_sets"."set"
LEFT JOIN (
SELECT
"bricktracker_parts"."id",
SUM("bricktracker_parts"."missing") AS "missing_parts",
SUM("bricktracker_parts"."damaged") AS "damaged_parts"
FROM "bricktracker_parts"
GROUP BY "bricktracker_parts"."id"
) "problem_stats" ON "bricktracker_sets"."id" = "problem_stats"."id"
LEFT JOIN (
SELECT
"bricktracker_minifigures"."id",
SUM("bricktracker_minifigures"."quantity") AS "minifigure_count"
FROM "bricktracker_minifigures"
GROUP BY "bricktracker_minifigures"."id"
) "minifigure_stats" ON "bricktracker_sets"."id" = "minifigure_stats"."id"
WHERE "bricktracker_sets"."storage" IS NOT NULL
GROUP BY "bricktracker_sets"."storage"
),
-- Individual part statistics by storage
individual_part_storage_stats AS (
SELECT
"storage" AS "storage_id",
COUNT(*) AS "individual_part_count",
SUM("quantity") AS "individual_part_quantity",
SUM("missing") AS "individual_missing_parts",
SUM("damaged") AS "individual_damaged_parts",
COUNT(CASE WHEN "purchase_price" IS NOT NULL THEN 1 END) AS "individual_parts_with_price",
ROUND(SUM("purchase_price"), 2) AS "individual_total_value"
FROM "bricktracker_individual_parts"
WHERE "storage" IS NOT NULL AND "lot_id" IS NULL
GROUP BY "storage"
),
-- Individual minifigure statistics by storage
individual_minifig_storage_stats AS (
SELECT
"storage" AS "storage_id",
COUNT(*) AS "individual_minifig_count",
SUM("quantity") AS "individual_minifig_quantity",
COUNT(CASE WHEN "purchase_price" IS NOT NULL THEN 1 END) AS "individual_minifigs_with_price",
ROUND(SUM("purchase_price"), 2) AS "individual_minifig_total_value"
FROM "bricktracker_individual_minifigures"
WHERE "storage" IS NOT NULL
GROUP BY "storage"
),
-- Part lot statistics by storage
part_lot_storage_stats AS (
SELECT
"storage" AS "storage_id",
COUNT(*) AS "lot_count",
COUNT(CASE WHEN "purchase_price" IS NOT NULL THEN 1 END) AS "lots_with_price",
ROUND(SUM("purchase_price"), 2) AS "lot_total_value"
FROM "bricktracker_individual_part_lots"
WHERE "storage" IS NOT NULL
GROUP BY "storage"
)
-- Combine all statistics
SELECT
"bricktracker_sets"."storage" AS "storage_id",
COALESCE(sss.storage_id, ipss.storage_id, imss.storage_id, plss.storage_id) AS "storage_id",
"bricktracker_metadata_storages"."name" AS "storage_name",
COUNT("bricktracker_sets"."id") AS "set_count",
COUNT(DISTINCT "bricktracker_sets"."set") AS "unique_set_count",
SUM("rebrickable_sets"."number_of_parts") AS "total_parts",
ROUND(AVG("rebrickable_sets"."number_of_parts"), 0) AS "avg_parts_per_set",
-- Financial statistics per storage
COUNT(CASE WHEN "bricktracker_sets"."purchase_price" IS NOT NULL THEN 1 END) AS "sets_with_price",
ROUND(SUM("bricktracker_sets"."purchase_price"), 2) AS "total_value",
ROUND(AVG("bricktracker_sets"."purchase_price"), 2) AS "avg_price",
-- Problem statistics per storage
COALESCE(SUM("problem_stats"."missing_parts"), 0) AS "missing_parts",
COALESCE(SUM("problem_stats"."damaged_parts"), 0) AS "damaged_parts",
-- Minifigure statistics per storage
COALESCE(SUM("minifigure_stats"."minifigure_count"), 0) AS "total_minifigures"
FROM "bricktracker_sets"
INNER JOIN "rebrickable_sets" ON "bricktracker_sets"."set" = "rebrickable_sets"."set"
LEFT JOIN "bricktracker_metadata_storages" ON "bricktracker_sets"."storage" = "bricktracker_metadata_storages"."id"
LEFT JOIN (
SELECT
"bricktracker_parts"."id",
SUM("bricktracker_parts"."missing") AS "missing_parts",
SUM("bricktracker_parts"."damaged") AS "damaged_parts"
FROM "bricktracker_parts"
GROUP BY "bricktracker_parts"."id"
) "problem_stats" ON "bricktracker_sets"."id" = "problem_stats"."id"
LEFT JOIN (
SELECT
"bricktracker_minifigures"."id",
SUM("bricktracker_minifigures"."quantity") AS "minifigure_count"
FROM "bricktracker_minifigures"
GROUP BY "bricktracker_minifigures"."id"
) "minifigure_stats" ON "bricktracker_sets"."id" = "minifigure_stats"."id"
WHERE "bricktracker_sets"."storage" IS NOT NULL
GROUP BY "bricktracker_sets"."storage", "bricktracker_metadata_storages"."name"
ORDER BY "set_count" DESC, "storage_name" ASC
-- Set counts
COALESCE(sss.set_count, 0) AS "set_count",
COALESCE(sss.unique_set_count, 0) AS "unique_set_count",
-- Individual item counts
COALESCE(ipss.individual_part_count, 0) AS "individual_part_count",
COALESCE(imss.individual_minifig_count, 0) AS "individual_minifig_count",
COALESCE(plss.lot_count, 0) AS "lot_count",
-- Part counts
COALESCE(sss.total_parts, 0) + COALESCE(ipss.individual_part_quantity, 0) AS "total_parts",
CASE
WHEN COALESCE(sss.set_count, 0) > 0
THEN ROUND(CAST(COALESCE(sss.total_parts, 0) AS FLOAT) / sss.set_count, 0)
ELSE 0
END AS "avg_parts_per_set",
-- Financial statistics
COALESCE(sss.sets_with_price, 0) + COALESCE(ipss.individual_parts_with_price, 0) +
COALESCE(imss.individual_minifigs_with_price, 0) + COALESCE(plss.lots_with_price, 0) AS "items_with_price",
ROUND(COALESCE(sss.total_value, 0) + COALESCE(ipss.individual_total_value, 0) +
COALESCE(imss.individual_minifig_total_value, 0) + COALESCE(plss.lot_total_value, 0), 2) AS "total_value",
CASE
WHEN (COALESCE(sss.sets_with_price, 0) + COALESCE(ipss.individual_parts_with_price, 0) +
COALESCE(imss.individual_minifigs_with_price, 0) + COALESCE(plss.lots_with_price, 0)) > 0
THEN ROUND((COALESCE(sss.total_value, 0) + COALESCE(ipss.individual_total_value, 0) +
COALESCE(imss.individual_minifig_total_value, 0) + COALESCE(plss.lot_total_value, 0)) /
(COALESCE(sss.sets_with_price, 0) + COALESCE(ipss.individual_parts_with_price, 0) +
COALESCE(imss.individual_minifigs_with_price, 0) + COALESCE(plss.lots_with_price, 0)), 2)
ELSE 0
END AS "avg_price",
-- Problem statistics
COALESCE(sss.missing_parts, 0) + COALESCE(ipss.individual_missing_parts, 0) AS "missing_parts",
COALESCE(sss.damaged_parts, 0) + COALESCE(ipss.individual_damaged_parts, 0) AS "damaged_parts",
-- Minifigure counts
COALESCE(sss.total_minifigures, 0) + COALESCE(imss.individual_minifig_quantity, 0) AS "total_minifigures"
FROM set_storage_stats sss
FULL OUTER JOIN individual_part_storage_stats ipss ON sss.storage_id = ipss.storage_id
FULL OUTER JOIN individual_minifig_storage_stats imss ON COALESCE(sss.storage_id, ipss.storage_id) = imss.storage_id
FULL OUTER JOIN part_lot_storage_stats plss ON COALESCE(sss.storage_id, ipss.storage_id, imss.storage_id) = plss.storage_id
LEFT JOIN "bricktracker_metadata_storages" ON COALESCE(sss.storage_id, ipss.storage_id, imss.storage_id, plss.storage_id) = "bricktracker_metadata_storages"."id"
ORDER BY "set_count" DESC, "storage_name" ASC