พบกับบทความเกี่ยวกับคอมพิวเตอร์และ programming ได้ที่ http://articles.modoeye.com | บล็อกอาหาร
PHP development / การหาค่าความถี่ของคอลัมน์
nobuts
nobuts
Thu 20 Dec 2007 16:06:01

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

คราวนี้มาถึงปัญหาของผมบ้าง คือผมมีฐานข้อมูลนักเรียนอยู่ชุดหนึ่ง (4000+ แถว) เป็นข้อมูลการใช้งานศูนย์คอมพิวเตอร์ของโรงเรียนน่ะครับ และผมต้องการจะทำรายงานประจำเดือนขึ้นมาเพื่อหาความถี่ของนักเรียนที่เข้าใช้ จำแนกตามสาขา และวัน โดยข้อมูลที่ต้องการมีดังนี้

1.แผนก
2.ชั้นปี

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

http://rapidshare.com/files/77823801/Student_report.rar

nobuts
nobuts
Thu 20 Dec 2007 16:09:01

ขอโทษทีครับ เปลี่ยนลิ้งค์หน่อย

http://rapidshare.com/files/77825716/Student_report.rar

webmaster
webmaster
Thu 20 Dec 2007 22:11:43
เราในฐานะผู้ดูแลเว็บไซท์ขอขอบคุณมากครับสำหรับคำชม

ผมได้ลองทำการดูตารางแล้ว เขียน Query ได้ประมาณนี้
SELECT u.udate, SUBSTR(i.id, 1, 2) AS uYear, SUBSTR(i.id, 3,4) AS uDept, COUNT(i.no) AS total FROM inet_usage i INNER JOIN (SELECT no, STR_TO_DATE(CONCAT(SUBSTR(s.date, 1, 2 ), '/', SUBSTR(s.date, 4, 2 ), '/', (CAST(SUBSTR(s.date, 7, 2 ) AS UNSIGNED) - 43)),  '%d/%m/%y') AS udate FROM inet_usage s) AS u ON u.no = i.no WHERE MONTH(u.udate)=11 AND YEAR(u.udate)=2007 GROUP BY uYear, uDept, u.udate

จะใช้เวลาในการ Query ประมาณ 3.7989 sec นับว่าช้ามากๆ ผมจึงทำการสร้าง Primary key ที่ no และทำการ Index ที่ id, date เพื่อให้ทำการได้เร็วขึ้น ได้ผลความเร็วที่ 0.0967 sec จะเห็นได้ว่าความเร็วต่างกันเกือบ 3 วินาที/ครั้ง ในทีนี้คุณจะสามารถลดการทำงานของ php จาก ~600 บรรทัด เหลือเพียง ~100 บรรทัดเท่านั้น

ที่ทำงานค่อนข้างช้าเนื่องจากผมทำการ Convert field date ของคุณซึ่งเก็บแบบ Varchar และเป็นปี พ.ศ. ให้เป็นชนิด Date ใน MySQL เผื่อการใช้งานได้ง่ายขึ้นถ้าต้องการใช้ในการคำนวน

ส่วนการแสดงผลอื่นๆลองเอาไป GROUP ดูเองนะครับ

ปล. ผมขออนุญาตเอากรณีของคุณเป็นกรณีศึกษาเพื่อเขียนบทความได้หรือไม่ครับ
nobuts
nobuts
Fri 21 Dec 2007 08:24:10

ด้วยความยินดีครับ เอาสคริปต์ของผมมาเผยแพร่ก็ได้ (แทนคำขอบคุณ) เผื่อบทความที่เกิดจากปัญหาของผมจะเป็นประโยชน์ในการพัฒนาเว็บไซต์ทางการศึกษาและด้านอื่นๆ

webmaster
webmaster
Fri 21 Dec 2007 10:19:45
ขอบคุณมากครับ
nobuts
nobuts
Fri 21 Dec 2007 10:58:34

ผมได้ทดลองใช้ query แบบนี้ดูแล้วปรากฏว่าได้ผลดีมาก วันเรียงกันออกมาทุกวันในเดือน 11 แต่มีปัญหาอยู่อีกอย่างหนึ่งคือว่า

มันนับจำนวนข้อมูลทั้งหมดที่อยู่ในเงื่อนไข (เช่น 2007-11-01 มี 164 ข้อมูล มันก็จะแสดงทั้งหมดเลย) ถ้าผมจะกรองเอาเฉพาะที่ต้องการ (เช่น 502101% ในวันที่ 2007-11-01 ซึ่งค่าเท่ากับ 13 จากคำสั่ง select count(*) from inet_usage where id like '502101%' and date = '01/11/50';)

คือจากคอนเซ็ปต์เก่าของผม ผมใช้วิธีในการหาค่าความถี่ของแต่ละแผนก-ชั้นปี (502=ปวช.1/503=ปวส.1,482=ปวช.2/483=ปวส.2 ; 101-แผนก1, 102-แผนก2....) เช่น 502101%,492101%,482101%, 503101% และ 493101% ณ วันที่ 2007/11/01 ดังนี้

503101
select count(*) from inet_usage where id like '502101%' and date = $mon ซึ่งผมจะได้ค่าของจำนวนนักเรียนกลุ่มนี้ ณ วันที่ $mon ออกมา ซึ่งผมต้องทำทั้งหมด เท่ากับจำนวนแผนกที่มี แต่ว่าปัญหามันอยู่ที่
ผมจะทำอย่างไรให้มันแจกแจงจำนวนนักเรียนแต่ละแผนก ณ วันหนึ่งๆโดยเฉพาะ แล้วค่อยนับเอาจำนวนทั้งหมดมาเป็น total

กรุณาด้วยนะครับ อยากรู้มากๆ

webmaster
webmaster
Fri 21 Dec 2007 14:08:02
ตามที่ผมได้แจ้งแล้วว่ามีการแปลงข้อมูลของคุณเป็น Date ใน MySQL แล้ว โดยใน Where clause นั้น WHERE MONTH(u.udate)=11 AND YEAR(u.udate)=2007 เป็นการระบุเดือนและปีที่ต้องการ ถ้าคุณต้องการเป็นรายวัน จะได้ WHERE DAY(u.date)=1 MONTH(u.date)=11 AND YEAR(u.date)=2007 หรือ WHERE u.date=STR_TO_DATE('2007/11/01', '%Y/%m/%d') ตามแต่สะดวกครับ

ส่วนการแบ่งกลุ่มของนักศึกษานั้น จะมีการใช้งานแบ่ง String ออกมาเช่น
SUBSTR(i.id, 1, 2) AS uYear เป็นปีที่เข้าศึกษา ถ้าคุณต้องการก็สามารถประยุกต์ใช้ได้เช่น
SUBSTR(i.id, 1, 3) AS uYear คุณก็จะได้ชั่นปีตามต้องการครับ

SUBSTR(i.id, 4,3) AS uDept คุณก็จะได้แผนก

จะเห็นว่าผมทำการสร้าง Alias ของทุก field ที่มีการแปลงเช่น uYear, uDept เพื่อนำมาใช้ประโยชน์ในการ GROUP BY clause ด้วย

เมื่อนำทั้งหมดมารวมเข้าด้วยกัน SELECT .... FROM .... WHERE u.date=STR_TO_DATE('2007/11/01', '%Y/%m/%d') GROUP BY uYear, uDept จะไม่มี u.udate ใน GROUP BY clause เนื่องจากคุณทำการระบุวันที่ลงไปแล้ว

ประโยชน์อีกอย่างของการแปลงข้อมูล String เป็นชนิด Date คือคุณสามารถใช้ WHERE
u.date>=STR_TO_DATE('2007/11/01', '%Y/%m/%d') AND u.date<=STR_TO_DATE('2007/12/10', '%Y/%m/%d') คือการเลือกข้อมูลระหว่างวันที่ที่ระบุได้

ลองเล่นดูครับ การใช้คำสั่งต่างๆไม่จำเป็นต้องใช้ php ถ้ามันมีอยู่ใน MySQL อยู่แล้วก็พยายามใช้คำสั่งใน MySQL จะทำงานได้เร็วกว่า คำสั่งต่างๆก็อ้างอิงได้ตาม
http://dev.mysql.com/doc/refman/5.0/en/functions.html
nobuts
nobuts
Fri 21 Dec 2007 15:12:50

ผมได้ทดลองใช้ q ดังนี้

<?
include_once('connect.php');
mysql_select_db($dbname);

 $sql = "SELECT u.udate, SUBSTR(i.id, 1, 3) AS uYear, SUBSTR(i.id, 4,3) AS uDept, COUNT(i.no) AS total FROM inet_usage i INNER JOIN (SELECT no, STR_TO_DATE(CONCAT(SUBSTR(s.date, 1, 2 ), '/', SUBSTR(s.date, 4, 2 ), '/', (CAST(SUBSTR(s.date, 7, 2 ) AS UNSIGNED) - 43)),  '%d/%m/%y') AS udate FROM inet_usage s) AS u ON u.no = i.no WHERE u.date>=STR_TO_DATE('2007/11/01', '%Y/%m/%d') AND u.date<=STR_TO_DATE('2007/12/10', '%Y/%m/%d') GROUP BY uYear, uDept, u.udate ";
  $result=mysql_query($sql);
 while($record = mysql_fetch_array($result)){

echo $record[udate]."&nbsp;";
echo "uYear:&nbsp;".$record[uYear]."&nbsp;";
echo "uDept:&nbsp;".$record[uDept]."&nbsp;";
echo "Total:&nbsp;".$record[total]."<br />";
 }
?>

แล้วปรากฏว่ามันแสดงผลดังนี้

Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in C:\AppServ\www\iNet\D101A2.php on line 7

แล้วพอเอาไปรันบน mySql ด้วย

SELECT u.udate, SUBSTR( i.id, 1, 3 ) AS uYear, SUBSTR( i.id, 4, 3 ) AS uDept, COUNT( i.no ) AS total
FROM inet_usage i
INNER JOIN (


SELECT no, STR_TO_DATE( CONCAT( SUBSTR( s.date, 1, 2 ) , '/', SUBSTR( s.date, 4, 2 ) , '/', (
CAST( SUBSTR( s.date, 7, 2 ) AS UNSIGNED ) -43 ) ) , '%d/%m/%y'
) AS udate
FROM inet_usage s

) AS u ON u.no = i.no
WHERE u.date >= STR_TO_DATE( '2007/11/01', '%Y/%m/%d' )
AND u.date <= STR_TO_DATE( '2007/12/10', '%Y/%m/%d' )
GROUP BY uYear, uDept, u.udate

มันกลับฟ้องว่า...

#1054 - Unknown column 'u.date' in 'where clause'

ไม่ทราบว่ามันผิดตรงไหน รบกวนดูให้หน่อยครับ


SELECT no, STR_TO_DATE( CONCAT( SUBSTR( s.date, 1, 2 ) , '/', SUBSTR( s.date, 4, 2 ) , '/', (
CAST( SUBSTR( s.date, 7, 2 ) AS UNSIGNED ) -43 ) ) , '%d/%m/%y'
) AS udate
FROM inet_usage s

) AS u ON u.no = i.no
WHERE u.date >= STR_TO_DATE( '2007/11/01', '%Y/%m/%d' )
AND u.date <= STR_TO_DATE( '2007/12/10', '%Y/%m/%d' )
GROUP BY uYear, uDept, u.udate

มันกลับฟ้องว่า...

#1054 - Unknown column 'u.date' in 'where clause'

ไม่ทราบว่ามันผิดตรงไหน รบกวนดูให้หน่อยครับ

webmaster
webmaster
Fri 21 Dec 2007 16:12:52
ขออภัยด้วยครับ ใน Subquery ไม่มี field นั้นครับที่ถูกมันต้องเป็น WHERE u.udate >= STR_TO_DATE( '2007/11/01', '%Y/%m/%d' )
AND u.udate <= STR_TO_DATE( '2007/12/10', '%Y/%m/%d' ) ขออภัยครับเขียนตกไปครับ
nobuts
nobuts
Sat 22 Dec 2007 16:15:28
 วันที่

  แผนกที่1

 1 พฤศจิกายน 2550

 ปวช.1

 ปวช.2 

 ปวช.3 

 ปวส. 1

ปวส.2 

 13 10  15 

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

webmaster
webmaster
Sat 22 Dec 2007 17:47:11
จริงๆแล้วต้องนำไปประยุกต์ต่อครับ เนื่องจากไม่มี query แบบใดที่นำไป apply ได้ทุกอย่างครับ นอกจากว่าจะเอาข้อมูลทั้ง database ออกมาแล้วเลือกเอาเฉพาะที่ต้องการ ซึ่งก็ทำให้ช้าโดยเปล่าประโยชน์

จากตัวอย่างการแสดงผลที่ยกตัวอย่างมานั้น ต้องใช้ query ตามจำนวนแผนกที่มีครับ จะเร็วที่สุด

SELECT u.udate, SUBSTR(i.id, 1, 3) AS uYear, COUNT(i.no) AS total FROM inet_usage i INNER JOIN (SELECT no, STR_TO_DATE(CONCAT(SUBSTR(s.date, 1, 2 ), '/', SUBSTR(s.date, 4, 2 ), '/', (CAST(SUBSTR(s.date, 7, 2 ) AS UNSIGNED) - 43)),  '%d/%m/%y') AS udate FROM inet_usage s) AS u ON u.no = i.no WHERE u.udate>=STR_TO_DATE('2007/11/01', '%Y/%m/%d') AND u.udate<=STR_TO_DATE('2007/11/30', '%Y/%m/%d') AND SUBSTR(i.id, 4,3)='100' GROUP BY u.udate, uYear

จะเห็นว่าผมระบุเงื่อนไข SUBSTR(i.id, 4,3)='100' เพื่อระบุแผนกที่ต้องการ และก็ตัดการ GROUP BY uDept ออกเนื่องจากเราระบุแผนกลงไปแล้วจึงไม่มีความจำเป็นให้ MySQL ไป Group ให้ทำงานหนักโดยไม่จำเป็นออก

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

ปล. ผมได้เขียนบทความเกี่ยวกับการ Optimization โดยยกกรณีของคุณเป็นตัวอย่าง ผิดพลาดประการใดโปรดแจ้งด้วยนะครับ
nobuts
nobuts
Sun 23 Dec 2007 13:36:15
ขอบคุณมากนะครับ สำหรับความอดทนและความพยายามในการคอยตอบคำถามทุกคำถาม ตอนนี้โปรแกรมผมพัฒนาไปมากแล้ว (แต่ก็ยังไม่เสร็จอยู่ดี) ผมได้ส่ง Rapidshare Premium Account ไปให้เป็นของขวัญปีใหม่และแทนคำขอบคุณในอีเมล์ webmaster@modoeye.com แล้วนะครับ ขอให้มีความสุขในเทศกาลปีใหม่นะครับ
webmaster
webmaster
Mon 24 Dec 2007 23:15:37
ขอบคุณมากครับ สำหรับ account ได้รับเรียบร้อยแล้วครับ
nobuts
nobuts
Tue 25 Dec 2007 19:25:57

 no id  date  ID_NEW 
 1 5031018745 2007-11-01   503101
 2 4921033214 2007-11-05  492103

สวัสดีครับ หลังจากหายไปหลายวันผมก็ยังทำตามวิธีการเดิมไม่ได้ แต่ได้ไอเดียใหม่ขึ้นมาอีกอย่างคือ เป็นไปได้ไหมถ้าหลังจากที่ผมนำข้อมูลเข้าฐานข้อมูลมาแล้ว และจะสร้างคอลัมน์ขึ้นมาใหม่เพื่อเก็บค่าใหม่โดยเฉพาะ (SUBSTR(i.id, 4,3)) เพื่อที่เวลานับจะได้ทำได้ง่ายขึ้น (count(*) where id_new='503101' and date='2007-11-01') อย่างนี้น่ะครับ ถ้าทำได้จะต้องใช้โค้ดว่าไงครับ ผมไม่แน่ใจว่ามันจะทำให้ฐานข้อมูลยุ่งยากไปหรือเปล่า ยังไงก็ขอคำแนะนำอีกครั้งนะครับ
webmaster
webmaster
Tue 25 Dec 2007 21:36:35
ก็สามารถทำได้ครับ แต่ตารางจะใหญ่กว่าเดิมอีกเยอะครับ 7 Bytes ต่อ record ลองดูก็ได้ครับแล้วดูผลเวลาในการ query ว่าอย่างไหนจะมีประสิทธิภาพมากกว่ากัน ส่วนโค๊ดต้องดูว่าต้องการนำมาแสดงผลแบบใดครับ
นา
นา
Sat 20 Sep 2008 23:32:35
อยากทราบวิธีหาค่าความถี่
webmaster
webmaster
Sun 21 Sep 2008 02:36:59
ความถี่ของอะไรครับ
Reply
Name:
E-mail:
Home | Services | Forum | Classified | Directories | Support | Contact
ATOM feed RSS 0.9 feed RSS 1.0 feed RSS 2.0 feed
Copyright © 2005 - 2007 Modoeye.com, All Rights Reserved.
Disclaimer | Privacy policy | Term of Use | Term of Services
Valid XHTML Valid CSS! PHP: Hypertext Preprocessor MySQL database Apache Powered! FreeBSD Power to serve
Modoeye Sitemap Client login