J'ai un tableau avec des chiffres comme celui-ci (le statut est GRATUIT ou ASSIGNÉ)
id_set numéro statut ----------------------- 1 000001 ATTRIBUÉ 1 000002 GRATUIT 1 000003 ASSIGNÉ 1 000004 GRATUIT 1 000005 GRATUIT 1 000006 ASSIGNÉ 1 000007 ASSIGNÉ 1 000008 GRATUIT 1 000009 GRATUIT 1 000010 GRATUIT 1 000011 ASSIGNÉ 1 000012 ASSIGNÉ 1 000013 ASSIGNÉ 1 000014 GRATUIT 1 000015 ATTRIBUÉ
et j'ai besoin de trouver "n" nombres consécutifs, donc pour n = 3, la requête retournerait
1 000008 GRATUIT 1 000009 GRATUIT 1 000010 GRATUIT
Il ne devrait renvoyer que le premier groupe possible de chaque id_set (en fait, il ne serait exécuté que pour id_set par requête)
Je vérifiais les fonctions WINDOW, essayais quelques requêtes comme COUNT(id_number) OVER (PARTITION BY id_set ROWS UNBOUNDED PRECEDING)
, mais c'est tout ce que j'ai eu :) Je ne pouvais pas penser à la logique, comment faire cela dans Postgres.
Je pensais à créer une colonne virtuelle en utilisant les fonctions WINDOW en comptant les lignes précédentes pour chaque numéro où status = 'FREE', puis sélectionnez le premier nombre, où count est égal à mon numéro "n".
Ou peut-être regrouper les numéros par statut, mais uniquement d'un ASSIGNÉ à un autre ASSIGNÉ et sélectionner uniquement les groupes contenant au moins "n" numéros
[~ # ~] modifier [~ # ~]
J'ai trouvé cette requête (et l'ai un peu modifiée)
WITH q AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY id_set, status ORDER BY number) AS rnd,
ROW_NUMBER() OVER (PARTITION BY id_set ORDER BY number) AS rn
FROM numbers
)
SELECT id_set,
MIN(number) AS first_number,
MAX(number) AS last_number,
status,
COUNT(number) AS numbers_count
FROM q
GROUP BY id_set,
rnd - rn,
status
ORDER BY
first_number
qui produit des groupes de numéros GRATUITS/ATTRIBUÉS, mais j'aimerais avoir tous les numéros du seul premier groupe qui remplit la condition
C'est un problème lacunes-et-îles . En supposant qu'il n'y ait pas de lacunes ou de doublons dans le même ensemble id_set
:
WITH partitioned AS (
SELECT
*,
number - ROW_NUMBER() OVER (PARTITION BY id_set) AS grp
FROM atable
WHERE status = 'FREE'
),
counted AS (
SELECT
*,
COUNT(*) OVER (PARTITION BY id_set, grp) AS cnt
FROM partitioned
)
SELECT
id_set,
number
FROM counted
WHERE cnt >= 3
;
Voici une démonstration SQL Fiddle* lien pour cette requête: http://sqlfiddle.com/#!1/a2633/1 .
[~ # ~] mise à jour [~ # ~]
Pour renvoyer un seul ensemble, vous pouvez ajouter un autre tour de classement:
WITH partitioned AS (
SELECT
*,
number - ROW_NUMBER() OVER (PARTITION BY id_set) AS grp
FROM atable
WHERE status = 'FREE'
),
counted AS (
SELECT
*,
COUNT(*) OVER (PARTITION BY id_set, grp) AS cnt
FROM partitioned
),
ranked AS (
SELECT
*,
RANK() OVER (ORDER BY id_set, grp) AS rnk
FROM counted
WHERE cnt >= 3
)
SELECT
id_set,
number
FROM ranked
WHERE rnk = 1
;
Voici une démo pour celui-ci aussi: http://sqlfiddle.com/#!1/a2633/2 .
Si jamais vous avez besoin d'en faire un ensemble par id_set
, changez l'appel RANK()
comme ceci:
RANK() OVER (PARTITION BY id_set ORDER BY grp) AS rnk
De plus, vous pouvez faire en sorte que la requête renvoie le plus petit ensemble correspondant (c'est-à-dire essayer d'abord de renvoyer le premier ensemble d'exactement trois nombres consécutifs s'il existe, sinon quatre, cinq, etc.), comme ceci:
RANK() OVER (ORDER BY cnt, id_set, grp) AS rnk
ou comme ça (un par id_set
):
RANK() OVER (PARTITION BY id_set ORDER BY cnt, grp) AS rnk
* Les démos SQL Fiddle liées dans cette réponse utilisent l'instance 9.1.8 car celle 9.2.1 ne semble pas fonctionner pour le moment.
Une variante simple et rapide :
SELECT min(number) AS first_number, count(*) AS ct_free
FROM (
SELECT *, number - row_number() OVER (PARTITION BY id_set ORDER BY number) AS grp
FROM tbl
WHERE status = 'FREE'
) x
GROUP BY grp
HAVING count(*) >= 3 -- minimum length of sequence only goes here
ORDER BY grp
LIMIT 1;
Nécessite une séquence de chiffres sans intervalle dans number
(comme prévu dans la question).
Fonctionne pour n'importe quel nombre de valeurs possibles dans status
en plus de 'FREE'
, Même avec NULL
.
La principale caractéristique est de soustraire row_number()
de number
après avoir éliminé les lignes non éligibles. Les nombres consécutifs se retrouvent dans le même grp
- et grp
est également garanti d'être dans ordre croissant.
Ensuite, vous pouvez GROUP BY grp
Et compter les membres. Puisque vous semblez vouloir l'occurrence d'abord, ORDER BY grp LIMIT 1
Et vous obtenez la position de départ et la longueur de la séquence (peut être> = n) .
Pour obtenir un ensemble réel de nombres, ne recherchez pas le tableau une autre fois. Beaucoup moins cher avec generate_series()
:
SELECT generate_series(first_number, first_number + ct_free - 1)
-- generate_series(first_number, first_number + 3 - 1) -- only 3
FROM (
SELECT min(number) AS first_number, count(*) AS ct_free
FROM (
SELECT *, number - row_number() OVER (PARTITION BY id_set ORDER BY number) AS grp
FROM tbl
WHERE status = 'FREE'
) x
GROUP BY grp
HAVING count(*) >= 3
ORDER BY grp
LIMIT 1
) y;
Si vous voulez réellement une chaîne avec des zéros non significatifs comme vous l'affichez dans vos valeurs d'exemple, utilisez to_char()
avec le modificateur FM
(mode de remplissage):
SELECT to_char(generate_series(8, 11), 'FM000000')
SQL Fiddle avec cas de test étendu et les deux requêtes.
Réponse étroitement liée:
C'est une façon assez générique de le faire.
Gardez à l'esprit que cela dépend de votre colonne number
consécutive. Si ce n'est pas une fonction Window et/ou une solution de type CTE sera probablement nécessaire:
SELECT
number
FROM
mytable m
CROSS JOIN
(SELECT 3 AS consec) x
WHERE
EXISTS
(SELECT 1
FROM mytable
WHERE number = m.number - x.consec + 1
AND status = 'FREE')
AND NOT EXISTS
(SELECT 1
FROM mytable
WHERE number BETWEEN m.number - x.consec + 1 AND m.number
AND status = 'ASSIGNED')
Cela ne renverra que le premier des 3 chiffres. Il ne nécessite pas que les valeurs de number
soient consécutives. Testé à SQL-Fiddle:
WITH cte3 AS
( SELECT
*,
COUNT(CASE WHEN status = 'FREE' THEN 1 END)
OVER (PARTITION BY id_set ORDER BY number
ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
AS cnt
FROM atable
)
SELECT
id_set, number
FROM cte3
WHERE cnt = 3 ;
Et cela montrera tous les nombres (où il y a 3 ou plus 'FREE'
positions):
WITH cte3 AS
( SELECT
*,
COUNT(CASE WHEN status = 'FREE' THEN 1 END)
OVER (PARTITION BY id_set ORDER BY number
ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
AS cnt
FROM atable
)
, cte4 AS
( SELECT
*,
MAX(cnt)
OVER (PARTITION BY id_set ORDER BY number
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
AS maxcnt
FROM cte3
)
SELECT
id_set, number
FROM cte4
WHERE maxcnt >= 3 ;
select r1.number from some_table r1,
some_table r2,
some_table r3,
some_table r4
where r3.number <= r2.number
and r3.number >= r1.number
and r3.status = 'FREE'
and r2.number = r1.number + 4
and r4.number <= r2.number
and r4.number >= r1.number
and r4.status = 'ASSIGNED'
group by r1.number, r2.number having count(r3.number) = 5 and count(r4.number) = 0 order by r1.number asc limit 1 ;
Dans ce cas, 5 nombres consécutifs - donc la différence doit être 4 ou en d'autres termes count(r3.number) = n
et r2.number = r1.number + n - 1
.
Avec jointures:
select r1.number
from some_table r1 join
some_table r2 on (r2.number = r1.number + :n -1) join
some_table r3 on (r3.number <= r2.number and r3.number >= r1.number) join
some_table r4 on (r4.number <= r2.number and r4.number >= r1.number)
where
r3.status = 'FREE' and
r4.status = 'ASSIGNED'
group by r1.number, r2.number having count(r3.number) = :n and count(r4.number) = 0 order by r1.number asc limit 1 ;