2016年12月21日 星期三

PostgreSQL 建立高階分頁 API 技巧

簡單濃縮一下一些工作上建 API 經驗,以下用一個簡單的案例說明。

一個仿 Facebook 的動態文章系統假設有 3 個 Table,如下所示:

Schema

Post (文章)
pid (流水號) message (訊息) since (發布時間)
Comment (留言)
cid (流水號) message (訊息) since (發布時間)
Reply (回覆關聯)
pid (Post流水號) cid (Comment流水號)

先不考慮 Comment 底下還可以 Reply 的狀況,假設只有一層

Post List

呈現所有文章列表應該沒有疑問,直接上語法

select * from post order by since desc offset 0 limit 10;

其中,可以透過offsetlimit作為 API 的參數,除了能避免一次吐出太多 Post 外,也能做到分頁的效果。

基本範例 - 使用頁數分頁:

假設每分頁呈現 25 筆 Post

第 1 頁 SQL (offset=(1-1)*25)
select * from post order by since desc offset 0 limit 25;
第 2 頁 SQL (offset=(2-1)*25)
select * from post order by since desc offset 25 limit 25;
第 3 頁 SQL (offset=(3-1)*25)
select * from post order by since desc offset 50 limit 25;

以此類推,只要照著offset = (頁數-1)*(每頁筆數)這個規則,就可以做出能夠分頁的 API 了。

如果需要知道共有幾頁,只需要將資料總數/每分頁筆數 + 1即可:

select count(*)/25+1 from post;
每分頁的筆數,可以由前端傳送,或由後端決定。若由前端傳送則要注意檢查最大筆數,以免遭到惡意攻擊。


進階範例 - 先驗分頁:

上面的作法,已經有了基本的樣子。但是如果遇到以下這種變形的 case 怎麼辦呢?

每頁 25筆 Post,給我 Post (pid=251) 那一頁的 Post,並且要可以上翻頁或下翻頁

這個案例實際的情況會用在哪裡? 常見的就是 web RESTful 的設計的需求, 這種請求通常只會給一個 Post(pid) 然後畫面必須呈現該 Post 與其上下相鄰的 Posts。該怎麼做 ?


首先,必須先預測所有 Post 所在頁數,做成 View 表,但前提是每分頁的筆數需要故定。

create or replace view v_post as
select *,row_number() over(order by since desc) as p
from post order by since desc;


其中row_number()是一個 PostgreSQL 的 Window function,顧名思義就是取得 output 的所有資料中,每個 row 自己是第幾筆資料,後面一定要接over(order by ...)來告訴它該依照什麼排序來編號。

由於row_number會從 1 開始編號,因此透過(row_number-1)/每頁數量+1的計算,就可以知道該筆資料所在的頁數了。將 View 改成這樣:

create or replace view v_post as
select *,(row_number() over(order by since desc)-1)/25+1 as p
from post order by since desc;


有了這個頁數後,透過一個 subquery 就可以得到需要的效果了

select * from v_post where p = ( select p from v_post where pid=251 )

前端人員此時就可以透過目前的頁數p來進行上翻頁或下翻頁,此時也只需要這個 View 就可以取出翻頁後的資料。


Comment List

承上的先驗分頁技巧,在"取得某個 Post 的 Comments "情況下無法預先產生 View 來計算頁數,因為pid是從前端動態傳入的。

select r.pid,c.* from reply as r,comment as c
where r.cid=c.cid where r.pid=??



範例 - function 取代 View:

但是藉由 PostgreSQL 靈活的 function 功能照樣達到這個需求。

create or replace function fn_comment(_pid int)
returns table(pid int,cid int,message varchar,since timestamp,p bigint) as $$
begin

  return query
    select r.pid,c.cid,c.message,c.since,row_number() over(order by c.since desc) as p
    from replay r,comment c
    where r.cid=c.cid and r.pid=_pid;

end;
$$ language plpgsql;


上面這個 function 就可以動態產生類似於 View 的效果,但唯一不同的是它可以等到Post(pid)傳入後,再去抓取其Comment資料。

再透過上個段落的取得頁數方法,將 function 改成這樣:

create or replace function fn_comment(_pid int_)
returns table(pid int,cid int,message varchar,since timestamp,p bigint) as $$
begin

  return query
    select r.pid,c.cid,c.message,c.since,(row_number() over(order by c.since desc)-1)/25+1 as p
    from replay r,comment c
    where r.cid=c.cid and r.pid=_pid
    order by since desc;

end;
$$ language plpgsql;


如此一來,透過呼叫fn_comment就可以取得某個 Post 下的某一分頁所有 Comments,就跟一般的 View 用法完全一樣呢!

select * from fn_comment(251) where p=2



範例 - 透過 Comment(cid) 取得該頁資料:

透過剛才的fn_comment就可以藉由Comment(cid)取得該頁的資料,且也能夠提供前端人員進一步取得前後翻頁的資料。

select * from fn_comment(251) where p=( select p from fn_comment(251) where cid=999 );



結論

上面列舉的案例,僅用3張Table與簡單的關聯來說明,目的是希望聚焦在 API 批次取資料這件事情上面,但並非唯一的作法。

透過 View 表建立預測頁數的作法其實每次的 request 都在消耗電腦的運算,較好的做法應該是在 Reply Table 就預先建立頁數的資料或其他 cache 的策略,不僅加速 response 效能外,也能減少 server 的負擔。


以上就是對資料進行分頁技巧的一些經驗分享,
提供給需要跟前端合作的後端 Developers。


2014年9月12日 星期五

網路爬蟲 - 武功密技

學習 網路爬蟲 一段時間了

也算是略懂一二
分享一下小的領略的二三式


第一式 - WebClient
  


第二式 - WebRequest


終式 - WebBrowser


最後一招










以上小小的分享一下

2014年5月22日 星期四

Zap Coding學習之路

回想起自己學習「網頁技術」的點點滴滴,可以說是一段驚奇之旅。 
我想這一切,必須從我高中開始說起。


無名小站
在我高中的回憶裡,印象最深刻的兩個資訊相關產品,莫過於「Yahoo即時通」與「無名小站」!無名當時可說是無人不知無人不曉的個人網誌,大多數的人拿它來發洩心情、風花雪月、講些聽都聽不懂的詩詞... 。但我卻把它拿來分享我的畫作,也因為如此得到不少鼓勵,讓人更想在上面與人交流。從此,對它的依賴也更強了。

 



 


Blog 就是要與眾不同
好吧,為什麼要講到無名小站呢?你知道的,玩部落格除了發文要夠吸引人閱讀之外,更重要的就是部落格的樣式。一個樣式的好壞,決定了一個部落格的風格,這可以說是一個重要的門面。
當你逛了許多別人非常熱門的部落格後,就會發現他們擁有一個與別人與眾不同的樣式,我認為這就是啟發我對於網頁學習的第一把鑰匙。





套用樣式
我開始搜尋有關「部落格樣式」的相關資料。起初,我找到的是「套用別人寫好的樣式」。這是一個快速的方式,網路上也不乏設計質感精美的樣式讓人使用。

一段時間後,當我想要修改網頁中某的部份的樣式時,我突然發現「套用別人寫好的樣式」已經不能滿足我了。我需要擁有更多的控制權!


自訂 CSS 樣式
當我照著 Google 搜尋的教學一步一步開啟了「自訂樣式」時,我傻眼了。
眼前是我完全沒見過的「程式碼」(那時不知道叫CSS),當我發現眼前美麗的畫面是用一行又一行的程式碼使其呈現的時候。我徹底的顛覆了對於網頁的認知。

但是,我根本看不懂這些程式碼,也無從改起。再者,我連CSS是什麼都不知道。在這樣的情況下,我很肯定的告訴自己 「改不了,跳過」 !




...

程式對我的意義
對於一個上大學前連「程式」是什麼一點概念都沒有的我,甚至覺得自己未來會朝美術相關科系發展 。「寫程式」只是我對於網頁「樣式」的一個終極解決法。
既然「寫程式」在我目前的人生規劃中,並不存在。我自然沒有想去學習撰寫CSS的動力。





人生的轉變 - 大學
在我高三的那年,我沉迷於電玩。導致自己的高中成績瞬間一落千丈,名次從前十名變成倒數最後。其實當時心中萬分痛苦,甚至在班上都抬不起頭來。

但我與其他同學唯一不同的是,我很確定我未來要走的方向 ─ 「藝術」相關。
在大學指考的志願卡上,我填了 70多所藝術相關的科系,在大夥都認為如預期地走向規劃的
目標時,上帝關上了我一扇門...




長榮大學 - 資管系
我無言了,我徹底無言了...。資訊相關科系我之所以填在最後 20幾個 志願裡,單純只是我覺得寫程式可以解決「樣式」的問題,因此才隨便亂填的。沒想到,還是備到了長榮資管。

既然備上了,既然來了...

「 那就不能空手而回阿!! 」

這是我除了認命之外第一個想法。既然我的過去充滿失敗。那我就從一個人生地不熟的地方,重新開始。















就只是 一股傻勁
直到這一刻起,我都還是認為:只要把程式學好,有一天我也能做出漂亮的樣式。

於是,大一上的 VB 課,我用了200%的努力。我不用別人逼、不用考試逼,我知道寫程式要用 Visual Studio 時,我第一時間就回去安裝好,老師說的每個範例、每句程式碼,我都往死裡念,總是自己孤戰到凌晨時分,就只是為了....

「我絕對要學會怎麼寫程式!」

當然,付出的代價是有回報的,當我拿到第一個視覺化程式設計分數時,我開始不後悔來到這個科系了!甚至開始覺得  .... 與程式相見恨晚....

然而學會寫程式,只是一個必要的條件,我知道我必須先學會寫程式,我才能夠寫出漂亮的樣式。雖然我現在寫的不是CSS...,但我始終認為他們都應該算是是「寫程式」。









這個強,是要多強?
我不要充滿敵意的眼神
在這個過程中,我先後接觸了 C, PHP , JAVA , C# , javascript ....等程式語言。

然後就是不斷的比賽、解題、比賽、解題
我都快忘記我當初來這裡的原因了...

 總會有人告訴你:「會解題,才能解決問題!」

不斷的努力,迎接的是不斷的挫折...
當每個晚上重複著練習又練習,提升所謂的Coding技巧時,我就告訴自己

「如果連這關都過不了,以後要怎麼寫程式?」


但挑戰不斷地來,過了一關還有一關,似乎那些題目就像解不完的漩渦一樣,讓人感到快要窒息。

我把自己搞得生活作息混亂,卻依舊無法超越那些在全國學生競賽台大來的選手,我不禁想問... 「我到底在做什麼?」 、「我的目標是要超越他們嗎?」「難道就要像他們一樣,才有資格寫程式嗎?」



















這次我選擇相信我的直覺
「我不願傻傻地相信總有一天學校會教我 CSS」
我決定在我專題結束後,朝著我心中想要的那條道路前進

但這一路來,不是完全沒有價值的
紮實的Coding訓練,讓我在學習網頁技術時,比同學都要來的快



在我選擇放下的那天,我覺得Coding不再是一件痛苦的事情。
不再是需要我比你強、你比我強的事情。
Coding可以是種互相學習、互相分享、不斷的進步...

至少,我現在已經可以自己寫出自訂樣式了
但我卻在這個過程中,看得更透徹了....




















當你贏別人時,你就饒恕別人
當別人贏你時,你就饒恕自己


做自己喜歡的事,寫自己喜歡的程式


當你用熱情
全心全意的投注一件事情
我相信別人也會被你感動


當你能夠感動別人
再艱澀的程式 也就不再這麼冰冷




 Zap Coding學習之路...

2014年4月8日 星期二

深入淺出 CSS block 垂直置中

在進行網頁排版時,常常會遇到需要 block 垂直置中排放的需求。
網路上也不乏解決方法可供參考,但鮮少解釋其設計原理。
在這篇文章中,將會以 Facebook 如何處理垂直置中排版,深入淺出的進行說明。



inline 與 inline-block

在解釋垂直置中排版之前,要先對 CSS 的 inline 與 inline-block 元素有基本的認識。
因為與本主題無關,請參閱 CSS 屬性 display 的值 inline block inline-block none



圖片是最常見的 inline-block

我們知道,當在網頁中放置一張圖片,並在其後面添加幾個文字。會呈現如下圖的樣貌。


在CSS預設的樣式中,文字會對齊 inline-block 元素的底部,下圖將解釋得更清楚。

我們可以將這些網頁上的文字,先視為一個 inline 元素。
可以發現 inline 元素與 inline-block 元素的排列是以底部為基準,原因如下圖。




vertical-align 設定垂直置中

CSS 的 vertical-align 可以調整自己在該元素中垂直的相對位置,且只作用於 inline、inline-block、table-cell 元素上。vertical-align 預設為 baseline,也可以說是 bottom。換句話說,元素的垂直位置會靠底部對齊。詳細可參考 vertical-align

因此,藉由設定 vertical-align 樣式,就可以達到初步的垂直置中,如下圖。




兩行文字馬上破功

文字若出現2行以上時,便會出現破圖的情況。原因是 inline 內部不是一個 block。
所以如果出現 段落 或 換行 ,文字便會被推擠到下一行顯示,如下圖。



將文字包覆在 inline-block 中

為了讓多行內容也能自動垂直置中顯示,我們將文字包覆到 inline-block 元素中(橘色)。如此一來,他們會被視作在同一個 block 中,不會被推擠出去,如下圖。


inline-block 中,相對的 vertical-align

我們知道 vertical-align 是相對的,它會影響到該元素在垂直的相對位置,可以從下面兩張圖中了解他們相對的差異。






用兩個 inline-block 產生垂直置中

了解 vertical-align 的相對關係後,此時同時給予2個 inline-block 的樣式
vertical-align : middle,便能輕易產生一個相對關係下的垂直置中排列,如下圖。





產生最終結果

到目前為止,您已知道垂直置中的原理與設計模式了。
承接上圖,橘色的元素才是我們需要的垂直置中元素,藍色的部分只是輔助。
因此需另做微調,如下圖。


偽元素的妙用

如果每次為了製作一個垂直置中的排版,就要增加一個沒有意義的藍色 inline-block ,再用CSS設法將其隱藏,實在有違 HTML5 精簡的精神。

我們可以利用 CSS 中的  :before 偽元素來取代藍色 inline-block 的位置,讓整個 HTML 語法撰寫起來更加優雅。這也是目前 Facebook 處理 垂直置中排版的方法。

整個切版圖如下:






參考資料
https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align
http://blog.xuite.net/vexed/tech/29221717-CSS+%E5%B1%AC%E6%80%A7+display+%E7%9A%84%E5%80%BC+inline+block+inline-block+none
http://www.w3schools.com/cssref/pr_class_display.asp

2014年2月28日 星期五

用 PHP 存取 SQL Server (使用AppServ 2.5.10)

PHP 存取 SQL Server 目前已知有2種做法

  1. 使用  mssql  lib
  2. 使用  sql_srv  lib
但是,第一 種方法不管我怎麼嘗試,就是無法連線成功。網路上許多人的解法也都大同小異,我每個都嘗試過,依舊不能連線。


 這邊轉貼一下 第一種作法的 教學文 (有興趣可以自行研究)

-----------------------------------------------------------------------------------
 以下開始第二種方法的教學


STEP.1 安裝AppServ 2.5.10

STEP2. 停止Apache服務 (萬無一失做法)


( 如圖,開始 → 搜尋程式與檔案 → 鍵入'"服務" → 點選「服務」) 

 



( 如圖,找到 "Apache2.2" 雙擊 → 停止 → 先別關掉,等等還要啟動)


STEP.3 前往 Microsoft for PHP connect SQL Server 下載補丁

 (如上圖,點選Download)


( SQLSRV30.EXE 是 PHP5.4 ~PHP5.5 版本的補丁
  SQLSRV20.EXE 是 PHP5.2 ~PHP5.3 版本的補丁

  由於我安裝的 AppServ  2.5.10 內建 PHP5.2,所以下載 SQLSRV20.EXE )



STEP4. 安裝補丁 (其實只是把檔案解壓縮,隨便選個空資料夾 暫放)


STEP5. 將補丁「php_sqlsrv_52_ts_vc6.dll」與「php_pdo_sqlsrv_52_ts_vc6.dll」複製到以下路徑內
  • C:\AppServ\php5\ext



STEP6. 開啟  php.ini   ( C:\Windows\php.ini )

在最底下加入這兩行 →儲存檔案

extension=php_sqlsrv_52_ts_vc6.dll
extension=php_pdo_sqlsrv_52_ts_vc6.dll



STEP7. 啟動Apache服務  (同STEP.2 但要點 "啟動")


STEP8. 確認安裝成功
用瀏覽器開啟  http://localhost/phpinfo.php
查看是不是有 sqlsrv 字樣 (可以用Ctrl+F 搜尋),有找到代表成功


STEP9. 撰寫一隻 PHP程式,測試是否連線 (Code供參考)

<?
   $serverName = "Server名稱";
   $database = "資料庫名稱";
   $uid = "登入帳號";
   $pwd = "登入密碼";

   try {
      $conn = new PDO( "sqlsrv:server=$serverName;Database = $database", $uid, $pwd);
      $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

   } catch( PDOException $e ) {
      die( "Error connecting to SQL Server".$e->getMessage() );
   }
   echo "Connected to SQL Server";
?>

如果輸出 Connected to SQL Server 代表連線成功 !!



參考資料

https://bugs.php.net/bug.php?id=49315
http://social.technet.microsoft.com/Forums/sqlserver/en-US/5d9e84c2-1c05-449a-aae5-503e2ad6f9ca/named-pipes-provider-could-not-open-a-connection-to-sql-server-2?forum=sqldataaccess
http://www.sqlservercentral.com/Forums/Topic1127826-146-1.aspx
http://www.channaly.info/connect-to-microsoft-sql-server-2005-2008-with-php/
http://social.msdn.microsoft.com/Forums/sqlserver/en-US/b1206aff-1fb3-444c-ab09-0b0298e5d975/cannot-get-php-to-connect-to-sqlserver?forum=sqldriverforphp
http://blog.csdn.net/yanch7/article/details/6335911
http://www.dotblogs.com.tw/joe80075/archive/2011/02/09/21288.aspx
http://www.dotblogs.com.tw/jellycheng/archive/2010/11/02/18743.aspx 


2013年7月14日 星期日

使用 PreferenceFragment 快速攻略

快速入門,撰寫一個使用 PreferenceFragment
做出設定的畫面
        
( ↑ 使用 PreferenceFragment 撰寫的畫面,如上圖)



Step1. 建立一個Android專案與Activity (細部省略)


Step2. 建立一個 PreferenceFragment 的 XML 設置檔
  • 在專案下新增一個 Android XML File
  • Resource Type 選擇 Preference
  • Root Element 選擇 PreferenceScreen
  • 輸入 File名稱
  • 完成 (注意該XML檔案是在目錄 res > xml 底下)



Step3. 開始新增元素
  1. 所有元素都要新增在根元素(PreferenceScreen)下
  2. 詳細元素使用請參閱官網



Step4. 在主Activity中呼叫 PreferenceFragment

  1. 首先必須知道 PreferenceFragment 屬於一個 Fragment 的其中一個子類別,如不知Fragment為何,請先閱讀介紹文章
  2. 接下來請為 PreferenceFragment 新增一個 class,目的是為了將 PreferenceFragment的XML檔與 PreferenceFragment 程式連接起來,並呼叫它啟動

    (1) 新增一個class,在superclass 填入 PreferenceFragment,目的是繼承PreferenceFragment類別


    (2) 因為每個 Fragment 都有自己的生命周期,在PreferenceFragment啟動(onCreate)時,應設定PreferenceFragment 的 XML配置檔

    (3) 點選 Source → Override/Implement Methods... → 選擇 PreferenceFragment下onCreate(Bundle),快速建立 onCreate 函式


    (4) 最後在 onCreate 內指定 PreferenceFragment 配置的XML檔案
    addPreferencesFromResource(R.xml.你的XML檔名);

    上述步驟完整Code:
    public void onCreate(Bundle savedInstanceState) {
      // TODO Auto-generated method stub
      super.onCreate(savedInstanceState);
      addPreferencesFromResource(R.xml.eee);
     }
  3. 接下來要讓主 Activity 喚醒上一步寫好的 FragmentManager ,在主Activity中寫入以下 :

    (1) 首先為了控制 Fragment,首先要先取得 Fragment 管理器
     FragmentManager fm = this.getFragmentManager();

    (2)接下來取得管理器編輯與操作的物件,才能開始操作
     FragmentTransaction ft = fm.beginTransaction();

    (3) 接下來要將主View 切換到 PreferenceFragment
    FragmentTransaction ft2 = ft.replace(android.R.id.content, new PrefsFragement());

    (4) 最後呼叫 commit 方法執行
     ft2.commit();
    上述步驟完整Code:
     FragmentManager fm = this.getFragmentManager();
     FragmentTransaction ft = fm.beginTransaction();
     FragmentTransaction ft2 = ft.replace(android.R.id.content, new PrefsFragement());
     ft2.commit();
  4. 完成上述步驟後,一個 PreferenceFragment 就完成囉 ~,趕快執行看看吧~