Journal retour sur SQL noir 🎭

Posté par  (site web personnel, Mastodon) . Licence CC By‑SA.
Étiquettes :
18
13
avr.
2025

Sommaire

AprĂšs avoir lu la rĂ©cente dĂ©pĂȘche de BenoĂźt, j’ai voulu rĂ©viser mon SQL
 (non, en fait seulement faire « mumuse » car je bouffe dĂ©jĂ  assez de SQL ces temps ci.) Ceci est mon retour d’expĂ©rience.

prise en mains

Commençons simplement par le premier cas.

On arrive sur l’onglet « Case Brief » (soit « RĂ©sumĂ© du Cas » en gros), que j’ai vite passĂ© pour cliquer sur l’onglet « SQL Workspace » (soit « Espace de travail » mais) on comprend vite que c’est la fenĂȘtre pour faire les requĂȘtes.

inventaire des objets

Chiche, beaucoup vont pouvoir plonger tĂȘte baissĂ©e dans du « requĂȘtage » sauvage. đŸŽâ€â˜ ïž Mais quels objets se trouvent dans ce schĂ©ma ? Mes souvenirs (et quelques notes) me disent :

  • Oracle : SELECT owner, table_name FROM dba_tables; 
si on a de supers droits (les vues commençant par dba_ sont rĂ©servĂ©es Ă  l’usage des DBA), ou alors SELECT owner, table_name FROM all_tables; qui devrait lister toutes les tables auxquelles l’on peut accĂ©der
 Minute papillon, il y a aussi SELECT table_name FROM user_tables; qui ne liste que les tables qui nous appartiennent (un peu comme si on avait fait plutĂŽt un SELECT table_name FROM all_tables WHERE owner = (SELECT user FROM DUAL); directement.) Je me souviens aussi de SELECT * FROM tab; mais je ne sais pas si c’est officiel ou si le « hack » est toujours supportĂ©.
    Pour les vues, il faut remplacer _tables par _views et donc faire l’union pour les deux. J’aime bien trier par table_name.
  • MySQL et MariaDB : SHOW TABLES simplement pour les deux, et il faut utiliser SHOW FULL TABLES pour avoir l’information de qui est qui. On peut filtrer sur le nom des tables et vue avec LIKE mais on ne peut pas trier la rĂ©sultat renvoyĂ©.
  • SQL Server (en tout cas pour le souvenir que j’ai des versions 2000 et prĂ©cĂ©dentes) : SELECT * FROM sysobjects WHERE xtype = 'U'
  • SQLite : depuis le client en ligne de commande, l’incantation .tables (qui peut ĂȘtre abrĂ©gĂ© .table ou mĂȘme .ta) permet de lister tables et vues en filtrant Ă©ventuellement sur le nom mais sans pouvoir trier le rĂ©sultat.
    Depuis fin novembre 2021 (version 3.37 si je me souviens bien), on peut aussi lancer le sort PRAGMA table_list; depuis n’importe quelle session (autrement que via la CLI officielle donc) et obtenir une table sur laquelle on n’a pas plus de contrĂŽle mais qui offre plus d’informations (la colonne « type » par exemple indique si c’est une table ou une vue). Ce « pragma » ajoute des informations en plus (ncol pour le nombre de colonnes, wr pour indiquer si la table est crĂ©Ă© avec WITHOUT ROWID —1— ou pas —0—, strict pour indiquer si table est crĂ©Ă© avec l’option STRICT ou pas, schema) comparĂ© Ă  la requĂȘte SELECT name, type FROM sqlite_schema; qui permet filtrage et tri
 Historiquement (j’espĂšre quand mĂȘme que vous avez fait les mises Ă  jour de sĂ©curitĂ©) la table sqlite_schema s’appelait sqlite_master et ce nom est toujours reconnu (alias) pour des raisons de compatibilitĂ© (je crois pour une partie de l’API)
  • PostgreSQL : depuis le client en ligne de commandes (psql normalement), il faut lancer l’incantation \dt (pour les tables) ou \dv (pour les vues), Ă©ventuellement suffixĂ©e de + (pour afficher deux fois plus de colonnes dont celle de la description). On peut filter sur le nom mais on ne peut pas trier le rĂ©sultat renvoyĂ©.
    Depuis n’importe quelle session (pas juste le client CLI), on peut aussi simplement lancer la requĂȘte SELECT tablename, schemaname, tableowner FROM pg_catalog.pg_tables; avec toute la capacitĂ© de filtrage et de tris.
    Oracle, IBM DB2, Firebird, Derby, ont aussi un catalogue similaire.
  • etc. Oui, c’est un peu la plaie Ă  ce niveau mais il y a eu une standardisation (si je ne dis pas de bĂȘtise c’est arrivĂ© avec SQL-92) et on devrait pouvoir faire de nos jours cette requĂȘte commune : SELECT table_name, table_schema, table_type FROM information_schema.tables; Cette vue peut comporter d’autres informations selon les SGBD, liste aussi bien les tables que les vues, est triable et filtrable.


ce que je teste en vain (Ă  ce stade je commence Ă  connaĂźtre par cƓur le « Error in query no such table: ») đŸ„Č

C’est mieux de ne pas se prĂ©cipiter et prendre le temps de faire le tour du propriĂ©taire. AprĂšs avoir perdu un bon quart d’heure, je clique sur l’onglet « Schema » (soit schĂ©ma) qui me donne la liste que je cherchais.

inventaire des champs

Bien, on a les tables prĂ©sentes et donc une base de dĂ©part. Sans liste des colonnes et graphe des relations ça va cependant ĂȘtre coton. Je me lance aussi :

Comme on peut s’y attendre (puisque la base virtuelle des mĂ©ta-donnĂ©es, on
l’a vu, n’est pas reconnue —mais je garde espoir que c’est peut-ĂȘtre la
table prĂ©cĂ©demment demandĂ©e qui n’est pas prĂ©sente et que d’autres seront
lĂ ), le standard

SELECT column_name, data_type,
  character_maximum_length AS max_length,
  numeric_scale            AS num_length,
  numeric_precision        AS num_precis
FROM information_schema.columns
WHERE table_name = 'suspects';


ne passe pas. đŸ„Č Et comme ce n’est pas du My/Maria non plus (puisque, j’ai eu l’erreur « Error in query near "show": syntax error » plus tĂŽt
) ceci ne fonctionne pas non plus :

  • SHOW COLUMNS IN suspects ou
  • SHOW FIELDS FROM suspects ou
  • EXPLAIN suspects moins connu.

Enfin j’ai testĂ© le standard de fait introduit par Oracle : DESCRIBE suspects; ou son abrĂ©viation DESC suspects; Cela se solde par « Error in query near "describe": syntax error »

Avant de me rĂ©signer Ă  tout « dumper » (ça va piquer s’il y a des milliers ou des millions de rĂ©sultats), je tente un dernier coup de poker

  • Oracle : SELECT * FROM suspects WHERE ROWNUM <= 1; ⇒ « Error in query no such column: ROWNUM » (off course
)
  • avec PostgreSQL≄9.0 et probablement d’autres SGBD, on peut explorer la piste du fenĂȘtrage pour mimer l’oracle : SELECT ROW_NUMBER() OVER () AS i, s.* FROM suspects s WHERE i <= 1; ⇒ « Error in query misuse of aliased window function i » (toute façon le OVER () —sans ORDER BY ici— me plait pas trop)
    SELECT * FROM suspects WHERE ROW_NUMBER() <= 1; ⇒ « Error in query misuse of window function row_number() » (idem avec RANK())
  • Microsoft (Access, SQL Server), SAP (ASE, IQ), Teradata, etc.: SELECT * FROM suspects TOP 1; ⇒ « Error in query near "1": syntax error » (cela laisse entendre que le top est accepté ?)
    
ou plutĂŽt (je confonds souvent) SELECT TOP 1 * FROM suspects; ⇒ « Error in query near "1": syntax error » (en tout cas pas mieux ici
)
  • Ingres/Informix/Firebird : SELECT FIRST 1 * FROM suspects; ⇒ « Error in query near "1": syntax error »
  • Pg/My/Maria/Db2/SQLite/HSQLDB/H2/etc. : SELECT * FROM suspects LIMIT 1; ⇒ ça marche et j’ai un joli tableau qui va bien 😊 Par contre, contrairement Ă  une description en bonne et due forme, on peut juste dĂ©duire si le champ non null est numĂ©rique ou textuel

  • ANSI SQL:2008 prĂ©conise plutĂŽt : SELECT * FROM suspects FETCH FIRST 1 ROWS ONLY; (supportĂ© par : Oracle≄12c, DB2≄11, PostgreSQL≄8.4, partiellement par T-SQL≄2008, HSQLDB≄2.0, etc.) ⇒ « Error in query near "FIRST": syntax error » (dommage, mais on en dĂ©duit que le SGBD sous-jacent n’a pas ce niveau de norme/standard.)

C’est seulement aprĂšs avoir rĂ©solu le cas que j’ai remarquĂ© dans l’onglet « Schema » que la structure (nom et type des colonnes, ainsi que si c’est une clĂ© primaire/Ă©trangĂšre) s’affiche en cliquant sur le nom de la table
 puis se masque si on refait un clic sur le nom.
Mon Ă©ternel souci avec les cliquodromes : il faut cliquer partout et dĂ©couvrir au petit bonheur la chance (surtout que l’on part trop souvent du principe que c’est facile et qu’il n’y a donc pas besoin de manuel
)

le reste

Fort de mes dĂ©boires, je clique sur les autres onglets pour voir un peu. J’ai Ă©tĂ© dans un vague indescriptible en arrivant sur « Notes » puis j’ai eu l’eureka en lisant la note en bas : Il s’agit d’un bloc-notes perso et ça utilise le stockage persistant.
Je regarde le suivant, « Submit » qui dit d’entrer la rĂ©ponse trouvĂ©e. Bien, il faudra donc recopier la rĂ©ponse finale.
Du coup, je remarque qu’on ouvre avec le « Case Brief » qui prĂ©sente le dossier et qu’on termine avec le « Submit » qui permet de le clĂŽturer. Dans ma logique, j’aurais mis le « Schema » avant le « Workspace » ; mais ce n’est que mon avis.

Et toi, journal, as-tu vu des choses intĂ©ressantes que j’ai loupĂ©es ?

1er cas

Pour commencer, je poursuis mon exploration avec quelques requĂȘtes simples :

SELECT COUNT(*) FROM suspects; -- 400
SELECT COUNT(*) FROM interviews; -- 200
SELECT COUNT(*) FROM crime_scene; -- 100

SELECT COUNT(DISTINCT name) FROM suspects; -- 218
SELECT COUNT(DISTINCT attire) FROM suspects; -- 13
SELECT COUNT(DISTINCT scare) FROM suspects; -- 19

SELECT COUNT(DISTINCT suspect_id) FROM interviews; -- 200

SELECT COUNT(DISTINCT type) FROM crime_scene; -- 3
SELECT COUNT(DISTINCT location) FROM crime_scene; -- 77
SELECT COUNT(DISTINCT date) FROM crime_scene; -- 99

Je m’empresse de noter tout cela dans mon calepin ainsi :

400 suspects (id): 218 names, 19 scars, 13 attires
200 interviews (suspect_id):
100 crime_scene (id): 3 types, 77 locations, 99 dates

C’est que j’aime me faire une petite idĂ©e que de juste balancer toutes les lignes sur ma sortie.
À partir d’ici, ça va un peu « divugalcher ».

3/13/400

L’exploration en nombre me permet d’examiner les (treize) tenues et les (dix-neuf) marques/cicatrices des suspects.

SELECT DISTINCT attire FROM suspects LIMIT 6;
SELECT DISTINCT scar FROM suspects LIMIT 6;

Et là, je relis les objectifs indiqués dans le « Case Brief »

  1. Identify the suspect whose profile matches the witness description.

Puis la description de l’affaire juste au dessus

A witness reported that a man in a trench coat was seen fleeing the scene.

L’examen de la table m’indique que ce sera trùs simple en fait.

SELECT id, name FROM suspects WHERE attire LIKE 'trench coat';

Lequel de ces mousquetaires a fait le coup ? Balafre à gauche ou à droite ?

1/77/100

C’est encourageant et je retourne voir la liste des objectifs

  1. Retrieve the correct crime scene details to gather the key clue.


et le rĂ©sumĂ© de l’affaire au dessus

Set in the gritty 1980s, a valuable briefcase has disappeared from the Blue Note Lounge.

L’exploration en nombre me permet d’examiner les (trois) types de dĂ©lits, ainsi que quelques lieux (septante sept) lieux et (nonante neuf) dates.

SELECT DISTINCT type FROM crime_scene;
SELECT DISTINCT date FROM crime_scene LIMIT 6;
SELECT DISTINCT location FROM crime_scene LIMIT 6;

Petite digression avant de poursuivre : les noms date et type sont des mots Ă  Ă©viter (ou Ă  utiliser comme prĂ©fixe ou suffixe), mais bon pas des mots rĂ©servĂ©s sinon la table n’aurait pas pu se crĂ©er simplement. Ici, les requĂȘtes sont passĂ©es sans souci, sinon il aurait fallu protĂ©ger ces noms en Ă©crivant (toutes ces formes sont reconnues, suis perplexe
)

  • "date" et "type" (c’est la forme du standard ANSI SQL et dans MySQL/MariaDB quand SQL_MODE=ANSI_QUOTES est actif)
  • `date` et `type` (c’est la variante spĂ©cifique de MySQL/MariaDB probablement reconnue ailleurs
)
  • [date] et [type] (c’est la forme T-SQL reconnue aussi par MariaDB avec SQL_MODE=MSSQL
)

Revenons Ă  notre vol. L’aperçu des donnĂ©es indique que l’on peut Ă©crire simplement :

SELECT *
FROM crime_scene
WHERE type LIKE 'theft' AND location LIKE '%Lounge%';
-- WHERE type location LIKE '%Blue%';

La question demeure : lequel de ces loustics a fait le coup ?

1/2/200

Retour sur le dossier pour consulter l’objectif suivant

  1. Verify the suspect using their interview transcript.

On va donc juste lire les interrogatoires de nos trois gredins.

SELECT * FROM interviews WHERE suspect_id IN (3,183,237);

Il y a un qui n’a pas Ă©tĂ© interrogĂ© (en fait pas qu’un seul puisqu’il y moins de retranscriptions d’interrogatoires que de suspects) et un autre pour lequel la transcription est indĂ©terminĂ©e/inconnue/inexistante (reste Ă  savoir le sens exact de ce NULL.) Quand Ă  notre coupable, il est passĂ© aux aveux.
Il n’y en avait que trois Ă  examiner donc il m’était facile de recopier les clĂ©s prĂ©cĂ©demment rĂ©cupĂ©rĂ©es, sinon il aurait suffit de lui demander de rĂ©cupĂ©rer la liste en lui passant la sous-requĂȘte

SELECT * FROM interviews
WHERE suspect_id IN (
  SELECT id FROM suspects
  WHERE attire = 'trench coat'
);


ou mieux encore, rĂ©cupĂ©rer directement le(s) nom(s)
 soit en imbriquant la prĂ©cĂ©dente requĂȘte dans une autre, soit en joignant les deux tables :

SELECT s.name, i.transcript
FROM interviews i
  INNER JOIN suspects s ON i.suspect_id = s.id
WHERE s.attire = 'trench coat' ;

Les autres variations sont laissĂ©es Ă  titre d’exercice. 😌

cri tic

L’art est difficile et la critique facile, mais je vais faire la remarque que cette base de donnĂ©es doit ĂȘtre peu utile dans la vraie vie oĂč on n’a pas juste des transcriptions associĂ©es Ă  un nom. Il faut que les interrogatoires soient :

  • DatĂ©es bien que pas utile ici. Ceci est utile classer les entretiens lorsqu’une personne est interrogĂ©e plusieurs fois. C’est Ă©galement utile pour retrouver la chronologie des entrevues dans certaines affaires.
  • Une transcription doit certes ĂȘtre associĂ©e Ă  une personne (je pars du principe qu’il n’y a pas de truc en groupe) mais aussi Ă  une affaire (certaines personnes se retrouvent dans plusieurs affaires, sinon j’aurais mis cette rĂ©fĂ©rence au niveau de la table des suspects et tĂ©moins.)
  • Et probablement d’autres (on n’a par exemple pas Ă©voquĂ© les enquĂȘteurs et enquĂȘtrices sinon il faudrait indiquer la paire qui a interrogĂ© ici.)

J’ai passĂ© un bon moment sur ce premier cas qui prend bien cinq minutes (si l’on exclu ma dĂ©couverte de l’interface.) Cela m’a donnĂ© envie de faire le second


2nd cas

Comme j’ai pris le pli, je vais regarder le schĂ©ma (la liste des tables donc) : on retrouve les mĂȘmes, sauf que les suspects ont maintenant un bandana et un accessoire, et il y a en plus une table de tĂ©moignages ou piĂšces Ă  convictions

Je vais aussi suivre les Ă©tapes indiquĂ©es en objectifs. C’est parti.

affaire

On nous indique :

  1. Retrieve the crime scene report for the record theft using the known date and location.

Sachant que :

In the neon glow of 1980s Los Angeles, the West Hollywood Records store was rocked by a daring theft. A prized vinyl record, worth over $10,000, vanished during a busy evening, leaving the store owner desperate for answers.

On va explorer la premiùre table comme de coutume


SELECT COUNT(*) FROM crime_scene; -- 100
SELECT COUNT(DISTINCT type) FROM crime_scene; -- 3
SELECT COUNT(DISTINCT date) FROM crime_scene; -- 99
SELECT COUNT(DISTINCT location) FROM crime_scene; -- 77
SELECT DISTINCT type FROM crime_scene; -- theft, murder, bribery
SELECT DISTINCT date FROM crime_scene LIMIT 2; -- 20120909, 19920415
SELECT DISTINCT location FROM crime_scene LIMIT 4;
-- 'City Street', 'Downtown Bank', 'Corner Café', 'Metro Station'


et en déduire une tactique de recherche

SELECT COUNT(*) FROM crime_scene
WHERE date LIKE '198%' AND type = 'theft'; -- 2

SELECT id, date, location, description FROM crime_scene
WHERE date LIKE '198%' AND type = 'theft';

SELECT COUNT(*) FROM crime_scene
WHERE location LIKE 'West%'; -- 1

On note l’identifiant (champ id) et/ou la requĂȘte affinĂ©e Ă  rĂ©utiliser par la suite.

indices

On nous indique :

  1. Retrieve witness records linked to that crime scene to obtain their clues.

Avec le résultat précédent, on va simplement récupérer les informations


SELECT clue FROM witnesses
WHERE crime_scene_id = (SELECT id
  FROM crime_scene WHERE location LIKE 'West%');

La chose peut se faire via une sous-requĂȘte (ci-avant) ou une jointe (ci-aprĂšs)

SELECT w.clue
FROM witnesses w
  INNER JOIN crime_scene c ON w.crime_scene_id = c.id
WHERE c.location LIKE 'West%' ;

La premiĂšre indique la couleur du bandana.
La deuxiùme indique l’accessoire vestimentaire remarquable.
Je note les deux lignes dans le calepin : je pense pas pouvoir les rĂ©cupĂ©rer simplement par une sous-requĂȘte.

suspects

On nous indique :

3.Use the clues from the witnesses to find the suspect in the suspects table.

J’explore aussi cette troisiĂšme table pour dĂ©cider de comment organiser la recherche.

SELECT COUNT(*) FROM suspects; -- 100
SELECT COUNT(DISTINCT bandana_color) FROM suspects; -- 11
SELECT COUNT(DISTINCT accessory) FROM suspects; -- 41
SELECT DISTINCT bandana_color FROM suspects LIMIT 3; -- green, purple, yellow
SELECT DISTINCT accessory FROM suspects LIMIT 2; -- 'silver chain', 'gold necklace'

OK, je sais donc comment extraire les lignes recherchée en utilisant les informations précédemment recueillies.

SELECT id, name FROM suspects
WHERE bandana_color = 'red' AND accessory = 'gold watch';

VoilĂ  donc le(s trois du) podium, dont on prend note si on ne pense pas pouvoir combiner la requĂȘte plus tard.

coupable

On nous indique :

  1. Retrieve the suspect's interview transcript to confirm the confession.

Comme précédemment, il suffit de coller les identifiants récupérés plus tÎt

Ou d’utiliser une requĂȘte imbriquĂ©e dans le filtrage
 et rĂ©cupĂ©rer le nom correspond notĂ© plus tĂŽt.

SELECT suspect_id, transcript FROM interviews
WHERE suspect_id IN (SELECT id FROM suspects
  WHERE bandana_color = 'red' AND accessory = 'gold watch');

Ou de faire une jointure pour lire directement le nom


SELECT s.name, i.transcript FROM interviews i
  INNER JOIN suspects s ON i.suspect_id = s.id
WHERE bandana_color = 'red' AND accessory = 'gold watch';

Dans tous les cas, la confession donne le clĂ©. Encore une affaire rondement menĂ©e dĂ©tective Escuelle. đŸ•”ïžâ€â™€ïž

re marque

Bon, cette fois ci la table des tĂ©moignages, contrairement Ă  celle des interrogatoires, a bien Ă©tĂ© liĂ©e Ă  celle des affaires. Par contre, inversement, on n’a pas les noms des tĂ©moins. Et pareillement, il manque la date du recueil et le policier ou la policiĂšre qui pris le tĂ©moignage.

Pendant qu’on y est, il aurait aussi, dans la vraie vie, une table des victimes d’une affaire (donc reliĂ©e Ă  la table des dossiers
) Personne n’a dit cependant qu’il s’agissait de modĂ©liser la vie rĂ©elle, et le jeu ici fourni juste le nĂ©cessaire pour rĂ©soudre les cas soumis.

3Ăšme cas

Je ne l’ai pas signalĂ©, mais je me suis crĂ©Ă© un compte pour recevoir les points. Je ne sais pas si c’est liĂ© (aprĂšs tout, s’il y a du stockage local ça pourrait le faire aussi) mais j’ai les deux cas intermĂ©diaires suivants qui sont dĂ©bloquĂ©s.

crime scene

Tiens, dans le « Case Brief » il n’y a plus qu’un seul objectif (bien que le titre soit au pluriel)

  1. Find the murderer. ( Start by finding the crime scene and go from there )

En fait les deux premiers cas nous ont pris par la main et maintenant nous allons rouler sans les petites roues supplĂ©mentaires sur notre vĂ©lo. Bien, ouvrons le dossier : la table, dans l’onglet « Schema », a toujours les mĂȘmes colonnes
 Donc en partant de cet extrait de la prĂ©sentation dans l’onglet de dĂ©briefing

[
] near the docks of Coral Bay Marina in the early hours of August 14, 1986.

Je tente directement

SELECT * FROM crime_scene WHERE date = '19860814'; -- 3 lignes
SELECT * FROM crime_scene WHERE location LIKE 'Coral%'; -- 5 lignes

SELECT * FROM crime_scene WHERE date = 19860814 AND location LIKE 'Coral%';
SELECT * FROM crime_scene WHERE location = 'Coral Bay Marina';

Je prends note de la seconde phrase puis m’oriente sur la table des personnes.

persons of interest

On va juste traduire les notes prises plus tĂŽt en requĂȘtes pour trouver les suspects


--- one who lives on 300ish "Ocean Drive"
SELECT * FROM person WHERE address LIKE '%Ocean%Drive%'; -- 5 lignes
SELECT * FROM person WHERE address LIKE '3__ Ocean Drive'; -- 1 ligne

-- another whose first name ends with "ul" and his last name ends with "ez"
SELECT * FROM person WHERE name LIKE '%ul %ez' -- 1 ligne

On suppose que ces personnes ont été entendues et on va donc rechercher les comptes rendus de ces entretiens


SELECT * FROM interviews WHERE person_id IN (
  SELECT id FROM person WHERE
    (address LIKE '3__ Ocean Drive')
    OR
    (name LIKE '%ul %ez')
);

Il aurait fallu passer par une jointure si l’on voulait afficher aussi les noms au/en lieu/plus des « id » mais je n’en ai pas le besoin pour le moment.

Les deux tĂ©moignages nous orientent vers des hĂŽtels. On va donc interroger les fiches quotidiennes obligatoires de l’hĂŽtellerie cette fois « pour la manifestation de la vĂ©ritĂ© judiciaire » et non Ă  fins marketing
 Par chance pour nous, tout est numĂ©risĂ© et tout le monde dans la chaĂźne a travaillĂ© proprement
 🙃

-- I saw someone check into a hotel on August 13.
SELECT COUNT(*) FROM hotel_checkins WHERE check_in_date = 19860813; -- 87

-- I heard someone checked into a hotel with "Sunset" in the name.
SELECT COUNT(*) FROM hotel_checkins WHERE hotel_name LIKE '%Sunset%'; -- 50

-- both
SELECT COUNT(*) FROM hotel_checkins
WHERE (check_in_date = 19860813) AND (hotel_name LIKE '%Sunset%') ; -- 50

-- The guy looked nervous.
SELECT COUNT(*) FROM surveillance_records
WHERE person_id IN (
  SELECT person_id FROM hotel_checkins
  WHERE (check_in_date = 19860813) AND (hotel_name LIKE '%Sunset%')
) AND (suspicious_activity IS NOT NULL); -- 30

J’ai affichĂ© les trente et y a pas grand chose de folichon


the final


mais on peut retenir par exemple

6   6   34  Spotted entering late at night
7   7   89  Seen arguing with an unknown person
8   8   2   Left suddenly at 3 AM
15  15  44  Asked for directions to beach

De là, on peut se focaliser sur ces alibis ou confessions (là, c’est un peu cousu du fil blanc mais bon)

SELECT COUNT(*) FROM confessions; -- 100
SELECT * FROM confessions WHERE person_id IN (6,7,8,15); -- 4

Tiens, l’introduction nous a averti que

This case might require the use of JOINs, wildcard searches, and logical deduction.

Alors, pour la route, voici quelques idées de jointures.

SELECT COUNT(*) FROM surveillance_records s
  INNER JOIN hotel_checkins h ON h.person_id = s.person_id
                              AND h.id = s.hotel_checkin_id
WHERE
  (h.check_in_date = 19860813)
  AND
  (h.hotel_name LIKE '%Sunset%')
  AND
  (s.suspicious_activity IS NOT NULL); -- 0

SELECT COUNT(*) FROM surveillance_records s
  INNER JOIN hotel_checkins h ON h.person_id = s.person_id
WHERE
  (h.check_in_date = 19860813)
  AND
  (h.hotel_name LIKE '%Sunset%')
  AND
  (s.suspicious_activity IS NOT NULL); -- 30

SELECT COUNT(*) FROM surveillance_records s
  INNER JOIN hotel_checkins h ON h.person_id = s.person_id
  INNER JOIN interviews i ON i.person_id = s.person_id
WHERE
  (h.check_in_date = 19860813)
  AND
  (h.hotel_name LIKE '%Sunset%')
  AND
  (s.suspicious_activity IS NOT NULL); -- 50

SELECT COUNT(*) FROM surveillance_records s
  INNER JOIN hotel_checkins h ON h.person_id = s.person_id
  INNER JOIN interviews i ON i.person_id = s.person_id
WHERE
  (h.check_in_date = 19860813)
  AND
  (h.hotel_name LIKE '%Sunset%')
  AND
  (s.suspicious_activity IS NOT NULL)
  AND
  (i.transcript IS NOT NULL); -- 27

SELECT COUNT(*) FROM surveillance_records s
  INNER JOIN hotel_checkins h ON h.person_id = s.person_id
  INNER JOIN confessions i ON c.person_id = s.person_id
WHERE
  (h.check_in_date = 19860813)
  AND
  (h.hotel_name LIKE '%Sunset%')
  AND
  (s.suspicious_activity IS NOT NULL)
  AND
  (c.confession IS NOT NULL); -- 30

Sur ce, nous remercions detective Esucelle. đŸ•”đŸ»â€â™‚ïž

6Ăšme cas

Ici aussi, on a assez rodage pour ne pas ĂȘtre guidĂ©s ; on a juste pour instruction :

  1. Find who stole the diamond.

VoilĂ  le dĂ©cor plantĂ©, c’est parti.

find case

La phrase d’introduction est lacunaire, mais c’est la seule piste de dĂ©part

SELECT COUNT(DISTINCT location) FROM crime_scene; -- 99
SELECT DISTINCT location FROM crime_scene LIMIT 10;
SELECT COUNT(*) FROM crime_scene WHERE location LIKE '%Miami%'; -- 19
SELECT COUNT(*) FROM crime_scene WHERE location LIKE '%Fontaineb%'; -- 1
SELECT COUNT(*) FROM crime_scene WHERE location LIKE '%Hotel%'; -- 1
SELECT * FROM crime_scene WHERE location LIKE '%Fontaineb%'; -- Fontainebleau Hotel

On aurait pu passer aussi par la description : aprùs coup, je peux constater qu’il y a le nom du fameux diamant.

find clues

La description indique qu’il n’y a que deux tĂ©moignages valables et donne des pistes pour les atteindre

SELECT COUNT(DISTINCT occupation) FROM guest; -- 93
SELECT DISTINCT occupation FROM guest LIMIT 10;

-- One of them is a really famous actor.
SELECT COUNT(*) FROM guest WHERE occupation LIKE 'actor'; -- 5
SELECT id, name, invitation_code FROM guest WHERE occupation LIKE 'actor';

-- The other one is a woman who works as a consultant for a big company
-- and her first name ends with "an".
SELECT COUNT(*) FROM guest WHERE occupation LIKE 'consultant'; -- 8
SELECT id, name, invitation_code FROM guest
WHERE occupation LIKE 'consultant' AND name LIKE '%an %';

En effet, l’acteur est bien connu des dinosaures dont je fais partie.

Bien, suivons le fil d’Ariane et voyons ce que nous ont confiĂ© ces deux tĂ©moins.

SELECT * FROM witness_statements WHERE guest_id IN (
  SELECT id FROM guest WHERE
    (occupation='Actor' AND name LIKE 'c% e%') OR
    (occupation='Consultant' AND name LIKE '%an %')
);

Y a plus qu’à passer les personnes prĂ©sentes en revue


find suspects

C’est marrant qu’il y ait un registre des attirails, mais cela va ĂȘtre bien utile ici.

SELECT note FROM attire_registry LIMIT 10;

SELECT g.*, a.note
FROM guest g
  INNER JOIN attire_registry a ON g.id = a.guest_id
WHERE
  -- I saw someone holding an invitation ending with "-R".
  g.invitation_code LIKE '%-R'
  AND
  -- He was wearing a navy suit and a white tie.
  a.note LIKE 'navy suit, white tie'
;

Les registres de plaisance maritime vont ĂȘtre mis Ă  profit aussi.

SELECT dock_number FROM marina_rentals LIMIT 3;

SELECT g.*, m.boat_name, m.rental_date
FROM guest g
  INNER JOIN marina_rentals m ON g.id = m.renter_guest_id
WHERE
  -- I overheard someone say, "Meet me at the marina, dock 3."
  m.dock_number = 3
;

Ici, il y a du monde
 (remplacer g.*, m.boat_name, m.rental_date par COUNT(*) pour afficher 20) 
mais deux dates quand on y prĂȘte attention (du coup, on peut remplacer le COUNT(*) par COUNT(*), m.rental_date, et ajouter un GROUP BY m.rental_date Ă  la fin
) donc seulement la moitiĂ© utile quand on se restreint au jour des faits


find culprit

Pour finir, il faut lire les aveux de notre liste réduite


SELECT COUNT(*) FROM guest; -- 200
SELECT COUNT(*) FROM final_interviews; -- 199

SELECT g.*, i.confession
FROM guest g
  INNER JOIN final_interviews i ON g.id = i.guest_id
WHERE
  (g.invitation_code LIKE '%-R'
    AND
    g.id = (SELECT guest_id FROM attire_registry
      WHERE note = 'navy suit, white tie')
  ) OR
  guest_id IN (
    SELECT renter_guest_id FROM marina_rentals
      WHERE dock_number = 3
      AND
      rental_date = (SELECT date FROM crime_scene
        WHERE location = 'Fontainebleau Hotel')
  );

La requĂȘte est un peu 
(mettre le mot qui vient Ă  l’esprit)
 mais illustre l’idĂ©e d’assemblage de puzzle
 😉 Ce n’est pas optimum quand on a beaucoup de donnĂ©es (des milliers de lignes
) mais ça c’est laissĂ© Ă  titre d’exercice au lectorat.

En fait dans les jointures de la sous-section prĂ©cĂ©dente, on pouvait interpoler (ou alors ne sĂ©lectionner que g.* pour les deux et appliquer INTERSECT
), la table des aveux servant alors juste Ă  valider. 😉

En tout cas, bon boulot dĂ©tective Escuelle ! đŸ•”đŸŸ

Ă  suivre

Je fatigue un peu et par consĂ©quence vais arrĂȘter ma rĂ©daction ici. Je pourrai faire une seconde partie s’il y a de la demande. Mais le plus important pour moi est de partager vos trouvailles, astuces, conseils, remarques, etc.

Pour le site, je trouve que c’est bien fait (et je donne un B+ comme notation) et espĂšre que de nouveaux cas vont s’ajouter rapidement. Une des amĂ©liorations que je demanderai serait d’amĂ©liorer la coloration syntaxique (attention qu’il ne s’agit pas de rajouter plus de couleurs mais de ne pas avoir les bizarreries comme avec le champ crime_scene.location.) Et toi, quelles amĂ©liorations verrais tu ?

  • # Cool deux Ă©nigmes de plus, j'y retourne

    Posté par  (site web personnel) . Évalué à 5 (+2/-0). DerniĂšre modification le 13 avril 2025 Ă  21:25.

    Bien, on a les tables prĂ©sentes et donc une base de dĂ©part. Sans liste des colonnes et graphe des relations ça va cependant ĂȘtre coton. Je me lance aussi :

    (la premiÚre fois, j'ai essayé plein de commandes pour avoir les tables et le schéma aussi
 avant de voir qu'ils sont donnés
 et aprÚs on dira aux enfants de lire les énoncés avant de commencer
)

    On a la liste des tables présentes, et si on clique sur une table on a son schéma :

    Exemple:

    witness_statements
    
    Column   Type    Key
    id       INTEGER ⚿ 
    guest_id INTEGER ⛓
    clue     TEXT   
    

    (donc 'id' est la clé primaire, et guest_id une clé étrangÚre vers le champ 'id' de la table 'guest')

    Sinon l'exploration des tables avec LIMIT 1 suffit aussi Ă  comprendre ce qu'elles contiennent.

    • [^] # Non, quatre de plus, dont deux indiquĂ©s ici.

      Posté par  (site web personnel, Mastodon) . Évalué à 2 (+0/-0).

      Oui, je l’ai vu aussi :) « C’est seulement aprĂšs avoir rĂ©solu le cas que j’ai remarquĂ© dans l’onglet “ Schema ” que la structure (nom et type des colonnes, ainsi que si c’est une clĂ© primaire/Ă©trangĂšre) s’affiche en cliquant sur le nom de la table
 puis se masque si on refait un clic sur le nom. »
      (ça va plus loin que lire l’énoncĂ©, il faut savoir le cliquer
)

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

  • # XP: 1900

    Posté par  . Évalué à 2 (+0/-0).

    J'ai beaucoup aimé, j'attends la suite avec impatience.

    J'ai pas résisté pour une des énigmes qui propose une table "confession". J'ai passé ça à un LLM: "dis moi qui a avoué le crime", il l'a trouvé direct.

    Pour le reste, beaucoup de jointures et un peu de scrutation oculaire.

    • [^] # cheater arrĂȘte de cheater :D

      Posté par  (site web personnel, Mastodon) . Évalué à 2 (+0/-0).

      Intéressant


      J'ai pas résisté pour une des énigmes qui propose une table "confession". J'ai passé ça à un LLM: "dis moi qui a avoué le crime", il l'a trouvé direct.

      Tu lui as recopiĂ© le rĂ©sultat du SELECT * FROM confession ou tu l’as branchĂ© sur la page ?

      Et les autres moules, avez-vous trouvé des moyens de tricher ? Pour quels énigmes et comment ?

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent Ă  celles et ceux qui les ont postĂ©s. Nous n’en sommes pas responsables.