Subquery, CTE និង Window Function

មាតិកា

  1. សំណួរក្នុងសំណួរ
  2. Scalar Subquery
  3. Subquery ជាមួយ IN និង EXISTS
  4. Derived Table៖ Subquery ក្នុង FROM
  5. CTE៖ ដាក់ឈ្មោះជំហានរបស់អ្នកដោយ WITH
  6. View៖ ការរក្សាទុក Query
  7. Window Function៖ ការគណនាដោយមិនបង្រួមជួរ
  8. ROW_NUMBER, RANK និង DENSE_RANK
  9. ផលបូកបន្តបន្ទាប់ដោយ SUM OVER
  10. PARTITION BY៖ Window ក្នុងមួយក្រុម
  11. Top-N ក្នុងមួយក្រុម
  12. UNION, INTERSECT និង EXCEPT
  13. សាកល្បងដោយខ្លួនឯង

1. សំណួរក្នុងសំណួរ

សំណួរខ្លះមិនអាចឆ្លើយបានក្នុងការឆ្លងកាត់តែមួយទេ។ "ផលិតផលណាខ្លះមានតម្លៃខ្ពស់ជាងផលិតផលមធ្យម?" ត្រូវការពីរជំហាន៖ ជាដំបូងគណនាមធ្យមភាគ បន្ទាប់មកប្រៀបធៀបផលិតផលនីមួយៗជាមួយវា។ "តើអតិថិជនល្អបំផុតរបស់យើងតាមការចំណាយសរុបគឺអ្នកណា?" ត្រូវការផលបូកក្នុងមួយអតិថិជន ហើយបន្ទាប់មកចំណាត់ថ្នាក់នៃផលបូកទាំងនោះ។

SQL អនុញ្ញាតឲ្យអ្នកដាក់ query មួយក្នុង query មួយទៀត (subquery) ដាក់ឈ្មោះឲ្យ query ហើយប្រើវាក្រោយ (CTE) ឬថែមទាំងរក្សាទុកមួយជាអចិន្ត្រៃយ៍ (view)។ ចុងក្រោយ window function អនុញ្ញាតឲ្យអ្នកគណនាអ្វីៗដូចជា rank និងផលបូកបន្តបន្ទាប់ ខណៈរក្សារាល់ជួរឲ្យនៅមើលឃើញ។ ទាំងនេះគឺជាឧបករណ៍ដែលញែក SQL មូលដ្ឋានចេញពីការងារពិតដែលអ្នកវិភាគ និងអ្នកអភិវឌ្ឍ backend ធ្វើ។

រក្សាទុក query នៃជំពូកនេះក្នុង shopdb/queries/09_subqueries_windows.sql ហើយដំណើរការវាតាមវិធីធម្មតា៖

docker exec -i shopdb-pg psql -U postgres -d shopdb < queries/09_subqueries_windows.sql

2. Scalar Subquery

scalar subquery ត្រឡប់តម្លៃតែមួយប៉ុណ្ណោះ (មួយជួរ មួយ column)។ អ្នកអាចប្រើវាគ្រប់កន្លែងដែលតម្លៃតែមួយសមស្រប ដូចជាក្នុងការប្រៀបធៀប WHERE។

ផលិតផលដែលមានតម្លៃលើសមធ្យមភាគ៖

SELECT name, price
FROM products
WHERE price > (SELECT avg(price) FROM products)
ORDER BY price DESC;
        name         | price
---------------------+-------
 Mechanical Keyboard | 79.00
 SQL Cookbook        | 32.00
 Clean Code          | 28.50
(3 rows)

query ខាងក្នុងដំណើរការមុន ហើយបង្កើត 24.6875។ query ខាងក្រៅបន្ទាប់មករក្សាផលិតផលដែលមានតម្លៃលើសពីនោះ។ ប្រសិនបើ subquery ត្រឡប់ច្រើនជាងមួយជួរ PostgreSQL នឹងបង្ក ERROR: more than one row returned by a subquery used as an expression ដូច្នេះត្រូវប្រាកដថាវាជាតម្លៃតែមួយពិតប្រាកដ។

3. Subquery ជាមួយ IN និង EXISTS

subquery ក៏អាចត្រឡប់ បញ្ជី នៃតម្លៃ ដែលប្រើជាមួយ IN។ តើអតិថិជនណាខ្លះមាន order ដែលបានបង់ប្រាក់?

SELECT name
FROM customers
WHERE id IN (SELECT customer_id FROM orders WHERE status = 'paid')
ORDER BY id;
    name
------------
 Sokha Chan
 Linda Vong
(2 rows)

EXISTS សួរសំណួរបាទ/ចាសក្នុងមួយជួរខាងក្រៅ៖ "តើមានយ៉ាងហោចណាស់មួយជួរក្នុង subquery នេះឬទេ?" subquery យោងទៅជួរខាងក្រៅ ដែលហៅថា correlated subquery។ តើអតិថិជនណាខ្លះមាន order ដែលបានលុបចោល?

SELECT c.name
FROM customers c
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.customer_id = c.id
    AND o.status = 'cancelled'
);
   name
----------
 Dara Kim
(1 row)

សម្រាប់អតិថិជននីមួយៗ query ខាងក្នុងត្រូវបានដំណើរការម្តងទៀតតាមគំនិតជាមួយ c.id របស់អតិថិជននោះ។ (ក្នុងការអនុវត្ត PostgreSQL បំប្លែងវាទៅជា semi-join ដែលមានប្រសិទ្ធភាព មិនមែន loop ទេ។) ប្រើ NOT EXISTS សម្រាប់ផ្ទុយ ជា anti-join ពីជំពូកទី 7។

IN និង EXISTS ជារឿយៗផ្តល់លទ្ធផលដូចគ្នា។ ការណែនាំល្អ៖ ប្រើ IN សម្រាប់បញ្ជីតម្លៃសាមញ្ញ ហើយ EXISTS ពេលអ្នកត្រូវការ correlate ជាមួយជួរខាងក្រៅ ឬពេល column របស់ subquery អាចជា NULL។

4. Derived Table៖ Subquery ក្នុង FROM

subquery ក្នុង clause FROM ប្រព្រឹត្តដូច table បណ្តោះអាសន្ន។ វា ត្រូវតែ មាន alias។ នេះគឺជារបៀបដែលអ្នកដោះស្រាយ "aggregate រួចធ្វើអ្វីមួយជាមួយ aggregate នោះ"៖

SELECT c.name, t.total
FROM customers c
JOIN (
  SELECT o.customer_id, sum(oi.quantity * oi.unit_price) AS total
  FROM orders o
  JOIN order_items oi ON oi.order_id = o.id
  GROUP BY o.customer_id
) t ON t.customer_id = c.id
ORDER BY t.total DESC;
    name    | total
------------+--------
 Sokha Chan | 105.00
 Dara Kim   |  82.50
 Linda Vong |  57.50
(3 rows)

query ខាងក្នុងបង្កើតមួយជួរក្នុងមួយ id អតិថិជន ជាមួយផលបូករបស់ពួកគេ។ query ខាងក្រៅភ្ជាប់ឈ្មោះ។ ដោយសារ aggregation កើតឡើង នៅខាងក្នុង មុន join ជាមួយអ្វីផ្សេងទៀត ដូច្នេះមិនមានបញ្ហាគុណជួរទេ។

វាដំណើរការ ប៉ុន្តែសូមកត់សម្គាល់ថាវាអានពីក្នុងចេញក្រៅ។ ពេល subquery ដាក់ក្នុងគ្នាជ្រៅពីរឬបីជាន់ វាក្លាយជាពិបាកតាមដាន។ នោះគឺជាមូលហេតុដែល CTE មាន។

5. CTE៖ ដាក់ឈ្មោះជំហានរបស់អ្នកដោយ WITH

common table expression (CTE) ផ្តល់ឈ្មោះឲ្យ subquery ហើយដាក់វា មុន query មេ ដូច្នេះអ្នកអានពីលើចុះក្រោមដូចរូបមន្តធ្វើម្ហូប។

WITH order_totals AS (
  SELECT o.id AS order_id,
         o.customer_id,
         o.status,
         sum(oi.quantity * oi.unit_price) AS total
  FROM orders o
  JOIN order_items oi ON oi.order_id = o.id
  GROUP BY o.id, o.customer_id, o.status
)
SELECT * FROM order_totals ORDER BY order_id;
 order_id | customer_id |  status   | total
----------+-------------+-----------+-------
        1 |           1 | paid      | 26.00
        2 |           1 | shipped   | 79.00
        3 |           2 | pending   | 60.50
        4 |           3 | paid      | 57.50
        5 |           2 | cancelled | 22.00
(5 rows)

ឥឡូវ order_totals គឺជា table បណ្តោះអាសន្នដែលមានឈ្មោះ ដែលអ្នកអាចប្រើឡើងវិញ។ សាងសង់ជំហានបន្ទាប់លើវា៖ ចំនួន order និងតម្លៃ order មធ្យមសម្រាប់អតិថិជននីមួយៗ ដោយមិនរាប់ order ដែលបានលុបចោល។

WITH order_totals AS (
  SELECT o.id AS order_id,
         o.customer_id,
         o.status,
         sum(oi.quantity * oi.unit_price) AS total
  FROM orders o
  JOIN order_items oi ON oi.order_id = o.id
  GROUP BY o.id, o.customer_id, o.status
)
SELECT c.name,
       count(*)                AS orders,
       round(avg(t.total), 2)  AS avg_order
FROM order_totals t
JOIN customers c ON c.id = t.customer_id
WHERE t.status <> 'cancelled'
GROUP BY c.name
ORDER BY c.name;
    name    | orders | avg_order
------------+--------+-----------
 Dara Kim   |      1 |     60.50
 Linda Vong |      1 |     57.50
 Sokha Chan |      2 |     52.50
(3 rows)

order ពីររបស់ Sokha គឺ 26.00 និង 79.00 មធ្យម 52.50។ order ដែលបានលុបចោលរបស់ Dara ត្រូវបានដកចេញ ដូច្នេះនៅសល់តែ 60.50 ដែលកំពុងរង់ចាំរបស់នាង។

អ្នកអាចកំណត់ CTE ច្រើនដែលបំបែកដោយសញ្ញាក្បៀស ដែលនីមួយៗអាចប្រើពួកដែលនៅមុន៖

WITH step_one AS (...),
     step_two AS (SELECT ... FROM step_one ...)
SELECT ... FROM step_two;

អត្ថប្រយោជន៍៖ ជំហាននីមួយៗមានឈ្មោះដែលពិពណ៌នាវា អ្នកអាចសាកល្បងផ្នែកនីមួយៗដាច់ដោយឡែកដោយ select ពីវា ហើយ CTE ដូចគ្នាអាចត្រូវបានយោងលើសពីមួយដង។ CTE មានតែសម្រាប់ statement តែមួយនោះប៉ុណ្ណោះ។ (មានទម្រង់ WITH RECURSIVE កម្រិតខ្ពស់សម្រាប់ដើរកាត់ដើមឈើ និងឋានានុក្រម ដែលយើងរំលងនៅទីនេះ។)

6. View៖ ការរក្សាទុក Query

query order_totals មានប្រយោជន៍ខ្លាំងដែលអ្នកនឹងចង់បានវានៅកន្លែងជាច្រើន។ view គឺជា query ដែលបានរក្សាទុកជាមួយឈ្មោះ។ បន្ទាប់មកអ្នកអាច select ពីវាដូចជា table ប៉ុន្តែវាដំណើរការថ្មីជានិច្ចលើទិន្នន័យបច្ចុប្បន្ន។ វាមិនរក្សាទុកជួរណាមួយផ្ទាល់ខ្លួនទេ។

បន្ថែមនេះទៅចុងបំផុតនៃ shopdb/schema.sql ដើម្បីឲ្យការសាងសង់ឡើងវិញបង្កើតវាឡើងវិញបន្ទាប់ពី table៖

CREATE OR REPLACE VIEW order_totals AS
SELECT o.id AS order_id,
       o.customer_id,
       o.status,
       o.ordered_at,
       sum(oi.quantity * oi.unit_price) AS total
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, o.customer_id, o.status, o.ordered_at;

បន្ទាប់មកពី psql៖

SELECT * FROM order_totals WHERE status = 'paid' ORDER BY order_id;
 order_id | customer_id | status |      ordered_at        | total
----------+-------------+--------+------------------------+-------
        1 |           1 | paid   | 2026-01-05 00:00:00+00 | 26.00
        4 |           3 | paid   | 2026-02-10 00:00:00+00 | 57.50
(2 rows)

(ទ្រង់ទ្រាយ timestamp និង offset តំបន់ម៉ោងអាស្រ័យលើការកំណត់ server របស់អ្នក។ ក្នុង Docker image លំនាំដើមវាជា UTC ដូច្នេះអ្នកជាធម្មតានឹងឃើញ +00។)

view មានប្រយោជន៍ដោយហេតុផលបី៖

  • ប្រើឡើងវិញ៖ និយមន័យតែមួយ ប្រើដោយរាល់របាយការណ៍។
  • ភាពសាមញ្ញ៖ លាក់ join table ប្រាំនៅពីក្រោយឈ្មោះមិត្តភាពតែមួយ។
  • ភាពស្របគ្នា៖ "ចំណូល" មានន័យដូចគ្នាគ្រប់ទីកន្លែង។

\dv បញ្ជី view របស់អ្នកក្នុង psql ហើយ DROP VIEW order_totals; លុបមួយ។ view ធម្មតាមិនធ្វើឲ្យ query លឿនជាងទេ វាគ្រាន់តែសន្សំការវាយ។ (MATERIALIZED VIEW រក្សាទុកលទ្ធផលរបស់វាដើម្បីល្បឿន ប៉ុន្តែត្រូវតែ refresh។ នោះជាប្រធានបទសម្រាប់ពេលក្រោយ។)

7. Window Function៖ ការគណនាដោយមិនបង្រួមជួរ

GROUP BY បង្រួមជួរ៖ order ប្រាំតាមអតិថិជនក្លាយជាបីជួរ ហើយ order នីមួយៗបាត់ទៅ។ ប៉ុន្តែជារឿយៗអ្នកចង់បានការគណនា និង ជួរដើម៖ order នីមួយៗជាមួយ rank របស់វា ឬ order នីមួយៗជាមួយផលបូកបន្តបន្ទាប់រហូតមកដល់ពេលនេះ។

window function ធ្វើបែបនេះពិតប្រាកដ។ វាគណនាតម្លៃកាត់តាមសំណុំនៃជួរដែលទាក់ទង ("window") ប៉ុន្តែត្រឡប់លទ្ធផលមួយក្នុងមួយជួរ input។ អ្នកស្គាល់វាតាម clause OVER (...)។

SELECT name,
       price,
       round(price - avg(price) OVER (), 2) AS diff_from_avg
FROM products
ORDER BY id;
        name         | price | diff_from_avg
---------------------+-------+---------------
 Wireless Mouse      | 15.00 |         -9.69
 USB-C Cable         |  5.50 |        -19.19
 Mechanical Keyboard | 79.00 |         54.31
 SQL Cookbook        | 32.00 |          7.31
 Clean Code          | 28.50 |          3.81
 Desk Lamp           | 22.00 |         -2.69
 Coffee Mug          |  6.00 |        -18.69
 Notebook Pack       |  9.50 |        -15.19
(8 rows)

avg(price) OVER () គឺជាមធ្យមភាគនៃ table ទាំងមូល (24.6875) ដែលបង្ហាញក្បែររាល់ជួរ។ វង់ក្រចកទទេមានន័យថា "window គឺរាល់ជួរ"។ ប្រៀបធៀបជាមួយ GROUP BY ដែលអាចផ្តល់តែជួរមធ្យមភាគតែមួយ។ ផលិតផលទាំងប្រាំបីនៅតែនៅទីនេះ។

8. ROW_NUMBER, RANK និង DENSE_RANK

ranking function ដាក់លេខជួរតាមលំដាប់ដែលអ្នកជ្រើសក្នុង OVER (ORDER BY ...)។ ដាក់ចំណាត់ថ្នាក់ order របស់យើងពីធំបំផុតទៅតូចបំផុត ដោយប្រើ view ពីផ្នែកទី 6 ឡើងវិញ៖

SELECT order_id,
       total,
       row_number() OVER (ORDER BY total DESC) AS rn
FROM order_totals
ORDER BY rn;
 order_id | total | rn
----------+-------+----
        2 | 79.00 |  1
        3 | 60.50 |  2
        4 | 57.50 |  3
        1 | 26.00 |  4
        5 | 22.00 |  5
(5 rows)

ranking function ទាំងបីខុសគ្នាតែក្នុងរបៀបដែលវាចាត់ទុក ពិន្ទុស្មើគ្នា (tie)។ស្រមៃពិន្ទុប្រឡងបី 90, 90 និង 80៖

ពិន្ទុROW_NUMBER()RANK()DENSE_RANK()
90111
90211
80332
  • ROW_NUMBER ផ្តល់លេខមិនស្ទួនជានិច្ច ដោយបំបែក tie តាមអំពើចិត្ត។
  • RANK ផ្តល់ rank ដូចគ្នាដល់ជួរដែលស្មើគ្នា ហើយបន្ទាប់មក រំលង (1, 1, 3)។
  • DENSE_RANK ផ្តល់ rank ដូចគ្នាដល់ជួរដែលស្មើគ្នា ហើយ មិនរំលង (1, 1, 2)។

សរុប order របស់យើងគ្មាន tie ទេ ដូច្នេះទាំងអស់ត្រូវគ្នានៅទីនេះ។ ជ្រើសតាមន័យ៖ "ជួរល្អទី 3" ត្រូវការ ROW_NUMBER តារាងពិន្ទុ (leaderboard) ត្រូវការ RANK។

9. ផលបូកបន្តបន្ទាប់ដោយ SUM OVER

ពេល aggregate function មាន OVER (ORDER BY ...) វាក្លាយជា aggregate បន្តបន្ទាប់៖ ផលបូកនៃរាល់ជួររហូតដល់ និងរួមទាំងជួរបច្ចុប្បន្ន។

SELECT order_id,
       ordered_at::date AS day,
       total,
       sum(total) OVER (ORDER BY ordered_at, order_id) AS running_total
FROM order_totals
ORDER BY ordered_at, order_id;
 order_id |    day     | total | running_total
----------+------------+-------+---------------
        1 | 2026-01-05 | 26.00 |         26.00
        2 | 2026-01-20 | 79.00 |        105.00
        3 | 2026-02-03 | 60.50 |        165.50
        4 | 2026-02-10 | 57.50 |        223.00
        5 | 2026-02-15 | 22.00 |        245.00
(5 rows)

ជួរនីមួយៗបន្ថែមសរុបផ្ទាល់ខ្លួនរបស់វាទៅអ្វីៗទាំងអស់មុនវា៖ 26 + 79 = 105 បន្ទាប់មក + 60.50 = 165.50 ហើយបន្តទៅទៀត។ ជួរចុងក្រោយ 245.00 គឺជាផលបូកនៃ order ទាំងអស់ (រួមទាំងមួយដែលបានលុបចោល ព្រោះយើងមិនបាន filter)។

ការបន្ថែម order_id ទៅក្នុង ORDER BY ខាងក្នុង OVER គឺជាទម្លាប់ល្អ។ ប្រសិនបើ order ពីរមាន timestamp ដូចគ្នា វាធ្វើឲ្យលំដាប់បន្តបន្ទាប់ ហើយដូច្នេះលេខ មានភាពកំណត់ច្បាស់លាស់ (deterministic)។

គំនិតដូចគ្នាផ្តល់ឲ្យអ្នក lag() និង lead() ដែលអានតម្លៃនៃជួរមុន ឬជួរបន្ទាប់ (ងាយស្រួលសម្រាប់ "ចំនួនថ្ងៃចាប់តាំងពី order ចុងក្រោយរបស់អតិថិជន") និង count(*) OVER ... សម្រាប់ការរាប់បន្តបន្ទាប់។

10. PARTITION BY៖ Window ក្នុងមួយក្រុម

PARTITION BY បំបែកជួរទៅជាក្រុម ហើយ window function ចាប់ផ្តើមឡើងវិញក្នុងក្រុមនីមួយៗ។ វាដូច GROUP BY សម្រាប់ window function ប៉ុន្តែមិនបង្រួមជួរ។

ផលបូកបន្តបន្ទាប់ ក្នុងមួយអតិថិជន៖

SELECT customer_id,
       order_id,
       total,
       sum(total) OVER (
         PARTITION BY customer_id
         ORDER BY ordered_at, order_id
       ) AS customer_running
FROM order_totals
ORDER BY customer_id, ordered_at;
 customer_id | order_id | total | customer_running
-------------+----------+-------+------------------
           1 |        1 | 26.00 |            26.00
           1 |        2 | 79.00 |           105.00
           2 |        3 | 60.50 |            60.50
           2 |        5 | 22.00 |            82.50
           3 |        4 | 57.50 |            57.50
(5 rows)

ផលបូកចាប់ផ្តើមឡើងវិញសម្រាប់អតិថិជននីមួយៗ។ អតិថិជន 1 ឈានដល់ 105.00 អតិថិជន 2 ឈានដល់ 82.50 (ត្រូវនឹងរបាយការណ៍ចំណូលពីជំពូកមុន) ហើយអតិថិជន 3 មាន order មួយ។

លំនាំដែលត្រូវចងចាំ៖

function() OVER (PARTITION BY <groups> ORDER BY <order within group>)

PARTITION BY ជាជម្រើស (លុបវាសម្រាប់ window ធំតែមួយ) ហើយ ORDER BY ក៏ដូចគ្នា (លុបវា ហើយ function ឃើញ partition ទាំងមូល)។

11. Top-N ក្នុងមួយក្រុម

សំណួរសម្ភាសន៍បុរាណ និងជាសំណួរក្នុងពិភពពិត៖ "ផលិតផលថ្លៃបំផុតពីរក្នុងប្រភេទនីមួយៗ"។ LIMIT 2 មិនអាចធ្វើបានទេ ព្រោះវាកំណត់លទ្ធផលទាំងមូល មិនមែនក្រុមនីមួយៗទេ។ Window function អាចធ្វើបាន៖ ដាក់លេខជួរក្នុងប្រភេទនីមួយៗ បន្ទាប់មករក្សាពីរដំបូង។

SELECT category_id, name, price
FROM (
  SELECT category_id, name, price,
         row_number() OVER (
           PARTITION BY category_id
           ORDER BY price DESC
         ) AS rn
  FROM products
) ranked
WHERE rn <= 2
ORDER BY category_id, price DESC;
 category_id |        name         | price
-------------+---------------------+-------
           1 | Mechanical Keyboard | 79.00
           1 | Wireless Mouse      | 15.00
           2 | SQL Cookbook        | 32.00
           2 | Clean Code          | 28.50
           3 | Desk Lamp           | 22.00
           3 | Coffee Mug          |  6.00
(6 rows)

ហេតុអ្វីបានជា query រុំ? តម្លៃរបស់ window function ត្រូវបានគណនាក្នុងជំហាន SELECT ដែលកើតឡើងបន្ទាប់ពី WHERE។ ដូច្នេះអ្នកមិនអាចសរសេរ WHERE row_number() OVER (...) <= 2 ដោយផ្ទាល់បានទេ។ អ្នកគណនា rn ក្នុង query ខាងក្នុង ជា derived table ឬ CTE ហើយ filter លើវានៅខាងក្រៅ។ query ដូចគ្នាជា CTE អានច្បាស់ជាង៖

WITH ranked AS (
  SELECT category_id, name, price,
         row_number() OVER (PARTITION BY category_id ORDER BY price DESC) AS rn
  FROM products
)
SELECT category_id, name, price
FROM ranked
WHERE rn <= 2
ORDER BY category_id, price DESC;

ប្រើ RANK() ជំនួស ROW_NUMBER() ប្រសិនបើអ្នកចង់រក្សា tie សម្រាប់ចំណាត់ថ្នាក់ចុងក្រោយ។ រូបរាងនេះ (ដាក់ rank ក្នុង partition រួច filter លើ rank) ដោះស្រាយ "order ចុងក្រោយបំផុតក្នុងមួយអតិថិជន", "ផលិតផលលក់ដាច់ជាងគេក្នុងមួយខែ" និងច្រើនទៀត។

12. UNION, INTERSECT និង EXCEPT

Join ផ្សំ table ចំហៀង (column ច្រើនជាង)។ set operation ដាក់លទ្ធផល query ជង់លើគ្នា (ជួរច្រើនជាង)។ query ទាំងពីរត្រូវតែត្រឡប់ចំនួន column ដូចគ្នាជាមួយប្រភេទដែលត្រូវគ្នា។

Operatorលទ្ធផល
UNIONជួរក្នុង query ណាមួយ ដោយលុបជួរស្ទួន
UNION ALLជួរក្នុង query ណាមួយ ដោយរក្សាជួរស្ទួន (លឿនជាង)
INTERSECTជួរក្នុងទាំងពីរ query
EXCEPTជួរក្នុង query ទីមួយ ប៉ុន្តែមិនមែនទីពីរ

អតិថិជនមកពីភ្នំពេញ ឬ មាន order ដែលបានបង់ប្រាក់៖

SELECT name FROM customers WHERE city = 'Phnom Penh'
UNION
SELECT name FROM customers
WHERE id IN (SELECT customer_id FROM orders WHERE status = 'paid')
ORDER BY name;
    name
------------
 Linda Vong
 Sokha Chan
(2 rows)

ក្រុមទាំងពីរចៃដន្យជាមនុស្សពីរនាក់ដូចគ្នា។ UNION បានលុបជួរស្ទួន ដូច្នេះឈ្មោះនីមួយៗលេចឡើងម្តង។ ជាមួយ UNION ALL អ្នកនឹងឃើញបួនជួរ។

EXCEPT គឺជាវិធីមួយទៀតដើម្បីរកអតិថិជនដែលមិនធ្លាប់ order៖

SELECT id FROM customers
EXCEPT
SELECT customer_id FROM orders
ORDER BY id;
 id
----
  4
  5
(2 rows)

id អតិថិជនទាំងអស់ ដកពួកដែលលេចឡើងលើ order នៅសល់ 4 និង 5 ដែលជា Vuthy និង Maly ដដែលដូចមុន។ INTERSECT ធ្វើការផ្ទុយ៖ SELECT customer_id FROM orders WHERE status = 'paid' INTERSECT SELECT customer_id FROM orders WHERE status = 'cancelled' រកអតិថិជនដែលមានទាំង order ដែលបានបង់ប្រាក់ និង order ដែលបានលុបចោល។ ក្នុងទិន្នន័យរបស់យើង នោះគ្មាននរណាទេ៖ Sokha និង Linda មាន order ដែលបានបង់ប្រាក់ ហើយ Dara មាន order ដែលបានលុបចោល។

ចូលចិត្ត UNION ALL លុះត្រាតែអ្នកត្រូវការលុបជួរស្ទួនពិតប្រាកដ។ ការលុបជួរស្ទួនមានន័យថាតម្រៀប ឬ hash រាល់ជួរ ដែលចំណាយពេល។

13. សាកល្បងដោយខ្លួនឯង

ទាំងនេះប្រើទិន្នន័យ shopdb។ ទាយចម្លើយជាមុន បន្ទាប់មកដំណើរការវា។

  1. សរសេរ query ដោយប្រើ scalar subquery ដើម្បីបញ្ជីផលិតផលដែលថោកជាងតម្លៃមធ្យមនៃប្រភេទ Electronics។ (ប្រើ join ឬ subquery ដែលដាក់ក្នុងគ្នាដើម្បីបានមធ្យមភាគនោះ។) តើអ្នកសង្ឃឹមថាបានប៉ុន្មាន?
  2. ដោយប្រើ EXISTS បញ្ជីអតិថិជនដែលមានយ៉ាងហោចណាស់ order មួយដែលមានស្ថានភាព shipped។ បន្ទាប់មកសរសេរវាឡើងវិញដោយប្រើ IN។ តើទាំងពីរស្របគ្នាទេ?
  3. សរសេរ CTE ឈ្មោះ customer_revenue ដែលមានការចំណាយសរុបក្នុងមួយអតិថិជន (មិនរាប់ order ដែលបានលុបចោល)។ បន្ទាប់មក select អតិថិជនដែលមានចំណូលខ្ពស់បំផុតពីវា។ តើអតិថិជនណា ហើយចំនួនប៉ុន្មាន?
  4. ដោយប្រើ view order_totals បង្ហាញ order នីមួយៗជាមួយ rank() លើ total DESC ក្នុងមួយ customer_id។ តើ order ណាជា order ធំបំផុតរបស់អតិថិជននីមួយៗ?
  5. បង្ហាញផលិតផលនីមួយៗជាមួយ rank តម្លៃរបស់វាក្នុងប្រភេទរបស់វាដោយប្រើ rank() និង PARTITION BY category_id។ បន្ទាប់មកប្តូរ query ឲ្យត្រឡប់តែផលិតផលថោកបំផុតក្នុងប្រភេទនីមួយៗ។
  6. ដោយប្រើ lag(ordered_at) ដែលបែងចែកតាមអតិថិជន បង្ហាញសម្រាប់ order នីមួយៗនូវកាលបរិច្ឆេទនៃ order មុនរបស់អតិថិជននោះ។ តើ order ណាមានកាលបរិច្ឆេទមុនជា NULL ហើយហេតុអ្វី?

ទៅទីណាបន្ត។ ឥឡូវអ្នកអាចសួរស្ទើរតែសំណួរណាក៏បានអំពីទិន្នន័យ។ ប៉ុន្តែតើ table ខ្លួនឯងត្រូវបានរចនាបានល្អឬទេ? ក្នុង Normalization និង Schema Design អ្នកនឹងមើលថាហេតុអ្វី schema ត្រូវបានបំបែកដូចនេះ រៀន normal form ជាពាក្យសាមញ្ញ និងឃើញអ្វីដែលខុសពេល table សំប៉ែតធំតែមួយព្យាយាមធ្វើអ្វីៗទាំងអស់។

ទិញកាហ្វេជូនខ្ញុំ

ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!