Mine

รูปภาพของฉัน
หาดใหญ่, ภาคใต้, Thailand
ชอบเอาใจ แต่ก็เอาแต่ใจ

วันอังคารที่ 28 กันยายน พ.ศ. 2553

มาดูความสามารถของ Group By กับการ Count

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

เราก็คงพอเข้าใจกับการทำ Group By มาแล้วนะคะ สำหรับใครที่ยังไม่เข้าใจว่า Group By คิดยังไงขอให้กลับไปทำความเข้าใจกันก่อนค่ะ ที่ มาทำความเข้าใจคำว่า Group By ขึ้นพื้นฐาน

งั้นเรามาต่อกันเลยค่ะ ... ส่วนใหญ่การ Group By เราจะนำความสามารถของมัน มาใช้ร่วมกับ Function ต่างๆ เช่น การนับ(Count), การรวม(Sum), หาค่าสูงสุด(Max), หาค่าต่ำสุด(Min), หาค่ากลาง (Avg) เป็นต้น
โดยที่ทำงานกับ Record ที่ทำ Group By กันได้ เช่นการ Count คือ การนับจำนวน Record หาก เรา Group By ได้ 3 Record ผลที่จะได้จากการใช้ Count คือ 3  ... คนเขียนก็ งง คนอ่านไม่รู้ งง หม้าย 

ความเดิมจาก ตาราง test_table

field_a field_b field_c
123ทดสอบ1
123 ทดสอบ 2
123ทดสอบ2
123 ทดสอบ 2
123ทดสอบ3

หากเราต้องการนับจำนวน Record ที่มีค่าเหมือนกัน
ใช้คำสั่ง
SELECT field_a, field_b, field_c, COUNT(*) as count_rec
FROM
test_table
GROUP BY field_a, field_b, field_c;

ผลลัพธ์ที่ได้ คือ

field_afield_bfield_ccount_rec
123ทดสอบ11
123ทดสอบ23
123ทดสอบ31

จะเห็นว่าจำนวน Record จะถูกนับใน Field count_rec โดยจะนับจำนวน Record ตามที่ Group By ได้
ในส่วนนี้จะขอยกตัวอย่างจากการ Query ข้อมูลของ Hospital OS ประกอบเพื่อจะได้เห็นภาพ การทำงานที่ชัดเจนยิ่งขึ้น(หรือเปล่า) มาทำพร้อมๆกันนะคะ
เริ่มจาก
โจทย์ : ต้องการหารายชื่อ และ จำนวนครั้งที่มารับบริการ ใน 1 เดือนของผู้ป่วยที่มารับบริการเกิน 4 ครั้ง
ตีโจทย์ :
- หากต้องการายชื่อของผู้ป่วย สามารถหามาได้จากตาราง t_patient
- หากต้องการหากข้อมูลรับบริการ สามารถหามาได้จากตาราง t_visit
- สรุปว่าข้อมูล 2 ตารางนี้ก็น่าจะเพียงพอกับ โจทย์ ที่ต้องการนะคะ
ลองทำดู ทำเป็นขั้นเป็นตอน ดูผลเป็นขั้นเป็นตอนเพื่อทำความเข้าใจ ดังนี้

ขั้นตอนที่ 1 : นำข้อมูลมาเชื่อมโยงกันเพื่อให้ได้ข้อมูลชื่อและรายการที่รับบริการออกมาก่อน โดยเลือกข้อมูลที่คิดว่าจำเป็นในการนำมาใช้
คำสั่ง
SELECT t_patient.t_patient_id as patient_id --ใช้ as เพื่อตั้งชื่อใหม่
,t_patient.patient_hn as hn
,t_patient.patient_firstname as fname
,t_patient.patient_lastname as lname
,t_patient.patient_birthday as dob
,t_patient.patient_pid as pid
,t_visit.t_visit_id as visit_id --Field นี้มาจากตาราง t_visit เป็นข้อมูลรับบริการ เราเอามานับได้ค่ะ
FROM t_patient INNER JOIN t_visit
ON t_patient.t_patient_id = t_visit.t_patient_id
WHERE t_visit.f_visit_status_id <> '4' --ไม่ยกเลิกการเข้ารับบริการ
and substring(t_visit.visit_financial_discharge_time,1,10) Between '2553-04-01' And '2553-04-30'; --ข้อมูลรับบริการเดือนเมษายน

ผลลัพธ์ คือ ข้อมูลรับบริการในเดือนเมษายน 2553 ตาม Field ที่เรา Select (ลองเอาไป "รัน" ดูนะคะ) ก็ประมาณนี้ค่ะ ขึ้นอยู่กับข้อมูลของเรา
patient_id hn fname lname dob pid visit_id
206000037798 330000001 ทดสอบชื่อ ทดสอบนามสกุล 2534-04-11 2554063684111
206000037798 330000001 ทดสอบชื่อ ทดสอบนามสกุล 2534-04-11
2554063684202
206000037798 330000001 ทดสอบชื่อ ทดสอบนามสกุล 2534-04-11 2554063684330
206000037798 330000001 ทดสอบชื่อ ทดสอบนามสกุล 2534-04-11
2554063684890
206000037798 330000001 ทดสอบชื่อ ทดสอบนามสกุล 2534-04-11 2554063684999
206000037798 330000001 ทดสอบชื่อ ทดสอบนามสกุล 2534-04-11
2554063685000
... ... ... ... ... ... ...

ขั้นตอนที่ 2 :จากผลลัพธ์ของครั้งแรกจะเห็นว่าข้อมูลที่ซ้ำๆ กัน จะมาจากตาราง t_patient(ข้อมูลผู้ป่วย) ส่วนข้อมูลที่เป็นการรับบริการจะไม่ซ้ำกัน สังเกตุใน field visit_id เป็นเลขลำดับไม่ซ้ำกัน ซึ่งข้อมูลมาจากตาราง t_visit(ข้อมูลรับบริการ)
ดังนั้น เราจะต้องนับจำนวนรับบริการตามรายละเอียดของผู้ป่วยที่เหมือนกัน(ที่มาจากตาราง t_patient ทั้งหมด) แสดงว่าข้อมูลที่มาจากตาราง t_patient เราจะต้องนำไปทำ Group By เพื่อรวมเป็น Record เดียวกัน และนับจำนวน Record ที่ Group By กันได้(ข้อมูลเหมือนกันในทุก Field ที่กำหนดให้ Group By) ...อ่านแล้ว งง เองอีกแระ มาดูคำสั่งและผลลัพธ์ดีกว่า
คำสั่ง
SELECT t_patient.t_patient_id as patient_id --ใช้ as เพื่อตั้งชื่อใหม่
,t_patient.patient_hn as hn
,t_patient.patient_firstname as fname
,t_patient.patient_lastname as lname
,t_patient.patient_birthday as dob
,t_patient.patient_pid as pid
,count(t_visit.t_visit_id) as count_visit
FROM t_patient INNER JOIN t_visit
ON t_patient.t_patient_id = t_visit.t_patient_id
WHERE t_visit.f_visit_status_id <> '4' --ไม่ยกเลิกการเข้ารับบริการ
and substring(t_visit.visit_financial_discharge_time,1,10) Between '2553-04-01' And '2553-04-30' --ข้อมูลรับบริการเดือนเมษายน
GROUP BY patient_id,hn,fname,lname,dob,pid; --ตรงนี้จะบอกว่าเอาชื่อ Field ที่เราทำ as มาใช้เลยก็ได้ค่ะ  แต่ถ้าอยากให้อ่านแล้วเข้าใจเลยก็เอาชื่อ Field มาอ้างได้ค่ะ ได้ผลเหมือนกัน
ผลลัพธ์ คือ ข้อมูลจำนวนครั้งของผู้ป่วยที่มารับบริการในเดือนเมษายน (ลองเอาไป "รัน" ดูนะคะ) เหมือนเดิม ข้อมูลก็อาจจะเป็น ขึ้นอยู่กับข้อมูลของเรา
patient_idhnfnamelnamedobpidcount_visit
206000037798330000001ทดสอบชื่อทดสอบนามสกุล2534-04-11
6
206000037799330000002ทดสอบชื่อ2ทดสอบนามสกุล22524-09-10
2
206000037800330000003ทดสอบชื่อ3ทดสอบนามสกุล32521-04-19
5

ขั้นตอนที่ 3 : จากผลลัพธ์ในข้อสองนั้น เราก็ยังไม่ได้ตามที่โจทย์ต้องการย้อนกลับไปอ่านโจทย์ เรายังขาดเรื่องของจำนวนครั้งที่มารับบริการมากกว่า 4 ครั้ง ดังนั้นเราก็มาทำตามที่ควรจะได้กันดีกว่า
การที่เราจะกำหนดเงื่อนไขจากผลลัพธ์ของ Function Count (หรือ Function อะไรก็แล้วแต่ที่ใช้ร่วมกับ Group By) เราจะต้องรู้จักคำนี้ Having -> นั่นคือตัวช่วยของเราค่ะ
Having
- จำเป็นต้องใช้ร่วมกับการทำ Group By ค่ะ จู่ๆ จะมา Having ไม่ได้ (หรือได้ งง)
- จะต้องอยู่หลัง Group By
- จะต้องยกคำสั่งมาเป็นเงื่อนไข หมายถึง ยกการทำงานของ Function มาเป็นเงื่อนไข เช่น count(t_visit.t_visit_id) > 4 
งั้นเรามาดูคำสั่งแบบเต็มๆ กันเลยนะคะ
คำสั่ง
SELECT t_patient.t_patient_id as patient_id --ใช้ as เพื่อตั้งชื่อใหม่
,t_patient.patient_hn as hn
,t_patient.patient_firstname as fname
,t_patient.patient_lastname as lname
,t_patient.patient_birthday as dob
,t_patient.patient_pid as pid
,count(t_visit.t_visit_id) as count_visit
FROM t_patient INNER JOIN t_visit
ON t_patient.t_patient_id = t_visit.t_patient_id
WHERE t_visit.f_visit_status_id <> '4' --ไม่ยกเลิกการเข้ารับบริการ
and substring(t_visit.visit_financial_discharge_time,1,10) Between '2553-04-01' And '2553-04-30' --ข้อมูลรับบริการเดือนเมษายน
GROUP BY patient_id,hn,fname,lname,dob,pid
HAVING count(t_visit.t_visit_id) > 4; --มารับบริการมากกว่า 4 ครั้งในช่วงเดือนที่กำหนด ในที่นี้คือ เมษายน
ผลลัพธ์ คือ ข้อมูลจำนวนครั้งของผู้ป่วยที่มารับบริการในเดือนเมษายนที่มารับบริการมากกว่า 4 (ลองเอาไป "รัน" ดูนะคะ) เหมือนเดิม ข้อมูลก็อาจจะเป็น ขึ้นอยู่กับข้อมูลของเรา
patient_idhnfnamelnamedobpidcount_visit
206000037798330000001ทดสอบชื่อทดสอบนามสกุล2534-04-11
6
206000037800330000003ทดสอบชื่อ3ทดสอบนามสกุล32521-04-195

เท่านี้เราก็ได้ข้อมูลตามโจทย์แล้วค่ะ ... วันนี้ยากจัง ก็ยากตรงที่เขียนยังไงให้คนที่มาอ่านเข้าใจ มันก็เป็นอะไรที่ต้องคิดหนักนะ แต่จะพยายามสู้ๆค่ะ 

งั้น ขอแถมอีกนิดนะคะ เผื่อมีประโยชน์ ถ้าเราต้องการเรียงลำดับจากมากไปหาน้องของโทจทย์นี้ก็เพิ่ม Order By ไปท้ายสุดเลยค่ะ

คำสั่ง
SELECT t_patient.t_patient_id as patient_id --ใช้ as เพื่อตั้งชื่อใหม่
,t_patient.patient_hn as hn
,t_patient.patient_firstname as fname
,t_patient.patient_lastname as lname
,t_patient.patient_birthday as dob
,t_patient.patient_pid as pid
,count(t_visit.t_visit_id) as count_visit
FROM t_patient INNER JOIN t_visit
ON t_patient.t_patient_id = t_visit.t_patient_id
WHERE t_visit.f_visit_status_id <> '4' --ไม่ยกเลิกการเข้ารับบริการ
and substring(t_visit.visit_financial_discharge_time,1,10) Between '2553-04-01' And '2553-04-30' --ข้อมูลรับบริการเดือนเมษายน
GROUP BY patient_id,hn,fname,lname,dob,pid
HAVING count(t_visit.t_visit_id) > 4 --มารับบริการมากกว่า 4 ครั้งในช่วงเดือนที่กำหนด ในที่นี้คือ เมษายน
ORDER BY count_visit DESC;

เพิ่มเติมสำหรับ Order By ที่ควรรู้
- Order By ต้องอยู่ท้ายสุดของการเขียน Query
- หากเราไม่เติมคำสั่งบังคับหลัง Order By จะถือว่าเรียงจากน้อยไปหามาก โดยปริยาย เช่น ORDER BY field_a
- หากต้องการกำกับให้เรียงจากมากไปหาน้อย ให้ใส่ DESC ตามหลัง Field ที่กำหนดให้เรียง เช่น ORDER BY field_a DESC
- หากต้องการกำกับให้เรียงจากน้อยไปหามาก ให้ใส่ ASC ตามหลัง Field ที่กำหนดให้เรียง เช่น ORDER BY field_a ASC
- สามารถเรียงลำดับมากกว่า 1 Field ได้ โดยจะทำงานจากซ้าย(ติดกับ Order By) โดยมี comma คั้นระหว่าง Field และมีตัวกำกับหลัง Field แตกต่างกับได้ (DESC,ASCเช่น ORDER BY field_a DESC, field_b ASC, field_c DESC


อันนี้ก็ต้องลองถึงจะรู้นะคะ ... สำหรับวันนี้คงพอก่อนเอาไว้มาต่อเรื่อง Group By , Distinct กันอีกนะคะ ...to be continue

มาทำความเข้าใจคำว่า Group By ขึ้นพื้นฐาน

สวัสดีค่ะ ช่วงนี้อากาศเปลี่ยนแปลงบ่อย ยังไงก็รักษาสุขภาพกันด้วยนะคะ ...

วันนี้เราจะมาว่าด้วยการ Group By ใน Query ค่ะ สำหรับคำจำกัดความภาษาบ้านๆ ก็น่าจะเป็น "การรวมข้อมูลเป็น Record เดียวกัน โดยที่ข้อมูลใน Field ทุกๆ Field จะ้ต้องเหมือนกัน" ยกตัวอย่างข้อมูลที่สามารถ Group By ให้เป็น Record เดียวกันได้ ดังนี้
ตาราง test_table
field_a field_b field_c
123ทดสอบ1
123 ทดสอบ 2
123ทดสอบ2
123 ทดสอบ 2
123ทดสอบ3

หากต้องการสร้างตารางเพื่อทดสอบข้อมูลให้ใช้คำสั่ง
CREATE TABLE test_table(
field_a varchar(255) NOT NULL,
field_b varchar(255) NOT NULL,
field_c varchar(255) NOT NULL
);
หลังจากนั้น INSERT ข้อมูลจากคำสั่ง
INSERT INTO test_table(field_a,field_b,field_c) VALUES('123','ทดสอบ','1');
INSERT INTO test_table(field_a,field_b,field_c) VALUES('123','ทดสอบ','2');
INSERT INTO test_table(field_a,field_b,field_c) VALUES('123','ทดสอบ','2');
INSERT INTO test_table(field_a,field_b,field_c) VALUES('123','ทดสอบ','2');
INSERT INTO test_table(field_a,field_b,field_c) VALUES('123','ทดสอบ','3');

หลังจากที่รันคำสั่ง CREATE TABLE และ INSERT INTO แล้ว เราก็จะได้ข้อมูลดังตารางข้างต้น

จากข้อมูลข้างต้นของตาราง test_table ถ้าเราเขียนคำสั่ง SQL แล้วทำการ Group By แล้วจะได้อะไร ลองมาดูจากคำสั่ง 3 ชุด
ชุดที่ 1 : เลือกแสดงข้อมูลทุก Field
SELECT field_a, field_b, field_c
FROM
test_table
GROUP BY field_a, field_b, field_c;

จากคำสั่งข้างต้นเราทำ GROUP BY field_a, field_b, field_c นั่นคือ ในแต่ละ Record(แถวของข้อมูล) จะต้องมีค่าแต่ละ Field เท่ากัน ทุก Field ในตัวอย่าง
field_a มีค่า '123' เหมือนกันทุก Record
field_b มีค่า 'ทดสอบ' เหมือนกันทุก Record
แต่ field_c มีความแตกต่างกัน โดยจะมีค่า เป็น '1' ,'2' หรือ '3'
ดังนั้น Record ที่เหมือนกันทุก Field จะรวมเป็น Record เดียวกัน

ผลลัพธ์ที่ได้ คือ
field_afield_bfield_c
123ทดสอบ1
123ทดสอบ2
123ทดสอบ3

ชุดที่ 2 : เลือกแสดงบาง Field
SELECT field_a, field_b
FROM
test_table
GROUP BY field_a, field_b;

จากคำสั่งข้างต้นเราทำ GROUP BY field_a, field_b นั่นคือ ในแต่ละ Record จะต้องมีค่าแต่ละ Field เท่ากัน ทุก Field ที่ทำ GROUP BY ในตัวอย่าง
field_a มีค่า '123' เหมือนกันทุก Record 
field_b มีค่า 'ทดสอบ' เหมือนกันทุก Record

ผลลัพธ์ที่ได้ คือ
field_afield_b
123ทดสอบ

ชุดที่ 3
: เลือกแสดงข้อมูลบาง Field แต่ Group By ทุก Field
SELECT field_a, field_b
FROM
test_table
GROUP BY field_a, field_b, field_c;

จากคำสั่งข้างต้นเราทำ GROUP BY field_a, field_b, field_c นั่นคือ ในแต่ละ Record จะต้องมีค่าแต่ละ Field เท่ากัน ทุก Field ที่ทำการ Group By ในตัวอย่าง
field_a มีค่า '123' เหมือนกันทุก Record
field_b มีค่า 'ทดสอบ' เหมือนกันทุก Record
แต่ field_c มีความแตกต่างกัน โดยจะมีค่า เป็น '1' ,'2' หรือ '3'
ดังนั้น Record ที่เหมือนกันทุก Field จะรวมเป็น Record เดียวกัน

ผลลัพธ์ที่ได้ คือ
field_afield_b
123ทดสอบ
123ทดสอบ
123ทดสอบ

* จากคำสั่งชุดที่ 3 ถึงแม้การแสดงผลจะแสดงออกมาไม่ครบทุก Field แต่ข้อมูลที่เรานำมา Group By ก็สามารถ นำ Filed ที่ไม่แสดงผลมา Goup By ด้วยก็ได้ อันนี้ก็ขึ้นอยู่กับผลลัพธ์ที่เราต้องการ

สิ่งที่ควรจำสำหรับการทำ Group By
- เราจำเป็นต้องนำ Field ที่เรา Select(แสดงผล) มา Group By ทุก Field เสมอ
- เราสามารถนำ Field อื่นที่ไม่แสดงผลมาร่วม Group By ได้ (จากตัวอย่าง ชุดคำสั่งที่ 3)
- Field ที่เราเขียน Group By ทุก Field จะถูกนำมาคิดร่วมกันเพื่อเช็คข้อมูลกลุ่มเดียวกัน นั่นคือค่าของทุก Field ต้องมีค่าเท่ากันทั้งหมด จึงจะมองว่าเป็น Record เดียวกัน 

ทิ้งท้ายซักนิด
ในบางครั้งถ้าเราต้องการทำ Group By เท่ากับ Field ที่เรา Select เราอาจจะเขียนแทนด้วย Distinct ก็ได้ค่ะ
ตัวอย่าง
จากชุดคำสั่งที่ 1
SELECT field_a, field_b, field_c
FROM
test_table
GROUP BY field_a, field_b, field_c;
เราอาจจะเขียนแทนเป็น

SELECT DISTINCT field_a, field_b, field_c
FROM
test_table;

ซึ่งจะได้ผลลัพธ์ เหมือนกัน พิเศษหน่อยตรงที่ Distinct จะเรียงลำดับให้เราด้วยค่ะ แล้วค่อยมาว่ากันต่อ กับ Group By ขั้นต่อไป และ Distinct สามารถทำงานให้ประสิทธิภาพมากกว่านี้ค่ะ ...โปรดติดตามตอนต่อไป

วันจันทร์ที่ 13 กันยายน พ.ศ. 2553

การเขียน SQL แบบอ้างอิงตารางเดียวกันแต่คนละข้อมูล

สวัสดีค่ะ วันนี้แอบเข้าไปดูในสถิติของตนเอง พบว่ามีคนค้นหาทาง Google ด้วยค่ะ คริคริ ...แล้วก็แอบมาคลิก web ของเราด้วย แสดงว่าที่เราเขียนไปก็ไม่เสียเปล่า (มีความสุขนะเนี่ยะ)

แล้วก็พบว่าส่วนหนึ่งของคำที่ค้นหา คือ "postgresql อ้างอิงตารางเดียวสองครั้ง" 
ทำให้กลับมาคิดว่า เราน่าจะเอาความรู้พื้นฐานมาเขียนบ้าง เพราะว่า บางทีเอาตัวอย่างการใช้งานจริงมาเขียนจะยากไปสำหรับคนที่เข้ามาศึกษาใหม่ แล้วอาจจะไม่เกิดประโยชน์ก็ได้ ดังนั้นวันนี้ก็ขอเอาใจกับคนที่ Search จากข้อความข้างต้นนะคะ ไม่รู้ว่าจะเข้ามาอ่านอีกหรือเปล่า

สำหรับการเขียน Query หรือ เขียน SQL นั้นโดยส่วนใหญ่ภาษาที่ใช้ในแต่ละฐานข้อมูลก็จะเหมือนกัน แต่อาจจะต่างกันบางคำสั่ง หรือบาง Function ค่ะ ในที่นี้ขออ้างอิง PostgreSQL แล้วกันนะ (ก็นู๋ถนัดอันนี้นี่นา)

รูปแบบการเขียน Query พื้นฐาน
select fieldName
from tableName
where condition

ส่วนคำสั่งพื้นฐานอื่นๆ จะไม่ขอกล่าวถึงใน บทความเรื่องนี้ นะคะ เอาไว้จะหาโอกาสเขียนให้อีกครั้งละกันค่ะ

postgresql อ้างอิงตารางเดียวสองครั้ง 

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


select t_patient_id
,t_patient.patient_hn
,t_patient.patient_firstname
,t_patient.patient_lastname
,t_patient.patient_birthday
,t_patient.patient_house
,t_patient.patient_road
,t_patient.patient_moo
--เวลาอ้างถึงเพื่อแสดงผล หรือเพื่อนำไปใช้เป็นเงื่อนไขก็ต้องอ้างชื่อที่เราตั้งใหม่นะคะ
--หากเราอ้างถึง f_address แล้วละก็ฐานข้อมูลจะมองเป็นตารางที่ 4 ทันทีเลย ทีนี้ข้อมูลเราเบิ้ลแน่ๆค่ะ เพราะไม่มีการ join

,district.address_description
as district
,amphur.address_description
as amphur
,province.address_description
as province
from t_patient
inner join f_address as district --ตรงนี้ตั้งชื่อเล่นใหม่ค่ะเป็น district
    on t_patient.patient_tambon = district.f_address_id
 
inner join f_address as amphur --ตรงนี้ตั้งชื่อเล่นใหม่ค่ะเป็น amphur
    on t_patient.patient_amphur = amphur.f_address_id
 inner join f_address as province
--ตรงนี้ตั้งชื่อเล่นใหม่ค่ะเป็น province
    on t_patient.patient_changwat = province.f_address_id
where t_patient.patient_active = '1'
    and t_patient.patient_record_date_time like '2553%'
limit 100;



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

วันจันทร์ที่ 6 กันยายน พ.ศ. 2553

โรคเรื้อรังครั้งแรกหรือไม่ ....

ไหนๆก็ไหนๆ แล้วนำมาฝากกันเพิ่มเติมอีกนิดค่ะ เพราะว่าลืมนึกถึงไปว่าโรคเดียวกันแต่มีหลาย ICD10 ดังนั้น รอบก่อนที่เขียนตาม ICD10 น่ะ ไม่ค่อยเป็นประโชย์มากนัก


รอบนี้เลยขอแก้ตัวใหม่ค่ะ ดูตามโรคกันไป แต่ในที่นี้ขอยกโรคเรื้อรัง ละกันนะคะ เพราะฐานข้อมูล Hospital OS จัดเก็บ โรคเรื้อรังไว้ค่ะ เราสามารถทราบได้ว่า ICD10 ไหนเป็นโรคเรื่อรังอะไรจากข้อมูลตรงนี้ค่ะ

เอาคำสั่งนี้ไปรัน นะคะ (รันครั้งเดียวก็พอ) 

 CREATE OR REPLACE FUNCTION check_new_disease(varchar, varchar, varchar, varchar,varchar) RETURNS bool AS
'
declare
    patient_id varchar;
    visit_id varchar;
    visit_icd10 varchar;   
    visit_datetime varchar;
    disease varchar;
    newvisit boolean;
    check_visit record;
begin
    patient_id = $1;
    visit_id = $2;
    visit_icd10 = $3;
    visit_datetime = $4;
    disease = $5; 
    newvisit = true;
  
    if (patient_id <> '''' and visit_id <> '''' and visit_icd10 <> '''' and disease <> '''' and visit_datetime <> '''')
    then  
            for check_visit in (select t_visit.t_visit_id as t_visit_id
                                        from t_visit inner join t_diag_icd10
                                                on t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn and t_diag_icd10.diag_icd10_active = ''1''
                                                inner join b_group_icd10
                                                on upper(substring(b_group_icd10.group_icd10_number,1,3)) = upper(substring(t_diag_icd10.diag_icd10_number,1,3))
                                                 inner join b_group_chronic
                                                on b_group_icd10.b_group_chronic_id = b_group_chronic.b_group_chronic_id
                                        where t_visit.t_visit_id <> visit_id
                                                and t_visit.t_patient_id = patient_id
                                                and b_group_chronic.b_group_chronic_id = disease
                                                and t_visit.f_visit_status_id <> ''4''
                                                and substring(t_visit.visit_financial_discharge_time,1,10) < substring(visit_datetime,1,10))        
            loop
                if check_visit.t_visit_id is not null
                then newvisit = false;
                else
                --กรณี Join กันไม่สำเร็จ
                end if;
            end loop;
    else      newvisit = true;       
    end if;
    return newvisit;
end;
'
LANGUAGE 'plpgsql';

ตัวอย่างการนำไปใช้ ดังนี้ค่ะ
SELECT t_visit.t_visit_id
,t_visit.t_patient_id
,t_diag_icd10.diag_icd10_number
,b_group_chronic.b_group_chronic_id
,b_group_chronic.group_chronic_number
,b_group_chronic.group_chronic_description_th
--เรียกใช้ตรงนี้
,check_new_disease(t_visit.t_patient_id,t_visit.t_visit_id,t_diag_icd10.diag_icd10_number,t_visit.visit_financial_discharge_time,b_group_chronic.b_group_chronic_id)
FROM t_visit inner join t_diag_icd10
on t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn and t_diag_icd10.diag_icd10_active = '1'
inner join b_group_icd10
on upper(substring(b_group_icd10.group_icd10_number,1,3)) = upper(substring(t_diag_icd10.diag_icd10_number,1,3))
inner join b_group_chronic
on b_group_icd10.b_group_chronic_id = b_group_chronic.b_group_chronic_id
where t_visit.f_visit_status_id <> '4'
and substring(t_visit.visit_financial_discharge_time,1,10) = '2553-04-01';


ยังไงก็ลองนำไปใช้ดูนะคะ ... ไม่แน่ใจว่าจะตรงตามที่ต้องการหรือเปล่า ก็สามารถนำไปปรับปรุงได้เลยค่ะ

วันเสาร์ที่ 4 กันยายน พ.ศ. 2553

อยากทราบมั้ยว่าผู้ที่มารับบริการและได้รับการวินิจฉัยนั้นเป็นครั้งแรกในปีหรือไม่

สวัสดีค่ะ ไหนๆวันนี้เราก็ได้ Function ไป 1 แล้ว เอาเพิ่มไปอีกอันแล้วกันค่ะ เป็นเรื่องของการหารายใหม่ในปีของโรคนั้นๆ ...

ก่อนอื่นมาทำความเข้าใจกับคำว่ารายใหม่กันก่อนนะคะ สำหรับรายใหม่ที่จริงก็ขึ้นอยู่กับว่ารายงานเราต้องการอะไรมากกว่า
1.ต้องการรายใหม่ในปีงบประมาณ
นั่นคือ ผู้มารับบริการรายนั้น มารับบริการครั้งแรกในปีงบประมาณ  (เช่น ปีงบประมาณ 2553 เริ่มจาก ตุลาคม 2552 - กันยายน 2553)
2.ต้องการรายใหม่ในปีปฏิทิน
นั่นคือ ผู้มารับบริการรายนั้น มารับบริการครั้งแรกในปีปฏิทิน  (เช่น ปี 2553 เริ่มจาก มกราคม 2553 - ธันวาคม 2553)

จาก 2 ข้อข้างต้นที่กล่าวมานั้นเป็นการหาผู้ที่มารับบริการรายใหม่ในปี แต่ที่จะกล่าวถึงต่อไปนี้เป็นการหารายใหม่ในปีของโรคนั้นๆ หมายความว่า เราต้องการทราบว่า ผู้ที่มารับบริการและได้รับการวินิจฉัยนั้นเป็นครั้งแรกหรือไม่ (ปีปฏิทินนะคะ)

ซึ่งเราก็สามารถเอาความรู้เรื่อง Function มาช่วยเขียนเพื่อลดความซับซ้อนลงได้ค่ะ

Function check_newvisit_icd เป็น Function สำหรับตรวจสอบว่า นั้นเป็นรายใหม่ในปีของโรคนั้นหรือไม่ โดยผลที่ส่งออกมาจะ เป็นค่า
true = รายใหม่  
false = รายเก่า

เอาคำสั่งนี้ไปรัน นะคะ (รันครั้งเดียวก็พอ)

CREATE OR REPLACE FUNCTION check_newvisit_icd (varchar, varchar, varchar, varchar) RETURNS bool AS
'
declare
    patient_id varchar;
    visit_id varchar;
    visit_datetime varchar;
    visit_icd10 varchar;
    newvisit boolean;
    check_visit record;
begin
    patient_id = $1;
    visit_id = $2;
    visit_icd10 = $3;
    visit_datetime = $4;
    newvisit = true;
   
    if (patient_id <> '''' and visit_id <> '''' and visit_datetime <> '''' and visit_icd10 <> '''')
    then   
            for check_visit in select t_diag_icd10.diag_icd10_number as icd10
                    from t_visit inner join t_diag_icd10 on t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
                    where t_visit.t_visit_id <> visit_id
                            and t_visit.t_patient_id = patient_id
                            and t_diag_icd10.diag_icd10_active = ''1''
                            and t_diag_icd10.diag_icd10_number = visit_icd10
                            and substring(t_visit.visit_financial_discharge_time,1,4) = substring(visit_datetime,1,4)
                            and substring(t_visit.visit_financial_discharge_time,1,10) < substring(visit_datetime,1,10)  
                           
            loop
                if check_visit.icd10 is not null
                then newvisit = false;
                else
                --กรณี Join กันไม่สำเร็จ
                end if;
            end loop;

    else      newvisit = true;
        
    end if;

    return newvisit;
end;
'
LANGUAGE 'plpgsql';

หลังจากนั้นเวลาเรียกใช้ 
select
t_visit.t_visit_id
,t_visit.t_patient_id
,t_visit.visit_vn
,t_visit.visit_financial_discharge_time
,t_diag_icd10.diag_icd10_number
-- บรรทัดข้างล่างเป็นการเรียกใช้ 
--โดยส่งค่า t_patient_id,t_visit_id,isit_financial_discharge_time,diag_icd10_number
,check_newvisit_icd(t_visit.t_patient_id,t_visit.t_visit_id,t_diag_icd10.diag_icd10_number,t_visit.visit_financial_discharge_time)
-- เป็นการดัดแปลงไปใช้งาน เพื่อแสดงผลตามที่ต้องการ โดยใช้ case เข้ามาช่วย เนื่องจากส่งค่า true กับ  false จึงไม่ต้องมีเงื่อนไขอะไรอีก when ไปได้เลยค่ะ
,case when check_newvisit_icd(t_visit.t_patient_id,t_visit.t_visit_id,t_diag_icd10.diag_icd10_number,t_visit.visit_financial_discharge_time)
    then 'รายใหม่'
    else 'รายเก่า'
end
from t_visit inner join t_diag_icd10
on t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
where t_visit.f_visit_status_id <> '4'
and t_diag_icd10.diag_icd10_active = '1'
and substring(t_visit.visit_financial_discharge_time,1,10) = '2553-03-29';

* ส่วนเราจะปรับใช้อย่างไร ก็ได้ค่ะ ...

แก้ปัญหาการเขียน Query ยาวๆ กับการจัดการเรื่องวันที่ใน Hospital OS

สวัสดีค่ะ สืบเนื่องมาจากได้คุยกับคุณลุงคนหนึ่งแกเป็น เภสัชกรอยู่ที่โรงพยาบาลท่าแพ จังหวัดสตูล และเราก็คุยกันหลายเรื่องค่ะ คือเราพบว่าเรามีเป้าหมายเดียวกัน คือ อยากให้สิ่งดีๆ เกิดขึ้นในชุมชนแห่งการเรียนรู้ การแบ่งปันความรู้ และอยากให้คงอยู่ตลอดไป อันนี้ไม่ได้เกี่ยวกับอะไรทั้งนั้น เป็นเพียงกลุ่มคน กลุ่มหนึ่งที่ต้องการสร้างสรรสิ่งดีๆ และแบ่งปันกัน ก็เท่านั้นเอง

แต่สัจจธรรมก็คือ ต่างคนก็ต่างมีภาระ หน้าที่ ความรับผิดชอบ ... ใช่เราไม่ได้หมายถึง 24 ชั่วโมงของเราจะมาทุ่มเทกับสิ่งหนึ่งเพราะมันเป็นไปไม่ได้ แต่เราหาเวลาเพื่อมาทุ่มเทต่างหาก เรามีงานมีหน้าที่ เรามีครอบครัว เรามีสังคม เรามีชุมชน เราสามารถมีความสุขและทำได้ถ้าเรารู้จักความพอดี ก็บอกแล้วความสุขของคนต่างกัน ... เลือกเอาว่าจะมีความสุขแบบไหน ลองถามตัวเองว่าเคยได้รับผลตอบแทนจากการกระทำที่เป็นรอยยิ้มบ้างมั้ย ได้มองสิ่งที่เราทำแล้วอมยิ้มอยู่คนเดียว จะมีความสุขมากแค่ไหน ... ลองทำดู

เกือบจะไปบวชชีแล้ว ... แต่ก็ยังละทางโลกไม่ได้ที อิอิ
งั้นเรามาเข้าเรื่องกันดีกว่าค่ะ  เพื่อให้เราทำงานง่ายขึ้นเราก็เขียนตัวช่วยมาซักตัวเอาไว้เรียกใช้กันนะคะ
สำหรับที่นำมาฝากกันวันนี้ เป็นเรื่องการหาผลต่างของเวลา ซึ่งโดยปกติ Hospital OS เก็บข้อมูลวันที่เป็น varchar ในลักษณะของปี พ.ศ. เช่น '2553-09-04,10:16:38'  จะเก็บข้อมูลไว้แบบนี้ค่ะเวลาเรานำข้อมูลมาใช้งาน เราก็ต้องเข้าใจว่าข้อมูลเก็บอยู่อย่างไร

มาเล่าเรื่องวันที่กันอีกทีนะคะ ขึ้นอยู่กับว่าเรานำข้อมูลไปใช้ยังไง
สมมติข้อมูล
t_visit.visit_financial_discharge_time = '2553-04-03,01:22:24'
t_visit.visit_begin_visit_time = '2553-03-30,13:46:48'

1. เรานำไปแสดงในรายงานโดยตรง ส่วนใหญ่ผู้ใช้งานต้องการ เป็น พ.ศ. อยู่แล้ว
เราสามารถ Substring ไปได้เลยค่ะ
ใช้คำสัง -> substring(t_visit.visit_financial_discharge_time,1,10)
ผลลัพธ์ -> 2553-04-03
2. ออกแฟ้มรายงาน หรือ ส่งเข้าโปรแกรมอื่นที่ต้องการข้อมูลรูปแบบ ค.ศ.
ใช้คำสัง -> cast(substring(t_visit.visit_financial_discharge_time,1,4) as numeric) - 543 || substring(t_visit.visit_financial_discharge_time,5,6)
ผลลัพธ์ -> 2010-04-03
3. ต้องการคำนวนวันจากวันที่ การใช้ Function เกี่ยวกับวันที่นั้นโดยปกติของฐานข้อมูลจะมองข้อมูลวันที่เป็น ค.ศ. ดังนั้นหากเราต้องการได้ข้อมูลที่ถูกต้องจริงๆ เราจะต้องแปลงข้อมูลให้เป็น ค.ศ. เสียก่อน (อ้างอิงจาก ข้อ 2 ) หลังจากนั้นเราก็ค่อยนำมาใช้กับ Function อีกที ในที่นี้จะยกตัวอย่างการแปลงวันที่ เพื่อลบกันได้ นั่นคือ
3.1 เราทำการแปลวันที่สิ้นสุดก่อน ในที่นี้ขอใช้ วันที่จำหน่ายทางการเงิน
cast(substring(t_visit.visit_financial_discharge_time,1,4) as numeric) - 543 || substring(t_visit.visit_financial_discharge_time,5,6)
จะได้วันที่ในรูปแบบ ค.ศ.
หลังจากนั้นก็ cast ให้เป็นวันที่ เพื่อจะนำไปลบ โดยยกข้างต้นมาทั้งชุดเลยนะคะ
เขียน cast( as date) วางไว้ก่อนก็ได้ จากนั้น copy มาทั้งชุดมาวางหลัง วงเล็บเปิด (
จะได้
cast(cast(substring(t_visit.visit_financial_discharge_time,1,4) as numeric) - 543 || substring(t_visit.visit_financial_discharge_time,5,6) as date)
จากข้อมูลสมมติ จะได้ 3/4/2553
3.2 เราทำการแปลงวันที่เริ่มต้น ในที่นี้ขอใช้วันที่ visit
cast(substring(t_visit.visit_begin_visit_time,1,4) as numeric) - 543 || substring(t_visit.visit_begin_visit_time,5,6)
จะได้วันที่ในรูปแบบ ค.ศ.
หลังจากนั้นก็ cast ให้เป็นวันที่ เพื่อจะนำไปลบ โดยยกข้างต้นมาทั้งชุดเลยนะคะ
เขียน cast( as date) วางไว้ก่อนก็ได้ จากนั้น copy มาทั้งชุดมาวางหลัง วงเล็บเปิด (
จะได้
cast(cast(substring(t_visit.visit_begin_visit_time,1,4) as numeric) - 543 || substring(t_visit.visit_begin_visit_time,5,6) as date)
จากข้อมูลสมมติ จะได้ 30/3/2553
3.3 นำข้อ 3.1 ลบด้วย 3.2
cast(cast(substring(t_visit.visit_financial_discharge_time,1,4) as numeric) - 543 || substring(t_visit.visit_financial_discharge_time,5,6) as date)
-
cast(cast(substring(t_visit.visit_begin_visit_time,1,4) as numeric) - 543 || substring(t_visit.visit_begin_visit_time,5,6) as date)

จากข้อมูลสมมติ จะได้ 4 หน่วยเป็นวัน (วันที่ - วันที่ = จำนวนวัน

ทีนี้เราทำแค่นี้อาจจะดูไม่ยุ่งยากนะคะ แต่ถ้าเรานำไปเขียนร่วมกับ Query ยาวแล้วอ่านจะตาลายมั้ยก็ไม่รู้ ...
ลองนำ Function นี้ไปใช้กันมั้ยคะ
function difference_datead_secs เป็น Function สำหรับหาผลต่างของวันที่และเวลา ได้ค่า วินาที ออกมาค่ะ

เริ่มต้นจากนำคำสั่งนี้ไปรัน นะคะ (รันครั้งเดียวก็พอ)
CREATE OR REPLACE FUNCTION difference_datead_secs (varchar, varchar) RETURNS int4 AS
'
declare
    date_start varchar;
    date_end varchar;
    result_secs int;
begin
    date_start = $1;
    date_end = $2;
   
    if (length(date_start) >= 10 and length(date_end) >= 10)
    then  
               result_secs = (date_part(''day'', (cast((cast(substring(date_end,1,4) as numeric) - 543) || substring(date_end,5) as timestamp)
                                                 -
                                               cast((cast(substring(date_start,1,4) as numeric) - 543) || substring(date_start,5) as timestamp))) * 60 * 60 * 24
                                    +
                                    date_part(''hour'', (cast((cast(substring(date_end,1,4) as numeric) - 543) || substring(date_end,5) as timestamp)
                                                 -
                                               cast((cast(substring(date_start,1,4) as numeric) - 543) || substring(date_start,5) as timestamp))) * 60 * 60
                                    +
                                    date_part(''min'', (cast((cast(substring(date_end,1,4) as numeric) - 543) || substring(date_end,5) as timestamp)
                                                 -
                                               cast((cast(substring(date_start,1,4) as numeric) - 543) || substring(date_start,5) as timestamp))) * 60
                                    +
                                    date_part(''sec'', (cast((cast(substring(date_end,1,4) as numeric) - 543) || substring(date_end,5) as timestamp)
                                                 -
                                               cast((cast(substring(date_start,1,4) as numeric) - 543) || substring(date_start,5) as timestamp))) ) ;

    else    result_secs = 0;
        
    end if;

    return result_secs;
end;
'
LANGUAGE 'plpgsql';

หลังจากนั้นเวลาเรียกใช้ 
select
t_visit.t_patient_id
,t_visit.t_visit_id
,t_visit.visit_vn
,t_visit.visit_begin_visit_time
,t_visit.visit_financial_discharge_time
,t_visit_service.b_service_point_id
,t_visit_service.assign_date_time
,t_visit_service.visit_service_treatment_date_time
,t_visit_service.visit_service_finish_date_time
-- บรรทัดข้างล่างเป็นการเรียกใช้ โดยส่งวันที่เข้าไปไม่ต้องแปลงข้อมูลใน Function ที่เราสร้างไปแล้วนั้นจะแปลงให้เองค่ะ
,difference_datead_secs(t_visit.visit_begin_visit_time,t_visit.visit_financial_discharge_time)
from t_visit inner join  t_visit_service
on t_visit.t_visit_id = t_visit_service.t_visit_id
where t_visit_service.visit_service_finish_date_time <> ''
and t_visit.f_visit_status_id <> '4'
and t_visit.f_visit_type_id = '0'
and substring(t_visit.visit_financial_discharge_time,1,10) = '2553-04-03'
order by
t_visit.t_patient_id
,t_visit.t_visit_id;

ลองดูนะคะ หากไม่เข้าใจก็ถามได้ค่ะ