1.分析背景: 在實際開發中碰到的一個問題。(會員等級:白金,金,銀,銅)
有一個工作清單,需要根據會員不同身份等級來顯示自己可見的任務(即金牌只能看到金牌可見的任務,自己等級見對應的你的任務)。 一般該怎麼解決呢。 當然是資料庫的任務表上建立一個欄位,標明一下該任務是哪個等級可見不就結了,so easy。
2.疑問: 那麼問題來了,如果我要跨等級顯示呢。即 讓 銅,金可見,銀不可見。即要滿足這四個的各種組合。。。(排3列組合公式 C(n,m)這麼算來著。。哈哈)
3.方法: 方法一:如上邊提到的,建一個新欄位varchar類型,並列的用逗號分隔,儲存哪些等級可見。比如 "金" 或者 "金,銅"。(不方便select)
方法二:建一個int新欄位,使用(8421方法)來加權值區分。
4.分析: 方法一好處是直觀,直接知道哪個等級可見,缺點也顯而易見,我存4個等級豈不是要寫3個逗號分隔,而且這樣是不利於select查詢的啊。方法二隻要建立一個int類型的欄位,裡邊儲存一個1~15兩位元即可(而且訪問量大了int類型的查詢不是更快嘛~~) 那麼,看看方法二是怎麼解決這個問題的。
5.過程 給各個會員等級賦權值如下。 1:銅牌 2:銀牌 4:金牌 8:白金 1.在建立任務insert的時候,比如我設定白金,銅牌可見,按上邊分配,將權值相加 1+8 = 9,資料庫權值欄位
level就存9。 2. 比如我現在是金牌,如何讓我看不到此條資料呢。先查詢出我是金牌對應等級為4,把4傳入下邊函數
toLevelShow(4),返回一個數組,傳回值就是涵蓋了所有金牌可能參與的組合。返回如下:(4,5,6,7,12,13,14,15) 。(4=4,金),(5=1+4 ,銅+金) (6=2+4,銀+金).....很顯然銅+白金=9 不再此傳回值當中。 3. 在select查詢時,select * from table where
`level` in ([傳回值]),這樣就查詢出了含有金牌參與的任務了。
6.結論
所以,這個8421權值區分法,本質是統計計算出所有傳入值對應的1248組合情況,然後查詢的時候where in 這些權裡邊就行了。
(那麼,他是如何找出對應等級的排列的呢這是關鍵核心。本質加入了2進位數進一的規律方法,詳細的可以查看研究下函數。)
此套方法不進局限於會員等級吧,所有符合這種組合分情況顯示的情景的都可以使用。
函數如下: /** * 輸入(1,2,4,8)其中的一個值,返回其1248所有相加的情況組合 * @param 輸入,(1,2,4,8)任意一個數 * @return 其所有組合相加的結果 * 比如,輸入8,返回array(8,9,10,11,12,13,14,15), * 因為 8; 8+1=9; 8+2=10; 8+4=12 ; 8+1+2=11 ; 8+1+4=13 ; 8+2+4=14 ; 8+4+2+1=15 * 15是這四個數肯定有的,因為8+4+2+1=15,權值最大 */ function toLevelShow($level_show){ $ret = array(); //把層級轉為二進位 $bin = decbin($level_show); $len = strlen($bin); for($i=15;$i>=1;$i--){ $tmp = decbin($i); $tmp = str_pad($tmp, 4,'0',STR_PAD_LEFT); $tmp1 = substr($tmp,-$len,1); if($tmp1==1){ $ret[]=$i; } } return $ret; }
/** * 分拆權值,查看是由(1,2,4,8)這四個數字中那幾個組合而成 * @param 輸入,1-15之間的數,返回1248組合情況。 * @return 1248組成的數組 * 比如,輸入15,返回array(1,2,4,8),因為1+2+4+8 = 15 */ function toLevel($level_show){ $ret = array(); //把層級轉為二進位 $bin = decbin($level_show); $len = strlen($bin); for($i=0;$i<$len;$i++){ $tmp = $len-$i; if($bin[$i]==1){ $dec = bindec(str_pad($bin[$i], $tmp,'0')); $ret[]=$dec; } } return $ret; }