φυβλαςのβλογ
phyblas的博客



จัดการข้อมูลด้วย pandas เบื้องต้น บทที่ ๒๐: การอ่านเขียนข้อมูลจากฐานข้อมูล sql
เขียนเมื่อ 2020/06/02 19:13
แก้ไขล่าสุด 2021/09/28 16:42
ฐานข้อมูล sql เป็นรูปแบบการเก็บข้อมูลที่ใช้กันอย่างกว้างขวาง การเก็บข้อมูลของ sql นั้นมีลักษณะเป็นตารางข้อมูลเป็นแถวๆ คล้ายกับ pandas

pandas มีคำสั่งที่ช่วยให้ติดต่อกับฐานข้อมูล sql ได้อย่างง่ายดายขึ้น โดยสามารถเอาตารางจาก pandas เขียนลงใน sql และอ่านตารางจาก sql เข้ามาเป็นตารางใน pandas

ความสามารถส่วนใหญ่ในส่วนนี้จะใช้กับมอดูล sqlalchemy เป็นหลัก ดังนั้นจำเป็นต้องติดตั้งมอดูล sqlalchemy ด้วย

เกี่ยวกับการใช้ sqlalchemy อ่านได้ใน https://phyblas.hinaboshi.com/20200529

เพียงแต่ว่าก็อาจไม่ต้องเรียกใช้ sqlalchemy โดยตรง แค่มีลงมอดูล sqlalchamy ไว้ก็สามารถใช้ความสามารถนี้ได้แล้ว

ฐานข้อมูล sql ยังแบ่งออกเป็นหลายแบบ เช่น sqlite, posgresql, mysql ซึ่ง sqlalchemy ก็รองรับฐานข้อมูลหลายชนิด ซึ่งก็ทำให้ใช้ใน pandas ได้ด้วยเช่นกัน ในที่นี้จะใช้ sqlite ซึ่งเป็นฐานข้อมูล sql แบบที่ง่ายที่สุด มีติดตัวอยู่ตั้งแต่แรกไม่ต้องติดตั้งเพิ่ม



เมื่อมีตารางข้อมูลเก็บอยู่ในเดตาเฟรมแล้วต้องการบันทึกลงฐานข้อมูล sql สามารถทำได้โดยใช้เมธอด .to_sql() จากตัวเดตาเฟรมนั้น

การใช้คำสั่งนี้มีการเขียนอยู่หลายวิธี ที่ง่ายที่สุดก็คือใส่ชื่อตารางและตามด้วยชื่อตารางที่เก็บข้อมูลนั้นอยู่

วิธีที่ง่ายที่สุดคือแค่ใส่ชื่อตาราง แล้วตามด้วยชื่อตัวฐานข้อมูลที่จะเก็บตารางข้อมูลนั้นไว้
df.to_sql(ชื่อตาราง,ชื่อฐานข้อมูล)

ตัวอย่างการใช้
import pandas as pd

p = {'สายพันธุ์':['ซันกูส','ฮาบุเนก','ลูนาโทน'],
     'ส่วนสูง':[1.3,2.7,1],
     'น้ำหนัก':[40.3,52.5,168]}
pokedf = pd.DataFrame(p,index=[335,336,337])
pokedf.to_sql('pokemon','sqlite:///pkdata.db')


ในที่นี้ใช้กับฐานข้อมูล sqlite ในส่วนของชื่อฐานข้อมูลจะเขียนเป็น 'sqlite:///ชื่อไฟล์' แบบนี้

อนึ่ง เดิมทีแล้ว .to_sql() ควรจะใช้กับตัวออบเจ็กต์เชื่อมต่อ ซึ่งใน sqlalchemy เรียกว่า engine

หากเขียนแบบเต็มๆตั้งแต่ขั้นตอนการสร้าง engine ก็อาจเขียนแบบนี้
import sqlalchemy

engine = sqlalchemy.create_engine('sqlite:///pkdata.db')
pokedf.to_sql('pokemon',engine)

เพียงแต่ว่าสามารถเขียนย่อเป็นแบบใส่แค่ชื่อฐานข้อมูลไปโดยตรงก็ได้ ดังนั้นจึงสะดวกกว่ามาก ไม่จำเป็นต้อง import sqlalchemy มาโดยตรงเลยด้วย

ส่วนการอ่านข้อมูลจากตารางใน sql ทำได้โดยฟังก์ชัน pd.read_sql_table() วิธีใช้ก็เช่นเดียวกับตอนเขียนข้อมูลลง sql นั่นคือใส่ชื่อตารางกับชื่อฐานข้อมูล
df = pd.read_sql_table(ชื่อตาราง,ชื่อฐานข้อมูล)

ตัวอย่างเช่นถ้าต้องการอ่านข้อมูลที่บันทึกลงไปในฐานข้อมูลในตัวอย่างที่แล้ว
df = pd.read_sql_table('pokemon','sqlite:///pkdata.db')
print(df)

ได้
  index สายพันธุ์ ส่วนสูง น้ำหนัก
0 335 ซันกูส 1.3 40.3
1 336 ฮาบุเนก 2.7 52.5
2 337 ลูนาโทน 1.0 168.0

จากในตัวอย่างที่แล้วทั้งตอนเขียนและอ่านล้วนไม่ได้ใส่ตัวเลือกเสริมอะไรลงไปเลย ทุกอย่างจึงเป็นไปตามค่าตั้งต้น

ซึ่งจะเห็นว่าตอนที่ใช้ .to_sql() นั้นตัวดัชนีก็ถูกเปลี่ยนเป็นคอลัมน์หนึ่งใน sql ไปด้วย โดยชื่อคอลัมน์ดัชนีก็จะกลายเป็นชื่อคอลัมน์ใน sql ด้วย

หากต้องการเปลี่ยนชื่อคอลัมน์ดัชนีใหม่อาจทำได้โดยใส่ในตัวเลือกเสริม index_label เช่น

pk = {'สายพันธุ์':['โซลร็อก','โดจ็อช'],
      'ส่วนสูง':[1.2,0.4],
      'น้ำหนัก':[154,1.9]}
index = pd.Series([338,339],name='หมายเลข')
pokedf = pd.DataFrame(pk,index=index)
pokedf.to_sql('pokemon','sqlite:///pkdt.db',index_label='id')
print(pd.read_sql_table('pokemon','sqlite:///pkdt.db'))

ได้
  id สายพันธุ์ ส่วนสูง น้ำหนัก
0 338 โซลร็อก 1.2 154.0
1 339 โดจ็อช 0.4 1.9



นอกจากนี้ถ้าหากไม่ได้ตั้งชื่อให้คอลัมน์ดัชนี แล้วก็ไม่ได้กำหนด index_label ก็จะถูกตั้งชื่อเป็น index โดยอัตโนมัติ

หากจะให้ทิ้งส่วนดัชนีไปเลยก็ใส่ตัวเลขเสริม index เป็น index=False

pk = {'สายพันธุ์':['นามาซึน','เฮย์งานิ'],
      'ส่วนสูง':[0.9,0.6],
      'น้ำหนัก':[23.6,11.5]}
pokedf = pd.DataFrame(pk,index=[340,341])
pokedf.to_sql('pokemon','sqlite:///pkdex.db',index=False)
print(pd.read_sql_table('pokemon','sqlite:///pkdex.db'))

ได้
  สายพันธุ์ ส่วนสูง น้ำหนัก
0 นามาซึน 0.9 23.6
1 เฮย์งานิ 0.6 11.5



หากชื่อตารางที่ใส่ไปนั้นซ้ำกับที่มีอยู่ในฐานข้อมูลนั้นแล้ว ปกติจะเกิดข้อผิดพลาดขึ้น

หากไม่ต้องการให้เป็นเช่นนั้นก็อาจกำหนดไปในตัวเลือกเสริม if_exists เพิ่มเติม

โดยถ้า id_exists='replace' ตารางเดิมจะหายไปแล้วเอาข้อมูลใหม่ใส่ลงไปแทน

เช่นลองใส่ตารางเดิมซ้ำในฐานข้อมูลเดียวกับตัวอย่างที่แล้ว
pk = {'สายพันธุ์':['เนนดอล','ลีลีลา'],
      'ส่วนสูง':[1.5,1],
      'น้ำหนัก':[108,23.8]}
pokedf = pd.DataFrame(pk,index=[344,345])
pokedf.to_sql('pokemon','sqlite:///pkdex.db',if_exists='replace')
print(pd.read_sql_table('pokemon','sqlite:///pkdex.db'))

ได้
  index สายพันธุ์ ส่วนสูง น้ำหนัก
0 344 เนนดอล 1.5 108.0
1 345 ลีลีลา 1.0 23.8



จะเห็นว่าข้อมูลเก่าหายไปแล้วกลายเป็นข้อมูลใหม่

ตรงนี้ถ้าไม่ได้ใส่ หรือใส่ if_exists='fail' ก็จะขึ้นมาว่า
ValueError: Table 'pokemon' already exists.

นอกจากนี้ ถ้า id_exists='append' จะเป็นการเพิ่มข้อมูลเข้าไปในตารางที่มีอยู่แล้ว

เช่นลองใส่ตารางเดิมซ้ำในฐานข้อมูลเดียวกับตัวอย่างที่แล้ว
pk = {'สายพันธุ์':['ชิซาริเกอร์','ยาจิลอน'],
      'ส่วนสูง':[1.1,0.5]}
pokedf = pd.DataFrame(pk,index=[342,343])
pokedf.to_sql('pokemon','sqlite:///pkdex.db',if_exists='append')
print(pd.read_sql_table('pokemon','sqlite:///pkdex.db'))

ได้
  index สายพันธุ์ ส่วนสูง น้ำหนัก
0 344 เนนดอล 1.5 108.0
1 345 ลีลีลา 1.0 23.8
2 342 ชิซาริเกอร์ 1.1 NaN
3 343 ยาจิลอน 0.5 NaN



ข้อมูลที่ใส่ต่อเข้าไปนั้นควรจะมีคอลัมน์ซ้ำกับตารางเดิม หรือจะขาดไปบางคอลัมน์ก็ได้ ค่าที่ขาดจะว่างไว้ แต่ถ้าหากมีคอลัมน์ที่ไม่มีอยู่เดิมก็จะเกิดข้อผิดพลาด



สำหรับชนิดของข้อมูลนั้น ถ้าไม่ได้กำหนดอะไรก็จะเป็นไปตามชนิดของข้อมูลที่สัมพันธ์กับที่อยู่ในเดตาเฟรม

เช่น ตัวอย่างนี้ เมื่อไม่ได้กำหนด dtype ก็จะเป็นแบบนี้
pk = {'สายพันธุ์':['มิโลคารอส','คาคุเรออน'],
      'ส่วนสูง':[6.2,1],
      'น้ำหนัก':[162.0,22.0]}
pokedf = pd.DataFrame(pk,index=pd.Series([350,352],name='เลข'))

pokedf.to_sql('pokemon','sqlite:///pkdex.db',if_exists='replace')
df = pd.read_sql_table('pokemon','sqlite:///pkdex.db',coerce_float=False)
print(df.values)

ได้เป็น int, str, float, float
[[350 'มิโลคารอส' 6.2 162.0]
 [352 'คาคุเรออน' 1.0 22.0]]



แต่หากต้องการให้เปลี่ยนชนิดข้อมูลเป็นแบบที่ต้องการก็สามารถกำหนดได้โดยตัวเลือกเสริม dtype

การใส่ชนิดข้อมูลนั้นให้ใส่ในรูปของชนิดข้อมูล sqlalchemy (ต้อง import มาใช้)

จากตัวอย่างที่แล้ว ลองกำหนด dtype เข้าไปได้ดังนี้
import sqlalchemy

dtype = {'ส่วนสูง':sqlalchemy.String,
         'น้ำหนัก':sqlalchemy.Integer,
         'เลข':sqlalchemy.Float}

pokedf.to_sql('pokemon','sqlite:///pkdex.db',dtype=dtype,if_exists='replace')
df = pd.read_sql_table('pokemon','sqlite:///pkdex.db')
print(df.values)

ได้เป็น float, str, str, int
[[350.0 'มิโลคารอส' '6.2' 162]
 [352.0 'คาคุเรออน' '1.0' 22]]



เวลาอ่านข้อมูลจาก sql ด้วย pd.read_sql_table() ก็มีตัวเลือกเสริมมากมายเพื่ออำนวยความสะดวกในการการกำหนดลักษณะการอ่าน

เช่น index_col ใช้กำหนดคอลัมน์ที่จะเป็นดัชนี ถ้าหากไม่กำหนดอะไรไปก็จะไม่มีคอลัมน์ไหนกลายเป็นดัชนี แล้วเป็นตัวเลข 0,1,2 ไป

ตัวอย่างการอ่านไฟล์โดยใส่ index_col ต่างๆกัน
pk = {'สายพันธุ์':['ฮินบาส','โปวาเลิน','คาเงะโบวซึ'],
      'ส่วนสูง':[0.6,0.3,0.6],
      'น้ำหนัก':[7.4,0.8,2.3]}
pokedf = pd.DataFrame(pk,index=pd.Series([349,351,353],name='เลข'))
pokedf.to_sql('pokemon','sqlite:///pkdex.db',if_exists='replace')

print(pd.read_sql_table('pokemon','sqlite:///pkdex.db'))
print(pd.read_sql_table('pokemon','sqlite:///pkdex.db',index_col='เลข'))
print(pd.read_sql_table('pokemon','sqlite:///pkdex.db',index_col='สายพันธุ์'))

ได้
  เลข สายพันธุ์ ส่วนสูง น้ำหนัก
0 349 ฮินบาส 0.6 7.4
1 351 โปวาเลิน 0.3 0.8
2 353 คาเงะโบวซึ 0.6 2.3

  สายพันธุ์ ส่วนสูง น้ำหนัก
เลข      
349 ฮินบาส 0.6 7.4
351 โปวาเลิน 0.3 0.8
353 คาเงะโบวซึ 0.6 2.3

  เลข ส่วนสูง น้ำหนัก
สายพันธุ์      
ฮินบาส 349 0.6 7.4
โปวาเลิน 351 0.3 0.8
คาเงะโบวซึ 353 0.6 2.3



ส่วนการเลือกเอาข้อมูลเฉพาะแค่บางคอลัมน์ก็ทำได้โดยใส่ตัวเลือกเสริม columns ระบุชื่อคอลัมน์ที่ต้องการลงไป
pk = {'สายพันธุ์':['ยูเรเดิล','อาโนปธ์','อาร์มัลโด'],
      'ส่วนสูง':[1.5,0.7,1.5],
      'น้ำหนัก':[60.4,12.5,68.2]}
pokedf = pd.DataFrame(pk,index=[346,347,348])
pokedf.to_sql('pokemon','sqlite:///pkdex.db',if_exists='replace')

print(pd.read_sql_table('pokemon','sqlite:///pkdex.db',columns=['index','สายพันธุ์']))
print(pd.read_sql_table('pokemon','sqlite:///pkdex.db',columns=['index','สายพันธุ์'],index_col='index'))

ได้
  index สายพันธุ์
0 346 ยูเรเดิล
1 347 อาโนปธ์
2 348 อาร์มัลโด

  สายพันธุ์
index  
346 ยูเรเดิล
347 อาโนปธ์
348 อาร์มัลโด



แต่ pd.read_sql_table() นั้นไม่สามารถกำหนดเงื่อนไขให้ข้อมูลออกมาเฉพาะบางแถวได้ จะอ่านข้อมูลออกมาทุกแถวเสมอ (เหมือนการใส่ where ในโค้ด sql) และกำหนดลำดับการเรียงข้อมูลไม่ได้ด้วย (เหมือนการใส่ order by ใน sql)



สำหรับการใช้งานที่ยืดหยุ่นกว่า อาจฟังก์ชันอีกตัวคือ pd.read_sql_query() แทน

ในขณะที่ pd.read_sql_table() นั้นแค่ระบุชื่อตารางก็ได้ข้อมูลมาแล้ว แต่ pd.read_sql_query() จะต้องเขียนโค้ด sql เพื่อสั่งฐานข้อมูลเโดยตรงเพื่อดึงเอาข้อมูลมา

การจะใช้ฟังก์ชันนี้ได้จึงต้องรู้โค้ด sql ด้วยทำให้อาจใช้ยากกว่า และเขียนยาวกว่า แต่ข้อดีคือเขียนได้ยืดหยุ่นกว่า สามารถเขียน where เพื่อกำหนดเงื่อนไขได้ หรือเขียน order by เพื่อเรียงลำดับข้อมูลได้

ส่วนการกำหนดคอลัมน์ที่จะใช้เป็นดัชนีก็ทำได้ด้วยการใส่ index_col เช่นกัน

ตัวอย่าง ลองใช้ select เพื่อดึงข้อมูลมา พร้อมทั้งใส่ where และ order by ดูเพื่อกำหนดเงื่อนไขและการเรียงลำดับ
pk = {'สายพันธุ์':['จูเพ็ตตา','โยมาวารุ','ซามาโยวรุ','โทรปิอุส'],
      'ส่วนสูง':[1.1,0.8,1.6,2],
      'น้ำหนัก':[12.5,35,30.6,100]}
pokedf = pd.DataFrame(pk,index=[354,355,356,357])
pokedf.to_sql('pokemon','sqlite:///pkdex.db',if_exists='replace')

sql = 'select * from pokemon where น้ำหนัก>32'
print(pd.read_sql_query(sql,'sqlite:///pkdex.db'))
sql = 'select สายพันธุ์,น้ำหนัก from pokemon order by น้ำหนัก desc'
print(pd.read_sql_query(sql,'sqlite:///pkdex.db'))
sql = 'select * from pokemon'
print(pd.read_sql_query(sql,'sqlite:///pkdex.db',index_col='index'))

ได้
  index สายพันธุ์ ส่วนสูง น้ำหนัก
0 355 โยมาวารุ 0.8 35.0
1 357 โทรปิอุส 2.0 100.0

  สายพันธุ์ น้ำหนัก
0 โทรปิอุส 100.0
1 โยมาวารุ 35.0
2 ซามาโยวรุ 30.6
3 จูเพ็ตตา 12.5

  สายพันธุ์ ส่วนสูง น้ำหนัก
index      
354 จูเพ็ตตา 1.1 12.5
355 โยมาวารุ 0.8 35.0
356 ซามาโยวรุ 1.6 30.6
357 โทรปิอุส 2.0 100.0



หากในโค้ดมีการใช้เครื่องหมายคำถาม ? ซึ่งแทนตัวพารามิเตอร์ สามารถใส่ค่าลงไปได้โดยเติมลิสต์ของพารามิเตอร์ที่ต้องการแทนใส่ในคีย์เวิร์ด params เช่น
pk = {'สายพันธุ์':['ชิรีน','อับโซล','โซนาโน'],
      'ส่วนสูง':[0.6,1.2,0.6],
      'น้ำหนัก':[1,47,14]}
pokedf = pd.DataFrame(pk,index=pd.Series([358,359,360],name='เลข'))
pokedf.to_sql('pokemon','sqlite:///pkdex.db',if_exists='replace')

sql = 'select * from pokemon where สายพันธุ์==?'
print(pd.read_sql_query(sql,'sqlite:///pkdex.db',params=['ชิรีน']))

ได้
  เลข สายพันธุ์ ส่วนสูง น้ำหนัก
0 358 ชิรีน 0.6 1



หรือใส่ในรูปของตัวแปรที่ชื่อขึ้นต้นด้วยโคลอน : ก็ได้ กรณีนี้ให้ใส่ params เป็นดิกชันนารี เช่น
sql = 'select * from pokemon where เลข=:lek'
print(pd.read_sql_query(sql,'sqlite:///pkdex.db',params={'lek': 359}))

ได้
  เลข สายพันธุ์ ส่วนสูง น้ำหนัก
0 359 อับโซล 1.2 47



นอกจากนี้มีฟังก์ชัน pd.read_sql() ซึ่งอาจใช้เพื่อแทน pd.read_sql_table() หรือ pd.read_sql_query() ได้

โดย pd.read_sql() จะดูจากค่าที่ใส่ไปเองว่าควรจะเรียก pd.read_sql_table() หรือ pd.read_sql_query() ถ้าใส่แค่ชื่อตารางก็จะเรียก pd.read_sql_table() แต่ถ้าใส่โค้ด sql ก็จะไปเรียก pd.read_sql_query()

ดังนั้นในทางปฏิบัติแล้วถ้าไม่อยากเขียนยาวจะใช้ pd.read_sql() อย่างเดียวตลอดก็ได้ ไม่ได้จำเป็นต้องใช้ pd.read_sql_table() หรือ pd.read_sql_query() โดยตรงเลย

ตัวอย่างการใช้ pd.read_sql()
pk = {'สายพันธุ์':['ยุกิวาราชิ','โอนิโกริ','ทามะซาราชิ'],
      'ส่วนสูง':[0.7,1.5,0.8],
      'น้ำหนัก':[16.8,256.5,39.5]}
pokedf = pd.DataFrame(pk,index=pd.Series([361,362,363],name='เลข'))
pokedf.to_sql('pokemon','sqlite:///pkdex.db',if_exists='replace')

print(pd.read_sql('pokemon','sqlite:///pkdex.db',columns=['เลข','สายพันธุ์']))
sql = 'select * from pokemon where น้ำหนัก<40'
print(pd.read_sql(sql,'sqlite:///pkdex.db'))

ได้
  เลข สายพันธุ์
0 361 ยุกิวาราชิ
1 362 โอนิโกริ
2 363 ทามะซาราชิ

  เลข สายพันธุ์ ส่วนสูง น้ำหนัก
0 361 ยุกิวาราชิ 0.7 16.8
1 363 ทามะซาราชิ 0.8 39.5



เช่นเดียวกับ .to_sql() ฟังก์ชัน pd.read_sql_table(), pd.read_sql_query() และ pd.read_sql() เองก็เดิมทีแล้วมีไว้ใช้กับตัวออบเจ็กต์เชื่อมต่อ (engine) ของ sqlalchemy เช่นกัน การเขียนแบบตัวอย่างที่ยกมาจึงเป็นแค่การเขียนย่อให้ง่าย

หากเขียนเต็มๆตั้งแต่สร้าง engine ของ sqlalchemy ก็อาจเขียนได้แบบนี้
import sqlalchemy

engine = sqlalchemy.create_engine('sqlite:///pkdex.db')
print(pd.read_sql('pokemon',engine))

นอกจากนี้ pd.read_sql_query() หรือ pd.read_sql() สามารถใช้กับมอดูล sqlite3 ได้ด้วย โดยใส่ตัวออบเจ็กต์เชื่อมต่อ (connect) ของ sqlite3 ลงไป โดยอาจเขียนแบบนี้
import sqlite3

conn = sqlite3.connect('pkdex.db')
print(pd.read_sql('select * from pokemon',conn))
conn.close()

เพียงแต่ .to_sql() กับ df.read_sql_table จะใช้ได้กับ sqlalchemy เท่านั้น ใช้กับ sqlite3 ไม่ได้



อ้างอิง


<< บทที่แล้ว บทถัดไป >>
หน้าสารบัญ


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> pandas
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> sql

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ