mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-07-12 18:43:45 +00:00
Merge pull request #92 from jcreek/91-forms-only-work-when-all-games-is-selected
91 forms only work when all games is selected
This commit is contained in:
Generated
+7717
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,17 @@ import { type CatchRecord, type CatchRecordDB } from '$lib/models/CatchRecord';
|
||||
import { type CombinedData } from '$lib/models/CombinedData';
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
// Raw row from game_pokedex_entry_details includes extra ordering columns not in PokedexEntryDB.
|
||||
type RawDexEntry = PokedexEntryDB & {
|
||||
dexNumber: number;
|
||||
dexSortOrder: number;
|
||||
formSortBucket: number;
|
||||
formSortRegionOrder: number;
|
||||
formSortRegionalSub: number;
|
||||
formSortLabel: string;
|
||||
unownSortOrder: number;
|
||||
};
|
||||
|
||||
class CombinedDataRepository {
|
||||
private static readonly MAX_ROWS_PER_REQUEST = 1000;
|
||||
|
||||
@@ -98,14 +109,55 @@ class CombinedDataRepository {
|
||||
return query;
|
||||
}
|
||||
|
||||
// Fetch all non-base form entries for a game from pokedex_entries, excluding already-seen IDs.
|
||||
// game_pokedex_entries is seeded with base forms only, so forms must be supplemented from here.
|
||||
private async fetchFormsForGame(
|
||||
game: string,
|
||||
region: string,
|
||||
excludeIds: Set<number>
|
||||
): Promise<PokedexEntryDB[]> {
|
||||
const allForms: PokedexEntryDB[] = [];
|
||||
let start = 0;
|
||||
const maxRows = CombinedDataRepository.MAX_ROWS_PER_REQUEST;
|
||||
|
||||
while (true) {
|
||||
const end = start + maxRows - 1;
|
||||
let query = this.supabase.from('pokedex_entries').select('*').not('form', 'is', null);
|
||||
|
||||
if (game) {
|
||||
query = query.contains('gamesToCatchIn', [game]);
|
||||
}
|
||||
if (region) {
|
||||
query = query.eq('regionToCatchIn', region);
|
||||
}
|
||||
|
||||
const { data, error } = await query.order('id', { ascending: true }).range(start, end);
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching forms for game:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!data || data.length === 0) break;
|
||||
|
||||
allForms.push(...(data as PokedexEntryDB[]).filter((e) => !excludeIds.has(e.id)));
|
||||
|
||||
if (data.length < maxRows) break;
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
return allForms;
|
||||
}
|
||||
|
||||
private async fetchAllDexEntries(
|
||||
dexScopes: string[],
|
||||
enableForms: boolean,
|
||||
region: string
|
||||
region: string,
|
||||
game: string = ''
|
||||
): Promise<PokedexEntryDB[]> {
|
||||
if (dexScopes.length === 0) return [];
|
||||
|
||||
const entries: PokedexEntryDB[] = [];
|
||||
const entries: RawDexEntry[] = [];
|
||||
let start = 0;
|
||||
const maxRows = CombinedDataRepository.MAX_ROWS_PER_REQUEST;
|
||||
|
||||
@@ -125,7 +177,7 @@ class CombinedDataRepository {
|
||||
break;
|
||||
}
|
||||
|
||||
entries.push(...data);
|
||||
entries.push(...(data as RawDexEntry[]));
|
||||
|
||||
if (data.length < maxRows) {
|
||||
break;
|
||||
@@ -134,7 +186,63 @@ class CombinedDataRepository {
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
return entries;
|
||||
// When forms are enabled, game_pokedex_entries only has base forms, so supplement from pokedex_entries.
|
||||
if (!enableForms || !game) {
|
||||
return entries as PokedexEntryDB[];
|
||||
}
|
||||
|
||||
// Build lookup: pokedexNumber → dex position (from the raw game dex data)
|
||||
const dexInfoByPokedexNumber = new Map<number, { dexNumber: number; dexSortOrder: number }>();
|
||||
for (const entry of entries) {
|
||||
if (!dexInfoByPokedexNumber.has(entry.pokedexNumber)) {
|
||||
dexInfoByPokedexNumber.set(entry.pokedexNumber, {
|
||||
dexNumber: entry.dexNumber ?? 0,
|
||||
dexSortOrder: entry.dexSortOrder ?? 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch forms not already covered by game_pokedex_entries
|
||||
const existingIds = new Set(entries.map((e) => e.id));
|
||||
const formEntries = await this.fetchFormsForGame(game, region, existingIds);
|
||||
|
||||
if (formEntries.length === 0) {
|
||||
return entries as PokedexEntryDB[];
|
||||
}
|
||||
|
||||
// Merge and sort: dex entries first (they are the canonical base form for the regional dex),
|
||||
// then supplemented forms. Within each group, sort by form fields.
|
||||
type TaggedEntry = RawDexEntry & { _isDexEntry: boolean };
|
||||
const merged: TaggedEntry[] = [
|
||||
...entries.map((e) => ({ ...e, _isDexEntry: true })),
|
||||
...(formEntries as RawDexEntry[]).map((e) => ({ ...e, _isDexEntry: false }))
|
||||
];
|
||||
|
||||
merged.sort((a, b) => {
|
||||
const aDex = dexInfoByPokedexNumber.get(a.pokedexNumber) ?? {
|
||||
dexNumber: 9999,
|
||||
dexSortOrder: 9999
|
||||
};
|
||||
const bDex = dexInfoByPokedexNumber.get(b.pokedexNumber) ?? {
|
||||
dexNumber: 9999,
|
||||
dexSortOrder: 9999
|
||||
};
|
||||
if (aDex.dexSortOrder !== bDex.dexSortOrder) return aDex.dexSortOrder - bDex.dexSortOrder;
|
||||
if (aDex.dexNumber !== bDex.dexNumber) return aDex.dexNumber - bDex.dexNumber;
|
||||
if ((a.unownSortOrder ?? 0) !== (b.unownSortOrder ?? 0))
|
||||
return (a.unownSortOrder ?? 0) - (b.unownSortOrder ?? 0);
|
||||
// Dex entries (canonical regional base form) sort before supplemented forms.
|
||||
if (a._isDexEntry !== b._isDexEntry) return a._isDexEntry ? -1 : 1;
|
||||
if ((a.formSortBucket ?? 0) !== (b.formSortBucket ?? 0))
|
||||
return (a.formSortBucket ?? 0) - (b.formSortBucket ?? 0);
|
||||
if ((a.formSortRegionOrder ?? 0) !== (b.formSortRegionOrder ?? 0))
|
||||
return (a.formSortRegionOrder ?? 0) - (b.formSortRegionOrder ?? 0);
|
||||
if ((a.formSortRegionalSub ?? 0) !== (b.formSortRegionalSub ?? 0))
|
||||
return (a.formSortRegionalSub ?? 0) - (b.formSortRegionalSub ?? 0);
|
||||
return (a.formSortLabel ?? '').localeCompare(b.formSortLabel ?? '');
|
||||
});
|
||||
|
||||
return merged as PokedexEntryDB[];
|
||||
}
|
||||
|
||||
private dedupeEntries(entries: PokedexEntryDB[]): PokedexEntryDB[] {
|
||||
@@ -264,7 +372,7 @@ class CombinedDataRepository {
|
||||
): Promise<CombinedData[]> {
|
||||
const entries =
|
||||
dexScopes.length > 0
|
||||
? this.dedupeEntries(await this.fetchAllDexEntries(dexScopes, enableForms, region))
|
||||
? this.dedupeEntries(await this.fetchAllDexEntries(dexScopes, enableForms, region, game))
|
||||
: await this.fetchAllEntries(enableForms, region, game);
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
@@ -310,10 +418,9 @@ class CombinedDataRepository {
|
||||
const to = from + limit - 1;
|
||||
const entries =
|
||||
dexScopes.length > 0
|
||||
? this.dedupeEntries(await this.fetchAllDexEntries(dexScopes, enableForms, region)).slice(
|
||||
from,
|
||||
to + 1
|
||||
)
|
||||
? this.dedupeEntries(
|
||||
await this.fetchAllDexEntries(dexScopes, enableForms, region, game)
|
||||
).slice(from, to + 1)
|
||||
: await this.fetchEntriesByRange(from, to, enableForms, region, game);
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
@@ -354,7 +461,7 @@ class CombinedDataRepository {
|
||||
): Promise<number> {
|
||||
if (dexScopes.length > 0) {
|
||||
const entries = this.dedupeEntries(
|
||||
await this.fetchAllDexEntries(dexScopes, enableForms, region)
|
||||
await this.fetchAllDexEntries(dexScopes, enableForms, region, game)
|
||||
);
|
||||
return entries.length;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Fix missing female form entries in pokemon_origin_games.
|
||||
--
|
||||
-- Female Sneasel was absent from all Gen 2 games despite the base form being present.
|
||||
-- Female Indeedee was absent from Sword despite the base form being present.
|
||||
-- Both were identified by comparing pokemon_origin_games coverage between base forms
|
||||
-- and their female counterparts.
|
||||
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Sneasel' AND p.form = 'Female'
|
||||
AND g."displayName" IN ('Gold', 'Silver', 'Crystal', 'Heart Gold', 'Soul Silver')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Indeedee' AND p.form = 'Female'
|
||||
AND g."displayName" = 'Sword'
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Add Scarlet and Violet to all Vivillon pattern forms.
|
||||
--
|
||||
-- All 18 pattern forms are obtainable in Scarlet/Violet via the Pokémon Portal
|
||||
-- Vivillon Points mechanic. The seed data only recorded X/Y (the origin games),
|
||||
-- which was wrong given the goal is to show all games where a form is catchable.
|
||||
--
|
||||
-- Fancy (Event) already has Scarlet/Violet.
|
||||
-- Pokéball (Event) was X/Y only and is not included here.
|
||||
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Vivillon'
|
||||
AND p.form NOT IN ('Fancy (Event)', 'Pokeball (Event)')
|
||||
AND g."displayName" IN ('Scarlet', 'Violet')
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -0,0 +1,196 @@
|
||||
-- Fix incorrect and missing game assignments for forms.
|
||||
--
|
||||
-- The original seed data recorded only the first game a form appeared in, but
|
||||
-- the goal is to show all games where a form is actively obtainable. This
|
||||
-- migration corrects wrong entries and adds missing ones based on in-game
|
||||
-- mechanics (form-change items, events, wild encounters) verified via Bulbapedia.
|
||||
--
|
||||
-- Magearna Original Color: confirmed Home-only reward — no change needed.
|
||||
|
||||
-- ============================================================
|
||||
-- REMOVALS (incorrect existing assignments)
|
||||
-- ============================================================
|
||||
|
||||
-- Rotom appliance forms: introduced in Platinum (Secret Key event).
|
||||
-- Diamond/Pearl only have base Rotom; the Secret Room mechanic was Platinum-only.
|
||||
DELETE FROM pokemon_origin_games
|
||||
WHERE "pokemonId" IN (
|
||||
SELECT id FROM pokemon WHERE pokemon = 'Rotom'
|
||||
AND form IN ('Fan', 'Fridge', 'Lightbulb', 'Mower', 'Oven', 'Washer')
|
||||
)
|
||||
AND "gameId" IN (SELECT id FROM games WHERE "displayName" IN ('Diamond', 'Pearl'));
|
||||
|
||||
-- Deoxys: in Gen 3 each form was version-exclusive with no form-change mechanic.
|
||||
-- Ruby/Sapphire only ever had Normal form. Emerald only had Speed form.
|
||||
-- Attack form wrong games: Ruby, Sapphire, Emerald, Alpha Sapphire, Omega Ruby.
|
||||
-- (OR/AS has no Deoxys meteorite mechanic — only Normal form is catchable there.)
|
||||
DELETE FROM pokemon_origin_games
|
||||
WHERE "pokemonId" IN (SELECT id FROM pokemon WHERE pokemon = 'Deoxys' AND form = 'Attack Form')
|
||||
AND "gameId" IN (SELECT id FROM games WHERE "displayName" IN ('Ruby', 'Sapphire', 'Emerald', 'Alpha Sapphire', 'Omega Ruby'));
|
||||
|
||||
-- Defense form wrong games: Ruby, Sapphire, Emerald, Alpha Sapphire, Omega Ruby.
|
||||
DELETE FROM pokemon_origin_games
|
||||
WHERE "pokemonId" IN (SELECT id FROM pokemon WHERE pokemon = 'Deoxys' AND form = 'Defense Form')
|
||||
AND "gameId" IN (SELECT id FROM games WHERE "displayName" IN ('Ruby', 'Sapphire', 'Emerald', 'Alpha Sapphire', 'Omega Ruby'));
|
||||
|
||||
-- Speed form wrong games: Ruby, Sapphire, Alpha Sapphire, Omega Ruby.
|
||||
-- (Emerald is correct — Speed form originated there.)
|
||||
DELETE FROM pokemon_origin_games
|
||||
WHERE "pokemonId" IN (SELECT id FROM pokemon WHERE pokemon = 'Deoxys' AND form = 'Speed Form')
|
||||
AND "gameId" IN (SELECT id FROM games WHERE "displayName" IN ('Ruby', 'Sapphire', 'Alpha Sapphire', 'Omega Ruby'));
|
||||
|
||||
-- Shaymin Sky Form: Gracidea was not in Diamond/Pearl (Platinum-first).
|
||||
-- Legends: Arceus has Shaymin only in Land Form — no Gracidea mechanic.
|
||||
DELETE FROM pokemon_origin_games
|
||||
WHERE "pokemonId" IN (SELECT id FROM pokemon WHERE pokemon = 'Shaymin' AND form = 'Sky Form')
|
||||
AND "gameId" IN (SELECT id FROM games WHERE "displayName" IN ('Diamond', 'Pearl', 'Legends: Arceus'));
|
||||
|
||||
-- Lycanroc Dusk: required the special Own Tempo Rockruff distributed only in
|
||||
-- Ultra Sun/Ultra Moon. Not available in the original Sun/Moon.
|
||||
DELETE FROM pokemon_origin_games
|
||||
WHERE "pokemonId" IN (SELECT id FROM pokemon WHERE pokemon = 'Lycanroc' AND form = 'Dusk')
|
||||
AND "gameId" IN (SELECT id FROM games WHERE "displayName" IN ('Sun', 'Moon'));
|
||||
|
||||
-- Hoopa Unbound: Prison Bottle was an ORAS-exclusive event item.
|
||||
-- X/Y had no way to obtain the Prison Bottle; Unbound form was ORAS-only.
|
||||
DELETE FROM pokemon_origin_games
|
||||
WHERE "pokemonId" IN (SELECT id FROM pokemon WHERE pokemon = 'Hoopa' AND form = 'Unbound')
|
||||
AND "gameId" IN (SELECT id FROM games WHERE "displayName" IN ('X', 'Y'));
|
||||
|
||||
-- ============================================================
|
||||
-- ADDITIONS (missing game assignments)
|
||||
-- ============================================================
|
||||
|
||||
-- Rotom appliance forms: form-change mechanic available in each generation:
|
||||
-- Gen 4: Platinum (Rotom's Room), HG/SS (Silph Co. Rotom's Room)
|
||||
-- Gen 5: B/W + B2/W2 (Castelia City)
|
||||
-- Gen 6: X/Y and OR/AS (box method)
|
||||
-- Gen 7: S/M and US/UM (box method)
|
||||
-- Gen 8: Sw/Sh, BD/SP (Rotom Catalog key item)
|
||||
-- Gen 9: Sc/Vi (Rotom Catalog, purchasable at Porto Marinada Market)
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Rotom'
|
||||
AND p.form IN ('Fan', 'Fridge', 'Lightbulb', 'Mower', 'Oven', 'Washer')
|
||||
AND g."displayName" IN (
|
||||
'Platinum', 'Heart Gold', 'Soul Silver',
|
||||
'Black', 'White', 'Black 2', 'White 2',
|
||||
'X', 'Y', 'Omega Ruby', 'Alpha Sapphire',
|
||||
'Sun', 'Moon', 'Ultra Sun', 'Ultra Moon',
|
||||
'Sword', 'Shield', 'Brilliant Diamond', 'Shining Pearl',
|
||||
'Scarlet', 'Violet'
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Deoxys Attack Form: FireRed-exclusive in Gen 3; meteorite form changes
|
||||
-- available in Veilstone City (D/P/Pt/BD/SP), Route 3 (HG/SS),
|
||||
-- Nacrene Museum (B/W/B2/W2), and via meteorites in X/Y.
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Deoxys' AND p.form = 'Attack Form'
|
||||
AND g."displayName" IN (
|
||||
'FireRed',
|
||||
'Diamond', 'Pearl', 'Platinum',
|
||||
'Heart Gold', 'Soul Silver',
|
||||
'Black', 'White', 'Black 2', 'White 2',
|
||||
'X', 'Y',
|
||||
'Brilliant Diamond', 'Shining Pearl'
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Deoxys Defense Form: LeafGreen-exclusive in Gen 3; same meteorite mechanic above.
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Deoxys' AND p.form = 'Defense Form'
|
||||
AND g."displayName" IN (
|
||||
'LeafGreen',
|
||||
'Diamond', 'Pearl', 'Platinum',
|
||||
'Heart Gold', 'Soul Silver',
|
||||
'Black', 'White', 'Black 2', 'White 2',
|
||||
'X', 'Y',
|
||||
'Brilliant Diamond', 'Shining Pearl'
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Deoxys Speed Form: Emerald-exclusive in Gen 3 (already in DB); same meteorite mechanic.
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Deoxys' AND p.form = 'Speed Form'
|
||||
AND g."displayName" IN (
|
||||
'Diamond', 'Pearl', 'Platinum',
|
||||
'Heart Gold', 'Soul Silver',
|
||||
'Black', 'White', 'Black 2', 'White 2',
|
||||
'X', 'Y',
|
||||
'Brilliant Diamond', 'Shining Pearl'
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Shaymin Land Form: missing HG/SS (Oak's Letter event) and Sc/Vi (Mystery Gift).
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Shaymin' AND (p.form = 'Normal Form' OR p.form IS NULL)
|
||||
AND g."displayName" IN ('Heart Gold', 'Soul Silver', 'Scarlet', 'Violet')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Shaymin Sky Form: Gracidea obtainable in Platinum (Floaroma Town), HG/SS
|
||||
-- (Goldenrod City), X/Y (Snowbelle City), and Sc/Vi (bundled with Mystery Gift).
|
||||
-- Diamond/Pearl and Legends: Arceus removed above.
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Shaymin' AND p.form = 'Sky Form'
|
||||
AND g."displayName" IN ('Platinum', 'Heart Gold', 'Soul Silver', 'X', 'Y', 'Scarlet', 'Violet')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Hoopa Confined: distributed via Mystery Gift for all Gen 6 games (X/Y + OR/AS).
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Hoopa' AND p.form = 'Confined'
|
||||
AND g."displayName" IN ('Omega Ruby', 'Alpha Sapphire')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Hoopa Unbound: Prison Bottle was an ORAS-exclusive event; X/Y removed above.
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Hoopa' AND p.form = 'Unbound'
|
||||
AND g."displayName" IN ('Omega Ruby', 'Alpha Sapphire')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Flabébé/Floette/Florges flower colors: all five colors obtainable in OR/AS
|
||||
-- (wild encounters confirmed) and US/UM (some via wild, others via SOS/breeding).
|
||||
-- Not in S/M (Flabébé is absent from base Sun/Moon).
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon IN ('Flabébé', 'Floette', 'Florges')
|
||||
AND p.form IN ('Red', 'Orange', 'Yellow', 'Blue', 'White')
|
||||
AND g."displayName" IN ('Omega Ruby', 'Alpha Sapphire', 'Ultra Sun', 'Ultra Moon')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Furfrou trims: grooming salons confirmed in X/Y (Lumiose City),
|
||||
-- OR/AS (Slateport City), and S/M + US/UM (Malie City / Hau'oli City).
|
||||
-- Go entries kept as-is.
|
||||
INSERT INTO pokemon_origin_games ("pokemonId", "gameId")
|
||||
SELECT p.id, g.id
|
||||
FROM pokemon p
|
||||
CROSS JOIN games g
|
||||
WHERE p.pokemon = 'Furfrou'
|
||||
AND p.form IN ('Dandy', 'Debutante', 'Diamond', 'Heart', 'Kabuki', 'La Reine', 'Matron', 'Pharaoh', 'Star')
|
||||
AND g."displayName" IN ('X', 'Y', 'Omega Ruby', 'Alpha Sapphire', 'Sun', 'Moon', 'Ultra Sun', 'Ultra Moon')
|
||||
ON CONFLICT DO NOTHING;
|
||||
Reference in New Issue
Block a user