Beispiel #1
0
def leaderboards(where: str = "ct.name = 'Gatherling'",
                 season_id: Optional[int] = None) -> List[Dict[str, Any]]:
    sql = """
        SELECT
            p.id AS person_id,
            {person_query} AS person,
            cs.name AS competition_series_name,
            sp.name AS sponsor_name,
            COUNT(DISTINCT d.id) AS tournaments,
            SUM(CASE WHEN dm.games > IFNULL(odm.games, 0) THEN 1 ELSE 0 END) AS wins,
            COUNT(DISTINCT d.id) + SUM(CASE WHEN dm.games > IFNULL(odm.games, 0) THEN 1 ELSE 0 END) AS points
        FROM
            competition AS c
        INNER JOIN
            competition_series AS cs ON cs.id = c.competition_series_id
        LEFT JOIN
            sponsor AS sp ON sp.id = cs.sponsor_id
        INNER JOIN
            competition_type AS ct ON ct.id = cs.competition_type_id
        INNER JOIN
            deck AS d ON d.competition_id = c.id
        INNER JOIN
            person AS p ON d.person_id = p.id
        LEFT JOIN
            deck_match AS dm ON dm.deck_id = d.id
        LEFT JOIN
            deck_match AS odm ON odm.match_id = dm.match_id AND odm.deck_id <> d.id
        {season_join}
        WHERE
            ({where}) AND ({season_query})
        GROUP BY
            cs.id,
            p.id
        ORDER BY
            cs.id,
            points DESC,
            wins DESC,
            tournaments DESC,
            person
    """.format(person_query=query.person_query(),
               season_join=query.season_join(),
               where=where,
               season_query=query.season_query(season_id))
    results = []
    current: Dict[str, Any] = {}
    for row in db().select(sql):
        k = row['competition_series_name']
        if current.get('competition_series_name', None) != k:
            if len(current) > 0:
                results.append(current)
            current = {
                'competition_series_name': row['competition_series_name'],
                'entries': [],
                'sponsor_name': row['sponsor_name']
            }
        row.pop('competition_series_name')
        current['entries'] = current['entries'] + [Container(row)]
    if len(current) > 0:
        results.append(current)
    return results
Beispiel #2
0
def load_competitions(where: str = '1 = 1',
                      season_id: Optional[int] = None) -> List[Competition]:
    sql = """
        SELECT c.id, c.name, c.start_date, c.end_date, c.url,
        COUNT(d.id) AS num_decks,
        sp.name AS sponsor_name,
        ct.name AS type
        FROM competition AS c
        LEFT JOIN deck AS d ON c.id = d.competition_id
        LEFT JOIN competition_series AS cs ON cs.id = c.competition_series_id
        LEFT JOIN competition_type as ct ON ct.id = cs.competition_type_id
        LEFT JOIN sponsor AS sp ON cs.sponsor_id = sp.id
        {season_join}
        WHERE ({where}) AND  ({season_query})
        GROUP BY c.id
        ORDER BY c.start_date DESC, c.name
    """.format(season_join=query.season_join(),
               where=where,
               season_query=query.season_query(season_id))
    competitions = [Competition(r) for r in db().execute(sql)]
    for c in competitions:
        c.start_date = dtutil.ts2dt(c.start_date)
        c.end_date = dtutil.ts2dt(c.end_date)
    set_decks(competitions)
    return competitions
def load_matchups(archetype_id, season_id=None):
    sql = """
        SELECT
            oa.id,
            oa.name,
            SUM(CASE WHEN dm.games > IFNULL(odm.games, 0) THEN 1 ELSE 0 END) AS all_wins, -- IFNULL so we still count byes as wins.
            SUM(CASE WHEN dm.games < odm.games THEN 1 ELSE 0 END) AS all_losses,
            SUM(CASE WHEN dm.games = odm.games THEN 1 ELSE 0 END) AS all_draws,
            IFNULL(ROUND((SUM(CASE WHEN dm.games > odm.games THEN 1 ELSE 0 END) / NULLIF(SUM(CASE WHEN dm.games <> IFNULL(odm.games, 0) THEN 1 ELSE 0 END), 0)) * 100, 1), '') AS all_win_percent
        FROM
            archetype AS a
        INNER JOIN
            deck AS d ON d.archetype_id IN (SELECT descendant FROM archetype_closure WHERE ancestor = a.id)
        INNER JOIN
            deck_match AS dm ON d.id = dm.deck_id
        INNER JOIN
            deck_match AS odm ON dm.match_id = odm.match_id AND odm.deck_id <> d.id
        INNER JOIN
            deck AS od ON od.id = odm.deck_id
        INNER JOIN
            archetype AS oa ON od.archetype_id IN (SELECT descendant FROM archetype_closure WHERE ancestor = oa.id)
        {season_join}
        WHERE
            (a.id = %s) AND ({season_query})
        GROUP BY
            oa.id
        ORDER BY
            `all_wins` DESC, oa.name
    """.format(season_join=query.season_join(),
               season_query=query.season_query(season_id))
    return [Container(m) for m in db().execute(sql, [archetype_id])]
Beispiel #4
0
def played_cards(where: str = '1 = 1',
                 season_id: Optional[int] = None) -> List[Card]:
    sql = """
        SELECT
            card AS name,
            {all_select},
            {season_select}, -- We use the season data on the homepage to calculate movement, even though we no longer use it on /cards/.
            {week_select}
        FROM
            deck_card AS dc
        INNER JOIN
            deck AS d ON dc.deck_id = d.id
        {season_join}
        {nwdl_join}
        WHERE ({where}) AND ({season_query})
        GROUP BY
            dc.card
        ORDER BY
            all_num_decks DESC,
            SUM(dsum.wins - dsum.losses) DESC,
            name
    """.format(all_select=deck.nwdl_all_select(),
               season_select=deck.nwdl_season_select(),
               week_select=deck.nwdl_week_select(),
               season_join=query.season_join(),
               nwdl_join=deck.nwdl_join(),
               where=where,
               season_query=query.season_query(season_id))
    cs = [Container(r) for r in db().execute(sql)]
    cards = oracle.cards_by_name()
    for c in cs:
        c.update(cards[c.name])
    return cs
Beispiel #5
0
def load_head_to_head(
        person_id: int,
        where: str = 'TRUE',
        order_by:
    str = 'num_matches DESC, record DESC, win_percent DESC, wins DESC, opp_mtgo_username',
        limit: str = '',
        season_id: int = None) -> Sequence[Container]:
    season_query = query.season_query(season_id)
    sql = f"""
        SELECT
            hths.person_id AS id,
            LOWER(opp.mtgo_username) AS opp_mtgo_username,
            SUM(num_matches) AS num_matches,
            SUM(wins) - SUM(losses) AS record,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent
        FROM
            _head_to_head_stats AS hths
        INNER JOIN
            person AS opp ON hths.opponent_id = opp.id
        WHERE
            ({where}) AND (hths.person_id = {person_id}) AND ({season_query})
        GROUP BY
            hths.person_id,
            hths.opponent_id
        ORDER BY
            {order_by}
        {limit}
    """
    return [Container(r) for r in db().select(sql)]
Beispiel #6
0
def load_matches_count(where: str = 'TRUE',
                       season_id: Union[int, str, None] = None) -> int:
    competition_join = query.competition_join()
    season_join = query.season_join()
    season_query = query.season_query(season_id, 'season.id')
    sql = f"""
        SELECT
            COUNT(DISTINCT m.id)
        FROM
            `match` AS m
        INNER JOIN
            deck_match AS dm ON m.id = dm.match_id
        INNER JOIN
            deck AS d ON dm.deck_id = d.id
        INNER JOIN
            deck_cache AS dc ON d.id = dc.deck_id
        INNER JOIN
            person AS p ON d.person_id = p.id
        LEFT JOIN
            deck_match AS odm ON dm.match_id = odm.match_id AND odm.deck_id <> d.id
        LEFT JOIN
            deck AS od ON odm.deck_id = od.id
        LEFT JOIN
            deck_cache AS odc ON od.id = odc.deck_id
        LEFT JOIN
            person AS o ON od.person_id = o.id
        {competition_join}
        {season_join}
        WHERE
            {where}
        AND
            {season_query}
    """
    return int(db().value(sql))
Beispiel #7
0
def load_people_stats(
        where: str,
        season_id: Optional[int] = None) -> Dict[int, Dict[str, int]]:
    season_join = query.season_join() if season_id else ''
    sql = """
        SELECT
            p.id,
            COUNT(d.id) AS num_decks,
            SUM(dc.wins) AS wins,
            SUM(dc.losses) AS losses,
            SUM(dc.draws) AS draws,
            SUM(CASE WHEN dc.wins >= 5 AND dc.losses = 0 AND d.source_id IN (SELECT id FROM source WHERE name = 'League') THEN 1 ELSE 0 END) AS perfect_runs,
            SUM(CASE WHEN d.finish = 1 THEN 1 ELSE 0 END) AS tournament_wins,
            SUM(CASE WHEN d.finish <= 8 THEN 1 ELSE 0 END) AS tournament_top8s,
            IFNULL(ROUND((SUM(dc.wins) / NULLIF(SUM(dc.wins + dc.losses), 0)) * 100, 1), '') AS win_percent,
            SUM(DISTINCT CASE WHEN d.competition_id IS NOT NULL THEN 1 ELSE 0 END) AS num_competitions
        FROM
            person AS p
        LEFT JOIN
            deck AS d ON d.person_id = p.id
        LEFT JOIN
            deck_cache AS dc ON d.id = dc.deck_id
        {season_join}
        WHERE
            {where} AND {season_query}
        GROUP BY
            p.id
    """.format(season_join=season_join,
               where=where,
               season_query=query.season_query(season_id, 'season.id'))
    return {r['id']: r for r in db().select(sql)}
Beispiel #8
0
def only_played_by(person_id: int,
                   season_id: Optional[int] = None) -> List[Card]:
    sql = """
        SELECT
            card AS name
        FROM
            deck_card AS dc
        INNER JOIN
            deck AS d ON dc.deck_id = d.id
        {season_join}
        WHERE
            deck_id
        IN
            (
                SELECT
                    DISTINCT deck_id
                FROM
                    deck_match
            ) -- Only include cards that actually got played competitively rather than just posted to Goldfish as "new cards this season" or similar.
        AND
            ({season_query})
        GROUP BY
            card
        HAVING
            COUNT(DISTINCT d.person_id) = 1
        AND
            MAX(d.person_id) = {person_id} -- In MySQL 5.7+ this could/should be ANY_VALUE not MAX but this works with any version. The COUNT(DISTINCT  p.id) ensures this only has one possible value but MySQL can't work that out.-- In MySQL 5.7+ this could/should be ANY_VALUE not MAX but this works with any version. The COUNT(DISTINCT  p.id) ensures this only has one possible value but MySQL can't work that out.
    """.format(season_join=query.season_join(),
               season_query=query.season_query(season_id),
               person_id=sqlescape(person_id))
    cards = {c.name: c for c in oracle.load_cards()}
    return [cards[r['name']] for r in db().execute(sql)]
Beispiel #9
0
def played_cards_by_person(person_id: int, season_id: int) -> List[Card]:
    sql = """
        SELECT
            name,
            SUM(num_decks) AS num_decks,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            SUM(wins - losses) AS record,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent
        FROM
            _played_card_person_stats
        WHERE
            person_id = %s
        AND
            {season_query}
        GROUP BY
            name
        HAVING
            name IS NOT NULL
    """.format(season_query=query.season_query(season_id))
    print(sql)
    cs = [Container(r) for r in decksite_db().select(sql, [person_id])]
    print(len(cs))
    cards = oracle.cards_by_name()
    for c in cs:
        c.update(cards[c.name])
    return cs
Beispiel #10
0
def set_head_to_head(people: List[Person], season_id: int = None) -> None:
    people_by_id = {person.id: person for person in people}
    sql = """
        SELECT
            hths.person_id AS id,
            LOWER(opp.mtgo_username) AS opp_mtgo_username,
            SUM(num_matches) AS num_matches,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent
        FROM
            _head_to_head_stats AS hths
        INNER JOIN
            person AS opp ON hths.opponent_id = opp.id
        WHERE
            hths.person_id IN ({ids}) AND ({season_query})
        GROUP BY
            hths.person_id,
            hths.opponent_id
        ORDER BY
            SUM(num_matches) DESC,
            SUM(wins - losses) DESC,
            win_percent DESC,
            SUM(wins) DESC,
            opp_mtgo_username
    """.format(ids=', '.join(str(k) for k in people_by_id.keys()),
               season_query=query.season_query(season_id))
    results = [Container(r) for r in db().select(sql)]
    for result in results:
        people_by_id[result.id].head_to_head = people_by_id[result.id].get(
            'head_to_head', []) + [result]
    for person in people:
        if person.get('head_to_head') is None:
            person.head_to_head = []
Beispiel #11
0
def load_cards(season_id: Optional[int] = None,
               person_id: Optional[int] = None,
               retry: bool = False) -> List[Card]:
    if person_id:
        table = '_card_person_stats'
        where = 'person_id = {person_id}'.format(
            person_id=sqlescape(person_id))
        group_by = 'person_id, name'
    else:
        table = '_card_stats'
        where = 'TRUE'
        group_by = 'name'
    sql = """
        SELECT
            name,
            SUM(num_decks) AS num_decks,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            SUM(wins - losses) AS record,
            SUM(num_decks_tournament) AS num_decks_tournament,
            SUM(wins_tournament) AS wins_tournament,
            SUM(losses_tournament) AS losses_tournament,
            SUM(draws_tournament) AS draws_tournament,
            SUM(wins_tournament - losses_tournament) AS record_tournament,
            SUM(perfect_runs) AS perfect_runs,
            SUM(tournament_wins) AS tournament_wins,
            SUM(tournament_top8s) AS tournament_top8s,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent,
            IFNULL(ROUND((SUM(wins_tournament) / NULLIF(SUM(wins_tournament + losses_tournament), 0)) * 100, 1), '') AS win_percent_tournament
        FROM
            {table} AS cs
        WHERE
            ({where}) AND ({season_query})
        GROUP BY
            {group_by}
        ORDER BY
            num_decks DESC,
            record,
            name
    """.format(table=table,
               season_query=query.season_query(season_id),
               where=where,
               group_by=group_by)
    try:
        cs = [Container(r) for r in db().select(sql)]
        cards = oracle.cards_by_name()
        for c in cs:
            c.update(cards[c.name])
        return cs
    except DatabaseException as e:
        if not retry:
            print(
                f"Got {e} trying to load_cards so trying to preaggregate. If this is happening on user time that's undesirable."
            )
            preaggregate()
            return load_cards(season_id, person_id, retry=True)
        print(f'Failed to preaggregate. Giving up.')
        raise e
 def percent(self, season_id: Optional[int] = None) -> float:
     season_condition = query.season_query(season_id)
     sql = f'SELECT SUM(CASE WHEN {self.key} > 0 THEN 1 ELSE 0 END) AS pnum, COUNT(*) AS mnum FROM _achievements WHERE {season_condition}'
     r = db().select(sql)[0]
     try:
         return int(r['pnum'] or 0) * 100.0 / int(r['mnum'])
     except ZeroDivisionError:
         return 0
def load_archetypes_deckless(
        order_by: str = '`num_decks` DESC, `wins` DESC, name',
        season_id: int = None,
        retry: bool = False) -> List[Archetype]:
    sql = """
        SELECT
            a.id,
            a.name,
            a.description,
            aca.ancestor AS parent_id,
            SUM(num_decks) AS num_decks,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            SUM(wins - losses) AS record,
            SUM(num_decks_tournament) AS num_decks_tournament,
            SUM(wins_tournament) AS wins_tournament,
            SUM(losses_tournament) AS losses_tournament,
            SUM(draws_tournament) AS draws_tournament,
            SUM(wins_tournament - losses_tournament) AS record_tournament,
            SUM(perfect_runs) AS perfect_runs,
            SUM(tournament_wins) AS tournament_wins,
            SUM(tournament_top8s) AS tournament_top8s,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent,
            IFNULL(ROUND((SUM(wins_tournament) / NULLIF(SUM(wins_tournament + losses_tournament), 0)) * 100, 1), '') AS win_percent_tournament
        FROM
            archetype AS a
        LEFT JOIN
            _archetype_stats AS ars ON a.id = ars.archetype_id
        LEFT JOIN
            archetype_closure AS aca ON a.id = aca.descendant AND aca.depth = 1
        WHERE
            {season_query}
        GROUP BY
            a.id,
            aca.ancestor -- aca.ancestor will be unique per a.id because of integrity constraints enforced elsewhere (each archetype has one ancestor) but we let the database know here.
        ORDER BY
            {order_by}
    """.format(season_query=query.season_query(season_id), order_by=order_by)
    try:
        archetypes = [Archetype(a) for a in db().select(sql)]
        archetypes_by_id = {a.id: a for a in archetypes}
        for a in archetypes:
            a.decks = []
            a.decks_tournament = []
            a.parent = archetypes_by_id.get(a.parent_id, None)
        return archetypes
    except DatabaseException as e:
        if not retry:
            print(
                f"Got {e} trying to load_archetypes_deckless so trying to preaggregate. If this is happening on user time that's undesirable."
            )
            preaggregate_archetypes()
            return load_archetypes_deckless(order_by=order_by,
                                            season_id=season_id,
                                            retry=True)
        print(f'Failed to preaggregate. Giving up.')
        raise e
def load_archetypes_deckless(order_by: Optional[str] = None,
                             person_id: Optional[int] = None,
                             season_id: Optional[int] = None,
                             tournament_only: bool = False) -> List[Archetype]:
    if person_id:
        table = '_arch_person_stats'
        where = 'person_id = {person_id}'.format(
            person_id=sqlescape(person_id))
        group_by = 'ars.person_id, a.id'
    else:
        table = '_arch_stats'
        where = 'TRUE'
        group_by = 'a.id'
    if tournament_only:
        where = f"({where}) AND deck_type = 'tournament'"
    sql = """
        SELECT
            a.id,
            a.name,
            a.description,
            aca.ancestor AS parent_id,
            SUM(num_decks) AS num_decks,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            SUM(wins - losses) AS record,
            SUM(perfect_runs) AS perfect_runs,
            SUM(tournament_wins) AS tournament_wins,
            SUM(tournament_top8s) AS tournament_top8s,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent
        FROM
            archetype AS a
        LEFT JOIN
            {table} AS ars ON a.id = ars.archetype_id
        LEFT JOIN
            archetype_closure AS aca ON a.id = aca.descendant AND aca.depth = 1
        WHERE
            ({where}) AND ({season_query})
        GROUP BY
            {group_by},
            aca.ancestor -- aca.ancestor will be unique per a.id because of integrity constraints enforced elsewhere (each archetype has one ancestor) but we let the database know here.
        ORDER BY
            {order_by}
    """.format(table=table,
               where=where,
               group_by=group_by,
               season_query=query.season_query(season_id),
               order_by=order_by or 'TRUE')
    archetypes = [Archetype(a) for a in db().select(sql)]
    archetypes_by_id = {a.id: a for a in archetypes}
    for a in archetypes:
        a.decks = []
        a.decks_tournament = []
        a.parent = archetypes_by_id.get(a.parent_id, None)
    if order_by is None:
        archetypes = preorder(archetypes)
    return archetypes
Beispiel #15
0
def load_people(where: str = '1 = 1',
                order_by: str = '`all_num_decks` DESC, name',
                season_id: Optional[int] = None) -> Sequence[Person]:
    sql = """
        SELECT
            p.id,
            {person_query} AS name,
            p.mtgo_username,
            p.tappedout_username,
            p.mtggoldfish_username,
            p.discord_id,
            p.elo,
            {all_select},
            SUM(DISTINCT CASE WHEN d.competition_id IS NOT NULL AND {season_query} THEN 1 ELSE 0 END) AS `all_num_competitions`
        FROM
            person AS p
        LEFT JOIN
            deck AS d ON p.id = d.person_id
        {season_join}
        {nwdl_join}
        WHERE
            ({where})
        GROUP BY
            p.id
        ORDER BY
            {order_by}
    """.format(person_query=query.person_query(),
               all_select=deck.nwdl_select('all_',
                                           query.season_query(season_id)),
               nwdl_join=deck.nwdl_join(),
               season_join=query.season_join(),
               where=where,
               season_query=query.season_query(season_id),
               order_by=order_by)

    people = [Person(r) for r in db().select(sql)]
    for p in people:
        p.season_id = season_id

    if len(people) > 0:
        set_achievements(people, season_id)
        set_head_to_head(people, season_id)
    return people
    def leaderboard(
            self,
            season_id: Optional[int] = None) -> Optional[List[Container]]:
        season_condition = query.season_query(season_id)
        person_query = query.person_query()
        sql = f"""
            SELECT
                {person_query} AS person,
                SUM({self.key}) AS points,
                p.id AS person_id
            FROM
                person AS p
            JOIN
                _achievements
            ON
                p.id = _achievements.person_id
            WHERE
                {season_condition}
            GROUP BY
                p.id
            HAVING
                points >=
                    (   -- Work out the minimum score to make top N, counting ties
                        SELECT
                            MIN(s)
                        FROM
                            (
                                SELECT
                                    SUM({self.key}) AS s
                                FROM
                                    _achievements
                                WHERE
                                    {season_condition}
                                GROUP BY
                                    person_id
                                HAVING
                                    s > 0
                                ORDER BY
                                    s DESC
                                LIMIT
                                    {LEADERBOARD_TOP_N}
                            ) AS _
                    )
            ORDER BY
                points DESC,
                name
            LIMIT {LEADERBOARD_LIMIT}
        """
        leaderboard = []
        for row in db().select(sql):
            c = Container(row)
            c.score = c.points
            leaderboard.append(c)

        return leaderboard if len(leaderboard) > 0 else None
 def load_summary(self, season_id: Optional[int] = None) -> Optional[str]:
     season_condition = query.season_query(season_id)
     sql = f'SELECT SUM(`{self.key}`) AS num, COUNT(DISTINCT person_id) AS pnum FROM _achievements WHERE `{self.key}` > 0 AND {season_condition}'
     for r in db().select(sql):
         res = Container(r)
         if res.num is None:
             return 'Not earned by any players.'
         times_text = ngettext(' once', ' %(num)d times', res.num) if res.num > res.pnum else ''
         players_text = ngettext('1 player', '%(num)d players', res.pnum)
         return f'Earned{times_text} by {players_text}.'
     return None
Beispiel #18
0
def load_cards(
    additional_where: str = 'TRUE',
    order_by: str = 'num_decks DESC, record, name',
    limit: str = '',
    person_id: Optional[int] = None,
    season_id: Optional[int] = None,
    tournament_only: bool = False,
) -> List[Card]:
    if person_id:
        table = '_card_person_stats'
        where = 'person_id = {person_id}'.format(
            person_id=sqlescape(person_id))
        group_by = 'person_id, name'
    else:
        table = '_card_stats'
        where = 'TRUE'
        group_by = 'name'
    if tournament_only:
        where = f"({where}) AND deck_type = 'tournament'"
    sql = """
        SELECT
            name,
            SUM(num_decks) AS num_decks,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            SUM(wins - losses) AS record,
            SUM(perfect_runs) AS perfect_runs,
            SUM(tournament_wins) AS tournament_wins,
            SUM(tournament_top8s) AS tournament_top8s,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent
        FROM
            {table} AS cs
        WHERE
            ({where}) AND ({additional_where}) AND ({season_query})
        GROUP BY
            {group_by}
        ORDER BY
            {order_by}
        {limit}
    """.format(table=table,
               season_query=query.season_query(season_id),
               where=where,
               additional_where=additional_where,
               group_by=group_by,
               order_by=order_by,
               limit=limit)
    cs = [Container(r) for r in db().select(sql)]
    cards = oracle.cards_by_name()
    for c in cs:
        c.update(cards[c.name])
    return cs
Beispiel #19
0
def load_cards_count(additional_where: str = 'TRUE',
                     person_id: Optional[int] = None,
                     season_id: Optional[int] = None) -> int:
    if person_id:
        table = '_card_person_stats'
        where = 'person_id = {person_id}'.format(
            person_id=sqlescape(person_id))
    else:
        table = '_card_stats'
        where = 'TRUE'
    season_query = query.season_query(season_id)
    sql = f'SELECT COUNT(DISTINCT name) AS n FROM {table} WHERE ({where}) AND ({additional_where}) AND ({season_query})'
    return int(db().value(sql))
def load_competitions(
        where: str = 'TRUE',
        having: str = 'TRUE',
        season_id: Optional[int] = None,
        should_load_decks: Optional[bool] = False) -> List[Competition]:
    sql = """
        SELECT
            c.id,
            c.name,
            c.start_date,
            c.end_date,
            c.url,
            c.top_n,
            COUNT(d.id) AS num_decks,
            SUM(CASE WHEN d.reviewed THEN 1 ELSE 0 END) AS num_reviewed,
            sp.name AS sponsor_name,
            cs.name AS series_name,
            ct.name AS type,
            season.id AS season_id
        FROM
            competition AS c
        LEFT JOIN
            deck AS d ON c.id = d.competition_id
        LEFT JOIN
            competition_series AS cs ON cs.id = c.competition_series_id
        LEFT JOIN
            competition_type as ct ON ct.id = cs.competition_type_id
        LEFT JOIN
            sponsor AS sp ON cs.sponsor_id = sp.id
        {season_join}
        WHERE
            ({where}) AND ({season_query})
        GROUP BY
            c.id
        HAVING
            {having}
        ORDER BY
            c.start_date DESC,
            c.name
    """.format(season_join=query.season_join(),
               where=where,
               season_query=query.season_query(season_id, 'season.id'),
               having=having)
    competitions = [Competition(r) for r in db().select(sql)]
    for c in competitions:
        c.start_date = dtutil.ts2dt(c.start_date)
        c.end_date = dtutil.ts2dt(c.end_date)
        c.decks = []
    if should_load_decks:
        load_decks(competitions)
    return competitions
Beispiel #21
0
def load_people(
        where: str = 'TRUE',
        order_by: str = 'num_decks DESC, p.name',
        limit: str = '',
        season_id: Optional[Union[str, int]] = None) -> Sequence[Person]:
    person_query = query.person_query()
    season_join = query.season_join() if season_id else ''
    season_query = query.season_query(season_id, 'season.id')
    sql = f"""
        SELECT
            p.id,
            {person_query} AS name,
            p.mtgo_username,
            p.tappedout_username,
            p.mtggoldfish_username,
            p.discord_id,
            p.elo,
            p.locale,
            SUM(1) AS num_decks,
            SUM(dc.wins) AS wins,
            SUM(dc.losses) AS losses,
            SUM(dc.draws) AS draws,
            SUM(wins - losses) AS record,
            SUM(CASE WHEN dc.wins >= 5 AND dc.losses = 0 AND d.source_id IN (SELECT id FROM source WHERE name = 'League') THEN 1 ELSE 0 END) AS perfect_runs,
            SUM(CASE WHEN d.finish = 1 THEN 1 ELSE 0 END) AS tournament_wins,
            SUM(CASE WHEN d.finish <= 8 THEN 1 ELSE 0 END) AS tournament_top8s,
            IFNULL(ROUND((SUM(dc.wins) / NULLIF(SUM(dc.wins + dc.losses), 0)) * 100, 1), '') AS win_percent,
            SUM(DISTINCT CASE WHEN d.competition_id IS NOT NULL THEN 1 ELSE 0 END) AS num_competitions
        FROM
            person AS p
        LEFT JOIN
            deck AS d ON d.person_id = p.id
        LEFT JOIN
            deck_cache AS dc ON d.id = dc.deck_id
        {season_join}
        WHERE
            ({where}) AND ({season_query})
        GROUP BY
            p.id
        ORDER BY
            {order_by}
        {limit}
    """
    people = [Person(r) for r in db().select(sql)]
    for p in people:
        p.season_id = season_id
    return people
Beispiel #22
0
def set_head_to_head(people: List[Person], season_id: int = None) -> None:
    people_by_id = {person.id: person for person in people}
    sql = """
        SELECT
            p.id,
            COUNT(p.id) AS num_matches,
            LOWER(opp.mtgo_username) AS opp_mtgo_username,
            SUM(CASE WHEN dm.games > odm.games THEN 1 ELSE 0 END) AS wins,
            SUM(CASE WHEN dm.games < odm.games THEN 1 ELSE 0 END) AS losses,
            SUM(CASE WHEN dm.games = odm.games THEN 1 ELSE 0 END) AS draws,
            IFNULL(ROUND((SUM(CASE WHEN dm.games > odm.games THEN 1 ELSE 0 END) / NULLIF(SUM(CASE WHEN dm.games <> IFNULL(odm.games, 0) THEN 1 ELSE 0 END), 0)) * 100, 1), '') AS win_percent
        FROM
            person AS p
        INNER JOIN
            deck AS d ON p.id = d.person_id
        INNER JOIN
            deck_match AS dm ON dm.deck_id = d.id
        INNER JOIN
            deck_match AS odm ON dm.match_id = odm.match_id AND dm.deck_id <> IFNULL(odm.deck_id, 0)
        INNER JOIN
            deck AS od ON odm.deck_id = od.id
        INNER JOIN
            person AS opp ON od.person_id = opp.id
        {season_join}
        WHERE
            p.id IN ({ids}) AND ({season_query})
        GROUP BY
            p.id, opp.id
        ORDER BY
            p.id,
            num_matches DESC,
            SUM(CASE WHEN dm.games > odm.games THEN 1 ELSE 0 END) - SUM(CASE WHEN dm.games < odm.games THEN 1 ELSE 0 END) DESC,
            win_percent DESC,
            SUM(CASE WHEN dm.games > odm.games THEN 1 ELSE 0 END) DESC,
            opp_mtgo_username
    """.format(ids=', '.join(str(k) for k in people_by_id.keys()),
               season_join=query.season_join(),
               season_query=query.season_query(season_id))
    results = [Container(r) for r in db().select(sql)]
    for result in results:
        people_by_id[result.id].head_to_head = people_by_id[result.id].get(
            'head_to_head', []) + [result]
    for person in people:
        if person.get('head_to_head') is None:
            person.head_to_head = []
def load_all_matchups(where: str = 'TRUE',
                      season_id: Optional[int] = None,
                      retry: bool = False) -> List[Container]:
    sql = """
        SELECT
            archetype_id,
            a.name AS archetype_name,
            opponent_archetype_id AS id,
            oa.name AS name,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            SUM(wins_tournament) AS wins_tournament,
            SUM(losses_tournament) AS losses_tournament,
            SUM(draws_tournament) AS draws_tournament,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent,
            IFNULL(ROUND((SUM(wins_tournament) / NULLIF(SUM(wins_tournament + losses_tournament), 0)) * 100, 1), '') AS win_percent_tournament
        FROM
            _matchup_stats AS ms
        INNER JOIN
            archetype AS a ON archetype_id = a.id
        INNER JOIN
            archetype AS oa ON opponent_archetype_id = oa.id
        WHERE
            ({where}) AND ({season_query})
        GROUP BY
            archetype_id,
            opponent_archetype_id
        ORDER BY
            wins DESC,
            oa.name
    """.format(where=where, season_query=query.season_query(season_id))
    try:
        return [Container(m) for m in db().select(sql)]
    except DatabaseException as e:
        if not retry:
            print(
                f"Got {e} trying to load_all_matchups so trying to preaggregate. If this is happening on user time that's undesirable."
            )
            preaggregate_matchups()
            return load_all_matchups(where=where,
                                     season_id=season_id,
                                     retry=True)
        print(f'Failed to preaggregate. Giving up.')
        raise e
def load_query(people_by_id: Dict[int, Person],
               season_id: Optional[int]) -> str:
    # keys have been normalised earlier but could still be reserved words
    columns = ', '.join(f'SUM(`{a.key}`) as `{a.key}`'
                        for a in Achievement.all_achievements if a.in_db)
    return """
        SELECT
            person_id AS id,
            {columns}
        FROM
            _achievements AS a
        WHERE
            person_id IN ({ids}) AND ({season_query})
        GROUP BY
            person_id
    """.format(columns=columns,
               ids=', '.join(str(k) for k in people_by_id.keys()),
               season_query=query.season_query(season_id))
Beispiel #25
0
def load_people_count(where: str = 'TRUE',
                      season_id: Optional[Union[str, int]] = None) -> int:
    season_join = query.season_join() if season_id else ''
    season_query = query.season_query(season_id, 'season.id')
    sql = f"""
        SELECT
            COUNT(DISTINCT p.id)
        FROM
            person AS p
        LEFT JOIN
            deck AS d ON d.person_id = p.id
        LEFT JOIN
            deck_cache AS dc ON d.id = dc.deck_id
        {season_join}
        WHERE
            ({where}) AND ({season_query})
    """
    return db().value(sql) or 0
 def detail(self, p: Person, season_id: Optional[int] = None) -> Optional[List[Deck]]:
     if self.detail_sql is None:
         return None
     sql = """
             SELECT
                 GROUP_CONCAT({k}_detail)
             FROM
                 _achievements
             WHERE
                 person_id={id} AND {season_query}
             GROUP BY
                 person_id
         """.format(k=self.key, id=p.id, season_query=query.season_query(season_id))
     ids = db().value(sql)
     result = Container()
     result.decks = deck.load_decks(where=f'd.id IN ({ids})') if ids is not None else []
     for f in self.flags:
         result[f] = True
     return result
def load_matchups(where: str = 'TRUE',
                  archetype_id: Optional[int] = None,
                  person_id: Optional[int] = None,
                  season_id: Optional[int] = None,
                  tournament_only: bool = False) -> List[Container]:
    if person_id:
        table = '_matchup_ps_stats'
        where = f'({where}) AND (mps.person_id = {person_id})'
    else:
        table = '_matchup_stats'
    if archetype_id:
        where = f'({where}) AND (a.id = {archetype_id})'
    if tournament_only:
        where = f"({where}) AND (mps.deck_type = 'tournament')"
    sql = """
        SELECT
            archetype_id,
            a.name AS archetype_name,
            opponent_archetype_id AS id,
            oa.name AS name,
            SUM(wins) AS wins,
            SUM(losses) AS losses,
            SUM(draws) AS draws,
            IFNULL(ROUND((SUM(wins) / NULLIF(SUM(wins + losses), 0)) * 100, 1), '') AS win_percent
        FROM
            {table} AS mps
        INNER JOIN
            archetype AS a ON archetype_id = a.id
        INNER JOIN
            archetype AS oa ON opponent_archetype_id = oa.id
        WHERE
            ({where}) AND ({season_query})
        GROUP BY
            archetype_id,
            opponent_archetype_id
        ORDER BY
            wins DESC,
            oa.name
    """.format(table=table,
               where=where,
               season_query=query.season_query(season_id))
    return [Container(m) for m in db().select(sql)]
def load_archetypes_deckless(
        where: str = '1 = 1',
        order_by: str = '`all_num_decks` DESC, `all_wins` DESC, name',
        season_id: int = None) -> List[Archetype]:
    sql = """
        SELECT
            a.id,
            a.name,
            aca.ancestor AS parent_id,
            {all_select}
        FROM
            archetype AS a
        LEFT JOIN
            archetype_closure AS aca ON a.id = aca.descendant AND aca.depth = 1
        LEFT JOIN
            archetype_closure AS acd ON a.id = acd.ancestor
        LEFT JOIN
            deck AS d ON acd.descendant = d.archetype_id
        {season_join}
        {nwdl_join}
        WHERE ({where}) AND ({season_query})
        GROUP BY
            a.id,
            aca.ancestor -- aca.ancestor will be unique per a.id because of integrity constraints enforced elsewhere (each archetype has one ancestor) but we let the database know here.
        ORDER BY
            {order_by}
    """.format(all_select=deck.nwdl_all_select(),
               season_join=query.season_join(),
               nwdl_join=deck.nwdl_join(),
               where=where,
               season_query=query.season_query(season_id),
               order_by=order_by)
    archetypes = [Archetype(a) for a in db().execute(sql)]
    archetypes_by_id = {a.id: a for a in archetypes}
    for a in archetypes:
        a.decks = []
        a.parent = archetypes_by_id.get(a.parent_id, None)
    return archetypes
Beispiel #29
0
def set_achievements(people: List[Person], season_id: int = None) -> None:
    people_by_id = {person.id: person for person in people}
    sql = """
        SELECT
            p.id,
            COUNT(DISTINCT CASE WHEN ct.name = 'Gatherling' THEN d.id ELSE NULL END) AS tournament_entries,
            COUNT(DISTINCT CASE WHEN d.finish = 1 AND ct.name = 'Gatherling' THEN d.id ELSE NULL END) AS tournament_wins,
            COUNT(DISTINCT CASE WHEN ct.name = 'League' THEN d.id ELSE NULL END) AS league_entries,
            CASE WHEN COUNT(CASE WHEN d.retired = 1 THEN 1 ELSE NULL END) = 0 THEN True ELSE False END AS completionist,
            SUM(CASE WHEN ct.name = 'League' AND dc.wins >= 5 AND dc.losses THEN 1 ELSE 0 END) AS perfect_runs,
            SUM(
                CASE WHEN d.id IN
                    (
                        SELECT
                            -- MAX here is just to fool MySQL to give us the id of the deck that crushed the perfect run from an aggregate function. There is only one value to MAX.
                            MAX(CASE WHEN dm.games < odm.games AND dm.match_id IN (SELECT MAX(match_id) FROM deck_match WHERE deck_id = d.id) THEN odm.deck_id ELSE NULL END) AS deck_id
                        FROM
                            deck AS d
                        INNER JOIN
                            deck_match AS dm
                        ON
                            dm.deck_id = d.id
                        INNER JOIN
                            deck_match AS odm
                        ON
                            dm.match_id = odm.match_id AND odm.deck_id <> d.id
                        WHERE
                            d.competition_id IN ({competition_ids_by_type_select})
                        GROUP BY d.id
                        HAVING
                            SUM(CASE WHEN dm.games > odm.games THEN 1 ELSE 0 END) >=4
                        AND
                            SUM(CASE WHEN dm.games < odm.games THEN 1 ELSE 0 END) = 1
                        AND
                            SUM(CASE WHEN dm.games < odm.games AND dm.match_id IN (SELECT MAX(match_id) FROM deck_match WHERE deck_id = d.id) THEN 1 ELSE 0 END) = 1
                    )
                THEN 1 ELSE 0 END
            ) AS perfect_run_crushes
        FROM
            person AS p
        LEFT JOIN
            deck AS d ON d.person_id = p.id
        LEFT JOIN
            deck_cache AS dc ON dc.deck_id = d.id
        {season_join}
        {competition_join}
        WHERE
            p.id IN ({ids}) AND ({season_query})
        GROUP BY
            p.id
    """.format(
        competition_join=query.competition_join(),
        competition_ids_by_type_select=query.competition_ids_by_type_select(
            'League'),
        ids=', '.join(str(k) for k in people_by_id.keys()),
        season_join=query.season_join(),
        season_query=query.season_query(season_id))
    results = [Container(r) for r in db().select(sql)]
    for result in results:
        people_by_id[result['id']].num_achievements = len(
            [k for k, v in result.items() if k != 'id' and v > 0])
        people_by_id[result['id']].achievements = result
        people_by_id[result['id']].achievements.pop('id')
Beispiel #30
0
def load_decks(where: str = '1 = 1',
               having: str = '1 = 1',
               order_by: Optional[str] = None,
               limit: str = '',
               season_id: Optional[int] = None
              ) -> List[Deck]:
    if not redis.enabled():
        return load_decks_heavy(where, having, order_by, limit, season_id)
    if order_by is None:
        order_by = 'active_date DESC, d.finish IS NULL, d.finish'
    sql = """
        SELECT
            d.id,
            d.finish,
            d.decklist_hash,
            cache.active_date,
            cache.wins,
            cache.losses,
            cache.draws,
            ct.name AS competition_type_name
        FROM
            deck AS d
        """
    if 'p.' in where or 'p.' in order_by:
        sql += """
        LEFT JOIN
            person AS p ON d.person_id = p.id
        """
    if 's.' in where or 's.' in order_by:
        sql += """
        LEFT JOIN
            source AS s ON d.source_id = s.id
        """
    if 'a.' in where or 'a.' in order_by:
        sql += """
        LEFT JOIN
            archetype AS a ON d.archetype_id = a.id
        """
    sql += """
        {competition_join}
        LEFT JOIN
            deck_cache AS cache ON d.id = cache.deck_id
        {season_join}
        WHERE
            ({where}) AND ({season_query})
        GROUP BY
            d.id,
            d.competition_id, -- Every deck has only one competition_id but if we want to use competition_id in the HAVING clause we need this.
            season.id -- In theory this is not necessary as all decks are in a single season and we join on the date but MySQL cannot work that out so give it the hint it needs.
        HAVING
            {having}
        ORDER BY
            {order_by}
        {limit}
    """
    sql = sql.format(person_query=query.person_query(), competition_join=query.competition_join(), season_query=query.season_query(season_id, 'season.id'), season_join=query.season_join(), where=where, having=having, order_by=order_by, limit=limit)
    db().execute('SET group_concat_max_len=100000')
    rows = db().select(sql)
    decks_by_id = {}
    heavy = []
    for row in rows:
        d = redis.get_container('decksite:deck:{id}'.format(id=row['id']))
        if d is None or d.name is None:
            heavy.append(row['id'])
        else:
            decks_by_id[row['id']] = deserialize_deck(d)
    if heavy:
        where = 'd.id IN ({deck_ids})'.format(deck_ids=', '.join(map(sqlescape, map(str, heavy))))
        loaded_decks = load_decks_heavy(where)
        for d in loaded_decks:
            decks_by_id[d.id] = d
    decks = []
    for row in rows:
        decks.append(decks_by_id[row['id']])
    return decks