{
  "assessmentTests": {
    "sql_test": {
      "name": "Test SQL",
      "desc": "30 questions de scénarios sur le filtrage, les jointures, l'agrégation, les sous-requêtes et les fonctions de fenêtre — découvrez si votre SQL correspond à ce qu'une offre d'emploi entend par maîtrise SQL.",
      "recommendation": "Votre profil de compétences SQL",
      "results": {
        "beginner": {
          "name": "Débutant",
          "desc": "Vous pouvez écrire une instruction SELECT fonctionnelle et filtrer sur quelques conditions, mais les questions que vous avez manquées se regroupent autour de la gestion des NULL et de la mécanique des jointures plutôt que la syntaxe — comparer une colonne à NULL avec =, ce que LEFT JOIN préserve réellement, comment les limites de BETWEEN fonctionnent. Rien de tout cela n'a à voir avec le fait de mal programmer les bases de données ; ce sont des règles spécifiques que les personnes qui ont appris SQL par essais et erreurs ne connaissent pas. Elles importent au travail car chacune est un endroit où une requête retourne un nombre de lignes plausible mais incorrect.",
          "recommendation": "Commencez par trois choses, dans cet ordre : pourquoi WHERE col = NULL ne correspond jamais à rien (et IS NULL est obligatoire), comment INNER JOIN supprime les lignes non appariées tandis que LEFT JOIN les conserve, et les limites exactes qu'inclut BETWEEN. Le tutoriel SQL de Mode Analytics et la documentation PostgreSQL couvrent tous les trois avec des exemples exécutables."
        },
        "intermediate": {
          "name": "Intermédiaire",
          "desc": "Vous gérez confortablement les requêtes de reporting du quotidien — jointures, GROUP BY, filtrage simple — et ne seriez pas ralenti par les travaux de maintenance de routine ou une petite fonctionnalité sur une base de code existante. L'écart entre ici et Avancé concerne surtout ce qui se passe quand les clauses interagissent : une clause WHERE sur la table jointe transformant silencieusement un LEFT JOIN en INNER JOIN, une jointure qui multiplie les comptages de lignes avant qu'un agrégat ne soit appliqué, un NOT IN qui retourne silencieusement rien parce qu'un NULL s'est glissé dans la sous-requête. Ce sont les types de bugs qui passent un examen superficiel et ne s'affichent que lorsque quelqu'un compare le total à un rapport différent.",
          "recommendation": "Concentrez-vous sur la façon dont les clauses interagissent plutôt que ce que chacune fait seule : filtrage WHERE sur les colonnes de droite d'une LEFT JOIN, multiplication des lignes par des jointures un-à-plusieurs avant agrégation, et pourquoi NOT IN échoue en présence de NULLs (EXISTS ne le fait généralement pas). Puis HAVING vs WHERE, puisque ce partage trompe les gens qui connaissent déjà les deux clauses individuellement."
        },
        "advanced": {
          "name": "Avancé",
          "desc": "C'est le niveau que la plupart des offres d'emploi entendent par « bon SQL ». Vous lisez une requête multi-jointure et pouvez prédire son nombre de lignes avant de la lancer, vous savez pourquoi un ensemble de résultats a changé plutôt que seulement qu'il l'a fait, et vous recourez à une CTE ou à une fonction de fenêtre au lieu d'une sous-requête imbriquée quand c'est l'outil plus clair. Ce qui sépare cette bande du sommet est le côté défensif du travail : savoir quelle fonction de classement utiliser quand les égalités comptent, ce qu'une transaction isole d'une session concurrente, et ce qu'une règle ON DELETE manquante fait réellement au niveau de la base de données.",
          "recommendation": "Progressez dans les parties qui protègent les données que d'autres gens touchent aussi : la différence entre RANK, DENSE_RANK et ROW_NUMBER quand il y a des égalités, ce que COMMIT rend visible et à qui, et le comportement des clés étrangères sur DELETE. Utilisez les plongées approfondies de The Index newsletter ou SQL for Devs sur les fonctions de fenêtre et l'isolation des transactions comme prochaine étape pour les deux."
        },
        "expert": {
          "name": "Expert",
          "desc": "Vous avez obtenu un score au sommet de chaque section — filtrage et sémantique NULL, jointures et opérations ensemblistes, agrégation et sous-requêtes, et fonctions de fenêtre, CTE et contraintes. Concrètement, cela signifie qu'on peut vous remettre une requête de rapport multi-jointure d'un étrange et que vous pouvez expliquer pourquoi elle retourne le nombre de lignes qu'elle fait, pas seulement ce que la syntaxe dit qu'elle devrait faire, ce qui est la compétence plus difficile et plus précieuse. À ce niveau, le langage de requête n'est rarement le facteur limitant ; la limite est généralement la conception du schéma ou la taille des données en dessous.",
          "recommendation": "Les rendements se font maintenant dans les plans d'exécution et la conception : lire la sortie EXPLAIN avant de supposer qu'une requête est lente, la stratégie d'indexation comme un compromis par rapport au coût d'écriture plutôt qu'une victoire gratuite, et les décisions de normalisation qui tiennent à mesure qu'un schéma croît. Si vous êtes filtré pour un rôle, décrivez un bug de requête comme la multiplication des jointures ou le piège NOT IN/NULL que vous avez trouvé en production plutôt que de nommer des fonctionnalités SQL — cela démontre le raisonnement, pas seulement le vocabulaire."
        }
      },
      "questions": [
        {
          "question": "Une table a une colonne nullable age. Vous exécutez SELECT * FROM users WHERE age = NULL. Combien de lignes cela retourne, même si plusieurs lignes ont age défini à NULL ?",
          "options": [
            {
              "icon": "",
              "label": "Chaque ligne où age est NULL, car = correspond à NULL comme à toute autre valeur"
            },
            {
              "icon": "",
              "label": "Zéro — comparer n'importe quoi à NULL avec = produit UNKNOWN, jamais TRUE, donc aucune ligne ne correspond ; IS NULL est obligatoire"
            },
            {
              "icon": "",
              "label": "Une erreur de syntaxe — NULL ne peut pas apparaître du côté droit de ="
            },
            {
              "icon": "",
              "label": "Chaque ligne du tableau, car les comparaisons NULL se font par défaut TRUE"
            }
          ]
        },
        {
          "question": "Vous exécutez SELECT DISTINCT department, role FROM employees. Qu'est-ce que DISTINCT supprime les doublons de ?",
          "options": [
            {
              "icon": "",
              "label": "La combinaison de department et role ensemble — une ligne est supprimée seulement si les deux colonnes correspondent exactement à une autre ligne"
            },
            {
              "icon": "",
              "label": "Les valeurs department en doublon uniquement, en conservant chaque role"
            },
            {
              "icon": "",
              "label": "Les valeurs role en doublon uniquement, en conservant chaque department"
            },
            {
              "icon": "",
              "label": "Rien — DISTINCT fonctionne seulement avec une seule colonne"
            }
          ]
        },
        {
          "question": "Une colonne name est filtrée avec WHERE name LIKE 'A_'. Laquelle de ces valeurs correspond : 'A', 'Al', 'Ana', 'Ally' ?",
          "options": [
            {
              "icon": "",
              "label": "'A' — le trait de soulignement est optionnel et correspond à zéro ou plusieurs caractères"
            },
            {
              "icon": "",
              "label": "'Al' — le trait de soulignement correspond exactement à un caractère, donc LIKE 'A_' correspond à n'importe quelle chaîne de deux caractères commençant par A"
            },
            {
              "icon": "",
              "label": "'Ana' et 'Ally' — le trait de soulignement correspond à n'importe quel nombre de caractères de fin"
            },
            {
              "icon": "",
              "label": "Les quatre valeurs correspondent, car LIKE ignore la longueur"
            }
          ]
        },
        {
          "question": "Une colonne price est filtrée avec WHERE price BETWEEN 10 AND 20. Les lignes avec price exactement 10 ou exactement 20 sont-elles incluses ?",
          "options": [
            {
              "icon": "",
              "label": "Non — BETWEEN exclut les deux extrémités"
            },
            {
              "icon": "",
              "label": "Oui — BETWEEN est inclusif aux deux extrémités, équivalent à price >= 10 AND price <= 20"
            },
            {
              "icon": "",
              "label": "Seulement price = 10 est inclus ; la limite supérieure est exclusive"
            },
            {
              "icon": "",
              "label": "Seulement price = 20 est inclus ; la limite inférieure est exclusive"
            }
          ]
        },
        {
          "question": "Une table orders a 10 lignes, et 3 d'entre elles ont un shipped_date NULL. Qu'est-ce que SELECT COUNT(*) FROM orders retourne, et qu'est-ce que SELECT COUNT(shipped_date) FROM orders retourne ?",
          "options": [
            {
              "icon": "",
              "label": "10, puis 10 — COUNT compte toujours les lignes indépendamment des NULLs"
            },
            {
              "icon": "",
              "label": "7, puis 7 — les deux formes ignorent les lignes NULL"
            },
            {
              "icon": "",
              "label": "10, puis 7 — COUNT(*) compte chaque ligne, COUNT(column) compte seulement les lignes où cette colonne n'est pas NULL"
            },
            {
              "icon": "",
              "label": "10, puis 3 — COUNT(column) compte seulement les valeurs NULL"
            }
          ]
        },
        {
          "question": "Vous exécutez SELECT name, salary FROM employees ORDER BY 2 DESC. À quoi le 2 se réfère-t-il ?",
          "options": [
            {
              "icon": "",
              "label": "Une erreur de syntaxe — ORDER BY n'accepte que les noms de colonnes, pas les nombres"
            },
            {
              "icon": "",
              "label": "La deuxième ligne du résultat"
            },
            {
              "icon": "",
              "label": "Une valeur littérale de 2, utilisée comme brise-égalité"
            },
            {
              "icon": "",
              "label": "La deuxième colonne de la liste SELECT, salary — ORDER BY accepte le numéro de position d'une colonne comme raccourci"
            }
          ]
        },
        {
          "question": "En SQL standard, à quoi 'Ana' || ' ' || 'Lee' évalue-t-il ?",
          "options": [
            {
              "icon": "",
              "label": "Une erreur de syntaxe — SQL n'a pas d'opérateur de concaténation"
            },
            {
              "icon": "",
              "label": "3 — || est traité comme un OR booléen et retourne un comptage"
            },
            {
              "icon": "",
              "label": "'AnaLee' — || concatène sans préserver les espaces"
            },
            {
              "icon": "",
              "label": "'Ana Lee' — || est l'opérateur de concaténation de chaînes standard SQL"
            }
          ]
        },
        {
          "question": "Lequel de ceux-ci est exactement équivalent à WHERE status IN ('open', 'pending', 'review') ?",
          "options": [
            {
              "icon": "",
              "label": "WHERE status = 'open' AND status = 'pending' AND status = 'review'"
            },
            {
              "icon": "",
              "label": "WHERE status != 'open' OR status != 'pending' OR status != 'review'"
            },
            {
              "icon": "",
              "label": "WHERE status = 'open' OR status = 'pending' OR status = 'review'"
            },
            {
              "icon": "",
              "label": "WHERE status LIKE 'open,pending,review'"
            }
          ]
        },
        {
          "question": "customers a 100 lignes ; 20 d'entre elles n'ont jamais passé de commande. Vous exécutez SELECT c.id FROM customers c INNER JOIN orders o ON c.id = o.customer_id. Les 20 clients sans commande apparaissent-ils dans le résultat ?",
          "options": [
            {
              "icon": "",
              "label": "Oui, une fois chacun, avec o.id montré comme NULL"
            },
            {
              "icon": "",
              "label": "Oui, mais seulement s'ils apparaissent aussi dans une clause WHERE"
            },
            {
              "icon": "",
              "label": "Non — INNER JOIN retourne seulement les lignes qui ont une correspondance dans les deux tables, donc les clients sans commande sont supprimés entièrement"
            },
            {
              "icon": "",
              "label": "Oui, dupliquées une fois par colonne dans orders"
            }
          ]
        },
        {
          "question": "Vous voulez chaque client qu'il ait des commandes ou non, vous écrivez donc SELECT c.id, o.total FROM customers c LEFT JOIN orders o ON c.id = o.customer_id WHERE o.total > 100. Cela retourne-t-il toujours les clients sans commande ?",
          "options": [
            {
              "icon": "",
              "label": "Non — filtrer sur o.total dans la clause WHERE rejette les lignes NULL que LEFT JOIN a produites pour les clients non appariés, donc il se comporte comme un INNER JOIN"
            },
            {
              "icon": "",
              "label": "Oui — LEFT JOIN préserve toujours chaque ligne de customers peu importe ce qui suit"
            },
            {
              "icon": "",
              "label": "Oui, avec o.total montré comme 0 pour les clients sans commande"
            },
            {
              "icon": "",
              "label": "Non — LEFT JOIN se convertit silencieusement lui-même en RIGHT JOIN quand une clause WHERE est ajoutée"
            }
          ]
        },
        {
          "question": "Une table employees a une colonne id et une colonne manager_id qui pointe vers l'id d'une autre ligne. Pour lister chaque employé à côté du nom de son manager, vous joignez la table à elle-même : SELECT e.name, m.name FROM employees e JOIN employees m ON e.manager_id = m.id. Comment s'appelle ce modèle ?",
          "options": [
            {
              "icon": "",
              "label": "Une cross join — chaque employé est appairé avec chaque manager"
            },
            {
              "icon": "",
              "label": "Une jointure récursive — elle récupère la chaîne de gestion complète"
            },
            {
              "icon": "",
              "label": "Une self-join — la même table est jointe à elle-même en utilisant deux alias différents"
            },
            {
              "icon": "",
              "label": "C'est du SQL invalide — une table ne peut pas être jointe à elle-même"
            }
          ]
        },
        {
          "question": "Deux requêtes SELECT avec les mêmes colonnes sont combinées avec UNION. Si les deux requêtes retournent une ligne identique, combien de copies de cette ligne apparaissent dans le résultat final ?",
          "options": [
            {
              "icon": "",
              "label": "Une — UNION supprime les lignes dupliquées dans le résultat combiné ; UNION ALL garderait les deux copies"
            },
            {
              "icon": "",
              "label": "Deux — UNION garde chaque ligne des deux requêtes"
            },
            {
              "icon": "",
              "label": "Zéro — UNION supprime toute ligne qui apparaît dans les deux requêtes"
            },
            {
              "icon": "",
              "label": "Cela dépend de quelle requête a listé la ligne en premier"
            }
          ]
        },
        {
          "question": "La table sizes a 3 lignes et colors a 4 lignes. Combien de lignes SELECT * FROM sizes CROSS JOIN colors retourne-t-il ?",
          "options": [
            {
              "icon": "",
              "label": "7 — CROSS JOIN ajoute les comptages de lignes ensemble"
            },
            {
              "icon": "",
              "label": "12 — une CROSS JOIN retourne chaque combinaison possible de lignes des deux tables (3 x 4)"
            },
            {
              "icon": "",
              "label": "3 — CROSS JOIN retourne une ligne par ligne de la première table"
            },
            {
              "icon": "",
              "label": "0 — CROSS JOIN nécessite une condition ON sinon il retourne rien"
            }
          ]
        },
        {
          "question": "orders a 1 ligne pour la commande #100, et order_items a 3 lignes pour la commande #100 (une par article de ligne). Vous exécutez SELECT o.id, o.total FROM orders o JOIN order_items i ON o.id = i.order_id WHERE o.id = 100. Combien de lignes reviennent pour la commande #100 ?",
          "options": [
            {
              "icon": "",
              "label": "3 — la jointure produit une ligne de sortie par ligne order_items correspondante, donc la ligne unique de la commande #100 est répétée une fois par article de ligne"
            },
            {
              "icon": "",
              "label": "1 — orders n'a qu'une seule ligne pour la commande #100, donc la jointure ne peut pas en produire plus"
            },
            {
              "icon": "",
              "label": "4 — une ligne pour la commande plus une par article de ligne"
            },
            {
              "icon": "",
              "label": "0 — joindre une table à une ligne à une table à trois lignes sur une clé non-unique échoue"
            }
          ]
        },
        {
          "question": "SELECT c.name, o.id FROM customers c RIGHT JOIN orders o ON c.id = o.customer_id retourne les mêmes lignes que lequel de ceux-ci ?",
          "options": [
            {
              "icon": "",
              "label": "SELECT c.name, o.id FROM customers c INNER JOIN orders o ON c.id = o.customer_id"
            },
            {
              "icon": "",
              "label": "SELECT c.name, o.id FROM orders o LEFT JOIN customers c ON c.id = o.customer_id — échanger l'ordre des tables et utiliser LEFT JOIN au lieu de RIGHT JOIN est équivalent"
            },
            {
              "icon": "",
              "label": "SELECT c.name, o.id FROM customers c LEFT JOIN orders o ON c.id = o.customer_id"
            },
            {
              "icon": "",
              "label": "SELECT c.name, o.id FROM orders o CROSS JOIN customers c"
            }
          ]
        },
        {
          "question": "En SQL standard, vous exécutez SELECT department, name, AVG(salary) FROM employees GROUP BY department. Est-ce une requête valide ?",
          "options": [
            {
              "icon": "",
              "label": "Oui — GROUP BY doit seulement inclure department car il est listé en premier"
            },
            {
              "icon": "",
              "label": "Non — name est sélectionné mais ni agrégé ni listé dans GROUP BY, et SQL standard exige que chaque colonne non-agrégée sélectionnée apparaisse dans la clause GROUP BY"
            },
            {
              "icon": "",
              "label": "Oui — SQL choisit automatiquement un nom arbitraire par department"
            },
            {
              "icon": "",
              "label": "Non — AVG() ne peut pas être combiné avec GROUP BY dans la même requête"
            }
          ]
        },
        {
          "question": "Vous voulez des departments dont le salaire moyen dépasse 80000. Quelle clause filtre sur une valeur agrégée comme AVG(salary) après le groupement — WHERE ou HAVING ?",
          "options": [
            {
              "icon": "",
              "label": "WHERE — HAVING est utilisé seulement avec les requêtes UNION"
            },
            {
              "icon": "",
              "label": "L'un ou l'autre fonctionne de manière identique avec les fonctions d'agrégation"
            },
            {
              "icon": "",
              "label": "Aucun — le filtrage d'agrégation nécessite une sous-requête"
            },
            {
              "icon": "",
              "label": "HAVING — WHERE filtre les lignes individuelles avant le groupement, HAVING filtre les groupes après l'agrégation"
            }
          ]
        },
        {
          "question": "Vous écrivez SELECT salary * 1.1 AS new_salary FROM employees WHERE new_salary > 50000. Cela s'exécute-t-il ?",
          "options": [
            {
              "icon": "",
              "label": "Oui — les alias définis dans SELECT sont toujours disponibles pour WHERE dans la même requête"
            },
            {
              "icon": "",
              "label": "Oui, mais seulement pour les alias numériques"
            },
            {
              "icon": "",
              "label": "Non — AS n'est pas autorisé dans une requête filtrée par WHERE"
            },
            {
              "icon": "",
              "label": "Non — WHERE est évalué avant que SELECT n'attribue l'alias new_salary, donc l'alias n'existe pas encore à ce stade de l'exécution"
            }
          ]
        },
        {
          "question": "SELECT name FROM employees e WHERE salary > (SELECT AVG(salary) FROM employees WHERE department = e.department). Pourquoi s'appelle-t-on une sous-requête corrélée ?",
          "options": [
            {
              "icon": "",
              "label": "Parce qu'elle utilise une JOIN au lieu d'une clause WHERE"
            },
            {
              "icon": "",
              "label": "La sous-requête interne référence e.department de la requête externe, donc elle doit être ré-évaluée pour chaque ligne que la requête externe considère"
            },
            {
              "icon": "",
              "label": "Parce qu'elle retourne plus d'une colonne"
            },
            {
              "icon": "",
              "label": "Parce qu'elle s'exécute exactement une fois avant que la requête externe ne commence"
            }
          ]
        },
        {
          "question": "Une sous-requête SELECT manager_id FROM employees retourne certaines valeurs NULL avec des vrais ids. Vous exécutez SELECT name FROM employees WHERE id NOT IN (SELECT manager_id FROM employees). Que se passe-t-il ?",
          "options": [
            {
              "icon": "",
              "label": "Elle retourne chaque employé qui n'est pas un manager, en ignorant les NULLs"
            },
            {
              "icon": "",
              "label": "Elle retourne zéro lignes — un seul NULL dans la liste NOT IN rend chaque comparaison UNKNOWN, donc aucune ligne ne peut satisfaire la condition"
            },
            {
              "icon": "",
              "label": "Elle lève une erreur car NOT IN ne peut pas être utilisé avec les sous-requêtes"
            },
            {
              "icon": "",
              "label": "Elle retourne chaque employé, car NULL est traité comme un joker"
            }
          ]
        },
        {
          "question": "SELECT name FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id). Qu'est-ce que cela retourne ?",
          "options": [
            {
              "icon": "",
              "label": "Chaque client qui a au moins une ligne dans orders — EXISTS vérifie seulement si la sous-requête retourne des lignes, pas quelles valeurs elles contiennent"
            },
            {
              "icon": "",
              "label": "Chaque client, car SELECT 1 retourne toujours true"
            },
            {
              "icon": "",
              "label": "Une erreur, car la sous-requête sélectionne un nombre au lieu d'un nom de colonne"
            },
            {
              "icon": "",
              "label": "Seulement les clients avec exactement une commande"
            }
          ]
        },
        {
          "question": "Vous écrivez SELECT name, (SELECT order_id FROM orders WHERE customer_id = c.id) AS last_order FROM customers c, et un client donné a 3 lignes dans orders. Que se passe-t-il quand cette requête s'exécute ?",
          "options": [
            {
              "icon": "",
              "label": "Elle retourne le premier order_id correspondant et ignore silencieusement les deux autres"
            },
            {
              "icon": "",
              "label": "Elle retourne une liste séparée par des virgules de tous les trois order_ids"
            },
            {
              "icon": "",
              "label": "Elle retourne 3 lignes pour ce client, une par commande"
            },
            {
              "icon": "",
              "label": "Elle lève une erreur lors de l'exécution — une sous-requête scalaire dans la liste SELECT doit retourner au maximum une ligne, et celle-ci en retourne trois"
            }
          ]
        },
        {
          "question": "Quatre lignes sont à égalité pour le score le plus élevé. Utilisant RANK() ORDER BY score DESC, les quatre obtiennent le rang 1. Quel rang obtient la ligne suivante plus bas ?",
          "options": [
            {
              "icon": "",
              "label": "5 — RANK() laisse une lacune égale au nombre de lignes attachées avant de continuer"
            },
            {
              "icon": "",
              "label": "2 — RANK() augmente toujours exactement de un après toute égalité"
            },
            {
              "icon": "",
              "label": "1 — chaque ligne suivante obtient aussi le rang 1"
            },
            {
              "icon": "",
              "label": "4 — RANK() reprend le comptage du nombre d'égalités"
            }
          ]
        },
        {
          "question": "Quatre lignes sont à égalité pour le score le plus élevé. Utilisant DENSE_RANK() ORDER BY score DESC, les quatre obtiennent le rang 1. Quel rang obtient la ligne suivante plus bas ?",
          "options": [
            {
              "icon": "",
              "label": "5 — DENSE_RANK() se comporte exactement comme RANK()"
            },
            {
              "icon": "",
              "label": "1 — DENSE_RANK() assigne le même rang à chaque ligne restante"
            },
            {
              "icon": "",
              "label": "2 — DENSE_RANK() ne laisse jamais de lacunes, donc la prochaine valeur distincte obtient toujours le rang consécutif suivant"
            },
            {
              "icon": "",
              "label": "3 — DENSE_RANK() saute un rang par groupe d'égalité"
            }
          ]
        },
        {
          "question": "SUM(amount) OVER (PARTITION BY region ORDER BY amount) est ajouté à une requête. Qu'est-ce que PARTITION BY region fait ici ?",
          "options": [
            {
              "icon": "",
              "label": "Il redémarre la somme courante séparément pour chaque région, au lieu de s'accumuler dans tout l'ensemble de résultats"
            },
            {
              "icon": "",
              "label": "Il filtre les résultats à une seule région"
            },
            {
              "icon": "",
              "label": "Il groupe et réduit les lignes en une par région, comme GROUP BY"
            },
            {
              "icon": "",
              "label": "Il trie les régions alphabétiquement avant de sommer"
            }
          ]
        },
        {
          "question": "ROW_NUMBER() OVER (ORDER BY score DESC) est appliqué à cinq lignes, deux desquelles sont exactement attachées en score. Deux lignes peuvent-elles jamais recevoir le même numéro de ligne ?",
          "options": [
            {
              "icon": "",
              "label": "Oui — les lignes attachées partagent toujours le même numéro de ligne"
            },
            {
              "icon": "",
              "label": "Seulement si PARTITION BY est aussi utilisé"
            },
            {
              "icon": "",
              "label": "Cela dépend si l'égalité est dans la première ou la dernière position"
            },
            {
              "icon": "",
              "label": "Non — ROW_NUMBER() assigne toujours un entier unique, strictement croissant à chaque ligne, même quand les valeurs sont attachées"
            }
          ]
        },
        {
          "question": "WITH high_earners AS (SELECT * FROM employees WHERE salary > 100000) SELECT department, COUNT(*) FROM high_earners GROUP BY department. Qu'est-ce que high_earners ?",
          "options": [
            {
              "icon": "",
              "label": "Une expression de table commune (CTE) — un ensemble de résultats nommé et temporaire que le reste de la requête peut référencer comme une table"
            },
            {
              "icon": "",
              "label": "Une table permanente créée dans la base de données"
            },
            {
              "icon": "",
              "label": "Une vue qui persiste après la fin de la requête"
            },
            {
              "icon": "",
              "label": "Une procédure stockée qui doit être appelée séparément"
            }
          ]
        },
        {
          "question": "Une colonne est déclarée PRIMARY KEY. Pouvez-vous insérer une ligne où cette colonne est NULL ?",
          "options": [
            {
              "icon": "",
              "label": "Oui — PRIMARY KEY applique seulement l'unicité, pas la non-nullité"
            },
            {
              "icon": "",
              "label": "Oui, mais une seule ligne NULL est autorisée, comme une contrainte UNIQUE"
            },
            {
              "icon": "",
              "label": "Non — une colonne PRIMARY KEY est implicitement NOT NULL, donc insérer NULL en elle est rejetée"
            },
            {
              "icon": "",
              "label": "Cela dépend si la colonne a aussi une valeur par défaut"
            }
          ]
        },
        {
          "question": "À l'intérieur d'une transaction ouverte, vous exécutez un UPDATE mais n'avez pas encore exécuté COMMIT. D'une deuxième connexion séparée à la base de données, cette mise à jour est-elle visible ?",
          "options": [
            {
              "icon": "",
              "label": "Oui — toutes les connexions voient chaque écriture l'instant où elle s'exécute"
            },
            {
              "icon": "",
              "label": "Oui, mais seulement si la deuxième connexion ouvre aussi une transaction"
            },
            {
              "icon": "",
              "label": "Cela dépend seulement de quelle table a été mise à jour"
            },
            {
              "icon": "",
              "label": "Non — un changement non commité n'est visible qu'à l'intérieur de la transaction qui l'a fait, jusqu'à ce que COMMIT le rende durable et visible aux autres"
            }
          ]
        },
        {
          "question": "products.category_id a une contrainte FOREIGN KEY référençant categories.id. Vous tentez de DELETE une ligne de categories qui a encore des products la pointant, sans règle ON DELETE spécifiée. Que se passe-t-il ?",
          "options": [
            {
              "icon": "",
              "label": "La ligne de category est supprimée et les valeurs products.category_id correspondantes sont automatiquement définies à NULL"
            },
            {
              "icon": "",
              "label": "La ligne de category est supprimée et chaque product qui la référençait est aussi supprimé"
            },
            {
              "icon": "",
              "label": "Le DELETE est rejeté — le comportement par défaut de la clé étrangère bloque la suppression d'une ligne référencée tant que les lignes dépendantes la pointent"
            },
            {
              "icon": "",
              "label": "Le DELETE réussit silencieusement, laissant le category_id des products pointant vers une category qui n'existe plus"
            }
          ]
        }
      ],
      "optionOrderVersion": "e0466e547cfd8a45"
    }
  }
}
