Fonctions SQL

Sur les différents SGBD, il y a une multitude de fonctions :

« Enseigner, ce n’est pas remplir un vase, c’est allumer un feu » Une phrase qui ne date pas d’hier puisqu’elle est de Montaigne …

L’objectif n’est pas de les voir toutes mais d’en utiliser certaines (si possible les plus pertinentes), pour voir les différences entre SGBD et en retenir quelques une.

fonction sur les dates

SELECT CURDATE();     --  fonction sur MYSQL (juste la date)
SELECT GETDATE();     -- fonction transactSQL de SQLSERVER
SELECT SYSDATE();     -- fonction sur ORACLE

SELECT CURRENT_TIMESTAMP;   -- norme SQL  : fonction "système" pas de  paramètres 
-- (fonctionne sur MYSQL et PostGreSQL , mais pas sur ORACLE et SQLSERVER)

SELECT NOW();    --  fonction sur MYSQL (date+heure)
  1. Quelles sont les fonctions pour obtenir la date d’aujourd’hui sur les principaux SGBDR ?

  2. Indiquer si ces fonctions tiennent compte du fuseau horaire et le format de la date retournée.

DROP TABLE IF EXISTS TD_telephone;
CREATE TABLE TD_telephone 
(
idTelephone       INTEGER AUTO_INCREMENT  
, type_id         CHAR(2)
, marque_id       INTEGER 
, date_achat   DATE
, prix         DECIMAL (9,2)       /* NUMBER(9,2) sur ORACLE */
, proprietaire_id INTEGER
, couleur       VARCHAR(25)
, PRIMARY KEY(idTelephone)
);

INSERT INTO TD_telephone (idTelephone,type_id,marque_id,date_achat,prix,proprietaire_id,couleur) VALUES (1,'SP' ,1,STR_TO_DATE('15/01/2020', '%d/%m/%Y'),139.99,190120,'ROUGE');
-- il est déconseillé de mettre une valeur à une clé primaire => pour vérification
INSERT INTO TD_telephone (idTelephone,type_id,marque_id,date_achat,prix,proprietaire_id) VALUES (NULL,'SP' ,2,STR_TO_DATE('14/03/2020', '%d/%m/%Y'), 99.99,190215);
INSERT INTO TD_telephone
(idTelephone,type_id,marque_id,date_achat,prix,proprietaire_id,couleur) VALUES
(NULL,'CL' ,3,STR_TO_DATE('02/05/2020', '%d/%m/%Y'), 49.11,190001,'NOIR');
INSERT INTO TD_telephone
(idTelephone,type_id,marque_id,date_achat,prix,proprietaire_id,couleur) VALUES
(NULL,'CO' ,4,STR_TO_DATE('25/07/2020', '%d/%m/%Y'), 89.14,190222,'BLANC');
INSERT INTO TD_telephone
(idTelephone,type_id,marque_id,date_achat,prix,proprietaire_id)         VALUES
(NULL,'IP' ,5,STR_TO_DATE('30/09/2020', '%d/%m/%Y'),359.49,190561);
INSERT INTO TD_telephone
(idTelephone,type_id,marque_id,date_achat,prix,proprietaire_id,couleur) VALUES
(NULL,'CO' ,5,STR_TO_DATE('01/01/2021', '%d/%m/%Y'), 99.51,122120,'BLANC'),
(NULL,'SP' ,1,'2013-01-15',189,190622,'ROUGE'),
(NULL,NULL ,NULL,'2013-01-15',20,190623,'ROUGE'),
(NULL,NULL ,1,'2013-01-15',NULL,NULL,NULL);

exemple :

-- MYSQL
SELECT date_achat, 
    YEAR(date_achat) as Annee,
    DATEDIFF(date_achat, CURDATE()) as "Difference" ,
    DATE_ADD(date_achat, INTERVAL 30 DAY) as Plus30j ,
    DATE_SUB(date_achat, INTERVAL 30 DAY) as Moins30j ,
    DATE_ADD(date_achat, INTERVAL -30 DAY) as Moins30j2    
FROM TD_telephone;

-- SQLSERVER
SELECT *, 
    YEAR(date_achat) as Annee,
    DATEDIFF(DAY, date_achat, DateDemandee) as [Difference],
    DATEADD(DAY, 30, date_achat) as Plus30j,
    DATEADD(DAY, -30, date_achat) as Moins30j
FROM TD_telephone;

-- ORACLE
SELECT *, 
    EXTRACT(YEAR FROM date_achat) as Annee,
    DateDemandee - date_achat as "Difference",
    date_achat + 30 as Plus30j,
    date_achat - 30 as Moins30j
FROM TD_telephone;

-- ADD_MONTHS(date_achat, 2)

exemple de configuration de la “time_zone”

documentation datetime timestamp - limite

DROP TABLE IF EXISTS posts;
CREATE TABLE posts (
id_post  INT  AUTO_INCREMENT
, title VARCHAR(255) NOT NULL
, content LONGTEXT
, online VARCHAR(255) DEFAULT 0
, dt_created_at DATETIME  NOT NULL DEFAULT CURRENT_TIMESTAMP
, dt_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
, ts_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
, ts_updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
, PRIMARY KEY (id_post)
);

Dans la documentation, beaucoup de synonymes :
https://dev.mysql.com/doc/refman/8.0/en/timestamp-initialization.html

INSERT INTO posts(title, online) VALUES('titre post1', 0);
INSERT INTO posts(title, online) VALUES('titre post2', 1);
INSERT INTO posts SET title='titre post3' , online=2;
INSERT INTO posts VALUES
(NULL, 'titre post4', NULL, 0, '2023-01-01 12:00:00', '2023-01-01 12:00:00', '2023-01-01 12:00:00', '2023-01-01 12:00:00');
SELECT * FROM posts;
UPDATE posts SET title='titre post1 new' WHERE id_post=1;
SELECT * FROM posts;

ATTENTION à la configuration (localisation) du serveur

SELECT @@global.time_zone, @@session.time_zone;

SET time_zone = '+03:00';

SELECT @@global.time_zone, @@session.time_zone;

INSERT INTO posts(title, online) VALUES('titre post5', 0);
INSERT INTO posts(title, online) VALUES('titre post6', 1);
SELECT * FROM posts;

timestamp nombre de seconde depuis 1970

différence timezone timestamp(=> sauvegarde au format UTC)


date au format UTC (temps universel) : UTC_TIMESTAMP() n’est pas affecté par les fuseaux horaires locaux, ce qui le rend utile pour les scénarios dans lesquels une mesure cohérente du temps est requise sur différents emplacements.

SELECT UTC_TIMESTAMP,UTC_TIMESTAMP();
SELECT UTC_TIMESTAMP()+0;

Pour connaitre la configuation du serveur

SELECT @@global.time_zone, @@session.time_zone;

SELECT TIMEDIFF(NOW(), CONVERT_TZ(NOW(),  @@session.time_zone, '+00:00'));

Documentation UTC :

https://fr.wikipedia.org/wiki/Temps_universel_coordonn%C3%A9 : calcul du temps UTC

sur pythonanywhere (AWS en angleterre)
INSERT INTO posts VALUES(NULL, 'titre post7', NULL, 0, NOW(), NOW(), NOW(), NOW() );
INSERT INTO posts VALUES(NULL, 'titre post8', NULL,1, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP() , CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP());
INSERT INTO posts VALUES(NULL, 'titre post9', NULL, 0, '2023-03-29 12:00:00', '2023-03-29 12:00:00', '2023-03-29 12:00:00', '2023-03-29 12:00:00');

SELECT * FROM posts;


Il existe beaucoup de fonctions : exemple de fonctions

fonction sur les chaînes de caractères

Exemple : Avec une table et un champ composé du nom et du prénom (pas d’espace dans le nom) récupérer le nom et le prénom des personnes

-- MYSQL
SELECT libelle,
    INSTR(libelle, ' '),
    LEFT(libelle, INSTR(libelle, ' ')- 1),
    SUBSTRING(libelle, INSTR(libelle, ' ')+ 1, 100),
    LTRIM(SUBSTRING(libelle, INSTR(libelle, ' '), 100))
FROM TD_marque_tel;

-- SQL SERVER
SELECT libelle,
    CHARINDEX(' ', libelle),
    LEFT(libelle, CHARINDEX(' ', libelle) - 1),
    SUBSTRING(libelle, CHARINDEX(' ', libelle) + 1, 100),
    LTRIM(SUBSTRING(libelle, CHARINDEX(' ', libelle), 100))
FROM TD_marque_tel;

-- ORACLE
SELECT libelle,
    INSTR(libelle, ' '),
    SUBSTR(libelle, 1, INSTR(libelle, ' ')- 1),
    SUBSTR(libelle, INSTR(libelle, ' ')+ 1, 100),
    TRIM(SUBSTR(libelle, INSTR(libelle, ' '), 100))
FROM TD_marque_tel;

fonctions sur les numériques

-- pour SQL Server et MySQL
SELECT 
    idTelephone
    , idTelephone / 2   -- attention au trans-typage CAST
    , prix
    , COALESCE(prix, 0) * 2
    , CEILING(prix)          -- CEIL sur ORACLE
    , FLOOR(prix)
    , ROUND(prix, 1)
FROM TD_telephone;

SELECT avancé

efficacité de la clause WHERE

IN et BETWEEN

IF

SELECT tel.prix AS PRIX
       , IF(tel.prix <99 ,"premier Prix", IF(tel.prix<259,"prix medium","prix élevé"))
FROM TD_telephone AS tel
WHERE tel.prix IS NOT NULL;

CASE

SELECT tel.date_achat AS ACHAT
       , tel.type_id AS type
       , tel.marque_id AS MARQUE
       , marq.libelle AS LIBELLE
       , CASE tel.marque_id
            WHEN 1 THEN 'MEILLEURS VENTES'
            WHEN 5 THEN 'Très bonnes ventes'
            ELSE 'AUTRES ventes'
        END AS type_de_vente
FROM TD_telephone tel        
INNER JOIN  TD_marque_tel AS marq
  ON tel.marque_id = marq.idMarque ;
SELECT tel.DATE_ACHAT AS dateAchat
       , type.libelle AS type
       , marq.libelle AS marque
       , tel.PRIX AS prix
       , CASE
            WHEN tel.PRIX <= 99 THEN 'premier Prix'
            WHEN tel.PRIX >= 300 THEN 'Prix élevé'
            WHEN tel.PRIX > 49 AND tel.PRIX < 300 THEN 'PRIX MOYEN'
        END AS type_PRIX  
FROM TD_telephone AS tel
INNER JOIN TD_type_tel AS type
  ON tel.type_id   = type.idType
INNER JOIN  TD_marque_tel AS marq
  ON tel.marque_id = marq.idMarque;

EXERCICE

exo 1 démonstration : données géolocalisées

– vente d’article (style le bon-coin)

DROP TABLE IF EXISTS TD_offre;
CREATE TABLE TD_offre (
id_offre  INT  AUTO_INCREMENT
, article_offre VARCHAR(255)
, ville VARCHAR(255)
, location POINT
, PRIMARY KEY (id_offre)
);
INSERT INTO TD_offre VALUES (null, 'article1', 'Belfort, Fr', ST_GEOMFROMTEXT('POINT(6.862100 47.638770)'));
INSERT INTO TD_offre VALUES (null, 'article2', 'Besancon, Fr', PointFromText('POINT(6.025530 47.241268)'));
INSERT INTO TD_offre VALUES (null, 'article3', 'Poitiers', ST_GeomFromText('POINT(0.340196 46.580260)'));
INSERT INTO TD_offre VALUES (null, 'article4', 'Montpellier', ST_GeomFromText('POINT(3.876734 43.611242)'));

SELECT *
FROM TD_offre;

doc mariadb - doc mysql - doc postgre
ST_DISTANCE_SPHERE(g1,g2,[r])
par défaut le radius est celui de la terre

SELECT ST_DISTANCE_SPHERE( ST_GEOMFROMTEXT('POINT(6.862100 47.638770)'),TD_offre.location )
, CONCAT('distance entre BELFORT et ',TD_offre.ville)
FROM TD_offre;

SELECT
ROUND( ST_DISTANCE_SPHERE( ST_GEOMFROMTEXT('POINT(6.862100 47.638770)'),TD_offre.location )/1000)
, CONCAT('distance entre BELFORT et ',TD_offre.ville)
FROM TD_offre;
SET @distance_max=100;
SET @ma_localisation=ST_GEOMFROMTEXT('POINT(6.862100 47.638770)');

SELECT
ROUND( ST_DISTANCE_SPHERE( @ma_localisation,TD_offre.location )/1000)
, CONCAT('distance entre BELFORT et ',TD_offre.ville)
FROM TD_offre;

SELECT *
FROM TD_offre
WHERE ROUND( ST_DISTANCE_SPHERE( @ma_localisation ,TD_offre.location )/1000) < @distance_max;

possibilité de modéliser un polygon https://www.data.gouv.fr/fr/datasets/contours-des-departements-francais-issus-d-openstreetmap/

exo 2 démonstration : Recherche fulltext

https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html
MATCH (col1,col2,...) AGAINST (expr [search_modifier])

DROP TABLE IF EXISTS TD_article;
CREATE TABLE TD_article (
id_article  INT  AUTO_INCREMENT
, nom VARCHAR(255)
, description_courte TEXT          -- 65,535 characters
, description_longue MEDIUMTEXT    --  16,777,215 characters 
, PRIMARY KEY (id_article)
, FULLTEXT ft_nom_desc_c_desc_long (nom, description_courte, description_longue)
);


INSERT INTO TD_article VALUES (null, 'article1'
, 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. '
, 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.');

INSERT INTO TD_article VALUES (null, 'article2 but'
, 'Lorem ipsum dolor sit amet, but consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore but magna aliqua. but 3'
, 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et but  dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. but Duis aute irure dolor in reprehenderit in but voluptate velit esse
cillum dolore eu fugiat nulla pariatur. but Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum. but 5');

INSERT INTO TD_article VALUES (null, 'article1 info'
, 'Lorem ipsum dolor info sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et info  dolore magna aliqua. info 3'
, 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut info labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi info ut aliquip ex ea commodo
consequat. Duis aute irure dolor info in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. info Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum. info 5');

INSERT INTO TD_article VALUES (null, 'article1 but info'
, 'Lorem ipsum dolor but info sit amet, consectetur adipisicing elit, but info sed do eiusmod
tempor incididunt ut labore but info et dolore magna aliqua. '
, 'Lorem ipsum dolor but info sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut but info labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco but info laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in but info reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. but info Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum. but info 5');
SELECT *
FROM TD_article;


ALTER TABLE TD_article ADD FULLTEXT ft_nom (nom);
ALTER TABLE TD_article ADD FULLTEXT ft_description_courte (description_courte);
ALTER TABLE TD_article ADD FULLTEXT ft_description_longue (description_longue);

SELECT
nom,
MATCH(nom) AGAINST('info')  AS  score_nom,
MATCH(description_courte) AGAINST('info') AS score_description_courte,
MATCH(description_longue) AGAINST('info') AS score_description_longue
FROM TD_article;


SELECT
nom,
MATCH(nom) AGAINST('but info')  AS  score_nom,
MATCH(description_courte) AGAINST('but info') AS score_description_courte,
MATCH(description_longue) AGAINST('but info') AS score_description_longue
FROM TD_article;


SET @recherche_texte='but info';
SELECT
nom,
MATCH(nom) AGAINST(@recherche_texte)  AS  score_nom ,
MATCH(description_courte) AGAINST(@recherche_texte) AS score_description_courte ,
MATCH(description_longue) AGAINST(@recherche_texte) AS score_description_longue , 
MATCH(nom) AGAINST(@recherche_texte) * 1
+ MATCH(description_courte) AGAINST(@recherche_texte) * 0.5
+ MATCH(description_longue) AGAINST(@recherche_texte) * 0.1 AS calcul_score
FROM TD_article
WHERE
MATCH(nom) AGAINST(@recherche_texte) OR
MATCH(description_courte) AGAINST(@recherche_texte) OR
MATCH(description_longue) AGAINST(@recherche_texte)
ORDER BY (score_nom+score_description_courte*0.5+score_description_longue*0.1) DESC;

plusieurs modes

SELECT nom 
FROM TD_article 
WHERE 
MATCH (description_courte)
AGAINST ('+info -but' IN BOOLEAN MODE);

SELECT nom , description_courte
FROM TD_article 
WHERE 
MATCH (description_courte)
AGAINST ('inf*' IN BOOLEAN MODE);
SELECT nom , description_courte
FROM TD_article 
WHERE 
MATCH (description_courte)
AGAINST ('info' WITH QUERY EXPANSION);

expansion aveugle des requêtes : Cela fonctionne en effectuant la recherche deux fois, où la phrase de recherche pour la deuxième recherche est la phrase de recherche d’origine concaténée avec les quelques documents les plus pertinents de la première recherche.

exo 3 :

+-------------+-------------------+------------+--------+------------+-------------------+
| idTelephone | libelle           | libelle    | prix   | date_achat | date_fin_garantie |
+-------------+-------------------+------------+--------+------------+-------------------+
|           7 | société SAMSUNG   | Smartphone | 189.00 | 2013-01-15 | 2014-01-15        |
|           1 | société SAMSUNG   | Smartphone | 139.99 | 2020-01-15 | 2021-01-15        |
|           2 | entreprise SONY   | Smartphone |  99.99 | 2020-03-14 | 2021-03-14        |
|           3 | groupe PHILIPS    | clapet     |  49.11 | 2020-05-02 | 2021-05-02        |
|           4 | marque MOTOROLA   | COULISSANT |  89.14 | 2020-07-25 | 2021-07-25        |
|           5 | SOCIETE APPLE     | IPHONE     | 359.49 | 2020-09-30 | 2021-09-30        |
+-------------+-------------------+------------+--------+------------+-------------------+

exo 4 :

+-------------+-------------------+------------+--------+------------+-------------------+
|           7 | société SAMSUNG   | Smartphone | 189.00 | 2013-01-15 | 2015-01-15        |
|           1 | société SAMSUNG   | Smartphone | 139.99 | 2020-01-15 | 2022-01-15        |
|           2 | entreprise SONY   | Smartphone |  99.99 | 2020-03-14 | 2021-03-14        |
|           3 | groupe PHILIPS    | clapet     |  49.11 | 2020-05-02 | 2021-05-02        |
|           4 | marque MOTOROLA   | COULISSANT |  89.14 | 2020-07-25 | 2021-07-25        |
|           5 | SOCIETE APPLE     | IPHONE     | 359.49 | 2020-09-30 | 2023-09-30        |
+-------------+-------------------+------------+--------+------------+-------------------+

exo 5:

+-----------------+-------------------+------------+--------+------------+------------+---------+
| proprietaire_id | libelle           | libelle    | prix   | date_achat | calcul_tmp | remise  |
+-----------------+-------------------+------------+--------+------------+------------+---------+
|          190120 | société SAMSUNG   | Smartphone | 139.99 | 2020-01-15 |       -709 |  9.7993 |
|          190215 | entreprise SONY   | Smartphone |  99.99 | 2020-03-14 |       -767 |  4.9995 |
|          190001 | groupe PHILIPS    | clapet     |  49.11 | 2020-05-02 |       -816 |  2.4555 |
|          190222 | marque MOTOROLA   | COULISSANT |  89.14 | 2020-07-25 |       -900 |  4.4570 |
|          190561 | SOCIETE APPLE     | IPHONE     | 359.49 | 2020-09-30 |       -967 | 35.9490 |
+-----------------+-------------------+------------+--------+------------+------------+---------+



exo 6:(concaténation, casting)

objectif : mettre les dates au format JJ/MM/AAAA “strict”, faire une recherche sur la fonction LPAD

+-----------------+-------------------+------------+--------+---------------+
| proprietaire_id | libelle           | libelle    | prix   | DATE_ACHAT_FR |
+-----------------+-------------------+------------+--------+---------------+
|          190622 | société SAMSUNG   | Smartphone | 189.00 | 15/01/2013    |
|          190120 | société SAMSUNG   | Smartphone | 139.99 | 15/01/2020    |
|          190215 | entreprise SONY   | Smartphone |  99.99 | 14/03/2020    |
|          190001 | groupe PHILIPS    | clapet     |  49.11 | 02/05/2020    |
|          190222 | marque MOTOROLA   | COULISSANT |  89.14 | 25/07/2020    |
|          190561 | SOCIETE APPLE     | IPHONE     | 359.49 | 30/09/2020    |
+-----------------+-------------------+------------+--------+---------------+

ANNEXES

les variables (voir PL/SQL) https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/user-variables.html

SET @t1=1, @t2=2, @t3:=4;
SELECT @t1, @t2, @t3, @t4 := @t1+@t2+@t3;

https://docs.oracle.com/cd/E11882_01/server.112/e16604/ch_twelve050.htm#SQPUG133
* sur sqlserver

https://docs.microsoft.com/fr-fr/sql/t-sql/language-elements/variables-transact-sql?view=sql-server-ver15

https://docs.postgresql.fr/8.1/plpgsql-declarations.html https://www.postgresqltutorial.com/plpgsql-variables/

critiques mysql