Window Fonctions
1
Home.md
1
Home.md
@@ -2,3 +2,4 @@
|
|||||||
|
|
||||||
- [Fonctions d'agrégation](aggregation.md)
|
- [Fonctions d'agrégation](aggregation.md)
|
||||||
- [Arbres](Arbres.md)
|
- [Arbres](Arbres.md)
|
||||||
|
- [Fonctions de fenêtrage](window.md)
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
#
|
|
||||||
213
Réponses/aggregation.sql
Normal file
213
Réponses/aggregation.sql
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
-- Réponses
|
||||||
|
|
||||||
|
-- 1 : Quel est le nombre total de ticket enregistrés dans la base ?
|
||||||
|
SELECT COUNT(*) AS nb_ticket
|
||||||
|
FROM ticket;
|
||||||
|
|
||||||
|
-- 2 : Calculer le chiffre d’affaires global.
|
||||||
|
SELECT SUM(quantite * prix_unitaire) AS montant_total
|
||||||
|
FROM Lignesticket;
|
||||||
|
|
||||||
|
-- 3 : Pour chaque client, afficher son nom et le nombre de ticket qu’il a effectués.
|
||||||
|
SELECT c.nom, COUNT(t.id_ticket) AS nb_ticket
|
||||||
|
FROM client c
|
||||||
|
JOIN ticket t ON c.id_client = t.id_client
|
||||||
|
GROUP BY c.nom;
|
||||||
|
|
||||||
|
-- 4 : Calculer le montant moyen d’un ticket (total ventes ÷ nombre de ticket).
|
||||||
|
SELECT AVG(total_ticket) AS panier_moyen
|
||||||
|
FROM (
|
||||||
|
SELECT t.id_ticket, SUM(lt.quantite * lt.prix_unitaire) AS total_ticket
|
||||||
|
FROM ticket t
|
||||||
|
JOIN Lignesticket lt ON t.id_ticket = lt.id_ticket
|
||||||
|
GROUP BY t.id_ticket
|
||||||
|
) sous_requete;
|
||||||
|
|
||||||
|
-- 5 : Afficher le produit le plus cher et le produit le moins cher (avec leur prix).
|
||||||
|
SELECT nom_produit, prix_unitaire
|
||||||
|
FROM Produits
|
||||||
|
ORDER BY prix_unitaire DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
SELECT nom_produit, prix_unitaire
|
||||||
|
FROM Produits
|
||||||
|
ORDER BY prix_unitaire ASC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 6 : Pour chaque famille de produits, afficher le nom de la famille et le nombre de produits associés.
|
||||||
|
SELECT f.nom_famille, COUNT(p.id_produit) AS nb_produits
|
||||||
|
FROM Familles f
|
||||||
|
LEFT JOIN Produits p ON f.id_famille = p.id_famille
|
||||||
|
GROUP BY f.nom_famille;
|
||||||
|
|
||||||
|
-- 7 : Afficher, pour chaque mois, le chiffre d’affaires réalisé.
|
||||||
|
SELECT DATE_TRUNC('month', t.date_vente) AS mois,
|
||||||
|
SUM(lt.quantite * lt.prix_unitaire) AS montant_mensuel
|
||||||
|
FROM ticket t
|
||||||
|
JOIN Lignesticket lt ON t.id_ticket = lt.id_ticket
|
||||||
|
GROUP BY DATE_TRUNC('month', t.date_vente)
|
||||||
|
ORDER BY mois;
|
||||||
|
|
||||||
|
-- 8 : Trouver le client qui a dépensé le plus en montant total.
|
||||||
|
SELECT c.nom, SUM(lt.quantite * lt.prix_unitaire) AS total_depense
|
||||||
|
FROM client c
|
||||||
|
JOIN ticket t ON c.id_client = t.id_client
|
||||||
|
JOIN Lignesticket lt ON t.id_ticket = lt.id_ticket
|
||||||
|
GROUP BY c.nom
|
||||||
|
ORDER BY total_depense DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 9 : Afficher le produit ayant généré le plus de ventes en quantité totale vendue.
|
||||||
|
SELECT p.nom_produit, SUM(lt.quantite) AS total_vendu
|
||||||
|
FROM Produits p
|
||||||
|
JOIN Lignesticket lt ON p.id_produit = lt.id_produit
|
||||||
|
GROUP BY p.nom_produit
|
||||||
|
ORDER BY total_vendu DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 10 : Pour chaque famille, afficher le montant total des ventes.
|
||||||
|
SELECT f.nom_famille, SUM(lt.quantite * lt.prix_unitaire) AS total_famille
|
||||||
|
FROM Familles f
|
||||||
|
JOIN Produits p ON f.id_famille = p.id_famille
|
||||||
|
JOIN Lignesticket lt ON p.id_produit = lt.id_produit
|
||||||
|
GROUP BY f.nom_famille
|
||||||
|
ORDER BY total_famille DESC;
|
||||||
|
|
||||||
|
-- ----------
|
||||||
|
|
||||||
|
-- 1 : Intersection (INNER JOIN)
|
||||||
|
SELECT t.id_ticket, t.date_vente, c.nom AS client
|
||||||
|
FROM ticket t
|
||||||
|
INNER JOIN client c ON c.id_client = t.id_client;
|
||||||
|
|
||||||
|
-- 2 : LEFT JOIN (client sans ticket)
|
||||||
|
SELECT c.id_client, c.nom, COUNT(t.id_ticket) AS nb_ticket
|
||||||
|
FROM client c
|
||||||
|
LEFT JOIN ticket t ON t.id_client = c.id_client
|
||||||
|
GROUP BY c.id_client, c.nom
|
||||||
|
ORDER BY c.nom;
|
||||||
|
|
||||||
|
-- 3 : RIGHT JOIN (ticket sans client)
|
||||||
|
SELECT t.id_ticket, t.date_vente, c.nom AS client
|
||||||
|
FROM client c
|
||||||
|
RIGHT JOIN ticket t ON c.id_client = t.id_client;
|
||||||
|
|
||||||
|
-- 4 : FULL OUTER JOIN (union)
|
||||||
|
SELECT
|
||||||
|
c.id_client, c.nom,
|
||||||
|
t.id_ticket, t.date_vente,
|
||||||
|
CASE
|
||||||
|
WHEN t.id_ticket IS NULL THEN 'client_sans_ticket'
|
||||||
|
WHEN c.id_client IS NULL THEN 'ticket_sans_client'
|
||||||
|
ELSE 'apparié'
|
||||||
|
END AS statut
|
||||||
|
FROM client c
|
||||||
|
FULL OUTER JOIN ticket t ON c.id_client = t.id_client
|
||||||
|
ORDER BY statut, c.nom NULLS LAST, t.id_ticket;
|
||||||
|
|
||||||
|
|
||||||
|
-- 5 : SEMI-JOIN (existence)
|
||||||
|
|
||||||
|
--Lister uniquement les client qui ont acheté au moins un produit. en SQL standard, cela se fait avec EXISTS.
|
||||||
|
|
||||||
|
--Lister tous les produits et indiquer s’ils ont été vendus au moins une fois (utiliser un CASE ou COALESCE).
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
p.id_produit,
|
||||||
|
p.nom_produit,
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM Lignesticket lt
|
||||||
|
WHERE lt.id_produit = p.id_produit
|
||||||
|
) THEN TRUE ELSE FALSE END AS deja_vendu
|
||||||
|
FROM Produits p
|
||||||
|
ORDER BY p.nom_produit;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
p.id_produit,
|
||||||
|
p.nom_produit,
|
||||||
|
COALESCE(SUM(CASE WHEN lt.id_ligne IS NOT NULL THEN 1 ELSE 0 END), 0) > 0 AS deja_vendu
|
||||||
|
FROM Produits p
|
||||||
|
LEFT JOIN Lignesticket lt ON lt.id_produit = p.id_produit
|
||||||
|
GROUP BY p.id_produit, p.nom_produit;
|
||||||
|
|
||||||
|
-- 6 : ANTI-JOIN (différence)
|
||||||
|
|
||||||
|
Lister les client qui n’ont jamais acheté de produit.
|
||||||
|
utiliser NOT EXISTS ou une jointure + WHERE.
|
||||||
|
|
||||||
|
-- 7 : Jointure avec condition multiple
|
||||||
|
|
||||||
|
Lister les produits avec leur famille, même si certains produits n’ont pas de famille renseignée.
|
||||||
|
|
||||||
|
-- 8 : Auto-join (self join)
|
||||||
|
|
||||||
|
SELECT DISTINCT
|
||||||
|
c1.nom AS client_1,
|
||||||
|
c2.nom AS client_2
|
||||||
|
FROM client c1
|
||||||
|
JOIN ticket t1 ON t1.id_client = c1.id_client
|
||||||
|
JOIN Lignesticket lt1 ON lt1.id_ticket = t1.id_ticket
|
||||||
|
JOIN Lignesticket lt2 ON lt2.id_produit = lt1.id_produit
|
||||||
|
JOIN ticket t2 ON t2.id_ticket = lt2.id_ticket
|
||||||
|
JOIN client c2 ON c2.id_client = t2.id_client
|
||||||
|
WHERE c1.id_client < c2.id_client;
|
||||||
|
|
||||||
|
|
||||||
|
-- 9 : Produit cartésien
|
||||||
|
|
||||||
|
Lister toutes les combinaisons possibles de familles et de client (sans condition de jointure).
|
||||||
|
Attention : nombre de lignes = nb_familles × nb_client.
|
||||||
|
|
||||||
|
-- 10 : Jointure filtrée
|
||||||
|
|
||||||
|
Lister les ticket contenant au moins un produit de la famille "Boissons".
|
||||||
|
|
||||||
|
SELECT DISTINCT c.id_client, c.nom
|
||||||
|
FROM client c
|
||||||
|
JOIN ticket t ON t.id_client = c.id_client
|
||||||
|
JOIN Lignesticket lt ON lt.id_ticket = t.id_ticket
|
||||||
|
JOIN Produits p ON p.id_produit = lt.id_produit
|
||||||
|
JOIN Familles f ON f.id_famille = p.id_famille
|
||||||
|
WHERE f.nom_famille = 'Boissons'
|
||||||
|
ORDER BY c.nom;
|
||||||
|
|
||||||
|
-- 11
|
||||||
|
SELECT c.id_client, c.nom
|
||||||
|
FROM client c
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM ticket t
|
||||||
|
JOIN Lignesticket lt ON lt.id_ticket = t.id_ticket
|
||||||
|
JOIN Produits p ON p.id_produit = lt.id_produit
|
||||||
|
JOIN Familles f ON f.id_famille = p.id_famille
|
||||||
|
WHERE t.id_client = c.id_client
|
||||||
|
AND f.nom_famille = 'Boissons'
|
||||||
|
)
|
||||||
|
ORDER BY c.nom;
|
||||||
|
|
||||||
|
SELECT c.id_client, c.nom
|
||||||
|
FROM client c
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT DISTINCT t.id_client
|
||||||
|
FROM ticket t
|
||||||
|
JOIN Lignesticket lt ON lt.id_ticket = t.id_ticket
|
||||||
|
JOIN Produits p ON p.id_produit = lt.id_produit
|
||||||
|
JOIN Familles f ON f.id_famille = p.id_famille
|
||||||
|
WHERE f.nom_famille = 'Boissons'
|
||||||
|
) acheteurs_boissons
|
||||||
|
ON acheteurs_boissons.id_client = c.id_client
|
||||||
|
WHERE acheteurs_boissons.id_client IS NULL
|
||||||
|
ORDER BY c.nom;
|
||||||
|
|
||||||
|
|
||||||
|
-- total des ventes par famille
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
f.nom_famille,
|
||||||
|
SUM(lt.quantite * lt.prix_unitaire) AS total_ventes
|
||||||
|
FROM Familles f
|
||||||
|
JOIN Produits p ON p.id_famille = f.id_famille
|
||||||
|
JOIN Lignesticket lt ON lt.id_produit = p.id_produit
|
||||||
|
GROUP BY f.nom_famille
|
||||||
|
ORDER BY total_ventes DESC;
|
||||||
205
window.md
Normal file
205
window.md
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
# Fonctions de fenêtrage
|
||||||
|
|
||||||
|
> Les fonctions de fenêtrage permettent de réaliser des calculs sur un ensemble de lignes (fenêtre) sans les regrouper comme le ferait une agrégation classique avec GROUP BY.
|
||||||
|
Elles sont très utiles pour calculer des sommes cumulées, des rangs, des moyennes mobiles, ou comparer une ligne avec d’autres.
|
||||||
|
|
||||||
|
Contrairement aux agrégations, elles n’éliminent pas le détail des lignes : chaque ligne reste présente, mais enrichie avec des colonnes calculées sur un ensemble de ligne environnantes.
|
||||||
|
|
||||||
|
### Implémentation
|
||||||
|
|
||||||
|
- Oracle - version 8 en 2000,
|
||||||
|
- SQL Server - version 2005,
|
||||||
|
- PostgreSQL - version 8.4 en 2009,
|
||||||
|
- MariaDB - version 10.2 en 2016,
|
||||||
|
- MySQL - version 8 en 2018.
|
||||||
|
|
||||||
|
### Syntaxe générale
|
||||||
|
|
||||||
|
Les fonctions de fenêtrage s'écrivent en ajoutant la clause OVER() après la fonction.
|
||||||
|
|
||||||
|
Toute fonction sans clause _OVER_ n'est pas une fonction de fenêtrage, mais plutôt une fonction d'agrégation ou à ligne unique (scalaire).
|
||||||
|
|
||||||
|
```sql
|
||||||
|
fonction_de_fenêtrage() OVER (
|
||||||
|
[PARTITION BY colonne1, colonne2, ...]
|
||||||
|
[ORDER BY colonne3, ...]
|
||||||
|
[frame_clause]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**OVER()** : définit la fenêtre dans laquelle la fonction sera appliquée.
|
||||||
|
|
||||||
|
**PARTITION BY** : divise les lignes en groupes, un peu comme un GROUP BY, mais sans agrégation.
|
||||||
|
|
||||||
|
**ORDER BY** : définit l'ordre dans lequel les lignes sont traitées à l'intérieur de chaque partition.
|
||||||
|
|
||||||
|
## Fonctions
|
||||||
|
|
||||||
|
### Fonctions d'agrégation en mode fenêtre
|
||||||
|
|
||||||
|
Ces fonctions sont identiques aux fonctions d'agrégation classiques mais appliquées ici sur les fenêtres.
|
||||||
|
|
||||||
|
SUM() → somme
|
||||||
|
|
||||||
|
AVG() → moyenne
|
||||||
|
|
||||||
|
MIN() → minimum
|
||||||
|
|
||||||
|
MAX() → maximum
|
||||||
|
|
||||||
|
COUNT() → décompte
|
||||||
|
|
||||||
|
### Fonctions de classement
|
||||||
|
|
||||||
|
#### row_number()
|
||||||
|
|
||||||
|
La fonction fenêtre `ROW_NUMBER()` en SQL attribue un numéro de ligne unique et consécutif à chaque ligne d'un ensemble de résultats, selon l'ordre spécifié.
|
||||||
|
|
||||||
|
ROW_NUMBER() attribue un numéro unique à chaque ligne, même si les valeurs sont identiques dans la colonne de tri.
|
||||||
|
|
||||||
|
#### rank (rang)
|
||||||
|
|
||||||
|
En SQL, la fonction fenêtre `RANK()` attribue un rang à chaque ligne dans un ensemble de résultats, basé sur l'ordre spécifié. Le rang commence à 1 pour la première ligne. Contrairement à la fonction ROW_NUMBER(), qui attribue des numéros de ligne consécutifs, RANK() attribue le même rang à des lignes ayant des valeurs égales dans la colonne utilisée pour le tri, mais laisse des "trous" dans la numérotation.
|
||||||
|
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 2
|
||||||
|
- 4
|
||||||
|
- 5
|
||||||
|
|
||||||
|
#### dense_rank (rang dense)
|
||||||
|
|
||||||
|
La fonction fenêtre `DENSE_RANK()` en SQL attribue un rang à chaque ligne dans un ensemble de résultats, tout en attribuant le même rang aux lignes ayant des valeurs identiques, sans sauter de rangs dans la numérotation (contrairement à RANK() qui laisse des "trous").
|
||||||
|
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
|
||||||
|
#### ntile
|
||||||
|
|
||||||
|
La fonction fenêtre `NTILE()` en SQL divise un ensemble de résultats en un nombre spécifié de groupes égaux (ou presque égaux) et attribue un numéro de groupe à chaque ligne. Cela permet de classer les lignes dans des partitions, souvent appelées "quartiles", "quintiles" ou autres, selon le nombre de groupes que vous spécifiez.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
NTILE(4) OVER (ORDER BY colonne1) AS quartile
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fonctions de distribution
|
||||||
|
|
||||||
|
#### percent_rank (rang relatif)
|
||||||
|
|
||||||
|
La fonction fenêtre `PERCENT_RANK()` en SQL calcule le rang relatif d'une ligne par rapport à l'ensemble des résultats, sous forme de pourcentage. Elle attribue une valeur comprise entre 0 et 1, qui indique où se situe la ligne dans l'ensemble trié. Le premier élément a toujours une valeur de 0 et le dernier une valeur proche de 1 (mais jamais exactement 1, sauf dans certains cas spécifiques).
|
||||||
|
|
||||||
|
#### cume_dist (distribution cumulative)
|
||||||
|
|
||||||
|
La fonction de fenêtrage `CUME_DIST()` est une fonction analytique SQL qui calcule la distribution cumulative d'une ligne dans un ensemble de résultats. Elle renvoie la fraction ou le pourcentage des lignes d'un ensemble de données qui ont une valeur inférieure ou égale à celle de la ligne actuelle, en fonction d'un certain ordre.
|
||||||
|
|
||||||
|
#### PERCENTILE_CONT (percentile continu)
|
||||||
|
|
||||||
|
#### PERCENTILE_DISC (percentile discret)
|
||||||
|
|
||||||
|
### Fonctions de navigation
|
||||||
|
|
||||||
|
#### lead (valeur suivante)
|
||||||
|
|
||||||
|
La fonction de fenêtrage `LEAD()` en SQL est utilisée pour accéder à la valeur d’une ligne suivante par rapport à la ligne actuelle dans un ensemble de résultats, sans avoir à écrire une sous-requête complexe. Elle permet de "regarder" en avant dans les données d'une fenêtre pour obtenir une valeur future, ce qui peut être utile pour comparer des enregistrements successifs.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
LEAD(column_1) OVER (ORDER BY column_2)
|
||||||
|
```
|
||||||
|
|
||||||
|
column_1 est le nom de la colonne de la ligne suivante que vous souhaitez inclure dans la ligne courante.
|
||||||
|
|
||||||
|
ORDER BY est obligatoire lorsque LEAD() est utilisé. La séquence des lignes doit être prévisible, sinon la fonction n'a pas de sens. Toutefois, l'ordre choisi n'est pas nécessairement le même que celui du rapport final.
|
||||||
|
|
||||||
|
column_2 est la colonne qui défini l'ordre des lignes lors de la récupération de la valeur suivante. Vous pouvez spécifier plus d'une colonne.
|
||||||
|
|
||||||
|
#### lag (valeur précédente)
|
||||||
|
|
||||||
|
La fonction de fenêtrage `LAG()` en SQL permet d'accéder à la valeur d’une ligne précédente par rapport à la ligne actuelle dans un ensemble de résultats. Cela est utile pour comparer des enregistrements successifs ou calculer des différences entre la valeur actuelle et une valeur passée. C'est l'inverse de la fonction LEAD(), qui accède aux lignes suivantes.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
LAG(column_1) OVER (ORDER BY column_2)
|
||||||
|
```
|
||||||
|
|
||||||
|
column_1 est le nom de la colonne de la ligne précédente que vous souhaitez inclure dans la ligne courante.
|
||||||
|
|
||||||
|
ORDER BY est obligatoire lorsque LEAD() est utilisé. La séquence des lignes doit être prévisible, sinon la fonction n'a pas de sens. Toutefois, l'ordre choisi n'est pas nécessairement le même que celui du rapport final.
|
||||||
|
|
||||||
|
column_2 est la colonne qui défini l'ordre des lignes lors de la récupération de la valeur suivante. Vous pouvez spécifier plus d'une colonne.
|
||||||
|
|
||||||
|
|
||||||
|
#### first_value (première valeur)
|
||||||
|
|
||||||
|
first_value()
|
||||||
|
|
||||||
|
```sql
|
||||||
|
```
|
||||||
|
|
||||||
|
#### last_value (dernière valeur)
|
||||||
|
|
||||||
|
last_value()
|
||||||
|
|
||||||
|
```sql
|
||||||
|
```
|
||||||
|
|
||||||
|
#### nth_value (nième valeur)
|
||||||
|
|
||||||
|
nth_value()
|
||||||
|
|
||||||
|
```sql
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## partition
|
||||||
|
|
||||||
|
La clause PARTITION BY est une pièce maîtresse des fonctions de fenêtrage, car elle permet de découper logiquement le jeu de résultats avant d’appliquer les calculs.
|
||||||
|
|
||||||
|
Elle fonctionne comme un GROUP BY, mais sans regrouper les lignes.
|
||||||
|
|
||||||
|
Chaque partition est un "sous-ensemble" indépendant sur lequel la fonction de fenêtre s’applique.
|
||||||
|
|
||||||
|
Les partitions sont recalculées pour chaque ligne, et le résultat est répété pour toutes les lignes de la partition.
|
||||||
|
|
||||||
|
## frame clause
|
||||||
|
|
||||||
|
La clause de fenêtrage définit quelles lignes autour de la ligne courante font partie de la fenêtre.c
|
||||||
|
|
||||||
|
Sans frame, SQL choisit une fenêtre par défaut :
|
||||||
|
|
||||||
|
- Avec un ORDER BY → la fenêtre va de toutes les lignes depuis le début jusqu’à la ligne courante.
|
||||||
|
- Sans ORDER BY → toute la partition.
|
||||||
|
|
||||||
|
### Syntaxe
|
||||||
|
|
||||||
|
```sql
|
||||||
|
{ROWS | RANGE | GROUPS}
|
||||||
|
BETWEEN borne_inf AND borne_sup
|
||||||
|
```
|
||||||
|
- ROWS : défini par un nombre de lignes physiques.
|
||||||
|
- RANGE : défini par des valeurs (basé sur l’ORDER BY).
|
||||||
|
- GROUPS : défini par des groupes d’égalité dans l’ORDER BY
|
||||||
|
|
||||||
|
### Bornes
|
||||||
|
|
||||||
|
#### unbouded preceding
|
||||||
|
|
||||||
|
depuis le début de la partition
|
||||||
|
|
||||||
|
#### preceding
|
||||||
|
|
||||||
|
n lignes/valeurs avant la ligne courante
|
||||||
|
|
||||||
|
#### current row
|
||||||
|
|
||||||
|
la ligne courante
|
||||||
|
|
||||||
|
#### following
|
||||||
|
|
||||||
|
n lignes/valeurs après la ligne courante
|
||||||
|
|
||||||
|
#### unbouded following
|
||||||
|
|
||||||
|
jusqu’à la fin de la partition
|
||||||
Reference in New Issue
Block a user