วันจันทร์, สิงหาคม 08, 2548
เรื่องของ SQL ตอนที่ 1
ตอนบ่ายเข้าไปที่เวบ เห็นมีเพื่อนสมาชิกท่านหนึ่งกำลังกังวล เกี่ยวกับการจัดทำระเบียนผู้ป่วยโรคเบาหวานว่าต้องการรายชื่อผู้ป่วยเบาหวานออกมาโดยไม่ให้ชื่อซ้ำกัน หรือง่ายๆก็คือนับเฉพาะผู้ป่วยรายใหม่เท่านั้น ก็เลยลองมานั่งคิดว่า คนถึกอย่างเราพอจะทำได้อ่ะป่าว ลองดูน่ะ
แหล่งความรู้และอ้างอิง
แสดงความคิดเห็น
Powered for by Blogger Templates
- ประการแรกรายชื่อผู้ป่วยเบาหวานโดยไม่ให้ซ้ำชื่อกัน ดูๆ แล้วไม่น่าจะลำบากอะไรนัก แล้วจะเอาอะไรบ้างล่ะ
- 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