New FAMILUG

The PyMiers

Sunday, 30 March 2025

Điều gì tệ hơn "mất bò mới lo làm chuồng"

Nửa tháng sau khi GitHub action tj-actions/changed-file bị hack, thế giới có gì thay đổi?

Xưa các cụ có câu "mất bò mới lo làm chuồng", nhằm phê phán việc không quan tâm đến chuồng của con bò. Nhưng điều tệ hơn là gì?

mất bò xong vẫn không làm chuồng

Trong ngành IT (hay việc gì cũng thế), thất bại là chỗ người ta rút kinh nhiệm để tiến bộ hơn, phát triển hơn hôm qua. Thôi thì qua đã mất bò thì làm chuồng để mai không mất nữa. Nhưng như mọi "kinh nghiệm", nó không được học qua "chuyện kể"/"lời khuyên" mà chỉ thực sự thấm nếu đã phải trả giá. Internet vẫn tiếp tục dùng GitHub action không pinning tới SHA1 commit hash...

Blog FAMILUG do 1 phút lười biếng cũng dùng GHA nelsonjchen/gh-pages-pelican-action@master, để rồi hôm nay trả giá 30 phút gỡ bỏ GHA này. GHA build blog FAMILUG giờ chỉ còn dùng action chính thức của GitHub. Mà nếu GitHub bị hack... thì mọi lo lắng khác đều không có nghĩa lý gì khi blog familug.github.io chạy trên GitHub page.

Thêm 1 điều thú vị rằng: GHA https://github.com/nelsonjchen/gh-pages-pelican-action cũng đã chuyển sang chế độ "archived" mà người dùng không hề hay biết:

This repository was archived by the owner on Mar 23, 2025. It is now read-only.

Kết luận

Cuối cùng cũng đã làm chuồng.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Thursday, 27 March 2025

Rust tuple, array, slice và compile time size

Hay làm thế nào để "stack overflow" trong Rust.

Tuple và array là 2 kiểu dữ liệu primitive compound trong Rust.

Compound types can group multiple values into one type. Compound type có thể gộp nhiều giá trị vào 1 kiểu.

Xem code online và làm theo tại playground.

Tuple

Tuple là 1 cách để gộp nhiều giá trị có kiểu khác nhau vào 1 kiểu compound (kiểu gộp?). Các phần tử của tuple nằm trong cặp (), phân cách nhau bằng dấu phẩy ,. Tuple hỗ trợ phép destructuring, để tách 1 tuple thành nhiều phần (tương tự Python unpacking). Truy cập từng phần tử theo thứ tự bằng cú pháp T.0 T.1 T.2...

fn main() {
    let tup = ("Pika", 42); // type (&str, i32)
    let (name, number) = tup;
    println!("Name: {name}: number {number}");
    println!("Name: {}", tup.0);
}
// Name: Pika: number 42
// Name: Pika

Tuple không chứa phần tử nào gọi là 1 unit (). Các biểu thức không trả về gì sẽ tự động return unit ().

Array

An array is a fixed-size sequence of N elements of type T. The array type is written as [T; N]. Array là một cách khác đểu gộp nhiều giá trị cùng kiểu thành 1 kiểu. Kiểu của array ký hiệu [T; N] với T là kiểu của phần tử, và N là số phần tử. Cú pháp sử dụng dấu ; ở đây rất lạ mắt, vì dấu ; vốn thưởng chỉ dùng để kết thúc 1 biểu thức (thường thấy cuối mỗi dòng). Đồng thời, dấu ; cũng dùng trong cú pháp để tạo array chứa N lần 1 giá trị (ở ví dụ sau là 6 lần số 3)

fn main() {
    let a1: [i32; 5] = [2,3,5,8,13];
    let a2 = [3;6];
    println!("a1: {a1:?}");
    println!("a2: {a2:?}");
    println!("a2 first: {}", a2[0]);      
}
// a1: [2, 3, 5, 8, 13]
// a2: [3, 3, 3, 3, 3, 3]
// a2 first: 3

Rust compiler có thể phát hiện các trường hợp out of bounds (OOB) đơn giản khi sử dụng index là các số / phép toán đơn giản + - * / Compile output

    error: this operation will panic at runtime
  --> main.rs:12:29
   |
12 |     println!("a1 some: {}", a1[4+2-1]);
   |                             ^^^^^^^^^ index out of bounds: the length is 5 but the index is 5

nhưng không thể phát hiện khi index là các phép tính phức tạp như lũy thừa, hay gọi function, chương trình sẽ panic (runtime error) và tắt.

    // như trên 
    println!("a2 some: {}", a2[4usize.pow(2)]);                                                               

Run output

$ RUST_BACKTRACE=1 ./main
...
thread 'main' panicked at main.rs:13:29:
index out of bounds: the len is 6 but the index is 16
stack backtrace:
   0: rust_begin_unwind
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:665:5
   1: core::panicking::panic_fmt
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/panicking.rs:74:14
   2: core::panicking::panic_bounds_check
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/panicking.rs:276:5
   3: main::main

Để lấy 1 tập con của array, thử dùng cú pháp :

14 |     a1[2..4];
   |     ^^^^^^^^ the size of `[i32]` cannot be statically determined

Thấy có [i32] gần giống như kiểu của array, nhưng không có số lượng phần tử. Vì vậy kích thước của a1[2..4] không thể biết được khi compile, nên không compile được. Có thể cho rằng Rust dễ dàng tính toán được số phần tử là 4-2 = 2, nhưng việc tính toán này không khả thi nếu các số này là a..b với giá trị được tính toán sử dụng các phép toán phức tạp, vậy nên tốt nhất là không compile.

Stack

tuple và array là 2 kiểu primitive và chúng được chứa trong stack của chương trình nên cần có kích thước (size) cố định, không đổi, biết lúc compile (statically determined). Các kiểu khác như vector, map, set được chứa trên heap nên có kích thước tùy ý, thay đổi được lúc chạy (runtime).

Stack overflow in Rust

Stack của chương trình trên Linux có kích thước mặc định là:

$ ulimit --stack-size
8192

8192KB hay 8MB.

let aso = [1i64;1024*1000];  // OK

Một giá trị kiểu i64 có kích thước là 64bits == 8 bytes, tạo 1 array với 1048576 (1024*1024) phần tử sẽ dẫn tới stack overflow:

16 |     let aso = [1i64;1024*1024];
   |
thread 'main' has overflowed its stack
fatal runtime error: stack overflow

Slice

Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection. A slice is a kind of reference, so it does not have ownership.

Slice refer (trỏ - tương tự pointer/con trỏ) tới 1 chuỗi các phần tử trong 1 tập hợp hay vì cả tập hợp. Nó chứa:

  • địa chỉ của phần tử đầu tiên (trên máy 64bit có kích thước là 64bits == 8bytes)
  • kích thước của slice (kiểu usize, trên máy 64bit có kích thước là 64bits == 8bytes)

nên kiểu slice có kích thước biết khi compile.

In Rust, a slice on a 64-bit system typically has a size of 16 bytes

Trên máy 64-bit, 1 slice có kích thước 16 bytes.

Cú pháp: &a1[2..4], kiểu &[i32]

    let s: &[i32] = &a1[2..4];
    println!("{:?}", s);
    // [5, 8]

Kết luận

  • Tuple để chứa các giá trị khác loại/kiểu, truy cập bằng T.index.
  • Array có kiểu [T;N] với dấu ; kì lạ, chỉ hỗ trợ check OOB đơn giản và hoàn toàn có thể bị Stack Overflow.
  • Slice luôn biết trước size vì chỉ chứa 2 thông tin.

Tham khảo

https://github.com/rust-lang/book/blob/async-2024/src/ch03-02-data-types.md

Xem bài giới thiệu tổng quan về Rust tại https://pp.pymi.vn/article/aoc2021/.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Wednesday, 19 March 2025

[TIL] Bài học từ vụ GitHub Action tj-actions/changed-file bị hack

Ngày 14/3/2025, cộng đồng mạng hoảng loạn vì 1 GitHub Action có tên tj-actions/changed-file bị hack, thay đổi code, để in ra các token dùng khi chạy GitHub action. Lý do hoảng loạn: có tới hơn 23 nghìn repo sử dụng action này... để tìm các file đã đổi trong pull request.

Bài viết report sự việc: https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised

Các vấn về GitHub Action

Có rất nhiều vấn đề khi nhìn lại ở đây, đơn giản như:

  • tj-actions là ai?
  • gõ mấy câu lệnh để tìm được file đã thay đổi trong 1 git pull request?
  • uses: tj-actions/changed-files@v45 đã cố định ở v45?

git tag không cố định (mutable)

@v45 là sử dụng git tag v45. v45 là 1 git tag, và có thể thay đổi, có thể point tới commit khác. Theo git tag --help:

On Re-tagging What should you do when you tag a wrong commit and you would want to re-tag? .. 2. The insane thing. You really want to call the new version "X" too, even though others have already seen the old one. So just use git tag -f again, as if you hadn’t already published the old one.

Giải pháp phòng chống

Pinning to SHA commit hash

Vì vậy, để cố định 1 version (gọi là pinning version, phổ biến trong mọi ngôn ngữ lập trình), Theo tài liệu security best practice của GitHub Action:

You can help mitigate this risk by following these good practices:

Pin actions to a full length commit SHA

Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. When selecting a SHA, you should verify it is from the action's repository and not a repository fork.

Tức là viết:

uses: tj-actions/changed-files@48d8f15b2aaa3d255ca5af3eba4870f807ce6b3c #v45

fork action repo

1 giải pháp khác là fork github repo và sử dụng tại repo của mình thay vì repo gốc.

không dùng action

Nếu trường hợp không quá phức tạp, có thể viết lệnh git như git diff main --name-only để lấy các file thay đổi thay vì dùng github action.

https://git-scm.com/docs/git-diff#Documentation/git-diff.txt-code--name-onlycode

Điều may mắn

vì 1 lý do nào đó, hacker chỉ in token ra mà không gửi nó tới 1 server để thu thập "bí mật" trên toàn cầu.

Kết luận

git tag không cố định, hãy pin như pin dependency khi code.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

[TIL] podman có thể tạo ra systemd unit config file

podman - chương trình phổ biến ngày nay để thay thế docker, có tính năng sinh systemd unit configuration file giúp deploy container 1 cách thuận tiện.

$ podman run -p 8000:80 docker.io/nginx
$ podman ps
CONTAINER ID  IMAGE                           COMMAND               CREATED             STATUS                 PORTS                 NAMES
2d81df8a1a11  docker.io/library/nginx:latest  nginx -g daemon o...  About a minute ago  Up About a minute ago  0.0.0.0:8000->80/tcp  wizardly_ptolemy
# ở cửa sổ khác
$ podman generate systemd 2d81df8a1a11
# container-2d81df8a1a117e290052df6e18f592ddb21f83b1b159169cc3391f605aaa47a7.service
# autogenerated by Podman 3.4.4
# Wed Mar 19 20:08:06 JST 2025

[Unit]
Description=Podman container-2d81df8a1a117e290052df6e18f592ddb21f83b1b159169cc3391f605aaa47a7.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/run/user/1000/containers

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman start 2d81df8a1a117e290052df6e18f592ddb21f83b1b159169cc3391f605aaa47a7
ExecStop=/usr/bin/podman stop -t 10 2d81df8a1a117e290052df6e18f592ddb21f83b1b159169cc3391f605aaa47a7
ExecStopPost=/usr/bin/podman stop -t 10 2d81df8a1a117e290052df6e18f592ddb21f83b1b159169cc3391f605aaa47a7
PIDFile=/run/user/1000/containers/overlay-containers/2d81df8a1a117e290052df6e18f592ddb21f83b1b159169cc3391f605aaa47a7/userdata/conmon.pid
Type=forking

[Install]
WantedBy=default.target

copy file này vào /lib/systemd/system/xyz.service , systemctl daemon-reload, systemctl enable xyz; systemctl start xyz là xong.

Kết luận

Không chỉ thay thế docker, podman còn bảo mật (ko chạy với root) và tiện dụng hơn nhiều.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Wednesday, 12 March 2025

[clojure] update map với cond->

Kiểu dữ liệu map trong clojure tương tự dict trong Python. Do Clojure mọi kiểu dữ liệu đều là immutable, nên mọi thao tác với map đều trả về 1 map mới. Để update 1 map dựa theo điều kiện sẽ khá phức tạp cho đến khi dùng cond-> macro. Ví dụ code Python tương ứng

options = {"max-keys": 100}

if 1 == 1:
    options["next-token"] = "abcd"
if 1 != 1:
    print("won't run")
if 5 > 3:
    options["other-op"] = "hehe"

def call_api(url, options):
    print(url, options)

call_api("https://url", options)
# https://url {'max-keys': 100, 'next-token': 'abcd', 'other-op': 'hehe'}

Macro cond-> nhận vào các cặp điều kiện - function để chạy khi điều kiện đúng, sử dụng để update dict:

(def init-options {:max-keys 100})
(def options (cond->
                 options
               (= 1 1) (assoc :next-token "abcd")
               (not= 1 1) (prn "won't run")
               (> 5 3) (assoc :other-op "hehe")))

(defn call-api [url options]
  (println "calling " url "with options: " options))

(call-api "https://url" options)
;; calling  https://url with options:  {:max-keys 100, :next-token abcd, :other-op hehe}

Đầu vào đầu tiên của cond-> là giá trị ban đầu options, theo sau là các cặp điều kiện:

nếu (= 1 1) thì gọi (assoc optíons :next-token "abcd") để thêm cặp key-value vào map options, kết quả này lại được gán tiếp làm đầu vào cho function (prn KETQUA "wont'run"), nhưng điều kiện (not= 1 1) là false nên function không được chạy, và tiếp tục...

Toàn bộ phần (cond-> ...) có thể đưa trực tiếp vào trong (call-api ):


(defn call-api [url options]
  (println "calling " url "with options: " options))

(call-api "https://url"
          (cond->
            {:max-keys 100}
            (= 1 1) (assoc :next-token "abcd")
            (not= 1 1) (prn "won't run")
            (> 5 3) (assoc :other-op "hehe")))

Code Python liệu có thể viết trong 1 dòng như vậy?

Ứng dụng

Khi option được gọi sẽ thay đổi theo điều kiện, ví dụ gọi API khi cần paginate sang trang, dùng cond-> để update options gọi trang tiếp theo.

(def next-page-token "XYZ")
(call-api "https://url"
          (cond-> 
            init-options 
            (some? next-page-token) (assoc :next-page-token next-page-token)))

Source

Trong REPL

user=>
(source cond->)
(defmacro cond->
  "Takes an expression and a set of test/form pairs. Threads expr (via ->)
  through each form for which the corresponding test
  expression is true. Note that, unlike cond branching, cond-> threading does
  not short circuit after the first true test expression."
  {:added "1.5"}
  [expr & clauses]
  (assert (even? (count clauses)))
  (let [g (gensym)
        steps (map (fn [[test step]] `(if ~test (-> ~g ~step) ~g))
                   (partition 2 clauses))]
    `(let [~g ~expr
           ~@(interleave (repeat g) (butlast steps))]
       ~(if (empty? steps)
          g
          (last steps)))))
nil

Kết luận

cond-> macro giúp viết code update kiểu dữ liệu theo các điều kiện một cách sạch đẹp hơn.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Tuesday, 11 March 2025

Tổng hợp tài liệu tự học tiếng Nhật giao tiếp N5 N4

Bài này tổng hợp các tài liệu học tiếng Nhật trên máy tính/ điện thoại, bằng cả tiếng Việt và tiếng Anh, miễn phí lẫn có phí.

banh chung va mat troi

Hai bảng chữ cái Hiragana, Katagana

Học ngôn ngữ thì việc đầu tiên rõ ràng là cần thuộc bảng chữ cái, không thẻ nhìn vào cả bảng rồi "học" mà sẽ nhớ dần từng chữ qua 3-6 tháng trở đi.

Các tài liệu khác https://www.tofugu.com/japanese/best-hiragana-and-katakana-learning-resources/

Từ điển

Jisho https://jisho.org/ có thể tra cứu bằng tiếng Anh lẫn tiếng Nhật (kana & kanji)

Các mẫu câu hội thoại kèm nghe đọc

App

Anki - Free for android, $$ for iOS, free on Desktop

Homepage https://apps.ankiweb.net/. Sau khi tải app vào tìm trong "Shared Deck". Các "Deck"

Renshuu - free on Android & iOS

Free app học từ vựng, Kanji và ngữ pháp căn bản https://www.renshuu.org/

Tài liệu tập đọc/nghe đơn giản

Ngữ pháp

Eng: Website https://www.tofugu.com/japanese-grammar/ có các bài viết chi tiết về ngữ pháp. Nhưng các tài liệu trên đều có xen 1 phần ngữ pháp vào rồi

Sách

Vie: Quyển sách học tiếng Nhật bán chạy số 1 HÀN QUỐC lại bán ở Việt Nam ???!! https://megabook.vn/tu-hoc-tieng-nhat dễ xem, có màu, có tiếng, vui vui.

Film & Anime

và không thế thiếu việc "học tập" qua các bộ film/anime của Nhật bản.

Kết luận

Tự học là một cách tốt để tiết kiệm tiền, nhưng tốn thời gian, mà thời gian là vàng là bạc, nên cách tiết kiệm này có lãi suất âm.

Nhưng vẫn hơn là không.

Happy watching JaNime without sub after... 2 years++ maybe? :haha:

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Monday, 10 March 2025

[TIL] Argo Rollouts có nút bấm restart trên giao diện ArgoCD

Argo CD là gì

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. https://argoproj.github.io/cd/

Argo CD và FluxCD là 2 lựa chọn phổ biến để thực hiện "Continuous Delivery" (CD) trên Kubernetes. Argo CD có giao diện nên thường được ưa chuộng hơn.

Argo Rollouts là gì

Argo Rollouts is a Kubernetes controller and set of CRDs which provide advanced deployment capabilities such as blue-green, canary, canary analysis, experimentation, and progressive delivery features to Kubernetes. https://argoproj.github.io/argo-rollouts/

Kubernetes Deployment mặc định hỗ trợ strategy deploy RollingUpdate, nhưng chiến thuật này có nhiều hạn chế. Argo Rollouts hỗ trợ nhiều chiến thuật deploy như blue-green, canary với nhiều tính năng phức tạp hơn.

Restart rollout

Khi cần restart các pod trong 1 rollout, các cách làm:

  • xóa từng pod đi để replicaset sẽ tạo lại pod mới (không khả thi với số lượng pod lớn, bấm mỏi tay)
  • xóa replicaset đi để rollout tạo lại replicaset mới
  • restart rollout: đây là 1 tính năng của Argo Rollouts, nó thậm chí có cả 1 nút bấm trong menu của rollout object trên giao diện Argo UI. https://argoproj.github.io/argo-rollouts/features/restart/#how-it-works

How it works

During a restart, the controller iterates through each ReplicaSet to see if all the Pods have a creation timestamp which is newer than the restartAt time. For every pod older than the restartAt timestamp, the Pod will be evicted, allowing the ReplicaSet to replace the pod with a recreated one.

Code:

func (p *RolloutPodRestarter) Reconcile(roCtx *rolloutContext) error {
  ...
    restartedAt := roCtx.rollout.Spec.RestartAt
    needsRestart := 0
    restarted := 0
    for _, pod := range rolloutPods {
        if pod.CreationTimestamp.After(restartedAt.Time) || pod.CreationTimestamp.Equal(restartedAt) {
            continue
        }
        needsRestart += 1
        if canRestart <= 0 {
            continue
        }
        if pod.DeletionTimestamp != nil {
            continue
        }
        newLogCtx := logCtx.WithField("Pod", pod.Name).WithField("CreatedAt", pod.CreationTimestamp.Format(time.RFC3339)).WithField("RestartAt", restartedAt.Format(time.RFC3339))
        newLogCtx.Info("restarting Pod that's older than restartAt Time")
        evictTarget := policy.Eviction{
            ObjectMeta: metav1.ObjectMeta{
                Name:      pod.Name,
                Namespace: pod.Namespace,
            },
        }
        err := p.client.CoreV1().Pods(pod.Namespace).Evict(ctx, &evictTarget)
    ...

github.com/argoproj/argo-rollouts

Kết luận

Tính năng nhiều khi có ở đó, nhưng không dễ phát hiện ra, người dùng thường làm theo "bản năng" thay vì đọc doc.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Wednesday, 5 March 2025

Luôn test ít nhất 3 case

Khi viết 1 tool hay viết code, một câu hỏi có thể nảy sinh là: test bao nhiêu trường hợp cho đủ? Câu trả lời luôn nhận được là: càng nhiều càng tốt!

a peace can

Nhưng test ít nhất là bao nhiêu? 1 2 3 4? bài viết này xin đưa ra 1 con số kèm theo lý do: 3.

Giả sử đang viết 1 tool cli kiểm tra syntax của file yaml/json. Tạm gọi là checker. checker nhận vào argument là các file cần check.

3 case chụm lại nên hòn đá to

Tại sao lại là 3? vì 3 gồm những trường hợp sau:

  • 0
  • 1

0 và 1 phân biệt giữa không và có.

  • 1
  • 2 trở lên

1 và 2 (trở lên) phân biệt giữa ít và nhiều. Tool hỗ trợ nhận 1 file đầu vào HẦU HẾT chưa chắc nhận 2 (vd: --config).

Ngoài ra, có thể thêm test case thứ 4 khi bài toán có số N là max thì hãy test N.

0 - trường hợp luôn đặc biệt

checker nhận 0 đầu vào: chương trình sẽ làm gì? fail với yêu cầu phải có đầu vào? hay test tất cả các file trong thư mục hiện tại (thường chọn cách này)

1 khác với nhiều

Trong tiếng Anh, 1 bottle không có s, nhiều bottles thì có s. Khi test các file cần thử 2 file trở lên để tránh việc:

  • checker file1 : trường hợp 1 input, chạy ok
  • checker file1 file2: chương trình có thể chi đọc vào file1 và bỏ qua file2, hoặc chỉ xử lý file2 bỏ qua file 1.
  • checker 'file1 file2': script gọi chương trình lẽ ra gọi với 2 argument thì lại đưa vào 1 string. Cần sửa thành checker file1 file2.

99 bottles of beer on the wall

Bài toán phổ biến trong lập trình: 99 chai bia trong két cũng cần test đủ với các trường hợp:

  • nhiều chai
  • chỉ còn 1 chai
  • hết bia

Xem đề và 1500 loại code ở http://www.99-bottles-of-beer.net/lyrics.html

Kết luận

Dù viết code hay viết tool, luôn test tối thiểu 3 case mà nhiều hơn thì là 4:

  • 0
  • 1
  • random.choice(2 -> N-1)
  • N

Happy testing.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Tuesday, 4 March 2025

[TIL] SQL join có cả cross join

Một bức hình đáng giá ngàn lời nói, bức vẽ SVG này copy từ tài liệu của sqlite https://www.sqlite.org/syntax/join-operator.html

3 loại JOIN, 3 loại OUTER JOIN

NATURAL LEFT OUTER JOIN , RIGHT FULL INNER CROSS

Có 3 loại join:

  • (LEFT|RIGHT|FULL) [OUTER]
  • INNER
  • CROSS

Ví dụ cross join

$ sqlite3
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> -- Create temporary tables for our sets
WITH set1 AS (
  SELECT 1 AS a UNION ALL
  SELECT 2 UNION ALL
  SELECT 3
),
set2 AS (
  SELECT 4 AS b UNION ALL
  SELECT 5 UNION ALL
  SELECT 6
)

-- Perform cross join to create Cartesian product
SELECT set1.a, set2.b
FROM set1
CROSS JOIN set2
ORDER BY set1.a, set2.b;
1|4
1|5
1|6
2|4
2|5
2|6
3|4
3|5
3|6

Kết luận

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺