วันอังคาร, สิงหาคม 30, 2548
เรื่องของ SQL ตอนที่ 1 เพิ่มเติมอีกหน่อยนึง
ได้รับ Requirement กลับมาเกี่ยวกับ SQL ตอนที่ 1 ว่าถ้าหากต้องการดึงเอาวันที่มารับบริการ และอายุออกมาด้วยล่ะ จะทำได้อย่างไร ก็พยายามไปค้นคว้ามารับใช้ให้จนได้แม้ว่าจะหินโคตรๆ
ขอย้อนกลับไปที่ SQL ชุดนั้นอีกครั้งแล้วกันเผื่อคนที่พึ่งเข้ามาอ่านครั้งแรกจะได้ปะติดปะต่อเรื่องราวได้
อยากได้วันที่ที่มารับบริการก็เพิ่มเข้าไปได้ครับ เอาเป็นก่อนหน้า HN ดีไหม
แหล่งความรู้และอ้างอิง
ขอย้อนกลับไปที่ SQL ชุดนั้นอีกครั้งแล้วกันเผื่อคนที่พึ่งเข้ามาอ่านครั้งแรกจะได้ปะติดปะต่อเรื่องราวได้
SELECT
t_patient.patient_hn AS hn
,pat_address.patient_name AS name
,pat_address.address AS address
,t_patient.patient_pid AS pid
FROM
t_patient,pat_address,t_visit,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_patient.patient_hn = pat_address.patient_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-01-01' and '2548-03-31')
AND t_patient.patient_hn NOT IN
( /* เริ่มต้น */
SELECT
DISTINCT(t_patient.patient_hn)
FROM
t_patient
,t_visit
,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-01-01' and '2548-02-28')
) /* สิ้นสุด */
GROUP BY
hn
,pat_address.patient_name
,pat_address.address
,t_patient.patient_pid
ORDER BY hn
อยากได้วันที่ที่มารับบริการก็เพิ่มเข้าไปได้ครับ เอาเป็นก่อนหน้า HN ดีไหม
- เพิ่ม SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) AS date_visit
- เพิ่มอายุเข้าไปอีก อายุเนี่ยจะเอาอายุยังงัยล่ะ เอาอายุตอนไหน เอาอายุ ณ วันนี้เหรอ หรือว่าเป็นอายุตอนที่เข้ารับบริการ
- ถ้าเป็นอายุ ณ วันปัจจุบัน ก็เอาวันที่ปัจจุบันตั้ง เอาวันเกิดของผู้ป่วยมาลบออก ก็เป๊ะ
- ถ้าเป็นอายุ ตอนที่มารับบริการ ก็เอาวันที่จำหน่ายทางการเงินตั้ง เอาวันเกิดผู้ป่วยมาลบออก แต่คิดว่าคอนเซ็ปต์นี้น่าจะเวอร์คกว่า ไอเดียนี่ไม่ยาก แต่จะทำอย่างไรล่ะทีนี้ ที่ว่ายากประการแรกคือ ในฐานข้อมูลเก็บค่าวันที่อยู่ในรูปของ varchar (Character Varying) + เก็บเป็นพุทธศักราชอีกด้วย ต้องทำการแปลงให้อยู่ในรูปของวันที่เสียก่อน จึงจะเอาไปทำการคำนวณ บวก ลบ คูณ หาร ได้สะดวก คงไม่มีใครเอาตัวแปรแบบ varchar ตั้งแล้วเอา ตัวแปรแบบ varchar ไปลบออกเป็นแน่แท้ ถึงอยากทำก็คงทำไม่ได้
- เราลองเอาค่าของวันที่มาแตกย่อยดูก่อนเป็นไร ค่าวันที่ที่เก็บในฐานข้อมูลเป็นอย่างนี้ 2548-07-01,11:44:45 (ปี-เดือน-วัน , ชั่วโมง : นาที : วินาที)
- สิ่งที่เราต้องการคือ ปี เดือน วัน เท่านั้น ก็เอาแต่ 2548-07-01 มาเล่น วิธีตัดออกมาเป็นบางช่วงที่เราต้องการคือใช้ฟังก์ชั่น SUBSTRING ช่วย ฟอร์แมตของฟังก์ชั่น SUBSTRING นี้คือ SUBSTRING(text,n1,n2) หรือจะเขียนอีกแบบก็ได้คือ SUBSTRING(text FROM n1 FOR n2) เมื่อ text คือข้อความที่เราต้องการจะตัด , n1 คือตำแหน่งเริ่มต้นที่เราต้องการจะตัดเริ่มจากซ้ายมือ , n2 คือ จำนวนสตริงที่เราต้องการจะตัด
(n1 และ n2 ต้องเป็นจำนวนเต็มนะครับ ทศนิยมใช้ไม่ได้)
For Example
- SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 4) จะได้เท่ากับ 2548 ซึ่งจะได้ค่าออกมาเท่ากับ SUBSTRING(t_visit.visit_financial_discharge_time,1,4) หรือ SUBSTRING(t_visit.visit_financial_discharge_time from 0 for 5)
- SUBSTRING(t_visit.visit_financial_discharge_time,2,2) ได้เท่ากับ 54 เป็นต้น
- ลองตัดกันจริงแล้วนะ ฟอร์แมตวันที่ของ PostgreSQL คือ เดือน/วัน/ปี นะครับ ต้องทำให้ถูกต้องด้วย
- เอาเดือนออกมาก่อน substring(t_visit.visit_financial_discharge_time from 6 for 2) จะได้เท่ากับ 07 (ยังเป็น text อยู่)
- ที่นี้ก็เอาวันออกมา substring(t_visit.visit_financial_discharge_time from 9 for 2) จะได้เท่ากับ 01 (ยังเป็น text อยู่)
- ตามด้วยปี substring(t_visit.visit_financial_discharge_time from 1 for 4) (ยังเป็น text อยู่) เราก็ทำต่อให้เป็นตัวเลขซะเพื่อที่จะแปลงค่าเป็น ค.ศ. ได้ เนื่องจากยังเป็น พ.ศ. อยู่โดย ใช้พังก์ชั่น to_number ครอบ ได้เป็น
--> to_number(substring(t_visit.visit_financial_discharge_time) ,9999)-543 ก็จะได้เป็น 2005 (2548-543) ตอนนี้เป็นตัวเลขแล้ว - ลองเอาสิ่งที่ได้จากที่เราได้ลองตัดสตริงมาต่อกันดูซิด้วยตัวไปป์คู่ (||) ได้ดังนี้
substring(t_visit.visit_financial_discharge_time from 6 for 2)||'/'||
substring(t_visit.visit_financial_discharge_time from 9 for 2)||'/'||
CAST(to_number(substring(t_visit.visit_financial_discharge_time from 1 for 4) ,9999) - 543 AS text) เหตุที่เราต้อง CAST อีกทีก็เพราะว่า 2005 เป็น Numeric ในขณะที่ชาวบ้านเค้าเป็น text กัน ทำให้เป็น text เหมือนกันทั้งสามชุด สมมติว่าชุดนี้คือ เดือน/วัน/ปี ที่เราตัดสตริงมาเนี่ยมีค่าเป็น X (X อีกแล้วผมว่าพยัญชนะตัวนี้เหนื่อยที่สุดในโลกเลยก็ว่าได้)
- จากค่า X ที่เราได้มาแล้วก็ต้องมาทำการแปลงให้เป็น varchar อีกครั้งเพื่อสะดวกในการแปลงให้เป็น Date ก็เอา CAST ครอบ X ไปอีกที ได้เป็น
CAST(x AS varchar) แต่ยังไม่จบได้เป็น varchar แล้ว สมมติให้เป็น Y ต้องแปลงให้เป็น date อีกโดยการใช้ฟังก์ชั่น to_date ครอบ Y อีกที ได้เป็น
to_date(y,'MM-DD-YYYY') ในวงเล็บระบุด้วยว่าต้องแปลงให้เป็น เดือน-วัน-ปี ด้วยนะ ยุ่งมากมายเลยเนี่ย - เมื่อแทนค่า X และ Y ในสมการแล้วก็จะได้เป็นดังนี้
to_date(CAST(substring(t_visit.visit_financial_discharge_time from 6 for 2)
||'/'||substring(t_visit.visit_financial_discharge_time from 9 for 2)
||'/'||CAST(to_number(substring(t_visit.visit_financial_discharge_time from 1 for 4) ,9999) - 543 AS text) AS varchar),'MM-DD-YYYY')
- ฉันใดก็ฉันนั้นเมื่อเราได้วันที่มารับบริการแล้วก็น่าจะได้วันเกิดของผู้ป่วยด้วย โดยใช้การแทนค่าด้วยตัวแปรเหมือนข้างบน
to_date(CAST(substring(t_patient.patient_birthday from 6 for 2)
||'/'||substring(t_patient.patient_birthday from 9 for 2)
||'/'||CAST(to_number(substring(t_patient.patient_birthday from 1 for 4) ,9999) - 543 AS text) AS varchar),'MM-DD-YYYY')
- ผมอยากจะบอกว่า ไอ้ที่พูดมาข้างบนน่ะ ผมไม่ได้ทำด้วยตัวเอง ได้รับความอนุเคราะห์จากน้องๆ ที่น่ารัก (โอ๋ , เหน่ง) ทำให้ต่างหาก
- ความยากลำบากในการที่ต้องแปลงนั่นเป็นนี่ แปลงนี่ให้เป็นนั่น เนื่องมาจากค่าของวันที่นั้น ผมได้ Request ไปในเวอร์ชั่นสามแล้ว แต่ไม่รู้ว่าผลการ Request จะออกมาอย่างไรบ้าง
- เมื่อได้วันที่ที่มารับบริการและวันเกิดที่สามารถจะเอาทำการคำนวณได้แล้ว ก็เอาวันที่ที่มารับบริการตั้งแล้วลบด้วยวันเกิด แต่การที่จะทำอย่างนั้นแล้วได้ผลลัพท์ออกมาเลยน่ะ ค่อนข้างยาก และซับซ้อน โชคดีที่มีตัวช่วยอยู่ คือเมื่อก่อนเหน่ง (สุรชัย ต่อวงศ์) ได้ทำฟังก์ชันเกี่ยวกับวันที่ ไว้ให้ผมใช้สองสามอัน ก็เลยเอามาโมดิฟายเล่น เพื่อลดภาระในการเขียน SQL และช่วยในการคำนวณ ฟังก์ชันนี้ผมเรียกว่า ฟังก์ชันแสดงอายุแบบสั้น สร้างด้วยภาษา plpgsql หน้าตาเป็นอย่างนี้ครับ
วิธีการก็คือเอาไปฝังไว้ในฐานข้อมูล
CREATE OR REPLACE FUNCTION calage3(date,date)
RETURNS text AS
'DECLARE
d INTEGER;
BEGIN
d:= $1-$2;
IF d > 365 THEN RETURN d/365 || \' ป\';
ELSE
IF d = 365 THEN RETURN \'1 ป\';
ELSE
IF d > 30 THEN RETURN d/30 || \' ด\';
ELSE
IF d = 30 THEN RETURN \'1 ด\';
ELSE
IF d > 1 THEN RETURN d || \' ว\';
ELSE
IF d = 1 THEN RETURN \'1 ว\';
ELSE
IF d <= 0 THEN RETURN \'0 ว\';
END IF;
END IF;
END IF;
END IF;
END IF;
END IF;
END IF;
RETURN \' \';
END;'
LANGUAGE 'plpgsql' VOLATILE;
postgres@debian: ~ $ psql your_database < calage3.fnc.sql
ฟังก์ชั่นนี้จะสามารถทำงานได้ก็ต่อเมื่อ มีการส่งค่าพารามิเตอร์มาสองค่า จะมากหรือน้อยกั่วนี้ย่อมเป็นไปมิได้เด็ดขาด ค่าแรกที่ต้องส่งมาคือ t_visit.visit_financial_discharge_time และค่าที่สองคือ t_patient.patient_birthday ทั้งนี้ทั้งสองค่าต้องผ่านการแปลงค่าด้วยวิธีการข้างบนเรียบร้อยแล้วด้วยนะ รูปแบบการใช้ฟังก์ชั่นนี้ก็คือ
calage3(visit_date,birth_date)
มาดูค่าจริงๆ ที่เราต้องเขียนเป็น SQL ก็คือ
calage3(to_date(CAST(substring(t_visit.visit_financial_discharge_time from 6 for 2)
||'/'||substring(t_visit.visit_financial_discharge_time from 9 for 2)
||'/'||CAST(to_number(substring(t_visit.visit_financial_discharge_time from 1 for 4) ,9999) - 543 AS text) AS varchar),'MM-DD-YYYY')
,
to_date(CAST(substring(t_patient.patient_birthday from 6 for 2)
||'/'||substring(t_patient.patient_birthday from 9 for 2)
||'/'||CAST(to_number(substring(t_patient.patient_birthday from 1 for 4) ,9999) - 543 AS text) AS varchar),'MM-DD-YYYY')
) AS age
- เอาล่ะนะ ลองเอามาทำเป็น SQL เต็มๆ ดูบ้าง
SELECT
SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) AS date_visit
,t_patient.patient_hn AS hn
,pat_address.patient_name AS name
,calage3(to_date(CAST(substring(t_visit.visit_financial_discharge_time from 6 for 2)
||'/'||substring(t_visit.visit_financial_discharge_time from 9 for 2)
||'/'||CAST(to_number(substring(t_visit.visit_financial_discharge_time from 1 for 4) ,9999) - 543 AS text) AS varchar),'MM-DD-YYYY')
,
to_date(CAST(substring(t_patient.patient_birthday from 6 for 2)
||'/'||substring(t_patient.patient_birthday from 9 for 2)
||'/'||CAST(to_number(substring(t_patient.patient_birthday from 1 for 4) ,9999) - 543 AS text) AS varchar),'MM-DD-YYYY')
) AS age
,pat_address.address AS address
,t_patient.patient_pid AS pid
FROM
t_patient,pat_address,t_visit,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_patient.patient_hn = pat_address.patient_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-01-01' and '2548-03-31')
AND t_patient.patient_hn NOT IN
( /* เริ่มต้น */
SELECT
DISTINCT(t_patient.patient_hn)
FROM
t_patient
,t_visit
,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-01-01' and '2548-02-28')
) /* สิ้นสุด */
GROUP BY
hn
,pat_address.patient_name
,pat_address.address
,t_patient.patient_tambon
,t_visit.visit_financial_discharge_time
,t_patient.patient_birthday
,t_patient.patient_pid
ORDER BY date,hn,t_patient.patient_tambon
ประโยคที่ท่านเห็นเป็นสีแดงคือสิ่งที่เพิ่มเข้าไปใหม่ และต้องเพิ่ม Group By เข้าไปอีกสองสามบรรทัดนะครับ SQL จึงจะทำงานได้สมบูรณ์ ไม่มีเอ๋อเหล๋อ ได้ผลลัพท์อย่างนี้ครับ
ก็จบเรื่องราวของ SUBSTRING , การแปลงค่าด้วยฟังก์ชั่นต่างๆ รวมถึงการใช้งานฟังก์ชั่นที่สร้างขึ้นมาเอง ภาษาฝรั่งตามที่ได้ยินมาเค้าเรียกว่า User Define Function (UDF) ก็คงจะได้ไอเดียเอาเรื่องราวเหล่านี้ไปประยุกต์ใช้ให้เหมาะสมได้ทั่วหน้ากันนะครับ
2548-03-08 000000435 นางศรี อินปุ๊ด 53 ป 42 ม.05 ต.วอแก้ว อ.ห้างฉัตร จ.ลำปาง 3510500027099
2548-03-30 000001530 นายแก้ว วงศ์จันทร์ 73 ป 33 ม.07 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500074780
แหล่งความรู้และอ้างอิง
- John C. Worsley and Joshua D. Drake, Practical PostgreSQL
O'Reilly & Associates, Inc., CA ,USA
วันจันทร์, สิงหาคม 29, 2548
เรื่องของ SQL ตอนที่ 2
ตอนที่แล้วเขียนเรื่องเกี่ยวกับ SQL ไปตอนหนึ่งรู้สึกว่า เริ่มมันส์ในอารมณ์ เกิดความกระเหี้ยนกระหืออยากจะเขียนอีก ว่าแล้วก็สนองตัณหาของตัวเองอีกเป็นไรมี ใครคงจะไม่นินทา (ให้ได้ยิน) เป็นแน่แท้ ไหนๆ ก็อยู่กับเรื่อง Sub Query แล้วก็เขียนต่ออีกซะเลย เอาเรื่องเบาหวานนี่แหละ คนอ่านจะได้ต่อเนื่องไม่ต้องเสียเวลาลำดับความคิด ย้อนไปย้อนมา ให้ปวดหมอง
ก่อนที่จะมาเป็น SQL ในตอนที่แล้วเคยได้รับโจทย์จากบรรดาเจ้านาย ที่น่ารักทั้งหลาย เกี่ยวกับคนไข้เบาหวานนี่แหละว่า อยากจะได้รายชื่อคนไข้เบาหวาน ที่อยู่ อายุ และรายละเอียดอีกหลายอย่าง แล้วกำชับด้วยนะว่าเอารหัสออกมาให้ดูด้วย (คงประมาณว่าอยากจะรู้ว่าเรา SELECT มั่วมั่งอะป่าว) ก็ได้สิ จะไปยากอะไรกัน แถมรหัสการวินิจฉัยให้ด้วยก็ยังไหว
นี่เลย SQL โชว์ให้ดู เป็น SQL ชุดที่หนึ่งแล้วกัน
สังเกตุที่ตัวสีแดงใน SQL ชุุดที่สองนะครับ ทำไมต้องมี ตัวอักษรตามหลังตารางด้วย อย่างเช่น t_patient ตามด้วย t , pat_address ตามด้วย p ขอเฉลยตรงนี้ครับว่าตัวอักษรที่ตามหลังชื่อตารางคือ alias (ประมาณว่าเป็นชื่อเล่นของตาราง) การกำหนดเราสามารถกำหนดได้ตามใจชอบจะเป็นตัวอักษรหรือคำอะไรก้ได้แต่ไม่น่าจะซ้ำกัน มีประโยชน์ตรงที่เราสามารถจะอ้างอิงใช้ชื่อ alias แทนชื่อของตารางได้เวลาที่เรา จะ SELECT หรืออ้างถึงชื่อตารางเราก็สามารถจะใช้ชื่อ alias แทนได้ เช่นถ้าเราจะพิมพ์ว่า t_patient.hn เราก็อาจจะย่อเป็น t.hn ก็ได้ช่วยลดเวลาการพิมพ์ลงไปได้สำหรับผู้ที่พิมพ์ไม่ค่อยคล่องนัก แต่ก็มีผลเหมือนกันนะครับ อันนี้แล้วแต่ความสะดวกของคนพิมพ์ กับความชอบส่วนตัว ของใครของมัน เอาล่ะ มาต่อกันได้แล้ว ไม่ว่าจะรันด้วย SQL ชุดที่หนึ่งหรือว่าชุดที่สอง ก็จะได้ผลลัพท์ดังนี้
ก็ต้องอภัยสำหรับผู้ป่วยทุกท่านที่บังอาจนำเอาความลับส่วนตัวของท่านมาเผยแพร่ ขอเรียนว่ามิได้มีเจตนาเป็นอย่างอื่นนอกเหนือจากการศึกษา ได้ผลมาแล้วครับ ไม่ยากส์ คิดว่าจบหรือยังล่ะครับ มองเห็นอะไรที่มันทะแม่งๆ หรือปล่าว
ตอนแรกพอผมคิวรี่ได้ปุ๊บก็ส่งผลให้คนที่มาขอไปเลย ไม่ได้คิดอะไร อีกสักพักแกกลับมาครับ หน้างี้บอกว่าใช่เลย คือแกสงสัยว่าคนไข้บางคนรหัสการให้การวินิจฉัยทำไมเป็น 4 (other) บ้าง 2 (Comorbidity) บ้าง ทำไมไม่เป็นหนึ่งเหมือนกันทั้งหมด แล้วคนที่มีรหัสวินิจฉัยเป็นอย่างอื่นนอกจาก 1 (Primary Diagnosis) เนี่ยจะรู้ได้หรือปล่าวว่ามี รหัสโรคอะไรเป็นโรคร่วมได้อีก เอาออกมาให้หมดได้มั๊ย แล้วก็โยนผลการคิวรี่กลับมาให้ผมอย่างแรง ผมไม่ทันจะอธิบายอะไร แกก็ไปเสียแล้ว ทิ้งปริศนาให้ผมขบคิด ก็จริงอย่างที่แกว่า เรามานึกดูเงื่อนไขก็ไม่น่าจะผิดอะไร ก็ SELECT รหัส ICD10 ที่บ่งชี้ว่าเป็นโรคเบาหวานออกมาให้แล้วงัย มานั่งนึกดูอีกทีว่าเราลืมอะไรไปหรือปล่าว
ลืมจริงๆ ครับ ลืมนึกไปว่าก็เรา SELECT ออกมาเฉพาะรหัสโรคทีประกอบด้วย E10 E11 E14 E 15 แต่ผู้ป่วยน่ะไม่จำเป็นว่าเวลามารับการรักษาจะได้รหัสโรคอันเดียวนี่ อาจจะมีโรคร่วมด้วย เช่นปวดเมื่อยกล้ามเนื้อซึ่งเป็นอาการเด่นในการมารับบริการครั้งนั้นก็ได้ ซึ่งรหัสโรคอาจจะมีเท่ากับหนึ่งหรือมากกว่าก็ได้ใช่ป่าว แต่จะทำอย่างไรถึงจะ SELECT ออกมาได้ทั้งหมดและมีรหัสของโรคเบาหวานออกมาด้วย ทำอย่างไรล่ะ (โ.....๊ย)
ใช้หมองหน่อย เป็นอิ๊กคิวซํงอยู่สองสามวันได้ พบว่า
ก็จบไปสำหรับเรื่องราวมหาวิบาก ที่ได้ประสบพบพาน เอามาเล่าสู่กันฟัง ผมเองถูกมองว่าเป็นเซียนบ้างล่ะ ผู้เชี่ยวชาญบ้างล่ะ แต่ความจริงไม่ใช่ ก็เป็นยูสเซอร์ธรรมดาเหมือนๆกับท่านทั้งหลายแหละครับ ยังมีคนที่เก่งกว่าผมอีกมากนัก อาศัยลูกตื้อ อ่านให้มาก ทำให้เยอะ ลองผิดบ้างถูกบ้าง พอเราเริ่มเข้าใจแล้ว มันก็จะทำให้เราหาทางแก้ปัญหาที่เข้ามาได้เร็วขึ้น เนียนขึ้น อย่าพึ่งยอมแพ้ อาจจะต้องหาเวลาส่วนตัวให้มากขึ้นกว่าเดิม ของอย่างนี้ต้องใช้เวลาบ้างนะครับ ต้องยอมนอนดึกกว่าปกติ เล่นเกมส์ที่ชอบน้อยลง คุยโทรศัพท์กับกิ๊กน้อยลง แล้วความสำเร็จก็จะเป็นของเรา ขอให้กำลังใจกับทุกท่านครับ เราทำเองเราได้เอง โปรดติดตามตอนต่อไปนะครับ น่าสนใจทีเดียวแหละ เป็นเรื่องราวของ น้องจอย (JOIN) เกริ่นให้ฟังยั่วน้ำลายเล่นๆ ไปก่อน
แหล่งความรู้และอ้างอิง
ก่อนที่จะมาเป็น SQL ในตอนที่แล้วเคยได้รับโจทย์จากบรรดาเจ้านาย ที่น่ารักทั้งหลาย เกี่ยวกับคนไข้เบาหวานนี่แหละว่า อยากจะได้รายชื่อคนไข้เบาหวาน ที่อยู่ อายุ และรายละเอียดอีกหลายอย่าง แล้วกำชับด้วยนะว่าเอารหัสออกมาให้ดูด้วย (คงประมาณว่าอยากจะรู้ว่าเรา SELECT มั่วมั่งอะป่าว) ก็ได้สิ จะไปยากอะไรกัน แถมรหัสการวินิจฉัยให้ด้วยก็ยังไหว
นี่เลย SQL โชว์ให้ดู เป็น SQL ชุดที่หนึ่งแล้วกัน
หรือจะเขียนอีกแบบหนึ่งก็ได้ดังนี้ ตั้งชื่อว่าเป็น SQL ชุดที่สอง
SELECT
SUBSTRING(t_visit.visit_financial_discharge_time,0,11) AS DATE
,t_patient.patient_hn AS hn
,pat_address.patient_name AS name
,pat_address.address AS address
,t_patient.patient_pid AS pid
,t_diag_icd10.diag_icd10_number AS icd10
,t_diag_icd10.f_diag_icd10_type_id AS diag_type
FROM
t_patient,pat_address,t_visit,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_patient.patient_hn = pat_address.patient_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-07-01' and '2548-07-31')
GROUP BY
t_visit.visit_financial_discharge_time
,hn
,pat_address.patient_name
,pat_address.address
,t_patient.patient_pid
,t_diag_icd10.diag_icd10_number
,t_diag_icd10.f_diag_icd10_type_id
ORDER BY hn
SELECT
SUBSTRING(v.visit_financial_discharge_time,0,11) AS DATE
,t.patient_hn AS hn
,p.patient_name AS name
,p.address AS address
,t.patient_pid AS pid
,d.diag_icd10_number AS icd10
,d.f_diag_icd10_type_id AS diag_type
FROM
t_patient t,pat_address p ,t_visit v,t_diag_icd10 d
WHERE
t.patient_hn = v.visit_hn
AND t.patient_hn = p.patient_hn
AND v.t_visit_id = d.diag_icd10_vn
AND v.f_visit_type_id='0'
AND v.f_visit_status_id='3'
AND (d.diag_icd10_number LIKE 'E10%'
OR d.diag_icd10_number LIKE 'E11%'
OR d.diag_icd10_number LIKE 'E14%'
OR d.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(v.visit_financial_discharge_time from 1 for 10) between '2548-07-01' and '2548-07-31')
GROUP BY
v.visit_financial_discharge_time
,hn
,p.patient_name
,p.address
,t.patient_pid
,d.diag_icd10_number
,d.f_diag_icd10_type_id
ORDER BY hn
สังเกตุที่ตัวสีแดงใน SQL ชุุดที่สองนะครับ ทำไมต้องมี ตัวอักษรตามหลังตารางด้วย อย่างเช่น t_patient ตามด้วย t , pat_address ตามด้วย p ขอเฉลยตรงนี้ครับว่าตัวอักษรที่ตามหลังชื่อตารางคือ alias (ประมาณว่าเป็นชื่อเล่นของตาราง) การกำหนดเราสามารถกำหนดได้ตามใจชอบจะเป็นตัวอักษรหรือคำอะไรก้ได้แต่ไม่น่าจะซ้ำกัน มีประโยชน์ตรงที่เราสามารถจะอ้างอิงใช้ชื่อ alias แทนชื่อของตารางได้เวลาที่เรา จะ SELECT หรืออ้างถึงชื่อตารางเราก็สามารถจะใช้ชื่อ alias แทนได้ เช่นถ้าเราจะพิมพ์ว่า t_patient.hn เราก็อาจจะย่อเป็น t.hn ก็ได้ช่วยลดเวลาการพิมพ์ลงไปได้สำหรับผู้ที่พิมพ์ไม่ค่อยคล่องนัก แต่ก็มีผลเหมือนกันนะครับ อันนี้แล้วแต่ความสะดวกของคนพิมพ์ กับความชอบส่วนตัว ของใครของมัน เอาล่ะ มาต่อกันได้แล้ว ไม่ว่าจะรันด้วย SQL ชุดที่หนึ่งหรือว่าชุดที่สอง ก็จะได้ผลลัพท์ดังนี้
2548-07-01 000000435 นางศรี อินปุ๊ด 42 ม.05 ต.วอแก้ว อ.ห้างฉัตร จ.ลำปาง 3510500027099 E11.9 1
2548-07-02 000020659 นางตา อุดก้อน 26/1 ม.07 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500009147 E11 1
2548-07-04 000021908 นางบุญทอน สีสิงห์ 88 ม.07 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500012601 E10.9 1
2548-07-04 000009510 นายจันทร์แก้ว นกแดง 18/1 ม.9 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500057109 E10 1
2548-07-04 000013471 นายสุข ปุ๊ดแค 38/1 ม.09 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500059349 E11 1
2548-07-06 000019694 นางแว่น เปี้ยยะ 48 ม.08 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500079676 E11 1
2548-07-06 000019663 นางบุญเย็น อินมณี 2/1 ม.07 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน E11 1
2548-07-06 000012629 นางศรีออน อินมณี 60 ม.08 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500080143 E11 2
2548-07-06 000006196 นางบัวคำ มีจี๋ 57 ม.09 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500158291 E11.4 1
2548-07-07 000009510 นายจันทร์แก้ว นกแดง 18/1 ม.9 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500057109 E11 1
2548-07-07 000000083 นางจันทร์เป็ง ใฝนันตา 50 ม.08 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500020779 E11 1
2548-07-07 000024066 นางฟอง ป้อจุมปู 38/1 ม.02 ต.*บ้านไผ่ อ.ลี้ จ.ลำพูน E11 1
2548-07-07 000000284 นางหน้อย ขัดสาร 59 ม.08 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500039038 E11 1
2548-07-07 000000038 นางสุข เปอร์เขียว 165 ม.06 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500152675 E11 1
2548-07-07 000001082 นางแก้วลูน อินปุ๊ด 83 ม.06 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500152721 E11 1
2548-07-07 000000959 นายอดุลย์ อุตชมภู 195 ม.03 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500025754 E11 1
2548-07-07 000000435 นางศรี อินปุ๊ด 42 ม.05 ต.วอแก้ว อ.ห้างฉัตร จ.ลำปาง 3510500027099 E11 1
2548-07-07 000002336 นางวิไลวรรณ ทองขาว 12/3 ม.15 ต.แม่ตืน อ.ลี้ จ.ลำพูน 3510400354667 E10 1
2548-07-07 000000050 นางสี สุขเรือง 63 ม.01 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500101965 E11 1
2548-07-07 000018300 นางแพรวพรรณ ฤทัยธรรม 1979 ม. ต.บางจาก อ.พระโขนง จ.กรุงเทพฯ 3100905452325 E11 4
2548-07-07 000006051 นายอัมรินทร์ เชียงคำ 70 ม.04 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500039283 E10 4
2548-07-07 000002453 นางทองพูน ทองขาว 242/1 ม.15 ต.แม่ตืน อ.ลี้ จ.ลำพูน 3510400354772 E11 1
ก็ต้องอภัยสำหรับผู้ป่วยทุกท่านที่บังอาจนำเอาความลับส่วนตัวของท่านมาเผยแพร่ ขอเรียนว่ามิได้มีเจตนาเป็นอย่างอื่นนอกเหนือจากการศึกษา ได้ผลมาแล้วครับ ไม่ยากส์ คิดว่าจบหรือยังล่ะครับ มองเห็นอะไรที่มันทะแม่งๆ หรือปล่าว
ตอนแรกพอผมคิวรี่ได้ปุ๊บก็ส่งผลให้คนที่มาขอไปเลย ไม่ได้คิดอะไร อีกสักพักแกกลับมาครับ หน้างี้บอกว่าใช่เลย คือแกสงสัยว่าคนไข้บางคนรหัสการให้การวินิจฉัยทำไมเป็น 4 (other) บ้าง 2 (Comorbidity) บ้าง ทำไมไม่เป็นหนึ่งเหมือนกันทั้งหมด แล้วคนที่มีรหัสวินิจฉัยเป็นอย่างอื่นนอกจาก 1 (Primary Diagnosis) เนี่ยจะรู้ได้หรือปล่าวว่ามี รหัสโรคอะไรเป็นโรคร่วมได้อีก เอาออกมาให้หมดได้มั๊ย แล้วก็โยนผลการคิวรี่กลับมาให้ผมอย่างแรง ผมไม่ทันจะอธิบายอะไร แกก็ไปเสียแล้ว ทิ้งปริศนาให้ผมขบคิด ก็จริงอย่างที่แกว่า เรามานึกดูเงื่อนไขก็ไม่น่าจะผิดอะไร ก็ SELECT รหัส ICD10 ที่บ่งชี้ว่าเป็นโรคเบาหวานออกมาให้แล้วงัย มานั่งนึกดูอีกทีว่าเราลืมอะไรไปหรือปล่าว
ลืมจริงๆ ครับ ลืมนึกไปว่าก็เรา SELECT ออกมาเฉพาะรหัสโรคทีประกอบด้วย E10 E11 E14 E 15 แต่ผู้ป่วยน่ะไม่จำเป็นว่าเวลามารับการรักษาจะได้รหัสโรคอันเดียวนี่ อาจจะมีโรคร่วมด้วย เช่นปวดเมื่อยกล้ามเนื้อซึ่งเป็นอาการเด่นในการมารับบริการครั้งนั้นก็ได้ ซึ่งรหัสโรคอาจจะมีเท่ากับหนึ่งหรือมากกว่าก็ได้ใช่ป่าว แต่จะทำอย่างไรถึงจะ SELECT ออกมาได้ทั้งหมดและมีรหัสของโรคเบาหวานออกมาด้วย ทำอย่างไรล่ะ (โ.....๊ย)
ใช้หมองหน่อย เป็นอิ๊กคิวซํงอยู่สองสามวันได้ พบว่า
- ข้อแรก ถ้าเรารู้ว่ามี HN อะไรบ้างที่มีรหัสโรคของเบาหวาน เอาแค่ HN อย่างเดียวพอ อันอื่นไม่เอาก็คงได้ ก็เอา SQL ชุดข้างบนนี่แหละตัดแต่งนิดหน่อย ดังนี้ เป็น SQL ชุดที่สาม
SELECT
t.patient_hn AS hn
FROM
t_patient t,pat_address p ,t_visit v,t_diag_icd10 d
WHERE
t.patient_hn = v.visit_hn
AND t.patient_hn = p.patient_hn
AND v.t_visit_id = d.diag_icd10_vn
AND v.f_visit_type_id='0'
AND v.f_visit_status_id='3'
AND (d.diag_icd10_number LIKE 'E10%'
OR d.diag_icd10_number LIKE 'E11%'
OR d.diag_icd10_number LIKE 'E14%'
OR d.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(v.visit_financial_discharge_time from 1 for 10) between '2548-07-01' and '2548-07-31')
GROUP BY
hn
,d.diag_icd10_number
,d.f_diag_icd10_type_id
ORDER BY hn
ก็ได้ HN ออกมายาวยืด ดังนี้ (ตัดเอามาให้ดูบางส่วน)
"000000038"
"000000050"
"000000083"
"000000164"
"000000216"
"000000284"
"000000330"
"000000435"
"000000435"
"000002095"
"000002336"
"000002453"
"000002938"
"000003553"
"000003553"
"000003566" - ข้อต่อมาก็คือ ถ้าเราจะ SELECT รายละเอียดของผู้ป่วยตามโจทย์ของเราออกมา (โดยที่มีรหัสการวินิจฉัยโรคออกมาให้หมด) ก็คงจะเขียน SQL เหมือนกับ SQL ชุดแรก คือชุดที่ยังมีปัญหาอยู่ แต่เราก็คงไม่ต้องระบุว่าให้เอารหัสโรค คือ E10 E11 E14 E15 ออกมาด้วย ก็คงได้ประมาณนี้ โดยตัดเอาบรรทัดที่เป็นสีแดงออกไปซะ แล้วก็ให้เรียงตามวันที่ ตามด้วย HN ตามด้วยรหัสประเภทของการวินิจฉัยซะหน่อย (ORDER BY DATE,HN,diag_type) เป็น SQL ชุดที่สี่
ก็ได้ผลตามข้างล่างนี้สังเกตุดูผู้ป่วยที่ชื่อคุณศรีออน อินมณี (วันที่ 6 ) กับคุณแพรวพรรณ ฤทัยธรรม (วันที่ 7) นะครับ (ตัวแดง) เห็นว่าได้รหัสโรคออกมามากกว่าที่เห็นในผลลัพท์แรกแล้ว แต่ปํญหาคือว่ายังมีผู้ป่วยท่านอื่นที่ไม่ได้ป่วยด้วยเบาหวานออกมาด้วยอีก
SELECT
SUBSTRING(t_visit.visit_financial_discharge_time,0,11) AS DATE
,t_patient.patient_hn AS hn
,pat_address.patient_name AS name
,pat_address.address AS address
,t_patient.patient_pid AS pid
,t_diag_icd10.diag_icd10_number AS icd10
,t_diag_icd10.f_diag_icd10_type_id AS diag_type
FROM
t_patient,pat_address,t_visit,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_patient.patient_hn = pat_address.patient_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-07-01' and '2548-07-31')
GROUP BY
t_visit.visit_financial_discharge_time
,hn
,pat_address.patient_name
,pat_address.address
,t_patient.patient_pid
,t_diag_icd10.diag_icd10_number
,t_diag_icd10.f_diag_icd10_type_id
ORDER BY DATE,diag_type
2548-07-01 000000030 นางบัวสาย กาใจ 131 ม.03 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500023069 M15.0 1
2548-07-01 000000142 นางไพรลิน ขัดสาร 68 ม.02 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500024642 A09 1
2548-07-01 000000160 นางผัด ยาแปง 90/5 ม.03 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500115133 M79.1 1
2548-07-01 000000168 นางอำนวย จุนุ 163 ม.06 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500151610 N64.4 1
2548-07-01 000000435 นางศรี อินปุ๊ด 42 ม.05 ต.วอแก้ว อ.ห้างฉัตร จ.ลำปาง 3510500027099 E11.9 1
2548-07-01 000000435 นางศรี อินปุ๊ด 42 ม.05 ต.วอแก้ว อ.ห้างฉัตร จ.ลำปาง 3510500027099 J00 2
...........................
2548-07-06 000012288 นางสาย บุญเลี้ยง 257/2 ม.15 ต.แม่ตืน อ.ลี้ จ.ลำพูน 3330400850771 R10.0 1
2548-07-06 000012629 นางศรีออน อินมณี 60 ม.08 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500080143 I10 1
2548-07-06 000012629 นางศรีออน อินมณี 60 ม.08 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500080143 E11 2
2548-07-06 000012646 ส.ต.ท.เชษฐ์ ก้อนทอง สภอ.ทุ่งหัวช้าง ม.02 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน Z00.0 1
...........................
2548-07-07 000018204 นายภาสกร เต๋จา 185/1 ม.04 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 1510500032533 S05.0 1
2548-07-07 000018204 นายภาสกร เต๋จา 185/1 ม.04 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 1510500032533 W20.09 5
2548-07-07 000018300 นางแพรวพรรณ ฤทัยธรรม 1979 ม. ต.บางจาก อ.พระโขนง จ.กรุงเทพฯ 3100905452325 I10 1
2548-07-07 000018300 นางแพรวพรรณ ฤทัยธรรม 1979 ม. ต.บางจาก อ.พระโขนง จ.กรุงเทพฯ 3100905452325 E11 4
2548-07-07 000018486 นางจันทิมา แปงตำ 109/1 ม.02 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 2500400019363 Z34.8 1
2548-07-07 000018486 นางจันทิมา แปงตำ 109/1 ม.02 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 2500400019363 Z71.7 2 - สรุปแล้ว จาก SQL ชุดที่สี่ ถ้าหากว่าเราสามารถเพิ่มเงื่อนไขตรง WHERE ให้เอาเฉพาะ HN ที่ได้จาก SQL ชุดที่สาม ก็น่าจะได้ผลลัพท์ที่ เริ่ด เลอ เพอร์เฟ็ค เป๊ะมั๊ยทีนี้ ไม่เชื่อถามอาจารย์ประมาณ หรืออาจารย์วันชัย (รายการคนหัวหมอ) ก็ได้ เอาชุดที่สามเป็น SUB Query ซะ แล้วก็ใช้ Operator ชนิด IN ช่วย ร่างให้ดูก่อนก็ได้
SELECT
SUBSTRING(t_visit.visit_financial_discharge_time,0,11) AS DATE
,t_patient.patient_hn AS hn
,pat_address.patient_name AS name
,pat_address.address AS address
,t_patient.patient_pid AS pid
,t_diag_icd10.diag_icd10_number AS icd10
,t_diag_icd10.f_diag_icd10_type_id AS diag_type
FROM
t_patient,pat_address,t_visit,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_patient.patient_hn = pat_address.patient_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-07-01' and '2548-07-31')
AND t_patient.patient_hn IN
( /* เริ่ม SQL ชุดที่สาม */
SELECT
t.patient_hn AS hn
FROM
t_patient t,pat_address p ,t_visit v,t_diag_icd10 d
WHERE
t.patient_hn = v.visit_hn
AND t.patient_hn = p.patient_hn
AND v.t_visit_id = d.diag_icd10_vn
AND v.f_visit_type_id='0'
AND v.f_visit_status_id='3'
AND (d.diag_icd10_number LIKE 'E10%'
OR d.diag_icd10_number LIKE 'E11%'
OR d.diag_icd10_number LIKE 'E14%'
OR d.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(v.visit_financial_discharge_time from 1 for 10) between '2548-07-01' and '2548-07-31')
GROUP BY
hn
,d.diag_icd10_number
,d.f_diag_icd10_type_id
ORDER BY hn /* สิ้นสุด SQL ชุดที่สาม */
)
GROUP BY
t_visit.visit_financial_discharge_time
,t_patient.patient_hn
,pat_address.patient_name
,pat_address.address
,t_patient.patient_pid
,t_diag_icd10.diag_icd10_number
,t_diag_icd10.f_diag_icd10_type_id
ORDER BY DATE,t_patient.patient_hn,diag_type
อิ อิ ดูผลลัพท์เปรียบเทียบกันกับผลลัพท์ที่ได้จากชุดแรก
2548-07-06 000006196 นางบัวคำ มีจี๋ 57 ม.09 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500158291 E11.4 1
2548-07-06 000007187 นางสาวเสาวรีย์ หล้าเป็ง 65 ม.05 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500045429 E10.9 1
2548-07-06 000007431 นางเงิน สุปี 143 ม.05 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500045399 E10.9 1
2548-07-06 000008221 นางทองไพร มันจา 60 ม.09 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500160252 E11 1
2548-07-06 000012629 นางศรีออน อินมณี 60 ม.08 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500080143 I10 1
2548-07-06 000012629 นางศรีออน อินมณี 60 ม.08 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500080143 E11 2
2548-07-06 000013110 ด.ต.วิชัย มะโนวงศ์ สภอ.ทุ่งหัวช้าง ม.02 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3580200103326 Z00 1
2548-07-07 000018300 นางแพรวพรรณ ฤทัยธรรม 1979 ม. ต.บางจาก อ.พระโขนง จ.กรุงเทพฯ 3100905452325 I10 1
2548-07-07 000018300 นางแพรวพรรณ ฤทัยธรรม 1979 ม. ต.บางจาก อ.พระโขนง จ.กรุงเทพฯ 3100905452325 E11 4
2548-07-07 000019330 นางบัวคำ ทานา 40 ม.1 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500010113 E11 1
2548-07-07 000019330 นางบัวคำ ทานา 40 ม.1 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500010113 M79.1 2
2548-07-07 000020603 นายดอก คำแก้ง 70 ม.5 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500138206 E11 1 - กิ๊บ เก๋ ยูเรก้า ไปเลยครับท่านผู้ชม ข้อกฏหมายไม่มีพลิกอีกแล้ว เฉพาะ SUB Query กับ Operator ชนิด IN กับ NOT IN เนี่ยก็เอาไปหากินได้เยอะแยะ เช่นหาผู้ป่วยในหรือนอกเขตบริการของสถานพยาบาล ทีนี้ก็หวานหมู
ก็จบไปสำหรับเรื่องราวมหาวิบาก ที่ได้ประสบพบพาน เอามาเล่าสู่กันฟัง ผมเองถูกมองว่าเป็นเซียนบ้างล่ะ ผู้เชี่ยวชาญบ้างล่ะ แต่ความจริงไม่ใช่ ก็เป็นยูสเซอร์ธรรมดาเหมือนๆกับท่านทั้งหลายแหละครับ ยังมีคนที่เก่งกว่าผมอีกมากนัก อาศัยลูกตื้อ อ่านให้มาก ทำให้เยอะ ลองผิดบ้างถูกบ้าง พอเราเริ่มเข้าใจแล้ว มันก็จะทำให้เราหาทางแก้ปัญหาที่เข้ามาได้เร็วขึ้น เนียนขึ้น อย่าพึ่งยอมแพ้ อาจจะต้องหาเวลาส่วนตัวให้มากขึ้นกว่าเดิม ของอย่างนี้ต้องใช้เวลาบ้างนะครับ ต้องยอมนอนดึกกว่าปกติ เล่นเกมส์ที่ชอบน้อยลง คุยโทรศัพท์กับกิ๊กน้อยลง แล้วความสำเร็จก็จะเป็นของเรา ขอให้กำลังใจกับทุกท่านครับ เราทำเองเราได้เอง โปรดติดตามตอนต่อไปนะครับ น่าสนใจทีเดียวแหละ เป็นเรื่องราวของ น้องจอย (JOIN) เกริ่นให้ฟังยั่วน้ำลายเล่นๆ ไปก่อน
แหล่งความรู้และอ้างอิง
- John C. Worsley and Joshua D. Drake, Practical PostgreSQL
O'Reilly & Associates, Inc., CA ,USA
วันจันทร์, สิงหาคม 08, 2548
เรื่องของ SQL ตอนที่ 1
ตอนบ่ายเข้าไปที่เวบ เห็นมีเพื่อนสมาชิกท่านหนึ่งกำลังกังวล เกี่ยวกับการจัดทำระเบียนผู้ป่วยโรคเบาหวานว่าต้องการรายชื่อผู้ป่วยเบาหวานออกมาโดยไม่ให้ชื่อซ้ำกัน หรือง่ายๆก็คือนับเฉพาะผู้ป่วยรายใหม่เท่านั้น ก็เลยลองมานั่งคิดว่า คนถึกอย่างเราพอจะทำได้อ่ะป่าว ลองดูน่ะ
แหล่งความรู้และอ้างอิง
Powered for - ประการแรกรายชื่อผู้ป่วยเบาหวานโดยไม่ให้ซ้ำชื่อกัน ดูๆ แล้วไม่น่าจะลำบากอะไรนัก แล้วจะเอาอะไรบ้างล่ะ
- HN ได้จากตาราง t_patient
- เลขประจำตัวประชาชน 13 หลัก จาก ตาราง t_patient
- ชื่อ ได้จากตาราง pat_address
- ที่อยู่ ก็ได้จาก pat_address พอแค่นี้ก่อนก็แล้วกัน
เงื่อนไขที่ใช้ก็ ผู้ป่วยนอกที่จบกระบวนการแล้ว ตั้งแต่วันที่ ถึงวันที่ แล้วเอาเฉพาะผู้ป่วยเบาหวาน (รหัส ICD10 ก็น่าจะเป็น E10 ,E11 ,E14 ,E15) โอเชตามนี้ก็ลงมือ ลุยเลย ได้ SQL มาแบบนี้แหล่ะ
SELECT
t_patient.patient_hn AS hn
,pat_address.patient_name AS name
,pat_address.address AS address
,t_patient.patient_pid AS pid
FROM
t_patient,pat_address,t_visit,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_patient.patient_hn = pat_address.patient_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-01-01' and '2548-03-31')
GROUP BY
hn
,pat_address.patient_name
,pat_address.address
,t_patient.patient_pid
ORDER BY hn
ก็ได้ผลลัพท์ออกมาตามต้องการ ความจริงมีเยอะกว่านี้เอามาให้ดูแค่ช่วงเดียว
000000038 นางสุข เปอร์เขียว 165 ม.06 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500152675
000000050 นางสี สุขเรือง 63 ม.01 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500101965
000000083 นางจันทร์เป็ง ใฝนันตา 50 ม.08 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500020779
000000138 นายโยง ผัดดอก 48 ม.12 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500042331
000000164 นางเมือง ทาวี 24/1 ม.01 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500099367
000000216 นายฑูรย์ มะโนชัย 108 ม.07 ต.ตะเคียนปม อ.ทุ่งหัวช้าง จ.ลำพูน 3510500013187
000000284 นางหน้อย ขัดสาร 59 ม.08 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500039038
000000330 นางมิ่ง ตายะ 7/1 ม.06 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500026718
000000521 นางจันทร์เป็ง มะโนเนือง 3 ม.08 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500077819
000000853 นายหล้า ใต้ตา 65/2 ม.06 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน
000000959 นายอดุลย์ อุตชมภู 195 ม.03 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500025754
000001041 นางปัน วงศ์ศรีใส 92/1 ม.08 ต.ทุ่งหัวช้าง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500019967
แต่ลองมาคิดให้ละเอียดอีกที ก็พบว่ายังหลวมไปหน่อย คือถ้าหากว่าเรา ใช้เงื่อนไขนี้กับช่วงเวลาที่มากๆ หลายๆ เดือน ก็อาจจะพบว่าผู้ป่วยในเดือนก่อนๆ ก็จะแสดงผลออกมาด้วย แทนที่จะเป็นผู้ป่วยรายใหม่ตามที่ต้องการ เริ่มรู้สึกว่ายากขึ้นมาหน่อยๆ แล้ว
ทีนี้จะหาผู้ป่วยใหม่ได้อย่างไร นึกๆ ดูตาม common sense ถ้าหากว่าเราต้องการหา HN ผู้ป่วยใหม่ (เริ่มมารับบริการตั้งแต่ 1 มีนาคม - 31 มีนาคม) เอาง่ายๆว่าหา HN ของเดือนมีนาคม เราก็ต้องรู้ว่า HN ของผู้ป่วยเบาหวานตั้งแต่ 1 มกราคม ถึง 31 มีนาคม มี HN อะไรบ้าง (X) และก็ต้องรู้ว่าตั้งแต่ 1 มกราคม - 28 กุมภาพันธ์ มี HN อะไรบ้าง (Y) ได้สองข้อนี้เราก็จบ เราก็เลือก HN เอาที่มีใน X แต่ไม่มีใน Y ซะก็สิ้นเรื่อง ซึ่งอาจจะมีหรือไม่มีก็ได้ (ไม่แปลก) ถ้าไม่มีก็แสดงว่าไม่มีผู้ป่วยใหม่ ถ้ามีก็มี เราก็ใช้ SQL ข้างบนนั่นอีกทีดีกว่าแต่ตอนนี้เราคงเลือกเอาเฉพาะ HN มาอันเดียวแต่เงื่อนไขและช่วงเวลาแตกต่างกันไป สมมติว่า SQL ข้างล่างนี้เป็นชุดที่สองก็แล้วกัน สังเกตุวันที่นะครับ
SELECT
DISTINCT(t_patient.patient_hn) AS hn
FROM
t_patient
,t_visit
,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-08-01' and '2548-08-28')
ORDER BY hn
ทำไมต้องมี DISTINCT ด้วยก็เพราะว่าต้องการไม่ให้ HN ซ้ำอย่างไรเล่าถ้าไม่ใช้ DISTINCT HN อาจจะออกมาซ้ำกันก็ได้ หรือจะใช้แบบไม่มี DISTINCT ก็ต้อง GROUP HN ด้วยเหมือนข้างล่างนี้
SELECT
t_patient.patient_hn AS hn
FROM
t_patient
,t_visit
,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-08-01' and '2548-08-28')
GROUP BY hn
ORDER BY hn
ก็ได้ผลเหมือนกัน
เอาล่ะเมื่อเราได้ค่า X ค่า Y เรียบร้อยแล้วก็เอา SQL ชุดที่หนึ่งมาดัดแปลงอีกหน่อย โดยเพิ่มเงื่อนไขเข้าไปตรง WHERE อีกนิดเดียว ว่า HN ที่เรา SELECT มาในชุดที่หนึ่งต้องไม่มีใน HN ของที่เรา SELECT ออกมาจากชุดสอง เราจะใช้ SUB Query เข้ามาช่วย โดยให้ SQL ชุดที่สองเป็น SUB Query ซะ บวกกับใช้ NOT IN (Operator Precedence) ลองร่างๆ ดูหน่อยซิ
SELECT
t_patient.patient_hn AS hn
,pat_address.patient_name AS name
,pat_address.address AS address
,t_patient.patient_pid AS pid
FROM
t_patient,pat_address,t_visit,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_patient.patient_hn = pat_address.patient_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-01-01' and '2548-03-31')
/* เงือนไขที่เราจะเพิ่มเข้าไป */
AND t_patient.hn NOT IN (SQL ชุดที่สอง)
/* จบเงื่อนไข */
GROUP BY
hn
,pat_address.patient_name
,pat_address.address
,t_patient.patient_pid
ORDER BY hn
ดู SQL ที่เราจะใช้กันจริงๆ ดีกว่า
SELECT
t_patient.patient_hn AS hn
,pat_address.patient_name AS name
,pat_address.address AS address
,t_patient.patient_pid AS pid
FROM
t_patient,pat_address,t_visit,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_patient.patient_hn = pat_address.patient_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-01-01' and '2548-03-31')
AND t_patient.patient_hn NOT IN
( /* เริ่มต้น */
SELECT
DISTINCT(t_patient.patient_hn)
FROM
t_patient
,t_visit
,t_diag_icd10
WHERE
t_patient.patient_hn = t_visit.visit_hn
AND t_visit.t_visit_id = t_diag_icd10.diag_icd10_vn
AND t_visit.f_visit_type_id='0'
AND t_visit.f_visit_status_id='3'
AND (t_diag_icd10.diag_icd10_number LIKE 'E10%'
OR t_diag_icd10.diag_icd10_number LIKE 'E11%'
OR t_diag_icd10.diag_icd10_number LIKE 'E14%'
OR t_diag_icd10.diag_icd10_number LIKE 'E15%')
AND (SUBSTRING(t_visit.visit_financial_discharge_time from 1 for 10) between '2548-01-01' and '2548-02-28')
) /* สิ้นสุด */
GROUP BY
hn
,pat_address.patient_name
,pat_address.address
,t_patient.patient_pid
ORDER BY hn
ก็จะได้ผลลัพท์ออกมาประมาณนี้
000000435 นางศรี อินปุ๊ด 42 ม.05 ต.วอแก้ว อ.ห้างฉัตร จ.ลำปาง 3510500027099
000001530 นายแก้ว วงศ์จันทร์ 33 ม.07 ต.บ้านปวง อ.ทุ่งหัวช้าง จ.ลำพูน 3510500074780
ไม่ยากใช่ไหมครับ ถ้ารู้แนวทางนี้แล้ว อาจนำไปประยุกต์ไช้ได้อีกมากมาย หลากหลาย แต่มีข้อควรระวัง อยู่ว่า SQL ที่เราจะนำมาทำเป็น SUB Query นั้นเราสามารถที่จะ SELECT ออกมาได้เพียงค่าเดียวเท่านั้นนะครับ เหมือนที่ผม SELECT เอาแค่ HN ออกมาค่าเดียวใน SQL ในชุดที่สอง อีกประการหนึ่งก็คือว่าถ้าหากต้องการหารายละเอียดผู้ป่วยเดือนมกราคมแล้วล่ะก็ ใช้แค่ SQL ชุดที่หนึ่งก็พอเพียงแล้ว
ส่วนวิธีการสร้างตาราง pat_address ก็ทำได้โดยการโหลดไฟล์ pat_address.sql แล้วก็เอาไปไว้ที่ home ของ postgres แล้วก็postgres@debian:~$ psql your_database < pat_address.sql
ความจริงเป็นวิว (view) ครับไม่ใช่ตารางจริงๆ แต่เป็นตารางที่ทำขึ้นมาเทียมๆ (Pseudo Table) เพื่อช่วยให้การคิวรี่อะไรง่ายขึ้น
แหล่งความรู้และอ้างอิง
- John C. Worsley and Joshua D. Drake, Practical PostgreSQL
O'Reilly & Associates, Inc., CA ,USA


Copyright ? 2008-2009 Uthai Lueadnakrop. All Rights reserved