Salut Habr!
De mai bine de 3 ani predau SQL în diverse centre de formare, iar una dintre observațiile mele este că studenții stăpânesc și înțeleg mai bine SQL dacă li se dă o sarcină, și nu vorbesc doar despre posibilități și fundamente teoretice.
În acest articol, vă voi împărtăși lista mea de sarcini pe care le dau studenților drept teme și asupra cărora desfășurăm diverse tipuri de brainstorm, ceea ce duce la o înțelegere profundă și clară a SQL.
SQL (ˈɛsˈkjuˈɛl; eng. limbaj de interogare structurat) este un limbaj de programare declarativ folosit pentru a crea, modifica și gestiona date într-o bază de date relațională gestionată de un sistem adecvat de gestionare a bazelor de date.
Puteți citi despre SQL din diferite
Acest articol nu este destinat să vă învețe SQL de la zero.
Deci să mergem.
Vom folosi binecunoscutul
Menționez că vom lua în considerare doar sarcinile pe SELECT. Nu există sarcini pe DML și DDL.
sarcini
Restricționarea și sortarea datelor
Tabelul angajaților. Obțineți o listă cu informații despre toți angajații
decizie
SELECT * FROM employees
Tabelul angajaților. Obțineți o listă cu toți angajații cu numele „David”
decizie
SELECT *
FROM employees
WHERE first_name = 'David';
Tabelul angajaților. Obțineți o listă cu toți angajații cu job_id egal cu „IT_PROG”
decizie
SELECT *
FROM employees
WHERE job_id = 'IT_PROG'
Tabelul angajaților. Obțineți o listă cu toți angajații din cel de-al 50-lea departament (department_id) cu un salariu (salariu) mai mare de 4000
decizie
SELECT *
FROM employees
WHERE department_id = 50 AND salary > 4000;
Tabelul angajaților. Obțineți o listă cu toți angajații din departamentul 20 și 30 (department_id)
decizie
SELECT *
FROM employees
WHERE department_id = 20 OR department_id = 30;
Tabelul angajaților. Obțineți o listă cu toți angajații a căror ultima literă din numele lor este „a”
decizie
SELECT *
FROM employees
WHERE first_name LIKE '%a';
Tabelul angajaților. Obțineți o listă cu toți angajații din departamentul 50 și 80 (department_id) care au un bonus (valoarea din coloana commission_pct nu este goală)
decizie
SELECT *
FROM employees
WHERE (department_id = 50 OR department_id = 80)
AND commission_pct IS NOT NULL;
Tabelul angajaților. Obțineți o listă cu toți angajații al căror nume conține cel puțin 2 litere „n”
decizie
SELECT *
FROM employees
WHERE first_name LIKE '%n%n%';
Tabelul angajaților. Obțineți o listă cu toți angajații al căror nume are mai mult de 4 litere
decizie
SELECT *
FROM employees
WHERE first_name LIKE '%_____%';
Tabelul angajaților. Obțineți o listă cu toți angajații al căror salariu este între 8000 și 9000 (inclusiv)
decizie
SELECT *
FROM employees
WHERE salary BETWEEN 8000 AND 9000;
Tabelul angajaților. Obțineți o listă cu toți angajații al căror nume conține simbolul „%”
decizie
SELECT *
FROM employees
WHERE first_name LIKE '%%%' ESCAPE '';
Tabelul angajaților. Obțineți o listă cu toate codurile de manager
decizie
SELECT DISTINCT manager_id
FROM employees
WHERE manager_id IS NOT NULL;
Tabelul angajaților. Obțineți o listă de angajați cu pozițiile lor în formatul: Donald(sh_clerk)
decizie
SELECT first_name || '(' || LOWER (job_id) || ')' employee FROM employees;
Utilizarea funcțiilor cu un singur rând pentru a personaliza ieșirea
Tabelul angajaților. Obțineți o listă cu toți angajații al căror nume are mai mult de 10 litere
decizie
SELECT *
FROM employees
WHERE LENGTH (first_name) > 10;
Tabelul angajaților. Obțineți o listă cu toți angajații care au litera „b” în numele lor (fără diferențiere între majuscule și minuscule)
decizie
SELECT *
FROM employees
WHERE INSTR (LOWER (first_name), 'b') > 0;
Tabelul angajaților. Obțineți o listă cu toți angajații al căror nume conține cel puțin 2 litere „a”
decizie
SELECT *
FROM employees
WHERE INSTR (LOWER (first_name),'a',1,2) > 0;
Tabelul angajaților. Obțineți o listă cu toți angajații al căror salariu este multiplu de 1000
decizie
SELECT *
FROM employees
WHERE MOD (salary, 1000) = 0;
Tabelul angajaților. Obțineți primul număr de 3 cifre al numărului de telefon al angajatului dacă numărul acestuia este în formatul ХХХ.ХХХ.ХХХХ
decizie
SELECT phone_number, SUBSTR (phone_number, 1, 3) new_phone_number
FROM employees
WHERE phone_number LIKE '___.___.____';
Tabelul departamentelor. Obțineți primul cuvânt din numele departamentului pentru cei care au mai mult de un cuvânt în nume
decizie
SELECT department_name,
SUBSTR (department_name, 1, INSTR (department_name, ' ')-1)
first_word
FROM departments
WHERE INSTR (department_name, ' ') > 0;
Tabelul angajaților. Obțineți numele angajaților fără prima și ultima literă din nume
decizie
SELECT first_name, SUBSTR (first_name, 2, LENGTH (first_name) - 2) new_name
FROM employees;
Tabelul angajaților. Obțineți o listă cu toți angajații a căror ultima literă din nume este egală cu „m” și lungimea numelui este mai mare de 5
decizie
SELECT *
FROM employees
WHERE SUBSTR (first_name, -1) = 'm' AND LENGTH(first_name)>5;
Masa dubla. Obțineți data de vineri viitoare
decizie
SELECT NEXT_DAY (SYSDATE, 'FRIDAY') next_friday FROM DUAL;
Tabelul angajaților. Obțineți o listă cu toți angajații care lucrează în companie de peste 17 ani
decizie
SELECT *
FROM employees
WHERE MONTHS_BETWEEN (SYSDATE, hire_date) / 12 > 17;
Tabelul angajaților. Obțineți o listă cu toți angajații a căror ultima cifră a numărului de telefon este impar și constă din 3 numere separate printr-un punct
decizie
SELECT *
FROM employees
WHERE MOD (SUBSTR (phone_number, -1), 2) != 0
AND INSTR (phone_number,'.',1,3) = 0;
Tabelul angajaților. Obțineți o listă cu toți angajații a căror valoare job_id după semnul „_” are cel puțin 3 caractere, dar această valoare după „_” nu este egală cu „CLERK”
decizie
SELECT *
FROM employees
WHERE LENGTH (SUBSTR (job_id, INSTR (job_id, '_') + 1)) > 3
AND SUBSTR (job_id, INSTR (job_id, '_') + 1) != 'CLERK';
Tabelul angajaților. Obțineți o listă cu toți angajații prin înlocuirea tuturor „.” din valoarea PHONE_NUMBER pe '-'
decizie
SELECT phone_number, REPLACE (phone_number, '.', '-') new_phone_number
FROM employees;
Utilizarea funcțiilor de conversie și a expresiilor condiționate
Tabelul angajaților. Obțineți o listă cu toți angajații care au venit la muncă în prima zi a lunii (oricare)
decizie
SELECT *
FROM employees
WHERE TO_CHAR (hire_date, 'DD') = '01';
Tabelul angajaților. Obțineți o listă cu toți angajații care au venit la muncă în 2008
decizie
SELECT *
FROM employees
WHERE TO_CHAR (hire_date, 'YYYY') = '2008';
masă dublă. Afișați data de mâine în formatul: Mâine este a doua zi a lunii ianuarie
decizie
SELECT TO_CHAR (SYSDATE, 'fm""Tomorrow is ""Ddspth ""day of"" Month') info
FROM DUAL;
Tabelul angajaților. Obțineți o listă cu toți angajații și data de începere a acestora în formatul: 21 iunie 2007
decizie
SELECT first_name, TO_CHAR (hire_date, 'fmddth ""of"" Month, YYYY') hire_date
FROM employees;
Tabelul angajaților. Obțineți o listă cu angajații cu salarii mărite cu 20%. Arată salariul cu semnul dolarului
decizie
SELECT first_name, TO_CHAR (salary + salary * 0.20, 'fm$999,999.00') new_salary
FROM employees;
Tabelul angajaților. Obțineți o listă cu toți angajații care au venit la muncă în februarie 2007.
decizie
SELECT *
FROM employees
WHERE hire_date BETWEEN TO_DATE ('01.02.2007', 'DD.MM.YYYY')
AND LAST_DAY (TO_DATE ('01.02.2007', 'DD.MM.YYYY'));
SELECT *
FROM employees
WHERE to_char(hire_date,'MM.YYYY') = '02.2007';
masă dublă. Exportați data curentă, + secundă, + minut, + oră, + zi, + lună, + an
decizie
SELECT SYSDATE now,
SYSDATE + 1 / (24 * 60 * 60) plus_second,
SYSDATE + 1 / (24 * 60) plus_minute,
SYSDATE + 1 / 24 plus_hour,
SYSDATE + 1 plus_day,
ADD_MONTHS (SYSDATE, 1) plus_month,
ADD_MONTHS (SYSDATE, 12) plus_year
FROM DUAL;
Tabelul angajaților. Obțineți o listă cu toți angajații cu salarii complete (salariu + comision_pct(%)) în formatul: 24,000.00 USD
decizie
SELECT first_name, salary, TO_CHAR (salary + salary * NVL (commission_pct, 0), 'fm$99,999.00') full_salary
FROM employees;
Tabelul angajaților. Obțineți o listă cu toți angajații și informații despre disponibilitatea bonusurilor salariale (Da/Nu)
decizie
SELECT first_name, commission_pct, NVL2 (commission_pct, 'Yes', 'No') has_bonus
FROM employees;
Tabelul angajaților. Obțineți nivelul de salariu al fiecărui angajat: Sub 5000 este considerat Nivel scăzut, Mai mare sau egal cu 5000 și mai puțin de 10000 este considerat Nivel normal, Mai mare sau egal cu 10000 este considerat Nivel înalt
decizie
SELECT first_name,
salary,
CASE
WHEN salary < 5000 THEN 'Low'
WHEN salary >= 5000 AND salary < 10000 THEN 'Normal'
ELSE 'High'
END salary_level
FROM employees;
Tabelul țărilor. Pentru fiecare țară, arătați regiunea în care se află: 1-Europa, 2-America, 3-Asia, 4-Africa (fără Join)
decizie
SELECT country_name country,
DECODE (region_id,
1, 'Europe',
2, 'America',
3, 'Asia',
4, 'Africa',
'Unknown')
region
FROM countries;
SELECT country_name
country,
CASE region_id
WHEN 1 THEN 'Europe'
WHEN 2 THEN 'America'
WHEN 3 THEN 'Asia'
WHEN 4 THEN 'Africa'
ELSE 'Unknown'
END
region
FROM countries;
Raportarea datelor agregate utilizând funcțiile de grup
Tabelul angajaților. Obțineți un raport de departament_id cu salariul minim și maxim, datele de sosire anticipată și târzie și numărul de angajați. Sortați după numărul de angajați (desc)
decizie
SELECT department_id,
MIN (salary) min_salary,
MAX (salary) max_salary,
MIN (hire_date) min_hire_date,
MAX (hire_date) max_hire_Date,
COUNT (*) count
FROM employees
GROUP BY department_id
order by count(*) desc;
Tabelul angajaților. Câți angajați ale căror nume încep cu aceeași literă? Sortați după cantitate. Afișați numai cele în care numărul este mai mare decât 1
decizie
SELECT SUBSTR (first_name, 1, 1) first_char, COUNT (*)
FROM employees
GROUP BY SUBSTR (first_name, 1, 1)
HAVING COUNT (*) > 1
ORDER BY 2 DESC;
Tabelul angajaților. Câți angajați lucrează în același departament și primesc același salariu?
decizie
SELECT department_id, salary, COUNT (*)
FROM employees
GROUP BY department_id, salary
HAVING COUNT (*) > 1;
Tabelul angajaților. Obțineți un raport despre câți angajați au fost angajați în fiecare zi a săptămânii. Sortați după cantitate
decizie
SELECT TO_CHAR (hire_Date, 'Day') day, COUNT (*)
FROM employees
GROUP BY TO_CHAR (hire_Date, 'Day')
ORDER BY 2 DESC;
Tabelul angajaților. Obțineți un raport despre câți angajați au fost angajați. Sortați după cantitate
decizie
SELECT TO_CHAR (hire_date, 'YYYY') year, COUNT (*)
FROM employees
GROUP BY TO_CHAR (hire_date, 'YYYY');
Tabelul angajaților. Obțineți numărul de departamente care au angajați
decizie
SELECT COUNT (COUNT (*)) department_count
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id;
Tabelul angajaților. Obțineți lista departamentului_id cu mai mult de 30 de angajați
decizie
SELECT department_id
FROM employees
GROUP BY department_id
HAVING COUNT (*) > 30;
Tabelul angajaților. Obțineți o listă de departamente_id și salariul mediu rotunjit al angajaților din fiecare departament.
decizie
SELECT department_id, ROUND (AVG (salary)) avg_salary
FROM employees
GROUP BY department_id;
Tabelul țărilor. Obțineți o listă cu region_id suma tuturor literelor tuturor numelor de țară în care mai mult de 60
decizie
SELECT region_id
FROM countries
GROUP BY region_id
HAVING SUM (LENGTH (country_name)) > 60;
Tabelul angajaților. Obțineți o listă de departament_id în care lucrează angajații mai multor (>1) job_id
decizie
SELECT department_id
FROM employees
GROUP BY department_id
HAVING COUNT (DISTINCT job_id) > 1;
Tabelul angajaților. Obțineți o listă cu manager_id al cărui număr de subordonați este mai mare de 5 și suma tuturor salariilor subordonaților săi este mai mare de 50000
decizie
SELECT manager_id
FROM employees
GROUP BY manager_id
HAVING COUNT (*) > 5 AND SUM (salary) > 50000;
Tabelul angajaților. Obțineți o listă cu manager_id al căror salariu mediu al tuturor subordonaților săi este între 6000 și 9000 care nu primesc bonusuri (commission_pct este gol)
decizie
SELECT manager_id, AVG (salary) avg_salary
FROM employees
WHERE commission_pct IS NULL
GROUP BY manager_id
HAVING AVG (salary) BETWEEN 6000 AND 9000;
Tabelul angajaților. Obțineți salariul maxim de la toți angajații job_id care se termină cu cuvântul „CLERK”
decizie
SELECT MAX (salary) max_salary
FROM employees
WHERE job_id LIKE '%CLERK';
SELECT MAX (salary) max_salary
FROM employees
WHERE SUBSTR (job_id, -5) = 'CLERK';
Tabelul angajaților. Obțineți salariul maxim dintre toate salariile medii pentru departament
decizie
SELECT MAX (AVG (salary))
FROM employees
GROUP BY department_id;
Tabelul angajaților. Obțineți numărul de angajați cu același număr de litere în numele lor. În același timp, afișați numai cei al căror nume este mai mare de 5 și numărul de angajați cu același nume este mai mare de 20. Sortați după lungimea numelui
decizie
SELECT LENGTH (first_name), COUNT (*)
FROM employees
GROUP BY LENGTH (first_name)
HAVING LENGTH (first_name) > 5 AND COUNT (*) > 20
ORDER BY LENGTH (first_name);
SELECT LENGTH (first_name), COUNT (*)
FROM employees
WHERE LENGTH (first_name) > 5
GROUP BY LENGTH (first_name)
HAVING COUNT (*) > 20
ORDER BY LENGTH (first_name);
Afișarea datelor din mai multe tabele utilizând îmbinări
Tabel de angajați, departamente, locații, țări, regiuni. Obțineți o listă cu regiunile și numărul de angajați din fiecare regiune
decizie
SELECT region_name, COUNT (*)
FROM employees e
JOIN departments d ON (e.department_id = d.department_id)
JOIN locations l ON (d.location_id = l.location_id)
JOIN countries c ON (l.country_id = c.country_id)
JOIN regions r ON (c.region_id = r.region_id)
GROUP BY region_name;
Tabel de angajați, departamente, locații, țări, regiuni. Obțineți informații detaliate despre fiecare angajat:
Prenume, Prenume, Departament, Job, Stradă, Țară, Regiune
decizie
SELECT First_name,
Last_name,
Department_name,
Job_id,
street_address,
Country_name,
Region_name
FROM employees e
JOIN departments d ON (e.department_id = d.department_id)
JOIN locations l ON (d.location_id = l.location_id)
JOIN countries c ON (l.country_id = c.country_id)
JOIN regions r ON (c.region_id = r.region_id);
Tabelul angajaților. Afișați toți managerii care au mai mult de 6 angajați
decizie
SELECT man.first_name, COUNT (*)
FROM employees emp JOIN employees man ON (emp.manager_id = man.employee_id)
GROUP BY man.first_name
HAVING COUNT (*) > 6;
Tabelul angajaților. Afișați toți angajații care nu raportează nimănui
decizie
SELECT emp.first_name
FROM employees emp
LEFT JOIN employees man ON (emp.manager_id = man.employee_id)
WHERE man.FIRST_NAME IS NULL;
SELECT first_name
FROM employees
WHERE manager_id IS NULL;
Tabelul angajaților, Job_history. Tabelul Angajați stochează toți angajații. Tabelul Job_history stochează angajații care au părăsit compania. Obțineți un raport despre toți angajații și statutul acestora în companie (angajat sau părăsit din companie cu data plecării)
Exemplu:
prenume | stare
jennifer | A părăsit compania la 31 decembrie 2006
Clara | Momentan lucrez
decizie
SELECT first_name,
NVL2 (
end_date,
TO_CHAR (end_date, 'fm""Left the company at"" DD ""of"" Month, YYYY'),
'Currently Working')
status
FROM employees e LEFT JOIN job_history j ON (e.employee_id = j.employee_id);
Tabel de angajați, departamente, locații, țări, regiuni. Obțineți o listă cu angajații care locuiesc în Europa (region_name)
decizie
SELECT first_name
FROM employees
JOIN departments USING (department_id)
JOIN locations USING (location_id)
JOIN countries USING (country_id)
JOIN regions USING (region_id)
WHERE region_name = 'Europe';
SELECT first_name
FROM employees e
JOIN departments d ON (e.department_id = d.department_id)
JOIN locations l ON (d.location_id = l.location_id)
JOIN countries c ON (l.country_id = c.country_id)
JOIN regions r ON (c.region_id = r.region_id)
WHERE region_name = 'Europe';
Angajații de masă, departamente. Afișați toate departamentele cu peste 30 de angajați
decizie
SELECT department_name, COUNT (*)
FROM employees e JOIN departments d ON (e.department_id = d.department_id)
GROUP BY department_name
HAVING COUNT (*) > 30;
Angajații de masă, departamente. Afișați toți angajații care nu sunt în niciun departament
decizie
SELECT first_name
FROM employees e
LEFT JOIN departments d ON (e.department_id = d.department_id)
WHERE d.department_name IS NULL;
SELECT first_name
FROM employees
WHERE department_id IS NULL;
Angajații de masă, departamente. Afișați toate departamentele fără angajați
decizie
SELECT department_name
FROM employees e
RIGHT JOIN departments d ON (e.department_id = d.department_id)
WHERE first_name IS NULL;
Tabelul angajaților. Afișați toți angajații care nu au subordonați
decizie
SELECT man.first_name
FROM employees emp
RIGHT JOIN employees man ON (emp.manager_id = man.employee_id)
WHERE emp.FIRST_NAME IS NULL;
Tabelul de angajați, locuri de muncă, departamente. Afișați angajații în formatul: prenume, titlu_post, nume_departament.
Exemplu:
prenume | titlul postului | Numele departamentului
Donald | transport maritim | Grefier de transport
decizie
SELECT first_name, job_title, department_name
FROM employees e
JOIN jobs j ON (e.job_id = j.job_id)
JOIN departments d ON (d.department_id = e.department_id);
Tabelul angajaților. Obțineți o listă cu angajații ai căror manageri au obținut un loc de muncă în 2005, dar, în același timp, acești lucrători au obținut un loc de muncă înainte de 2005
decizie
SELECT emp.*
FROM employees emp JOIN employees man ON (emp.manager_id = man.employee_id)
WHERE TO_CHAR (man.hire_date, 'YYYY') = '2005'
AND emp.hire_date < TO_DATE ('01012005', 'DDMMYYYY');
Tabelul angajaților. Obțineți o listă cu angajații ai căror manageri au obținut un loc de muncă în luna ianuarie a oricărui an și lungimea job_title al acestor angajați este mai mare de 15 caractere
decizie
SELECT emp.*
FROM employees emp
JOIN employees man ON (emp.manager_id = man.employee_id)
JOIN jobs j ON (emp.job_id = j.job_id)
WHERE TO_CHAR (man.hire_date, 'MM') = '01' AND LENGTH (j.job_title) > 15;
Utilizarea subinterogărilor pentru a rezolva interogări
Tabelul angajaților. Obțineți o listă cu angajații cu cel mai lung nume.
decizie
SELECT *
FROM employees
WHERE LENGTH (first_name) =
(SELECT MAX (LENGTH (first_name)) FROM employees);
Tabelul angajaților. Obțineți o listă cu angajații cu un salariu mai mare decât salariul mediu al tuturor angajaților.
decizie
SELECT *
FROM employees
WHERE salary > (SELECT AVG (salary) FROM employees);
Tabelul de angajați, departamente, locații. Obțineți orașul în care angajații câștigă cel mai puțin în total.
decizie
SELECT city
FROM employees e
JOIN departments d ON (e.department_id = d.department_id)
JOIN locations l ON (d.location_id = l.location_id)
GROUP BY city
HAVING SUM (salary) =
( SELECT MIN (SUM (salary))
FROM employees e
JOIN departments d ON (e.department_id = d.department_id)
JOIN locations l ON (d.location_id = l.location_id)
GROUP BY city);
Tabelul angajaților. Obțineți o listă cu angajații al căror manager primește un salariu mai mare de 15000.
decizie
SELECT *
FROM employees
WHERE manager_id IN (SELECT employee_id
FROM employees
WHERE salary > 15000)
Angajații de masă, departamente. Afișați toate departamentele fără angajați
decizie
SELECT *
FROM departments
WHERE department_id NOT IN (SELECT department_id
FROM employees
WHERE department_id IS NOT NULL);
Tabelul angajaților. Afișați toți angajații care nu sunt manageri
decizie
SELECT *
FROM employees
WHERE employee_id NOT IN (SELECT manager_id
FROM employees
WHERE manager_id IS NOT NULL)
Tabelul angajaților. Afișați toți managerii care au mai mult de 6 angajați
decizie
SELECT *
FROM employees e
WHERE (SELECT COUNT (*)
FROM employees
WHERE manager_id = e.employee_id) > 6;
Angajații de masă, departamente. Arată angajații care lucrează în departamentul IT
decizie
SELECT *
FROM employees
WHERE department_id = (SELECT department_id
FROM departments
WHERE department_name = 'IT');
Tabelul de angajați, locuri de muncă, departamente. Afișați angajații în formatul: prenume, titlu_post, nume_departament.
Exemplu:
prenume | titlul postului | Numele departamentului
Donald | transport maritim | Grefier de transport
decizie
SELECT first_name,
(SELECT job_title
FROM jobs
WHERE job_id = e.job_id)
job_title,
(SELECT department_name
FROM departments
WHERE department_id = e.department_id)
department_name
FROM employees e;
Tabelul angajaților. Obțineți o listă cu angajații ai căror manageri au obținut un loc de muncă în 2005, dar, în același timp, acești lucrători au obținut un loc de muncă înainte de 2005
decizie
SELECT *
FROM employees
WHERE manager_id IN (SELECT employee_id
FROM employees
WHERE TO_CHAR (hire_date, 'YYYY') = '2005')
AND hire_date < TO_DATE ('01012005', 'DDMMYYYY');
Tabelul angajaților. Obțineți o listă cu angajații ai căror manageri au obținut un loc de muncă în luna ianuarie a oricărui an și lungimea job_title al acestor angajați este mai mare de 15 caractere
decizie
SELECT *
FROM employees e
WHERE manager_id IN (SELECT employee_id
FROM employees
WHERE TO_CHAR (hire_date, 'MM') = '01')
AND (SELECT LENGTH (job_title)
FROM jobs
WHERE job_id = e.job_id) > 15;
Asta este tot pentru acum.
Sper că sarcinile au fost interesante și interesante.
Voi adăuga la această listă cât mai mult posibil.
De asemenea, voi fi bucuros pentru orice comentarii și sugestii.
PS: Dacă cineva vine cu o sarcină interesantă pe SELECT, scrie în comentarii, o voi adăuga pe listă.
Mulțumesc.
Sursa: www.habr.com