J'ai le résultat suivant d'une requête SELECT
avec ORDER BY player_id ASC, time ASC
dans la base de données PostgreSQL:
player_id longest_streak time_began 395 1 2018-12-02 08:56:06.83033-05 399 1 2018-06-10 12:11:18.041521-04 756 2 2018-12-02 19:24:20.880022-05
J'essaie de trouver la plus longue séquence de chaque joueur où points = 100
, le bris d'égalité étant celui qui a commencé le plus récemment. Je dois également déterminer le moment auquel la plus longue séquence de ce joueur a commencé. Le résultat attendu serait:
player_id points time 395 0 2018-06-01 17:55:23.982413-04 395 100 2018-06-30 11:05:21.8679-04 395 0 2018-07-15 21:56:25.420837-04 395 100 2018-07-28 19:47:13.84652-04 395 0 2018-11-27 17:09:59.384-05 395 100 2018-12-02 08:56:06.83033-05 399 0 2018-05-15 15:28:22.782945-04 399 100 2018-06-10 12:11:18.041521-04 454 0 2018-07-10 18:53:24.236363-04 675 0 2018-08-07 20:59:15.510936-04 696 0 2018-08-07 19:09:07.126876-04 756 100 2018-08-15 08:21:11.300871-04 756 100 2018-08-15 16:43:08.698862-04 756 0 2018-08-15 17:22:49.755721-04 756 100 2018-10-07 15:30:49.27374-04 756 0 2018-10-07 15:35:00.975252-04 756 0 2018-11-27 19:04:06.456982-05 756 100 2018-12-02 19:24:20.880022-05 756 100 2018-12-04 19:57:48.961111-05
3 Réponses :
Ceci est un problème d'espace et d'îlot, vous pouvez essayer d'utiliser la fonction aggravée de condition SUM
avec la fonction de fenêtre, en obtenant le numéro de l'écart.
puis utilisez MAX
et COUNT fonction de la fenêtre à nouveau.
Requête 1 :
| player_id | longest_streak | longest_streak | |-----------|-----------------------------|----------------| | 756 | 2018-12-04T19:57:48.961111Z | 2 | | 399 | 2018-06-10T12:11:18.041521Z | 1 | | 395 | 2018-12-02T08:56:06.83033Z | 1 |
WITH CTE AS ( SELECT *, SUM(CASE WHEN points = 100 THEN 1 END) OVER(PARTITION BY player_id ORDER BY time) - SUM(1) OVER(ORDER BY time) RN FROM T ) SELECT player_id, MAX(longest_streak) longest_streak, MAX(cnt) longest_streak FROM ( SELECT player_id, MAX(time) OVER(PARTITION BY rn,player_id) longest_streak, COUNT(*) OVER(PARTITION BY rn,player_id) cnt FROM CTE WHERE points > 0 ) t1 GROUP BY player_id
p>
Une façon de faire est de regarder combien de lignes entre les résultats non-100 précédents et suivants. Pour obtenir la longueur des stries:
with s as ( select s.*, row_number() over (partition by player_id order by time) as seqnum, count(*) over (partition by player_id) as cnt from scores s ), streaks as ( select s.*, coalesce(next_seqnum - prev_seqnum) over (partition by player_id) as length, max(next_seqnum - prev_seqnum) over (partition by player_id) as max_length, max(next_seqnum) over (partition by player_id) as max_next_seqnum from (select s.*, coalesce(max(seqnum) filter (where score <> 100) over (partition by player_id order by time), 0) as prev_seqnum, coalesce(max(seqnum) filter (where score <> 100) over (partition by player_id order by time), cnt + 1) as next_seqnum from s ) s where score = 100 ) select s.* from streaks s where length = max_length and next_seqnum = max_next_seqnum;
Vous pouvez ensuite incorporer les autres conditions:
with s as ( select s.*, row_number() over (partition by player_id order by time) as seqnum, count(*) over (partition by player_id) as cnt from scores s ) select s.*, coalesce(next_seqnum, cnt + 1) - coalesce(prev_seqnum, 0) - 1 as length from (select s.*, max(seqnum) filter (where score <> 100) over (partition by player_id order by time) as prev_seqnum, max(seqnum) filter (where score <> 100) over (partition by player_id order by time) as next_seqnum from s ) s where score = 100;
Une lacunes-et -island en effet.
En supposant:
NOT NULL
. (Sinon, vous devez faire plus.) Cela devrait être le plus simple et le plus rapide car il ne nécessite que deux Fonctions de la fenêtre row_number ()
:
SELECT DISTINCT ON (player_id) player_id, count(*) AS seq_len, min(ts) AS time_began FROM ( SELECT player_id, points, ts , row_number() OVER (PARTITION BY player_id ORDER BY ts) - row_number() OVER (PARTITION BY player_id, points ORDER BY ts) AS grp FROM tbl ) sub WHERE points = 100 GROUP BY player_id, grp -- omit "points" after WHERE points = 100 ORDER BY player_id, seq_len DESC, time_began DESC;
db fiddle ici
En utilisant le nom de colonne ts
au lieu de time
, qui est un mot réservé dans SQL standard. C'est autorisé dans Postgres, mais avec des limitations et c'est toujours une mauvaise idée de l'utiliser comme identifiant.
Le "truc" est de soustraire les numéros de ligne pour que les lignes consécutives tombent dans le même groupe ( grp
) par (player_id, points)
. Ensuite filtrez ceux avec 100 points, regroupez-les par groupe et ne renvoyez que le résultat le plus long et le plus récent par joueur.
Explication de base de la technique:
Nous pouvons utiliser GROUP BY
et DISTINCT ON
dans le même SELECT
, GROUP BY
est appliqué avant DISTINCT ON
. Considérez la séquence d'événements dans une requête SELECT
:
À propos de DISTINCT ON
:
Vous devriez trouver la solution ici, avec les fonctions de fenêtre: postgresql.org/docs/9.1 /tutorial-window.html
Une séquence est-elle interrompue par des rangées d'autres joueurs? Aussi: votre version de Postgres?