URL: https://linuxfr.org/users/gilcot/journaux/retour-sur-sql-noir Title: retour sur SQL noir đ Authors: Gil Cot â Date: 2025ćčŽ04æ13æ„T12:05:00+02:00 License: CC By-SA Tags: sql, retour_d_expĂ©rience et jeu_libre Score: 22 AprĂšs avoir lu la [rĂ©cente dĂ©pĂȘche de BenoĂźt](https://linuxfr.org/news/reviser-sql-en-jouant-au-detective-sqlnoir), 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 ```sql 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 ? # 1^er cas Pour commencer, je poursuis mon exploration avec quelques requĂȘtes simples : ```sql 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 : ```text 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. ```sql 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 »> 2. 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. ```sql 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. ```sql 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 : ```sql 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> 3. Verify the suspect using their interview transcript. On va donc juste lire les interrogatoires de nos trois gredins. ```sql 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 ```sql 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 : ```sql 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... # 2^nd 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... ```sql 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 ```sql 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 :> 2. 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... ```sql 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) ```sql 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. ```sql 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. ```sql 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 :> 4. 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. ```sql 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... ```sql 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 ```sql 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... ```sql --- 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... ```sql 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](https://www.lhotellerie-restauration.fr/sos-experts/question-reponse/demander-les-papiers-d-identite-aux-clients-quelles-sont-les-obligations-reelles-de-l-hotelier-25338#) cette fois « pour la manifestation de la vĂ©ritĂ© judiciaire » et non [Ă fins marketing](https://www.lhotellerie-restauration.fr/sos-experts/question-reponse/demander-les-papiers-d-identite-aux-clients-quelles-sont-les-obligations-reelles-de-l-hotelier-25338#)... Par chance pour nous, tout est numĂ©risĂ© et tout le monde dans la chaĂźne a travaillĂ© [proprement](https://www.lhotellerie-restauration.fr/sos-experts/question-reponse/demander-les-papiers-d-identite-aux-clients-quelles-sont-les-obligations-reelles-de-l-hotelier-25338#)... đ ```sql -- 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 ```text 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) ```sql 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. ```sql 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 ```sql 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 ```sql 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. ```sql 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. ```sql 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. ```sql 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... ```sql 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](#toc-find-suspects), 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 ?