Mine

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

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

แก้ปัญหาการเขียน 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;

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

5 ความคิดเห็น:

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

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

    ตอบลบ
  3. เข้ามาอ่าน ...ฮิฮิ
    พี่โอ๋ สุดยอดสรรสาระ และความบันเทิง
    เดี๋ยวเอาไปใช้ม่าง

    ตอบลบ
  4. ว่าแต่ อ.โอ๋เขียน function เสียเป็นว่าเล่นเลย มีมั้ยที่ ไม่พอใจ functionที่เขียนแล้วต้องการจะลบ ฟังชั่นนั้นออกไปจาก postgres
    แบบไม่เหลือเยื่อใย
    อีกประเด็นที่ยังไม่รู้คือการเขียน function เข้าไปเนี้ยะทำให้ database เราโตขี้นเยอะป่าว เป็นห่วงพวก 6 gig เค้า

    ตอบลบ
  5. มีใีครคนนึงบอกมาว่าเรียนผูกก้อต้องเรียนแก้ ...งั้นก็เอาไปลบกันเลย นะคะกับคำสั่ง
    DROP FUNCTION difference_datead_secs(varchar, varchar);

    * เราต้องอ้างอิงชื่อ FUNCTION และ Parameter ที่รับให้ครบนะคะ เพราะว่าชื่อเดียวกันอาจจะรับค่าไม่เหมือนกันได้ค่ะ

    ส่วนจะทำให้ database เราโตขี้นเยอะมั้ย อันนี้ไม่แน่ใจนะคะ
    แต่คิดว่าไม่น่าเกียวกับการโตของฐานข้อมูลค่ะ ... เพราะเป็นแค่คำสั่ง
    แต่ถ้าเกี่ยวกับการทำ backup มั้ยนั้นอาจจะเกี่ยวค่ะ ยังไงก็ต้องสร้างเหมือนกันไว้ก็ได้ master และ slave

    ตอบลบ