Category: [บันทึกกันลืม]

  • วิธี github clone มาเฉพาะบาง Folder

    ถ้าต้องการเฉพาะบาง folder เช่นจากของ Google Research

    https://github.com/google-research/google-research

    Google Research มีหลาย Project ย่อย ถ้า Clone มาทั้งหมด ก็เยอะอยู่

    แต่เราต้องการเพียงแค่ project ย่อยเดียว เช่น tft (Temporal Fusion Transformer)

    วิธีการมีดังนี้

    0. ต้องใช้ git version ใหม่ ๆ ในที่นี้ผมใช้ 2.32.0

    # git --version
    git version 2.32.0

    1. จะมี sparse-checkout ซึ่งทำให้กำหนดได้ว่า ต้องการ folder ใด ให้ใช้คำสั่งแรกคือ

    git sparse-checkout init

    2. จากนั้น กำหนดว่า ต้องการเฉพาะ folder ชื่อ tft

    git sparse-checkout set 'tft/'

    3 . จากนั้นก็กำหนด remote repository ด้วยคำสั่ง

    git remote add origin https://github.com/google-research/google-research

    4 . แล้วก็ pull ด้วยคำสั่ง

    git pull origin master

    รอสักครู่ เราก็จะได้เฉพาะ folder ที่ต้องการ

    หวังว่าจะเป็นประโยชน์ครับ

  • วิธีแก้ปัญหา docker container ไม่ start cron อัตโนมัติ

    ปัญหา

    เวลาสร้าง docker container เพื่อ run งานแบบอัตโนมัติ เราก็จำเป็นต้องใช้ cron แต่ว่า เจ้า docker container images เนี่ย จะเป็นรุ่นตัดทุกอย่างที่ไม่จำเป็นออกหมด เอาไว้ให้ start – run – stop แต่ถึงกระนั้น เราก็ยังอยากได้การทำงานแบบ cron อยู่ดี

    Reproduce

    สร้าง docker container จาก ubuntu:20.04

    docker run -d -it  --name kxtest ubuntu:20.04

    ตรวจสอบว่ามี crontab หรือไม่ ก็ไม่มี

    docker exec -it kxtest bash
    
    root@93c827f2f9fc:/# crontab -l
    bash: crontab: command not found
    root@93c827f2f9fc:/# 

    /var/log/syslog มีไม๊ ก็ไม่มี

    root@93c827f2f9fc:/# ls -l /var/log/syslog
    ls: cannot access '/var/log/syslog': No such file or directory

    systemctl มีไม๊ ก็ไม่มี

    root@93c827f2f9fc:/# systemctl
    bash: systemctl: command not found

    แก้ไข

    ติดตั้ง 3 อย่าง cron, systemctl, rsyslog แล้ว ขอแถม vim ด้วย

    apt update ; apt install -y cron systemctl rsyslog vim

    ติดตั้งแล้ว ลองตรวจสอบ ก็พบว่า crontab มาแล้ว, systemctl มาแล้ว ส่วน /var/log/syslog ยังไม่ได้สร้าง

    root@93c827f2f9fc:/# crontab -l
    no crontab for root
    root@93c827f2f9fc:/# systemctl      
    apt-daily-upgrade.service	loaded inactive dead	Daily apt upgrade and clean activities
    apt-daily-upgrade.timer	loaded inactive dead	Daily apt upgrade and clean activities
    apt-daily.service	loaded inactive dead	Daily apt download activities
    apt-daily.timer	loaded inactive dead	Daily apt download activities
    cron.service	loaded inactive dead	Regular background program processing daemon
    dmesg.service	loaded inactive dead	Save initial kernel messages after boot
    e2scrub@.service	loaded inactive dead	Online ext4 Metadata Check for 
    e2scrub_all.service	loaded inactive dead	Online ext4 Metadata Check for All Filesystems
    e2scrub_all.timer	loaded inactive dead	Periodic ext4 Online Metadata Check for All Filesystems
    e2scrub_fail@.service	loaded inactive dead	Online ext4 Metadata Check Failure Reporting for 
    e2scrub_reap.service	loaded inactive dead	Remove Stale Online ext4 Metadata Check Snapshots
    fstrim.service	loaded inactive dead	Discard unused blocks on filesystems from /etc/fstab
    fstrim.timer	loaded inactive dead	Discard unused blocks once a week
    hwclock.sh.service	loaded inactive dead	
    logrotate.service	loaded inactive dead	Rotate log files
    logrotate.timer	loaded inactive dead	Daily rotation of log files
    motd-news.service	loaded inactive dead	Message of the Day
    motd-news.timer	loaded inactive dead	Message of the Day
    procps.service	loaded inactive dead	Loads kernel parameters that are specified in /etc/sysctl.conf
    rsyslog.service	loaded inactive dead	System Logging Service
    
    20 loaded units listed.
    To show all installed unit files use 'systemctl list-unit-files'.
    root@93c827f2f9fc:/# ls -l /var/log/syslog
    ls: cannot access '/var/log/syslog': No such file or directory

    ลอง start cron และ rsyslog ดู ก็พบว่า /var/log/syslog มาแล้ว

    root@93c827f2f9fc:/# systemctl start cron
    root@93c827f2f9fc:/# systemctl status cron
    cron.service - Regular background program processing daemon
        Loaded: loaded (/usr/lib/systemd/system/cron.service, disabled)
        Active: active (running)
    root@93c827f2f9fc:/# systemctl start rsyslog
    rsyslog.service - System Logging Service
        Loaded: loaded (/usr/lib/systemd/system/rsyslog.service, disabled)
        Active: active (running)
    root@93c827f2f9fc:/# ls -l /var/log/syslog
    -rw-r----- 1 syslog adm 517 Oct 10 01:36 /var/log/syslog

    ทดสอบสร้าง cron

    root@93c827f2f9fc:/# crontab -e

    ทำความเข้าใจก่อน crontab เอาไว้สร้าง cron ส่วนตัวของแต่ละ user

    แต่ที่ /etc/cron.d/ นั้น เอาไว้สร้างสำหรับ system

    2 วิธีการสร้าง cron นี้แตกต่างกันที่ crontab ไม่สามารถระบุผู้ใช้ได้ แต่ใน /etc/cron.d/ นั้น สามารถระบุ user ที่จะ run ได้

    ต่อไป จะสร้าง cron ง่าย ๆ ให้ echo เวลา ทุก 1 นาทีลงไฟล์

    root@93c827f2f9fc:~# echo 'echo $(date "+%Y%m%d-%H%M%S") >> test.log' > ~/testcron.sh ; chmod +x ~/testcron.sh
    root@93c827f2f9fc:~# crontab -e

    แล้วใส่ บรรทัดนี้ลงไป

    * * * * * /usr/bin/bash ~/testcron.sh

    ดูใน /var/log/syslog แล้ว ก็พบว่า ทำงานได้

    Oct 10 01:56:39 93c827f2f9fc crontab[1631]: (root) BEGIN EDIT (root)
    Oct 10 01:56:42 93c827f2f9fc crontab[1631]: (root) END EDIT (root)
    Oct 10 01:57:01 93c827f2f9fc CRON[1638]: (root) CMD (/usr/bin/bash ~/testcron.sh)

    ดูผลใน -/test.log

    root@93c827f2f9fc:~# cat ~/test.log 
    20211010-015701
    20211010-015801
    20211010-015901

    เป็นอันว่า OK ต่อไป ดูว่า ถ้า restart docker แล้ว cron จะทำงานไม๊ ?

    ทดสอบ restart docker container

    ออกจาก bash ของ docker แล้วใช้คำสั่ง

    docker restart kxtest

    รอสัก 3 นาที แล้วใช้คำสั่ง

    docker exec -it kxtest bash -c "date ; cat ~/test.log"

    ผลที่ได้คือ อ้าว cron ไม่ทำงานต่อ

    Sun Oct 10 02:02:33 UTC 2021
    20211010-015701
    20211010-015801
    20211010-015901

    มาดูเหตุกันก่อน

    เราต้องไปดู รากเหง้าของ ubuntu:20.04 image

    https://hub.docker.com/_/ubuntu

    Docker ทำงานโดยจะทำเพียงคำสั่งเดียว การสร้าง docker image จะสร้างจาก Dockerfile ซึ่ง ubuntu:20.04 มี Dockerfile ดังนี้

    FROM scratch
    ADD ubuntu-focal-oci-amd64-root.tar.gz /
    CMD ["bash"]

    แสดงว่า docker container ที่ใช้ ubuntu:20.04 (ตัวอื่น ๆ ก็ต้องตามไปดูกัน) ก็จะเรียก Bash เป็นคำสั่งสุดท้าย ก่อนจะยืนเป็น Service ให้ใช้งาน

    ดังนั้น … เราก็ต้องไปทำให้ start cron service ใน .bashrc กัน

    ประเด็นคือ มัน start ด้วย user ไหน ? ลองใช้คำสั่งนี้

    docker exec -it kxtest bash -c "id"
    
    uid=0(root) gid=0(root) groups=0(root)

    ก็แสดงว่า สำหรับ image ตัวนี้ run ด้วย root นั่นเอง

    วิธีแก้ไข ให้ cron ทำงานทุกครั้งที่ restart docker

    เข้าไปใน docker container ด้วยคำสั่ง

    docker exec -it kxtest bash

    จากนั้น แก้ไขไฟล์ ~/.bashrc เพิ่มบรรทัดนี้ลงไปที่ท้ายไฟล์

    systemctl start cron.service

    save แล้วออกมาจาก container

    แล้ว docker restart

    docker restart kxtest

    ดูผลลัพธ์

    ดูว่า cron ทำงานเลยไม๊

    docker exec -it kxtest bash -c "date ; systemctl status cron"
    Sun Oct 10 03:01:17 UTC 2021
    cron.service - Regular background program processing daemon
        Loaded: loaded (/usr/lib/systemd/system/cron.service, enabled)
        Active: active (running)
    

    ดูว่า ผลการทำงานเป็นอย่างไร

    docker exec -it kxtest bash -c "date ; cat ~/test.log"
    
    Sun Oct 10 03:02:05 UTC 2021
    20211010-015701
    20211010-015801
    20211010-015901
    20211010-030101
    20211010-030201

    เย้ ทำงานแล้ว

    UPDATE

    สำหรับ กรณีใช้ docker image อย่าง JupyterHub หรือ มันก็คือ Jupyter Notebook แบบที่ทำงานได้พร้อมกันหลายคน (แยก Login) ผมลองแล้ว สามารถใช้วิธีนี้ครับ

    อ้างอิงจาก Dockerfile ที่ https://hub.docker.com/r/jupyterhub/jupyterhub/dockerfile

    จะเห็นได้ว่า CMD ตัวสุดท้ายคือ [“jupyterhub”]

    เราสามารถไปสั่งให้ start cron ได้โดย

    docker exec -it myjupyterhub bash
    vi $(which jupyterhub)

    แล้วแก้ไข Code เพิ่มนิดเดียว จากของเดิม

    import re
    import sys
    from jupyterhub.app import main
    if __name__ == '__main__':
        sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
        sys.exit(main())

    เป็น

    import re
    import sys
    from jupyterhub.app import main
    if __name__ == '__main__':
        sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
        import subprocess
        subprocess.run(["service", "cron", "start"])
        sys.exit(main())

    จากนั้น restart ได้เลยครับ

    หวังว่าจะเป็นประโยชน์ครับ

  • วิธีแก้ปัญหา Let’s Encrypt กับ Root Certificate Expire ในวันที่ 30 กันยายน 2021

    ปัญหา เช้านี้ (30 กันยายน 2021) พบว่า อยู่ ๆ เว็บเซิร์ฟเวอร์ที่ให้บริการ ซึ่งใช้ Let’s Encrypt ก็ ขึ้นหน้า “This connection is not private” เป็นเฉพาะกับ เครื่อง Mac, iPhone, iPad ไม่ว่าจะใช้ Safari หรือ Chrome ก็ตาม

    เอ่ เพิ่ง Renew ไปเมื่อเดือนที่แล้ว เกิดอะไรขึ้น

    ลองคลิกที่รูป Key แล้ว View Certificate พบว่า

    อ้าว … ไรว้า

    ก็ Google ดู พบว่า Root CA ของ Let’s Encrypt “รุ่นเก่า” ทะยอยหมดอายุ

    https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/

    กล่าคือ ถ้าของใคร Root CA (บนสุดของภาพ) เป็น

    DST Root CA X3

    จะทำให้การใช้งานจาก Web Browser บน Apple ใช้งานไม่ได้

    ก็ restart ดู ก็ยังไม่หาย เอ่ ทำไมนะ

    ขอ Renew ใหม่ ก็ยังไม่หาย

    หาไปหามา ไปพบ

    https://community.letsencrypt.org/t/r3-certificate-expired-macos/160852

    เค้าบอกว่า ลองดูซิ ว่า Root CA ตัวใหม่ที่ได้มา เป็นของอะไร ด้วยคำสั่ง

    openssl crl2pkcs7 -nocrl -certfile "fullchain.pem" | openssl pkcs7 -noout -print_certs

    คำสั่งนี้ ทำที่เดียวกันกับไฟล์ fullchain.pem

    พบว่า

    issuer=C = US, O = Let's Encrypt, CN = R3
    
    
    subject=C = US, O = Let's Encrypt, CN = R3
    
    issuer=C = US, O = Internet Security Research Group, CN = ISRG Root X1
    
    
    subject=C = US, O = Internet Security Research Group, CN = ISRG Root X1
    
    issuer=O = Digital Signature Trust Co., CN = DST Root CA X3

    เอ่ …. ก็ได้ Root CA เป็น ISRG Root X1 แล้วนะ ทำไมยังใช้ไม่ได้

    ไปตรวจสอบดู httpd.conf

    อ้อ …

    <VirtualHost *:443>
       SSLEngine on
       SSLCertificateFile /etc/ssl/letsencrypt/cert.pem
       SSLCertificateKeyFile /etc/ssl/letsencrypt/privkey.pem
    </VirtualHost>

    เราอ้างอิงแค่ cert.perm

    วิธีแก้ไขคือ ไปเพิ่ม

       SSLCertificateChainFile /etc/ssl/letsencrypt/fullchain.pem

    ให้เป็นแบบนี้

    <VirtualHost *:443>
       SSLEngine on
       SSLCertificateChainFile /etc/ssl/letsencrypt/fullchain.pem
       SSLCertificateFile /etc/ssl/letsencrypt/cert.pem
       SSLCertificateKeyFile /etc/ssl/letsencrypt/privkey.pem
    </VirtualHost>

    จากนั้น ก็ restart

    เรียบร้อย

    หวังว่าจะเป็นประโยชน์ครับ
  • [บันทึกกันลืม] วิธีทำให้ jupyter notebook สามารถ export to PDF ได้

    บน OS ต้องติดตั้ง

    apt-get install -y texlive-xetex texlive-fonts-recommended texlive-latex-recommended pandoc

    แค่นั้น

  • วิธีรัน Jupyter Notebook, ปิด browser แล้วระบบส่ง LINE บอกพร้อมภาพ เมื่อเสร็จแล้ว ค่อยกลับมาดูผล

    ปัจจุบัน งานด้าน Data Science ก็มักจะใช้ Jupyter Notebook เพราะสะดวกในการทดลอง ทดสอบ ทำทีละบรรทัด ดูผล ปรับแต่งไปได้เรื่อย ๆ แถมสามารถซื้อ Server ส่วนกลาง ลงทุน GPU แล้วใช้พร้อม ๆ กันหลาย ๆ คนทั้งทีมก็ได้

    ปัญหาอยู่ตรงที่ การสร้าง Model มักจะใช้เวลานาน (มาก) แล้ว Jupyter มันเป็น Web-based จะปิด Browser ก็ได้ แต่หลายคนคงเคยเจอว่า พอกลับมาเปิด URL เดิม ก็ไม่เห็นผลที่รันแล้ว

    จริง ๆ แล้วคือ Jupyter Notebook นั้น ทำงานต่อไป จนเสร็จแล้วแหล่ะ แต่ เว็บ Browser น่าจะไม่สามารถต่อ Session ได้ (หวังว่าจะนึกภาพออก) ที่สำคัญ มันรันนานมาก เสร็จเมื่อไหร่ก็ไม่รู้ แล้วจะรู้ได้อย่างไรว่าเสร็จ เสร็จแล้วผลเป็นอย่างไร

    ในบทความนี้ จะนำเสนอวิธีการที่ทำให้

    1. สั่งรันงานที่ใช้เวลานาน (ขอยกตัวอย่าง 100 วินาที) แล้วปิด Browser ไปทำอย่างอื่นต่อได้เลย
    2. เมื่อระบบทำงานเสร็จ จะแจ้ง LINE Notify พร้อมแนบภาพ Graph ผลลัพธ์ ส่งมาด้วย
    3. พอกลับมาเปิด Browser กลับมาใน Jupyter Notebook เดิม สามารถดูผลการรันอื่น ๆ ได้อีกครั้งเหมือนกับไม่ได้ปิด Browser
    ภาพรวม

    เริ่มต้นจาก Import

    import sys
    from IPython.display import clear_output
    import logging
    import sys
    import requests
    import datetime,time,pytz
    import random

    Function ในการส่ง LINE Notify

    เป็นส่วนของ LINE TOKEN และ function “jobdone” ไว้ส่ง LINE Notify สามารถกำหนดข้อความ และกำหนดภาพที่จะแนบได้

    LINE_TOKEN="YOUR-LINE-TOKEN"
    
    def jobdone(LINE_TOKEN, message="Done!", img=None):
        notify_url = 'https://notify-api.line.me/api/notify'
        header={
            'Authorization': "Bearer " + LINE_TOKEN
        }        
        data=({
            'message': message
        })
    
        files = {'imageFile': open(img, 'rb')} if img else None
        session = requests.Session()
        response = session.post(
            notify_url,
            headers=header,
            files=files,
            data=data
        ).json()
        if files:
            files['imageFile'].close()

    Function สำหรับ Plot

    รับข้อมูล และ สร้างภาพเพื่อประกอบการส่ง Line

    import matplotlib.pyplot as plt
    def plot_something(data, resultfilename):
        fig, ax = plt.subplots()
        ax.plot(data)
        ax.grid()
        fig.savefig(resultfilename)
        plt.show()

    ส่วนที่ใช้เวลานาน

    %%capture capture_output
    resultfilename="result.png"
    start_time=time.time()
    
    data=[]
    for i in range(100):
        data.append(random.random()*100)
        time.sleep(1)
    plot_something(data, resultfilename)
    
    duration=time.time()-start_time
    now=datetime.datetime.now(pytz.timezone('Asia/Bangkok')).strftime("%Y-%m-%d %H:%M:%S")
    jobdone(LINE_TOKEN, f'{now} เสร็จแล้วนะโว้ย [{duration:.2f} sec.]', img=resultfilename)

    ตรงนี้ต้องอธิบายเพิ่ม

    ใน Jupyter จะมีสิ่งที่เรียกว่า “magic cell” ในที่นี้คือ

    %%capture capture_output

    โดยที่ %%capture คือคำสั่งที่จะเก็บผลลัพธ์ทุกอย่าง ที่ส่งออกจาก stdout, stderr รวมถึงภาพด้วย ในคำสั่งข้างต้น จะส่งออกไปยัง capture_output ซึ่งจะนำไปใช้ในภายหลัง

    ต่อมา จะจำลองการทำงานที่ยาวนาน คือ random ค่าระหว่าง 1-100 เอาไปใส่ใน array “data” จากนั้น ก็ sleep 1 วินาที รวมแล้ว ขั้นตอนนี้จะทำงาน 100 วินาที หรือ 1 นาที 40 วินาที (ในการทำงานจริง ยาวนานกว่านั้นมาก ๆ)

    เมื่อเสร็จแล้ว plot_something จะเอาข้อมูล data ไป plot แล้วแสดงผล และ save เป็นไฟล์ตามที่กำหนด คือ result.png

    คำสั่งต่อมา เป็นการแสดง วันเวลาปัจจุบัน โดยแสดงเป็น Timezone ของไทย

    สุดท้าย function jobdone กำหนดข้อความ และภาพที่จะส่ง

    Let’s go!

    รอไป 1 นาที 40 วินาที ก็จะได้ LINE Notify (อ่านขั้นตอนการได้มาซึ่ง LINE Token ได้จาก วิธีแจ้งเตือนจาก Google Forms เข้า LINE )

    กลับมาใน Jupyter Notebook

    ใน cell ใหม่ ใช้คำสั่ง

    capture_output()

    ก็จะแสดงผลสิ่งที่ดำเนินการไป ระหว่างนั้นได้ครับ

    หวังว่าจะเป็นประโยชน์ครับ

  • [บันทึกกันลืม] วิธีตั้งค่า DBeaver ให้สามารถใช้ Memory ได้สูงขึ้น

    พอดีใช้ MacBook RAM 16 GB และใช้งาน DBeaver เป็น Database Tool รุ่น Community ดีอ่ะ มันฟรี Run บน Java ทำให้ใช้งาน Cross platform ได้

    ส่วนคนในสถานศึกษา สามารถใช้งานรุ่น Pro ได้นะ ใช้ Email ของมหาวิทยาลัยไปขอ Academic Licence ได้ ใช้งานได้เหมือนรุ่น Enterprise แต่ก็ต้องขอใช้ปีต่อปี ข้อดีคือ มีพวก Visual Query Builder, Support NoSQL และ ติดต่อกับพวก Big Data ได้ ลองใช้งานดู ถ้าถูกใจใช้ถาวร ก็ซื้อเค้าเถอะ ดีจริง ๆ ดีกว่าไปใช้ขอ Crack เสี่ยงภัยมาก

    แต่ส่วนตัว ใช้รุ่น Community Edition ก็รู้สึกเพียงพอแล้ว ใช้ต่อไป

    มีปัญหาอยู่นิดเดียวคือ ค่า Default ของการใช้ Java Virtual Machine เนี่ย มันตั้ง configuration มาให้ใช้แค่ 1 GB ทำให้เวลาเขียน Query ที่บางทีต้อง Fetch ข้อมูลมาไว้ใน Local Memory บ้าง ก็จะขึ้น Error

    java.lang.OutOfMemoryError: Java heap space ”

    (RAM 16 GB นะโว้ย เต็มไรกัน)

    ซึ่งไปลองค้นหามา พบว่า เราสามารถตั้งค่าได้ โดยแก้ไขไฟล์ dbeaver.ini

    สำหรับคนใช้ MacOS ทำงี้นะครับ

    ไปที่ Finder > Application หาคำว่า DBeaver
    คลิกที่เมนู Show Package Contents
    Contents > Eclipse > dbeaver.ini
    ใช้ Editor ตามใจปรารถนา แก้ตรง -Xmx1024m เป็น สัก 1/4 หรือ 1/2 ของ RAM ก็ดี มากไป เครื่อง Hang ได้ ผมตั้งไว้ 4GB ก็แก้เป็น -Xmx4096m แล้ว Save

    ก็เท่านี้ ปิด เปิดใหม่ คราวนี้ เปิด Database เขียน Query ได้ตามสะดวกครับ

  • วิธี Word Wrap ใน Jupyter Notebook / Jupiter Lab

    มีคำถามมา หาคำตอบเจอ คิดว่าเป็นประโยชน์​เลยบันทึกไว้

    ปัญหาของคนใช้ Jupyter Notebook / Jupyter Lab คือ ถ้ามีโค๊ดยาว ๆ จะไม่ขึ้นบันทัดใหม่ให้ จริง ๆ ต้องบอกว่า ไม่ Word Wrap ตามภาพที่ 1

    ภาพที่ 1: Jupyter Notebook / Jupyter Lab ไม่ Word Wrap

    ไปค้นหามา พบข้อมูลจาก Alex Ioannides ตอบที่ https://stackoverflow.com/questions/48202340/enable-word-wrap-in-jupyterlab-code-editor

    เค้าแนะนำให้เอาแก้ไขโดยใส่สิ่งนี้ใน Settings

    {
       "codeCellConfig": {
          "lineWrap": "wordWrapColumn",
          "wordWrapColumn": 80
       }
    }

    แต่มันอยู่ตรงไหนหล่ะ

    มันอยู่ตรงนี้นะ Settings > Advanced Settings Editor

    แล้วก็คลิกที่ Notebook

    จากนั้นเอาโค๊ดข้างต้นไปแปะที่ User Preferences แล้วกดปุ่ม Save ด้านขวามือบน (รูปแผ่น Disk)

    ผล

    หวังว่าจะเป็นประโยชน์นะครับ

  • [บันทึกกันลืม] วิธีแก้ปัญหาข้อมูลประเภท timedelta64[ns] ใน Pandas ไม่สามารถเขียนลงฐานข้อมูล field ที่เป็น Time ได้

    เหตุ:

    import pymysql
    import pandas as pd
    import sqlalchemy
    
    import time
    import datetime
    
    conn = pymysql.connect(
            host="source.database.server",
            port=3306,
            user='username',
            password='password',
            database='databasename',
        )
    
    sql="""
       SELECT *
       FROM   table1
    """
    df1=pd.read_sql(sql,conn)
    df1.dtypes

    ผลคือ:

    .
    .
    .
    ref_visit_date                            object
    ref_visit_time                   timedelta64[ns]
    .
    .
    .
    

    ซึ่ง ref_visit_time นั้น ใน MySQL/MariaDB ใช้ชนิดเป็น Time

    เช่น 08:00:00 หมายถึง 8 นาฬิกา อะไรทำนองนั้น

    ปัญหา:

    อยู่ที่ตอนเอา Dataframe นี้ ไป Write ใส่อีก Table นึง

    host="destination.database.server"
    port=int(3306)
    user='user'
    password='password'
    dbname='databasename'
    db_string = f"mysql+pymysql://{user}:{password}@{host}:{port}/{dbname}"
    
    destination_conn = sqlalchemy.create_engine(db_string)
    
    df.to_sql('table2'
           , con=destination_conn
           , if_exists='append'
           , index=False
           , method='multi'
           , chunksize=10000)

    ซึ่ง table2 นั้น มีโครงสร้างเหมือนกับ table1 เลย โดยเฉพาะ ref_visit_time มีชนิดเป็น Time เช่นกัน

    แต่ถ้า run code นี้จะได้ Error/Warning ว่า

    UserWarning: the 'timedelta' type is not supported, and will be written as integer values (ns frequency) to the database.

    เหตุเพราะ แทนที่จะเก็บเป็น Time มันดันเปลี่ยนเป็น Integer ที่เป็นจำนวน Nanosecond แทนนั่นเอง

    วิธีแก้ไข

    ต้องตรวจสอบว่า มี column ใน destination table มีชนิดเป็น Time (timedelta64[ns]) ให้เอาไปบวกกับเวลาเริ่มต้น แล้ว เปลี่ยนชนิดเป็น Time แทน ดังนี้

    for column in df:
        if df[column].dtype == 'timedelta64[ns]':
            df[column]=df[column].apply(lambda x: (datetime.datetime.min+x).time())

    เราก็จะได้ข้อมูลที่ถูกต้องกลับมา

    ตามนั้นครับ

  • [บันทึกกันลืม] Pandas Dataframe กับการขึ้นบรรทัดใหม่ ชิดซ้าย และทำให้คำที่ค้นหา เป็นตัวหนาสีแดง

    from IPython.display import display, HTML
    finding='text to find'
    display(HTML(df1.to_html()
                 .replace('\\r\\n','<br>')
                 .replace('<td>','<td style="text-align: left; vertical-align: top;">')
                 .replace('<th>','<th style="text-align: left;">')
                 .replace(f'{finding}',f'<b style="color:red;">{finding}</b>')
                )
           )

    กันลืมไง สั้น ๆ