/
content.json
1 lines (1 loc) · 88.9 KB
/
content.json
1
{"pages":[{"title":"tags","text":"","link":"/tags/index.html"},{"title":"categories","text":"","link":"/categories/index.html"},{"title":"","text":"google-site-verification: google58d6fad2a0272c19.html","link":"/google58d6fad2a0272c19.html"}],"posts":[{"title":"Azure DevOps Stakeholder 權限看不到 Repo","text":"問題描述我們使用 CI/CD 平台是 Azure DevOps,我們預計想做 Side project,想把技術支援連結放在 Wiki,朋友說他有將我們每一個人權限設置為 Admin,另外也有用 git 控管 Wiki,而我遇到的問題是無法在平台的 side menu 找到 Repo 圖示,於是我跳出來組織的畫面,從 Project 的 Repo 圖示點,但卻看到 403 無權限存取。 解決辦法後來我才發現原來即便專案的權限開到最大,成員仍無法存取某些功能。 建立專案後,邀請其他人加入某一專案內,此人若沒有在這個組織內,系統會將其權限設定為 Stakeholder 的角色。 Stakeholder 的角色是沒有版本控制的功能可以使用,換句話說開發人員角色必須是 Basic 或 Visual Studio Subscriber。 第二點合理的原因是因為利害關係人不應該觸碰版本控制的東西,他的職責所在在於「驗收產品」。 操作設定首先,從專案頁面跳回去組織頁面,找到 side menu 的 Organization settings 從 Organization settings 找到 Users,可以發現邀請的組員都會在裡面 接著對應該是開發人員的組員 Access Level 調整成 Basic 或 Visual Studio Subscriber。 最後調整完,那我們就可以看到 Repo 的功能了 :D 參考資源[Fixed] Cannot see Repos in Azure DevOps with Stakeholder Access 【把玩Azure DevOps】Day3 Organization與Projects","link":"/2022/03/31/Azure-DevOps-Stakeholder-%E6%AC%8A%E9%99%90%E7%9C%8B%E4%B8%8D%E5%88%B0-Repo/"},{"title":"[Java] BigDecimal 精度計算","text":"問題實務上,我們在程式中處理與金錢相關的議題不會使用浮點數,因為那會造成 Truncate Error,算出來的金額會有浮點數誤差,通常我們會使用 BigDecimal 來計算,提高計算的精度。 而我曾經要計算某金額除以匯率,計算結果四捨五入小數點取到第二位,於是我就寫了以下範例,使用 BigDecimal 除法,分別填入除數與 MathContext , MathContext 的建構子的第一參數 setPrecision ,我以為設定的是小數點的精度,但事與願達,結果是2.4E2即 240 以科學記號表示。 1234BigDecimal dividend = new BigDecimal("999999.9999");BigDecimal divisor = new BigDecimal("4096.00");BigDecimal result = dividend.divide(divisor, new MathContext(2, RoundingMode.HALF_UP));System.out.println(result); Scale, Precision傻傻分不清楚由於上述計算結果並不是我想要,我在網路上找到 BigDecimal 對 Scale , Precision 的介紹,我才赫然發現一開始我就搞錯用法了,原來 BigDecimal 也像 IEEE-754 採用類似的表示方式。 BigDecimal表示法 Java BigDecimal 是由兩個數值所構成的,分別是隨機的精度整數、跟 32 位元整數範圍,通常BigDecimal以表示,其中n為小數位數。 Precision 精度定義是未經 BigDecimal 正規表示的數值之位數,即該數值整數加上小數的數位,例如: 123.45 ,此精度會回傳 5 。 Scale 範圍定義是經 BigDecimal 正規表示後的小數位數,如果 Scale 是零、正整數,則會將該數的數個位數挪動到小數點符號的右邊,如若 Scale 是負整數,則該值會乘上 10 的幂次,例如: 12345 , scale = 2,則回傳 123.45 , 12345 , scale = -1 ,則回傳 123450 。 解決方法承上述例子,因為精度是設定 2 ,舊版的寫法會造成四捨五入到十位數,十數位以下通通進位,並以科學記號表示。解決方法是改用 BigDecimal 另一個重載 divide 的方法,就可以在第二參數設定小數範圍為 2 ,最後結果為244.14。 12345public class BigDecimal extends Number implements Comparable<BigDecimal> { public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) { return divide(divisor, scale, roundingMode.oldMode); }} 1234BigDecimal dividend = new BigDecimal("999999.9999");BigDecimal divisor = new BigDecimal("4096.00");BigDecimal result = dividend.divide(divisor, 2, RoundingMode.HALF_UP);System.out.println(result);","link":"/2022/03/08/BigDecimal-%E7%B2%BE%E5%BA%A6%E8%A8%88%E7%AE%97/"},{"title":"[Heroku][Flask] Couldn't find that process type (web).","text":"問題描述朋友想製造一個 LINE 機器人,自學學習撰寫 Python Flask Web Framework ,並部署到 Heroku 上,預期傳訊息時機器人會自動回覆相同的訊息內容,但卻沒有顯示在對話視窗內,於是拜託我幫他看。 首先要看的就是 Heroku console log ,下 heroku logs --tail 從裡面一定可以找出一些蛛絲馬跡,果不其然看到一些訊息,從日誌檔可得知程式是包版成功的,但很顯然程式發生錯誤, Web server 根本就沒有啟動,官方網站給出的解決方法是下 heroku ps:scale web=1 這組指令,以手動方式將 Web server 程序部署到他們的引擎 dyno 上。 Heroku 上日誌檔122022-03-12T05:20:20.000000+00:00 app[api]: Build succeeded2022-03-12T05:20:30.962092+00:00 heroku[router]: at=error code=H14 desc="No web processes running" method=GET path="/favicon.ico" host=groupbuyingchatbot.herokuapp.com request_id=808970d4-836d-40e6-9f8a-1e3b3a999199 fwd="42.77.131.204" dyno= connect= service= status=503 bytes= protocol=https Heroku H14 錯誤代碼解釋1234567H14 - No web dynos runningThis is most likely the result of scaling your web dynos down to 0 dynos. To fix it, scale your web dynos to 1 or more dynos:heroku ps:scale web=1Use the heroku ps command to determine the state of your web dynos.2010-10-06T21:51:37-07:00 heroku[router]: at=error code=H14 desc="No web processes running" method=GET path="/" host=myapp.herokuapp.com fwd=17.17.17.17 dyno= connect= service= status=503 bytes= 你以這樣就解決問題了嗎?並沒有!我下了這道指令回覆訊息仍是找不是 Web Server 的程序,後續我確認了 Python 有沒有語法上的錯誤、部署腳本、套件 requirements.txt,也嘗試在本機上執行,皆無問題。 123gordonfang$ heroku ps:scale web=1Scaling dynos... !! couldn't find that process type (web). 解決方法隔一天再次檢查才發現原來朋友寫的 Procfile 多了副檔名,是 Procfile.txt , Procfile 是 Heroku 的部署腳本,告訴他包版完後要跑哪一個檔案將 Web Server 啟動起來,重新調整完後機器人就能順利回覆訊息。 1234@handler.add(MessageEvent, message=TextMessage)def handle_message(event): message = text=event.message.text line_bot_api.reply_message(event.reply_token, TextSendMessage(message))","link":"/2022/03/12/Heroku-Couldn-t-find-that-process-type-web/"},{"title":"[Hexo] 在不同電腦上寫部落格","text":"HEXO 簡介HEXO 是一套 OPEN SOURCE 寫部落格框架,專門用來部署靜態網頁的工具,並支持 MARKDOWN 的方式撰寫,對於工程師來說非常方便,使用方式則是使用 NODE.JS 軟體套件管理系統下載,利用指令 MARKDOWN 標記語言檔案,再透過解析方式生成靜態網頁,過程十分快速、方便。 但如果你想要在不同裝置上撰寫部落格的話,就需要透過版本控制實現。 在 GITHUB 上開好分支 假設你已經有自己的 Repository,\b\b 這個 Repository 必須與自己使用者名稱相同,例如:gordonfang199649.github.io 分支上面開立分支,我自己是取名為 hexo,輸入完分支即在下方按”Create branch from master” 在 Repository 中間分頁切到 Settings 後,左邊項目中選取 Branch(分支),我們將”hexo”設為預設分支 設置靜態網頁部署資訊這邊部署資訊要在 \b\b\b\b\b\b\b 撰寫部落格根目錄中的_config.yml 設定,我使用的是 GitHub 部署,所以 type 這邊輸入 git,repository 輸入你的 GitHub Repositroy 的 URL,分支則設置為 master,如果你是已經建立部署資訊的朋友們,可以先跳過此步驟。 123456# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy: type: git repository: 你的GitHub Repositroy的URL branch: master 移除主題.git 在將異動檔案上版至 hexo 分支前,要先將套主題中的.git 資料夾刪除因為 git 不容許一個以上.git 存在 12$pwd/你的hexo資料夾名稱/themes/套用主題資料夾名稱/.git 將異動檔案傳至遠端 Repository 部署文章到 master 分支 加入遠端 Repository 位置 開設分支 hexo 切換分支至 hexo 將異動檔案移動到暫存區 提交異動檔案 上版至遠端 Repository 12345678$hexo clean && hexo generate$hexo deploy$git remote add origin https://github.com/usrname/usrname.github.io$git branch hexo$git checkout hexo$git add .$git commit -m "commited message"$git push origin hexo 在不同裝置取得最新版本(環境是已建立) 只要下”Git pull”指令就可以取得當前部落格內容最新版本每次異動檔案要上傳遠端 Repository,先從遠端 PULL 一版下來,並輸入上一節第二到第四步驟的指令 12345678$git branch hexo$git checkout hexo$git pull$hexo clean && hexo generate$hexo deploy$git add .$git commit -m "commited message"$git push origin hexo 在不同裝置取得最新版本(環境是未建立) \b\b\b\b\b\b 若在尚未建立環境的裝置下,要先 Clone 自己的 Repository,再另行安裝 Hexo 1$git clone https://github.com/usrname/usrname.github.io.git 為什麼要另外開立分支的原因?最後部署至 GitHub 上,我們是透過”hexo deploy”指令部署到 GitHub 上的 master,另一個 hexo 分支則是儲存不同裝置上上版紀錄 \b、及資料,在替大家複習一下,若寫好文章要發佈上去,得先部署到”GitHub 的 master 分支”上,這樣才可以看得到靜態網頁,再來是將異動的檔案上到 hexo 分支,在不同裝置下都能複製一版下來再進行部落格的撰寫。 1234567$hexo clean && hexo generate$hexo deploy$git branch hexo$git checkout hexo$git add .$git commit -m "commited message"$git push origin hexo 循序圖","link":"/2020/06/08/%5BHexo%5D%20%E5%9C%A8%E4%B8%8D%E5%90%8C%E9%9B%BB%E8%85%A6%E4%B8%8A%E5%AF%AB%E9%83%A8%E8%90%BD%E6%A0%BC/"},{"title":"[LeetCode]180. Consecutive Numbers","text":"問題描述 Write a SQL query to find all numbers that appear at least three times consecutively. Id Num 1 1 2 1 3 1 4 2 5 1 6 2 7 2 For example, given the above Logs table, 1 is the only number that appears consecutively for at least three times. ConsecutiveNums 1 翻譯 請寫出一段 SQL 查詢能找出所有連續出現三次以上的數字 解題思維 利用 LEAD(), LAG() Oracle 分析函數可分別得出該數字的上下數字,再判斷上下數字、與該數字都是否相等,即為答案。 解題報告 Level: MediumRuntime: 678 ms, faster than 98.93% of Oracle online submissions for Consecutive Numbers.Memory Usage: 0B, less than 100.00% of Oracle online submissions for Consecutive Numbers. 程式完整解題123456789/* Write your PL/SQL query statement below */SELECT NUM AS ConsecutiveNums FROM(SELECT NUM ,LEAD(NUM) OVER (ORDER BY ID) AS NEXT_NUM ,LAG(NUM) OVER (ORDER BY ID) AS LAST_NUM FROM Logs) WHERE NEXT_NUM = LAST_NUM AND NEXT_NUM = NUM GROUP BY NUM; SQL Schema123456789Create table If Not Exists Logs (Id int, Num int)Truncate table Logsinsert into Logs (Id, Num) values ('1', '1')insert into Logs (Id, Num) values ('2', '1')insert into Logs (Id, Num) values ('3', '1')insert into Logs (Id, Num) values ('4', '2')insert into Logs (Id, Num) values ('5', '1')insert into Logs (Id, Num) values ('6', '2')insert into Logs (Id, Num) values ('7', '2')","link":"/2020/07/01/%5BLeetCode%5D%20180.%20Consecutive%20Numbers/"},{"title":"[LeetCode]184. Department Highest Salary","text":"問題描述The Employee table holds all employees. Every employee has an Id, a salary, and there is also a column for the department Id. Id Name Salary DepartmentId 1 Joe 70000 1 2 Jim 90000 1 3 Henry 80000 2 4 Sam 60000 2 5 Max 90000 1 The Department table holds all departments of the company. Id Name 1 IT 2 Sales Write a SQL query to find employees who have the highest salary in each of the departments. For the above tables, your SQL query should return the following rows (order of rows does not matter). Department Employee Salary IT Max 90000 IT Jim 90000 Sales Henry 80000 Explanation: Max and Jim both have the highest salary in the IT department and Henry has the highest salary in the Sales department. 翻譯Employee 這張資料表擁有所有員工資訊,每一筆資料包含員工編號、名稱、薪水、以及部門代碼Department 這張資料表擁有公司部門資訊請撰寫一段 SQL 查詢能找出各個部門薪資最高的員工,並參考以下查詢結果格式作為解答標準(次序非必要條件)。 解題思維 部門代號可以利用 JOIN 方式將 Employee 與 Department 兩張作關聯,如此以來就可以取得部門名稱 要取得部門中最高薪資,使用 Group By 部門方式,則可以取到該部門最高薪資 最後 \b\b\b 將 1.查詢結果 JOIN2.,以薪水、部門代號作為關聯條件,就可以得出答案 解題報告 Level: MediumRuntime: 983 ms, faster than 89.78% of Oracle online submissions for Department Highest Salary.Memory Usage: 0B, less than 100.00% of Oracle online submissions for Department Highest Salary. 程式完整解題1234567891011121314/* Write your PL/SQL query statement below */SELECT DP.NAME AS DEPARTMENT -- 部門名稱 ,EM.NAME AS EMPLOYEE -- 員工名稱 ,EM.SALARY AS SALARY -- 員工薪水 FROM EMPLOYEE EM INNER JOIN DEPARTMENT DP ON EM.DEPARTMENTID = DP.ID INNER JOIN (SELECT DEPARTMENTID ,MAX(SALARY) AS SALARY FROM EMPLOYEE GROUP BY DEPARTMENTID) MAX_SALARY ON EM.SALARY = MAX_SALARY.SALARY AND EM.DEPARTMENTID = MAX_SALARY.DEPARTMENTID; SQL Schema1234567891011Create table If Not Exists Employee (Id int, Name varchar(255), Salary int, DepartmentId int)Create table If Not Exists Department (Id int, Name varchar(255))Truncate table Employeeinsert into Employee (Id, Name, Salary, DepartmentId) values ('1', 'Joe', '70000', '1')insert into Employee (Id, Name, Salary, DepartmentId) values ('2', 'Jim', '90000', '1')insert into Employee (Id, Name, Salary, DepartmentId) values ('3', 'Henry', '80000', '2')insert into Employee (Id, Name, Salary, DepartmentId) values ('4', 'Sam', '60000', '2')insert into Employee (Id, Name, Salary, DepartmentId) values ('5', 'Max', '90000', '1')Truncate table Departmentinsert into Department (Id, Name) values ('1', 'IT')insert into Department (Id, Name) values ('2', 'Sales')","link":"/2020/06/12/%5BLeetCode%5D%20184-Department-Highest-Salary/"},{"title":"[LeetCode] 185. Department Top Three Salaries","text":"問題描述The Employee table holds all employees. Every employee has an Id, and there is also a column for the department Id. Id Name Salary DepartmentId 1 Joe 85000 1 2 Henry 80000 2 3 Sam 60000 2 4 Max 90000 1 5 Janet 69000 1 6 Randy 85000 1 7 Will 70000 1 The Department table holds all departments of the company. Id Name 1 IT 2 Sales Write a SQL query to find employees who earn the top three salaries in each of the department. For the above tables, your SQL query should return the following rows (order of rows does not matter). Department Employee Salary IT Max 90000 IT Randy 85000 IT Joe 85000 IT Will 70000 Sales Henry 80000 Sales Sam 60000 翻譯 Employee 表格紀錄員工資訊,每位員工擁有 Id, 及所屬部門代碼。 Department 表格紀錄公司所有部門資訊。 請撰寫一段 SQL 查詢,找出各部門業績排行的前三名,同名並列。 解題思維 利用 DENSE_RANK()函數以部門代號切割,業績從高至低排名最後撈取前三名(含),就可得出答案 解題報告 Level: HardRuntime: 1173 ms, faster than 80.22% of Oracle online submissions for Department Top Three Salaries.Memory Usage: 0B, less than 100.00% of Oracle online submissions for Department Top Three Salaries. 程式完整解題123456789101112/* Write your PL/SQL query statement below */SELECT DEPARTMENT ,EMPLOYEE ,SALARY FROM(SELECT DP.NAME AS DEPARTMENT ,EM.NAME AS EMPLOYEE ,EM.SALARY AS SALARY ,DENSE_RANK() OVER(PARTITION BY EM.DEPARTMENTID ORDER BY EM.SALARY DESC) AS RANK FROM EMPLOYEE EM JOIN DEPARTMENT DP ON EM.DEPARTMENTID = DP.ID) WHERE RANK <= 3; SQL Schema12345678910111213Create table If Not Exists Employee (Id int, Name varchar(255), Salary int, DepartmentId int)Create table If Not Exists Department (Id int, Name varchar(255))Truncate table Employeeinsert into Employee (Id, Name, Salary, DepartmentId) values ('1', 'Joe', '85000', '1')insert into Employee (Id, Name, Salary, DepartmentId) values ('2', 'Henry', '80000', '2')insert into Employee (Id, Name, Salary, DepartmentId) values ('3', 'Sam', '60000', '2')insert into Employee (Id, Name, Salary, DepartmentId) values ('4', 'Max', '90000', '1')insert into Employee (Id, Name, Salary, DepartmentId) values ('5', 'Janet', '69000', '1')insert into Employee (Id, Name, Salary, DepartmentId) values ('6', 'Randy', '85000', '1')insert into Employee (Id, Name, Salary, DepartmentId) values ('7', 'Will', '70000', '1')Truncate table Departmentinsert into Department (Id, Name) values ('1', 'IT')insert into Department (Id, Name) values ('2', 'Sales')","link":"/2020/07/03/%5BLeetCode%5D%20185.%20Department%20Top%20Three%20Salaries/"},{"title":"[LeetCode] 226. Invert Binary Tree","text":"問題描述Invert a binary tree. Example 翻譯翻轉這顆二元樹 解題思維 使用 level Order 方式拜訪這顆二元樹,用佇列將每一層 \b 節點儲存起來 同時檢查拜訪該節點是否有左右子樹,若有加入佇列裡面 交換左右節點 重複第二、三步驟直至佇列為空 解題報告 Level: EasyTime Complexity: O(n)Runtime: 0 ms, faster than 100.00% of Java online submissions for Invert Binary Tree.Memory Usage: 37.4 MB, less than 5.10% of Java online submissions for Invert Binary Tree. 程式完整解題12345678910111213141516171819202122232425262728293031323334/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */import java.util.Queue;import java.util.LinkedList;class Solution { public TreeNode invertTree(TreeNode root) { if(root==null) return root; Queue<TreeNode> q = new LinkedList<TreeNode>(); q.add(root); while(!q.isEmpty()){ TreeNode node = q.poll(); if(node.left!=null) q.add(node.left); if(node.right!=null) q.add(node.right); TreeNode temp_node = node.left; node.left = node.right ; node.right = temp_node; } return root; }}","link":"/2020/06/02/%5BLeetCode%5D%20226.%20Invert%20Binary%20Tree/"},{"title":"[LeetCode] 232. Implement Queue using Stacks","text":"問題描述Implement the following operations of a queue using stacks.1234push(x) -- Push element x to the back of queue.pop() -- Removes the element from in front of queue.peek() -- Get the front element.empty() -- Return whether the queue is empty. Example:1234567MyQueue queue = new MyQueue();queue.push(1);queue.push(2);queue.peek(); // returns 1queue.pop(); // returns 1queue.empty(); // returns false Notes:You must use only standard operations of a stack – which means only push to top, peek/pop from top, size, and is empty operations are valid.Depending on your language, stack may not be supported natively. You may simulate a stack by using a list or deque (double-ended queue), as long as you use only standard operations of a stack.You may assume that all operations are valid (for example, no pop or peek operations will be called on an empty queue). 解題思維首先我們要先釐清柱列(Queue)與 Stack(堆)的特性,前者為先進先出,後者為後進先出,題目是要我們使用堆來實現柱列的功能,我們需要用兩個堆來完成,一個存放資料,另一個暫存資料,解題主要思維是利用堆後進先出的特性,將儲存在第一個堆的資料,依序 pop 出來,同時 push 到暫存的堆裡,將要放入的資料放入第一堆,最後將暫存的堆裡依序吐出到第一堆,剛好最先進去的資料排在第一個。 Enqueue1234567891011121314151617181920/** * 實作Enqueue功能 * @param x 資料 * @remark */public void push(int x) { //依序先將存在第一個Stack吐到第二個裡 while(!stack1.empty()){ stack2.push(stack1.pop()); } //將我們儲存的元素存到第一個Stack裡 stack1.push(x); //依序將存在暫存Stack的元素依序吐回第一個Stack //維持先進去的元素排在最前面 while(!stack2.empty()){ stack1.push(stack2.pop()); } } Dequeue123456789/** * 實作Dequeue功能 * @return 返回Stack第一個元素 * @remark */public int pop() { //直接pop第一個Stack第一個元素 return stack1.pop();} 程式完整解題1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253/**Runtime: 0 ms, faster than 100.00% of Java online submissions for Implement Queue using Stacks.Memory Usage: 40.9 MB, less than 6.25% of Java online submissions for Implement Queue using Stacks.*/import java.util.Stack;class MyQueue { private Stack<Integer> stack1; private Stack<Integer> stack2; /** Initialize your data structure here. */ public MyQueue() { stack1 = new Stack<>(); stack2 = new Stack<>(); } /** Push element x to the back of queue. */ public void push(int x) { while(!stack1.empty()){ stack2.push(stack1.pop()); } stack1.push(x); while(!stack2.empty()){ stack1.push(stack2.pop()); } } /** Removes the element from in front of queue and returns that element. */ public int pop() { return stack1.pop(); } /** Get the front element. */ public int peek() { return stack1.peek(); } /** Returns whether the queue is empty. */ public boolean empty() { return stack1.empty(); }}/** * Your MyQueue object will be instantiated and called as such: * MyQueue obj = new MyQueue(); * obj.push(x); * int param_2 = obj.pop(); * int param_3 = obj.peek(); * boolean param_4 = obj.empty(); */","link":"/2020/01/19/%5BLeetCode%5D%20232.%20Implement%20Queue%20using%20Stacks/"},{"title":"[LeetCode] 262. Trips and Users","text":"問題描述The Trips table holds all taxi trips. Each trip has a unique Id, while Client_Id and Driver_Id are both foreign keys to the Users_Id at the Users table. Status is an ENUM type of (‘completed’, ‘cancelled_by_driver’, ‘cancelled_by_client’). Id Client_Id Driver_Id City_Id Status Request_at 1 1 10 1 completed 2013-10-01 2 2 11 1 cancelled_by_driver 2013-10-01 3 3 12 6 completed 2013-10-01 4 4 13 6 cancelled_by_client 2013-10-01 5 1 10 1 completed 2013-10-02 6 2 11 6 completed 2013-10-02 7 3 12 6 completed 2013-10-02 8 2 12 12 completed 2013-10-03 9 3 10 12 completed 2013-10-03 10 4 13 12 cancelled_by_driver 2013-10-03 The Users table holds all users. Each user has an unique Users_Id, and Role is an ENUM type of (‘client’, ‘driver’, ‘partner’). Users_Id Banned Role 1 No client 2 Yes client 3 No client 4 No client 10 No driver 11 No driver 12 No driver 13 No driver Write a SQL query to find the cancellation rate of requests made by unbanned users (both client and driver must be unbanned) between Oct 1, 2013 and Oct 3, 2013. The cancellation rate is computed by dividing the number of canceled (by client or driver) requests made by unbanned users by the total number of requests made by unbanned users. For the above tables, your SQL query should return the following rows with the cancellation rate being rounded to two decimal places. Day Cancellation Rate 2013-10-01 0.33 2013-10-02 0.00 2013-10-03 0.50 翻譯 Trips 表格紀錄所有計程車的乘車紀錄,每一筆資料 Id 為唯一值, 其中 Client_Id, Driver_Id 為外部鍵,參照 Users 表格的 Users_Id,Status 為列舉型態,表示三種狀態,分別為”完成乘車”、”司機取消載客”、”乘客取消乘車”。 Users 表格紀錄所有使用者,每一筆紀錄 Users_Id 為唯一值,其中 Role 欄位為列舉型態,分別為”司機”、”乘客”、”夥伴”。 請撰寫一段 SQL 查詢,找出 2013 年 10 月 1 日至 2013 年 10 月 3 日三日的當日乘車取消率,計算公式如下。 當日由”司機取消載客”、”乘客取消乘車”乘車紀錄次數 / 當日乘車紀錄總次數,且司機與乘客不得為禁用狀態。 解題思維 TRIPS 以 JOIN 方式關聯 Users 兩次,條件分別為乘客、司機非禁用狀態,時間篩選在 2013 年 10 月 1 日至 2013 年 10 月 3 日三日再 COUNT, SUM 函數分別對第一步驟的結果計算出乘車紀錄總次數、取消乘車紀錄次數最後使用 GROUP BY 方式,就可以得出三日當日的乘車取消率 解題報告 Level: HardRuntime: 796 ms, faster than 82.29% of Oracle online submissions for Trips and Users.Memory Usage: 0B, less than 100.00% of Oracle online submissions for Trips and Users. 程式完整解題12345678910111213141516171819202122232425/* Write your PL/SQL query statement below */WITH TAXI AS(SELECT TRIPS.STATUS AS STATUS ,TRIPS.REQUEST_AT AS REQUESTED_DATE FROM TRIPS JOIN Users CLIENTS ON (CLIENTS.Users_Id = TRIPS.CLIENT_ID) AND CLIENTS.BANNED = 'No' JOIN Users DRIVERS ON (DRIVERS.Users_Id = TRIPS.CLIENT_ID) AND DRIVERS.BANNED = 'No' WHERE TO_DATE(TRIPS.REQUEST_AT, 'YYYY-MM-DD') BETWEEN TO_DATE('2013-10-01', 'YYYY-MM-DD') AND TO_DATE('2013-10-03', 'YYYY-MM-DD'))SELECT TOTAL_REQ.REQ_DATE AS "Day" ,ROUND(CANCELLEDL_REQ.TIMES *1.0/ TOTAL_REQ.TIMES, 2) AS "Cancellation Rate" FROM (SELECT REQUESTED_DATE AS REQ_DATE ,COUNT(*) AS TIMES FROM TAXI GROUP BY REQUESTED_DATE) TOTAL_REQ JOIN(SELECT REQUESTED_DATE AS REQ_DATE ,SUM(CASE WHEN (STATUS = 'cancelled_by_client' OR STATUS = 'cancelled_by_driver') THEN 1 ELSE 0 END) AS TIMES FROM TAXI GROUP BY REQUESTED_DATE) CANCELLEDL_REQ ON TOTAL_REQ.REQ_DATE = CANCELLEDL_REQ.REQ_DATE ORDER BY TOTAL_REQ.REQ_DATE SQL Schema12345678910111213141516171819202122Create table If Not Exists Trips (Id int, Client_Id int, Driver_Id int, City_Id int, Status ENUM('completed', 'cancelled_by_driver', 'cancelled_by_client'), Request_at varchar(50))Create table If Not Exists Users (Users_Id int, Banned varchar(50), Role ENUM('client', 'driver', 'partner'))Truncate table Tripsinsert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('1', '1', '10', '1', 'completed', '2013-10-01')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('2', '2', '11', '1', 'cancelled_by_driver', '2013-10-01')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('3', '3', '12', '6', 'completed', '2013-10-01')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('4', '4', '13', '6', 'cancelled_by_client', '2013-10-01')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('5', '1', '10', '1', 'completed', '2013-10-02')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('6', '2', '11', '6', 'completed', '2013-10-02')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('7', '3', '12', '6', 'completed', '2013-10-02')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('8', '2', '12', '12', 'completed', '2013-10-03')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('9', '3', '10', '12', 'completed', '2013-10-03')insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('10', '4', '13', '12', 'cancelled_by_driver', '2013-10-03')Truncate table Usersinsert into Users (Users_Id, Banned, Role) values ('1', 'No', 'client')insert into Users (Users_Id, Banned, Role) values ('2', 'Yes', 'client')insert into Users (Users_Id, Banned, Role) values ('3', 'No', 'client')insert into Users (Users_Id, Banned, Role) values ('4', 'No', 'client')insert into Users (Users_Id, Banned, Role) values ('10', 'No', 'driver')insert into Users (Users_Id, Banned, Role) values ('11', 'No', 'driver')insert into Users (Users_Id, Banned, Role) values ('12', 'No', 'driver')insert into Users (Users_Id, Banned, Role) values ('13', 'No', 'driver')","link":"/2020/07/03/%5BLeetCode%5D%20262.%20Trips%20and%20Users/"},{"title":"[LeetCode] 509. Fibonacci Number","text":"問題描述The Fibonacci numbers, commonly denoted F(n) form a sequence, called the Fibonacci sequence, such that each number is the sum of the two preceding ones, starting from 0 and 1. That is, F(0) = 0, F(1) = 1F(N) = F(N - 1) + F(N - 2), for N > 1.Given N, calculate F(N). Example: 翻譯 經典不敗題型 費式數列,通常使用 F(n)來表示數列,每一個數都是由前兩個數所構成的,頭兩個數值分別為 0,1 解題思維 遞迴版本:每次呼叫 fib(N-1)+fib(N-2),若 N 為 0 則回傳 0,1 則回傳 1,最終回傳答案 迭代版本:先開好 N 個空間的陣列,頭兩個元素分別設為 1,1,透過迭代方式,最後回傳最後一個元素 解題報告 Level: EasyTime Complexity: O(n)Runtime: 0 ms, faster than 100.00% of Java online submissions for Fibonacci Number.Memory Usage: 36.3 MB, less than 5.51% of Java online submissions for Fibonacci Number. 程式完整解題1234567891011121314151617181920212223class Solution { //遞迴版本 public int fib(int N) { if(N<=1) return N; return fib(N-1)+fib(N-2); } //迭代版本 public int fib(int n) { if(n<2){ return n; }else { int[] ans = new int[n]; ans[0] = 1; ans[1] = 1; for(int i=2;i<n;i++) { ans[i]=ans[i-1]+ans[i-2]; } return ans[n-1]; } }}","link":"/2020/06/05/%5BLeetCode%5D%20509.%20Fibonacci%20Number/"},{"title":"[LeetCode] 66. Plus One","text":"問題描述Given a non-empty array of digits representing a non-negative integer, plus one to the integer. The digits are stored such that the most significant digit is at the head of the list, and each element in the array contain a single digit. You may assume the integer does not contain any leading zero, except the number 0 itself. Example 1:Input: [1,2,3]Output: [1,2,4]Explanation: The array represents the integer 123.Example 2: Input: [4,3,2,1]Output: [4,3,2,2]Explanation: The array represents the integer 4321. 翻譯給定一個非空存正整數位數的陣列,你要替這串數值加上一最高有效數位存放在陣列初始位置,數值每個數字各依序別存在陣列裡頭你能假定這串整數不以零開頭,除了本身為零 解題思維 先替陣列最後一個數值加上一 以迭代方式判斷是否大於 9,以此判斷是否要進位 最後判斷進位的變數是否不等於零,若不為零,將進位數填入陣列第一個位置,將計算後陣列依序填入新的陣列 解題報告 Level: EasyTime Complexity: O(n)Runtime: 0 ms, faster than 100.00% of Java online submissions for Plus One.Memory Usage: 37.9 MB, less than 5.64% of Java online submissions for Plus One. 程式完整解題12345678910111213141516171819202122232425262728class Solution { /** 數值陣列加一 * @param 數值陣列 * @return 加一後的數值陣列 */ public int[] plusOne(int[] digits) { digits[digits.length - 1 ] += 1; int carry = 0; for(int i = digits.length - 1 ; i >= 0 ; i--){ digits[i] += carry; if(digits[i] > 9){ digits[i] %= 10; carry = 1; } else{ carry = 0; } } if(carry == 1){ int[] answer = new int[digits.length + 1 ]; answer[0] = carry; System.arraycopy(answer, 1, digits, 0, digits.length); return answer; } return digits; }}","link":"/2020/05/31/%5BLeetCode%5D%2066.%20Plus%20One/"},{"title":"[LeetCode] 844. Backspace String Compare","text":"題目連結844. Backspace String Compare 問題描述Given two strings s and t, return true if they are equal when both are typed into empty text editors. ‘#’ means a backspace character. Note that after backspacing an empty text, the text will continue empty. Example 1:Input: s = “ab#c”, t = “ad#c”Output: trueExplanation: Both s and t become “ac”.Example 2: Input: s = “ab##”, t = “c#d#”Output: trueExplanation: Both s and t become “”.Example 3: Input: s = “a##c”, t = “#a#c”Output: trueExplanation: Both s and t become “c”.Example 4: Input: s = “a#c”, t = “b”Output: falseExplanation: s becomes “c” while t becomes “b”. 翻譯 給定兩字串分別是s、 跟t,被輸入到空的文字編輯器的這兩段字串如果是一樣,則回傳true,#表示一個退格鍵。 請注意在空字串輸入退格鍵,這段文字仍為空白。 解題思維從字串頭讀到字串尾的解法 準備一個Stack,以迭代方式讀取兩個字串中各個字元,將其儲存到Stack 當讀取到字元是退格鍵#,\b將\b\b目前儲存在Stack第一個字元pop ‘#’不儲到Stack 最後將仍儲存在Stack的字元輸出成字串,即可以比對兩串字串是否一樣 請參考Java解法 從字串尾讀到字串頭的解法 從字串尾開始讀取各個字元 當讀取到字元是退格鍵#,\b累加變數 變數用來決定讀取下一個字元是否要跳過 變數大於0,跳過該字元 最後比對去除刪除部分字元字串是否一樣 請參考C++解法 解題報告 Level: EasyTime Complexity: O(n)Runtime: 0 ms, faster than 100.00% of C++ online submissions for Backspace String Compare.Memory Usage: 6.3 MB, less than 57.92% of C++ online submissions for Backspace String Compare. 程式完整解題Java 解法123456789101112131415161718192021222324class Solution { public boolean backspaceCompare(String s, String t) { return removeBackSpaces(s).removeBackSpaces(t); } private String removeBackSpaces(String str){ Stack<Character> stack = new Stack<>(); StringBuilder builder = new StringBuilder(); for(char c : str.toCharArray()){ if(c == '#') { if(!stack.isEmpty()) { stack.pop(); } } else { stack.push(c); } } while(!stack.isEmpty()){ builder.append(stack.pop()); } return builder.toString(); }} C++ 解法123456789101112131415161718192021222324#include <string>class Solution {public: bool backspaceCompare(string s, string t) { return removeBackSpaces(s).compare(removeBackSpaces(t)) == 0; }private: string removeBackSpaces(string str) { string newString; int skip = 0; for(int i = str.length() - 1; i >= 0; i--) { if(str[i] == '#') { skip++; } else { if(skip > 0) { skip--; } else { newString.push_back(str[i]); } } } return newString; }};","link":"/2021/06/27/%5BLeetCode%5D%20844.%20Backspace%20String%20Compare/"},{"title":"[LeetCode] 88. Merge Sorted Array","text":"問題描述Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array. Note:The number of elements initialized in nums1 and nums2 are m and n respectively.You may assume that nums1 has enough space (size that is greater or equal to m + n) to hold additional elements from nums2. ExampleInput:nums1 = [1,2,3,0,0,0], m = 3nums2 = [2,5,6], n = 3 Output: [1,2,2,3,5,6] 翻譯給定兩個排序過整數陣列,請將兩組陣列合併成一個排序過後的陣列nums1、nums2 陣列大小分別為 m、n,你可以假定 nums1 有足夠的額外空間(空間可能是 n + m,或者更大)存放從 num2 陣列的元素。 解題思維 由於已知 num1 有足夠空間存放來自 num2 的元素,所以我們可以將 num2 元素從 num1 末端插入 重新排序 解題報告 Level: EasyTime Complexity: O(nlog(n))Runtime: 0 ms, faster than 100.00% of Java online submissions for Merge Sorted Array.Memory Usage: 39.7 MB, less than 5.94% of Java online submissions for Merge Sorted Array. 程式完整解題12345678910111213141516import java.util.Arrays;class Solution { /** *@param nums1 陣列一 *@param m 陣列一大小 *@param nums2 陣列二 *@param n 陣列二大小 *@return **/ public void merge(int[] nums1, int m, int[] nums2, int n) { for(int i = m ; i < n + m ; i++) nums1[i] = nums2[i-m]; Arrays.sort(nums1); }}","link":"/2020/06/01/%5BLeetCode%5D%2088-Merge-Sorted-Array/"},{"title":"[LeetCode]178. Rank Scores","text":"問題描述Write a SQL query to rank scores. If there is a tie between two scores, both should have the same ranking. Note that after a tie, the next ranking number should be the next consecutive integer value. In other words, there should be no “holes” between ranks. Id Score 1 3.50 2 3.65 3 4.00 4 3.85 5 4.00 6 3.65 For example, given the above Scores table, your query should generate the following report (order by highest score): score Rank 4.00 1 4.00 1 3.85 2 3.65 3 3.65 3 3.50 4 Important Note: For MySQL solutions, to escape reserved words used as column names, you can use an apostrophe before and after the keyword. For example Rank. 翻譯請撰寫一段 SQL 查詢排名以下分數,若兩個分數趨近一樣,將並列為同一名次,下一個名次取次排名,換言之,並列後的分數後不會空出排名。您必須產出如以下的依據分數從高至低的查詢結果。注意:如果是以 MySql 作答,你可以在 Rank 前後加入撇號(`)來跳脫保留字當作是欄位名稱。 解題思維用法: [ROW_NUM()|RANK()|DENSE_RANK()] OVER([PARTITION BY 欄位名稱] ORDER BY 欄位名稱 [DESC|ASC])解題是利用 Oracle 分析函數進行排名,詳細可參考[Oracle SQL] rank(), dense_rank(), row_number()分析函數用法 解題報告 Level: MediumRuntime: 544 ms, faster than 93.42% of Oracle online submissions for Rank Scores.Memory Usage: 0B, less than 100.00% of Oracle online submissions for Rank Scores. 程式完整解題1234/* Write your PL/SQL query statement below */SELECT SCORE ,DENSE_RANK() OVER (ORDER BY SCORE DESC) AS RANK FROM SCORES; SQL Schema12345678Create table If Not Exists Scores (Id int, Score DECIMAL(3,2))Truncate table Scoresinsert into Scores (Id, Score) values ('1', '3.5')insert into Scores (Id, Score) values ('2', '3.65')insert into Scores (Id, Score) values ('3', '4.0')insert into Scores (Id, Score) values ('4', '3.85')insert into Scores (Id, Score) values ('5', '4.0')insert into Scores (Id, Score) values ('6', '3.65')","link":"/2020/06/13/%5BLeetCode%5D178.%20Rank%20Scores/"},{"title":"[Oracle SQL] rank()分析函數用法","text":"分析函數簡介RANK(), DENSE_RANK(), ROW_RANK()都是 Oracle SQL 的分析函數,可根據分群、排序依據分配序列給每一筆資料,可以套用在成績、業績排名、或是群組內排名等等,以下使用員工薪水作為範例,範例會有兩張表格分別為 EMPLOYEE, DEPARTMENT,實作部分會告訴各位三種函數有何差別。 TABLE SCHEMA1234567891011121314151617181920212223242526CREATE TABLE DEPARTMENT ( DEPT_ID NUMBER NOT NULL, DEPT_NAME VARCHAR2(10 CHAR), CONSTRAINT DEPARTMENT_PK PRIMARY KEY(DEPT_ID));CREATE TABLE EMPLOYEE ( EMPLOYEE_ID NUMBER NOT NULL, DEPT_ID NUMBER NOT NULL, NAME VARCHAR2(20 CHAR), SALARY NUMBER, CONSTRAINT EMPLOYEE_PK PRIMARY KEY (EMPLOYEE_ID), CONSTRAINT DEPARTMENT_FK FOREIGN KEY (DEPT_ID) REFERENCES DEPARTMENT(DEPT_ID));INSERT INTO DEPARTMENT VALUES( 1, 'IT');INSERT INTO DEPARTMENT VALUES( 2, 'ACCT');INSERT INTO DEPARTMENT VALUES( 3, 'MAINT' );INSERT INTO EMPLOYEE VALUES( 1, 1, 'Terry' , 50000);INSERT INTO EMPLOYEE VALUES( 2, 1, 'Emily' , 50000);INSERT INTO EMPLOYEE VALUES( 3, 2, 'Joyce' , 38600);INSERT INTO EMPLOYEE VALUES( 4, 2, 'Bob' , 32800);INSERT INTO EMPLOYEE VALUES( 5, 2, 'Phoebe', 32800);INSERT INTO EMPLOYEE VALUES( 6, 3, 'Olive' , 28000);COMMIT; 語法使用ROW_NUM()、RANK()、DENSE_RANK()是分析函數用來分派序列給每一筆資料,ORDER BY 欄位名稱 [DESC|ASC]指的是要依據什麼欄位進行遞減[增]排序,此為必填子句,[PARTITION BY 欄位名稱]指的是要依照什麼欄位進行分群,例如:班級、單位內排序,此為選填子句,若沒有這段子句,則會針對所有的資料分派序列。 12[ROW_NUM()|RANK()|DENSE_RANK()]OVER([PARTITION BY 欄位名稱] ORDER BY 欄位名稱 [DESC|ASC]) RANK()使用方法 範例是依據員工薪水高低做排名,若排序資料值趨近一樣,RANK()函數會將排序名次並列,並列後的排名則佔用名次,再往後排序。 12345SELECT EM.EMPLOYEE_ID AS EMPLOYEE_ID ,EM.NAME AS EMPLOYEE_NAME ,EM.SALARY AS EMPLOYEE_SALARY ,RANK() OVER (ORDER BY EM.SALARY DESC) AS SALARY_RANK FROM EMPLOYEE EM; 查詢結果 EMPLOYEE_ID EMPLOYEE_NAME EMPLOYEE_SALARY SALARY_RANK 1 Terry 50000 1 2 Emily 50000 1 3 Joyce 38600 3 4 Bob 32800 4 5 Phoebe 32800 4 6 Olive 28000 6 DENSE_RANK()使用方法 DENSE_RANK()函數與 RANK()函數使用方式很相似,兩者差別在於並列後的名次並不會佔用。 12345SELECT EM.EMPLOYEE_ID AS EMPLOYEE_ID ,EM.NAME AS EMPLOYEE_NAME ,EM.SALARY AS EMPLOYEE_SALARY ,DENSE_RANK() OVER (ORDER BY EM.SALARY DESC) AS SALARY_RANK FROM EMPLOYEE EM; 查詢結果 EMPLOYEE_ID EMPLOYEE_NAME EMPLOYEE_SALARY SALARY_RANK 1 Terry 50000 1 2 Emily 50000 1 3 Joyce 38600 2 4 Bob 32800 3 5 Phoebe 32800 3 6 Olive 28000 4 ROW_NUMBER()使用方法 ROW_NUMBER()函數是並不考慮排序並列的情況,序列依序分派下來。 12345SELECT EM.EMPLOYEE_ID AS EMPLOYEE_ID ,EM.NAME AS EMPLOYEE_NAME ,EM.SALARY AS EMPLOYEE_SALARY ,ROW_NUMBER() OVER (ORDER BY EM.SALARY DESC) AS SALARY_RANK FROM EMPLOYEE EM; 查詢結果 EMPLOYEE_ID EMPLOYEE_NAME EMPLOYEE_SALARY SALARY_RANK 1 Terry 50000 1 2 Emily 50000 2 3 Joyce 38600 3 4 Bob 32800 4 5 Phoebe 32800 5 6 Olive 28000 6 分群排名 以下範例依據部門分群排名 12345678SELECT EM.EMPLOYEE_ID AS EMPLOYEE_ID ,EM.NAME AS EMPLOYEE_NAME ,DP.DEPT_NAME AS DEPT_NAME ,EM.SALARY AS EMPLOYEE_SALARY ,DENSE_RANK() OVER (PARTITION BY EM.DEPT_ID ORDER BY EM.SALARY DESC) AS SALARY_RANK FROM EMPLOYEE EM INNER JOIN DEPARTMENT DP ON EM.DEPT_ID = DP.DEPT_ID; 查詢結果 EMPLOYEE_ID EMPLOYEE_NAME DEPT_NAME EMPLOYEE_SALARY SALARY_RANK 1 Terry IT 50000 1 2 Emily IT 50000 1 3 Joyce ACCT 38600 1 4 Bob ACCT 32800 2 5 Phoebe ACCT 32800 2 6 Olive MAINT 28000 1 完整程式碼1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374CREATE OR REPLACE PROCEDURE PROC_DROP_TAB_IF_EXISTS( v_table_name IN VARCHAR2 --TABLE NAME) IS --INITIALIZE ROW NUMBER VARIABLE v_count NUMBER;BEGIN v_count := 0; SELECT COUNT(1) INTO v_count FROM USER_TABLES WHERE TABLE_NAME = v_table_name; IF v_count > 0 THEN EXECUTE IMMEDIATE 'DROP TABLE '|| v_table_name ||' CASCADE CONSTRAINTS PURGE'; END IF;END;EXECUTE PROC_DROP_TAB_IF_EXISTS('DEPARTMENT');EXECUTE PROC_DROP_TAB_IF_EXISTS('EMPLOYEE');CREATE TABLE DEPARTMENT ( DEPT_ID NUMBER NOT NULL, DEPT_NAME VARCHAR2(10 CHAR), CONSTRAINT DEPARTMENT_PK PRIMARY KEY(DEPT_ID));CREATE TABLE EMPLOYEE ( EMPLOYEE_ID NUMBER NOT NULL, DEPT_ID NUMBER NOT NULL, NAME VARCHAR2(20 CHAR), SALARY NUMBER, CONSTRAINT EMPLOYEE_PK PRIMARY KEY (EMPLOYEE_ID), CONSTRAINT DEPARTMENT_FK FOREIGN KEY (DEPT_ID) REFERENCES DEPARTMENT(DEPT_ID));INSERT INTO DEPARTMENT VALUES( 1, 'IT');INSERT INTO DEPARTMENT VALUES( 2, 'ACCT');INSERT INTO DEPARTMENT VALUES( 3, 'MAINT' );INSERT INTO EMPLOYEE VALUES( 1, 1, 'Terry' , 50000);INSERT INTO EMPLOYEE VALUES( 2, 1, 'Emily' , 50000);INSERT INTO EMPLOYEE VALUES( 3, 2, 'Joyce' , 38600);INSERT INTO EMPLOYEE VALUES( 4, 2, 'Bob' , 32800);INSERT INTO EMPLOYEE VALUES( 5, 2, 'Phoebe', 32800);INSERT INTO EMPLOYEE VALUES( 6, 3, 'Olive' , 28000);COMMIT;SELECT EM.EMPLOYEE_ID AS EMPLOYEE_ID ,EM.NAME AS EMPLOYEE_NAME ,EM.SALARY AS EMPLOYEE_SALARY ,RANK() OVER (ORDER BY EM.SALARY DESC) AS SALARY_RANK FROM EMPLOYEE EM;SELECT EM.EMPLOYEE_ID AS EMPLOYEE_ID ,EM.NAME AS EMPLOYEE_NAME ,EM.SALARY AS EMPLOYEE_SALARY ,ROW_NUMBER() OVER (ORDER BY EM.SALARY DESC) AS SALARY_RANK FROM EMPLOYEE EM;SELECT EM.EMPLOYEE_ID AS EMPLOYEE_ID ,EM.NAME AS EMPLOYEE_NAME ,EM.SALARY AS EMPLOYEE_SALARY ,DENSE_RANK() OVER (ORDER BY EM.SALARY DESC) AS SALARY_RANK FROM EMPLOYEE EM;SELECT EM.EMPLOYEE_ID AS EMPLOYEE_ID ,EM.NAME AS EMPLOYEE_NAME ,DP.DEPT_NAME AS DEPT_NAME ,EM.SALARY AS EMPLOYEE_SALARY ,DENSE_RANK() OVER (PARTITION BY EM.DEPT_ID ORDER BY EM.SALARY DESC) AS SALARY_RANK FROM EMPLOYEE EM INNER JOIN DEPARTMENT DP ON EM.DEPT_ID = DP.DEPT_ID; 參考Oracle ROW_NUMBERhttps://www.oracletutorial.com/oracle-analytic-functions/oracle-row_number/ Oracle PL/SQL: Rank 排名次查詢https://tomkuo139.blogspot.com/2009/04/plsql-rank.html Oracle 中 rank() over, dense_rank(), row_number() 的区别https://blog.csdn.net/baidu_37107022/article/details/78033513","link":"/2020/06/14/%5BOracle%20SQL%5D%20rank()%E5%88%86%E6%9E%90%E5%87%BD%E6%95%B8%E7%94%A8%E6%B3%95/"},{"title":"[Spring] Set exposeProxy property on Advised to true","text":"問題描述我在工作上有一個需求需要使用多執行緒完整多個工項,每一個工作都是獨立事務交易,所以我使用Java 8 CompletableFuture提供的方法實作Runnable,再利用Spring AOP代理機制處理事務交易,但當我發送Http Request到我的AP Server時,卻收到下述報錯訊息,從中可以得知發生此錯誤的原因有兩種可能,一、沒有打開代理Java @EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true),另一個可能是不是在同一條執行緒使用代理機制,後來我才意識到我使用了不同的執行緒代理是無法生效的。 1java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Cannot find current proxy: Set exposeProxy property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context. 我就好奇為什麼AOP代理機制為不能跨執行緒,於是我去翻AopContext的source code,我才發現原來它是從ThreadLocal取得目前的代理類別,ThreadLocal本身特性就專屬於一個執行緒使用,其他的執行緒不能存取、修改。 12345678910111213141516public final class AopContext { private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy"); private AopContext() { } public static Object currentProxy() throws IllegalStateException { Object proxy = currentProxy.get(); if (proxy == null) { throw new IllegalStateException( "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and " + "ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context."); } return proxy; }} 範例以下範例模擬我當時工作時發生的錯誤,在raiseEmployeeSalary()先到MongoDB撈所有員工的資料後,以多執行緒方式替員工加薪數額,由raiseSalary()完成,CompletableFuture.allOf意思是等所有員工加完薪水後,繼續往下作。如同上一節我所描述的,在透過代理機制去呼叫raiseSalary()就會報錯。 12345678910111213141516171819202122@Servicepublic class EmployeeService { @Autowired private EmployeeDao employeeDao; @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) public void raiseEmployeeSalary(RaiseSalaryBo raiseSalaryBo) throws ExecutionException, InterruptedException { List<Employee> employees = employeeDao.listAllEmployees(); List<CompletableFuture<?>> tasks = new ArrayList<>(); for (Employee employee : employees) { tasks.add(CompletableFuture.runAsync(()->((EmployeeService)AopContext.currentProxy()).raiseSalary(employee, raiseSalaryBo.getBonus()))); } CompletableFuture.allOf(tasks.toArray(new CompletableFuture<?>[0])).get(); } @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public void raiseSalary(Employee employee, BigDecimal bonus) { employeeDao.raiseSalary(employee, bonus); }} 解決方法解決方法十分簡單,如若真的需要切割出子事務交易,那就不能使多執行緒來處理,依據上述範例必須把CompletableFuture移除,程式才能正常執行。 12345678910111213141516171819@Servicepublic class EmployeeService { @Autowired private EmployeeDao employeeDao; @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) public void raiseEmployeeSalary(RaiseSalaryBo raiseSalaryBo) { List<Employee> employees = employeeDao.listAllEmployees(); for (Employee employee : employees) { ((EmployeeService) AopContext.currentProxy()).raiseSalary(employee, raiseSalaryBo.getBonus()); } } @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public void raiseSalary(Employee employee, BigDecimal bonus) { employeeDao.raiseSalary(employee, bonus); }} 資料庫更新後的結果","link":"/2022/03/07/%5BSpring%5D%20Set%20exposeProxy%20property%20on%20Advised%20to%20true/"},{"title":"[影像處理] 全彩圖片轉256色","text":"圖片資料壓縮 - 全彩轉換成索引色圖片索引色概念 - Indexed color 在計算機領域當中,索引色是一種資料壓縮的技巧,主要是用來快速呈現圖片、或是加速資料傳輸,也稱之「向量量化壓縮」。如果一張圖片是上述方式編碼,顏色資訊就不會直接存在該張圖片裡,而是另外一個檔案中稱「調色盤」,以陣列的方式儲存,陣列中的每一個元素都代表著一個顏色。 換言之,該張圖片並不包含原圖的所有顏色,而是參照另一個檔案所提供的顏色,編寫而成。 調色盤的大小 - Palette size ALPHA RED GREEN BLUE BIT POSITION 31-24 23-16 15-8 7-0 一張數位全彩影像(含透明值)由 32 個位元所組成,Alpha、紅、綠、藍各占 4 個 bit,可以表示的顏色為 2 的 24 次方,相當為 16,777,216 個顏色,我們可以得知控制位元大小可以控制可以表現出來的色彩。 調色盤為儲存索引顏色的地方,最常見有 4 色、 16 色、或 256 色,電腦數字表示都是 01 表示法,會根據位元的多寡來呈現,所以色彩種類都是 2 的次方。256 色就是由一個位元組(8 個位元)所組成的,4 個位元則可以表示 16 種顏色,以此類推。 PNG 圖檔、或是視訊覆蓋技術有使用到透明值,調色盤會額外保留一個位置來儲存透明值。 轉換公式 - Formula 在本作品裡頭是使用 256 種索引色彩,我使用歐幾里德距離公式對一張全彩的圖片進行色彩置換,我們國中所學的數學公式正好可以運用在此,利用巢狀走訪所有的像素值,將該像素值的 RGB 值與 256 色的索引色套此公式,會得到一個數值,求出數值差異最小的寫入一張空白的圖片上。 公式:dist((r1, r2), (g1, g2), (b1, b2)) = √((r1 - r2)² + (g1 - g2)² + (b1 - b2)²) 進一步說明,這套公式是在歐氏空間內求出距離,一顆像素值有紅、綠、藍,如果我們將紅、綠、藍視為座標,這顆像素則存在在三維空間,另外我們是用這顆像素值跟索引色比較,兩顆像素形成一向量。 主要程式1234567891011121314151617181920for(int i=0; i<height; i++){ for(int j=0; j<width; j++){ //變數是原圖像素值 Color c = new Color(image.getRGB(j, i)); //distance陣列存放歐幾里德公式算出的距離 double distance[]=new double[256]; int minindex=0; //抓取最小距離的索引色之索引 double min = Math.sqrt(Math.pow((c.getRed()-r[0]),2)+Math.pow((c.getGreen()-g[0]),2)+Math.pow((c.getBlue()-b[0]),2)); //兩兩比較算出最小距離 for(int d=1;d<256; d++){ distance[d]= Math.sqrt(Math.pow((c.getRed()-r[d]),2)+Math.pow((c.getGreen()-g[d]),2)+Math.pow((c.getBlue()-b[d]),2)); if(min>distance[d]){ min = distance[d]; minindex = d; } } //上色,填上索引色 buff.setRGB(j, i,palette[minindex]);} 作品結果原圖 處理過的圖片","link":"/2020/06/11/%5B%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%5D%20%E5%85%A8%E5%BD%A9%E5%9C%96%E7%89%87%E8%BD%89256%E8%89%B2/"},{"title":"[Hexo] 如何在 Hexo 開發環境的文章中加入 LikeCoin button","text":"前言LikeCoin 是一種加密虛擬貨幣,基於以太坊區塊鏈鎖延伸出來的代幣。不知道大家是不是在瀏覽網站是不是常常看到像 Medium 拍手按鈕, LikeCoin 基金會會依據別人給創作者的按讚數配發其發行虛擬幣幣給創作者,當 LikeCoin 數達到 2,000 時就可以兌換成法定貨幣。我想在自己的 Hexo 網頁放置 LikeCoin 主要原因為我很喜歡它的配色、 Logo 的設計,另一個原因是欣賞它的創作生態圈治理理念。 LikeCoin 加入到 Hexo 本篇文章是以 Hexo icarus 主題設定為主,且預設您已經註冊 LikeCoin 。 官方網站是以 Hexo next 主題為範例,而我的主題目前是使用 icarus , next 的模板引擎是用 ejs ,而 icarus 則是用 React 的 jsx ,所以設定上就有些許不同。 LikeCoin button Gitbook 官方範例1234567<div> <script type="text/javascript"> document.write( "<iframe scrolling='no' frameborder='0' sandbox='allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-storage-access-by-user-activation' style='height: 212px; width: 100%;' src='https://button.like.co/in/embed/[LikerID]/button?referrer=" + encodeURIComponent(location.href.split("?")[0].split("#")[0]) + "'></iframe>"); </script><div> icarus 主題預設可以設定 donate 的連結,可以透過 _config.icarus.yml 去做設定,它根據 yml 屬性找到對應連結的圖片,再渲染到前端頁面的卡片上。 _config.icarus.yml12345678910111213donates: # "Buy me a coffee" donate button configurations - type: buymeacoffee # URL to the "Buy me a coffee" page url: '你的buymeacoffee URL' # Paypal donate button configurations - type: paypal # Paypal business ID or email address business: 'paypal ID' # Currency code currency_code: USD #收款幣別 donates.jsx 原始碼12345678910111213141516171819202122232425262728293031323334const logger = require('hexo-log')();const { Component } = require('inferno');const view = require('hexo-component-inferno/lib/core/view');module.exports = class extends Component { render() { const { config, helper } = this.props; const { __ } = helper; const { donates = [] } = config; if (!Array.isArray(donates) || !donates.length) { return null; } return <div class="card"> <div class="card-content"> <h3 class="menu-label has-text-centered">{__('donate.title')}</h3> <div class="buttons is-centered"> {donates.map(service => { const type = service.type; if (typeof type === 'string') { try { let Donate = view.require('donate/' + type); Donate = Donate.Cacheable ? Donate.Cacheable : Donate; return <Donate helper={helper} donate={service} />; } catch (e) { logger.w(`Icarus cannot load donate button "${type}"`); } } return null; })} </div> </div> </div>; }}; 魔改 icarus 原始碼由於我是使用 npm 下載主題,所以主題外框、文章、頁面元件會放在你資料夾的 node_modules/hexo-theme-icarus/layout/common/ 底下,我是在 common 資料夾底下新建一個 jsx 檔元件,此元件去繼承 React.Component ,繼承下來的 sub class 去覆寫 render() 方法,我們方法回傳官方網站提供 iframe 標籤的內容,iframe src 屬性的 [LikerID] 換成自己的 LikerID ,最後我們將建立好的類別輸出出去,我的檔案取名叫 likecoin.jsx。 likecoin.jsx1234567const { Component } = require('inferno');module.exports = class extends Component { render() { return <iframe scrolling='no' frameborder='0' sandbox='allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-storage-access-by-user-activation' style='height: 212px; width: 100%;' src='https://button.like.co/in/embed/[LikerID]/button?referrer=" + encodeURIComponent(location.href.split("?")[0].split("#")[0]) + "'></iframe>; }} 原先包住在 <div class="buttons is-centered">...</div> 原始碼以迭代方式尋找在使用者在 _config.icarus.yml 設定的 donate type 有沒有在對應的type,再把type帶進去 Donate渲染出來,那我們要改的就是這一段,接著我們將 likecoin.jsx 引入到 donates.jsx ,使用標籤將元件包住,並取代剛剛我們提到的地方,由於我們不需要再透過 config 檔尋找 donate type ,所以我有把 _config.icarus.yml donate 那一塊刪掉,原始碼原本判斷 donates 是否為陣列及陣列長度也一併移除。 donates.jsx12345678910111213141516171819const logger = require('hexo-log')();const { Component } = require('inferno');const LikeCoin = require('./likecoin');const view = require('hexo-component-inferno/lib/core/view');module.exports = class extends Component { render() { const { helper } = this.props; const { __ } = helper; return <div class="card"> <div class="card-content"> <h3 class="menu-label has-text-centered">{__('donate.title')}</h3> <div class="is-centered"> <LikeCoin></LikeCoin> </div> </div> </div>; }}; 參考資源 如何在 Hexo 開發環境的文章中加入 LikeCoin button React.Component","link":"/2022/03/12/%E5%A6%82%E4%BD%95%E5%9C%A8-Hexo-%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83%E7%9A%84%E6%96%87%E7%AB%A0%E4%B8%AD%E5%8A%A0%E5%85%A5-LikeCoin-button/"},{"title":"在 Visual Studio Code 遠端開發(客戶端篇)","text":"研究所有時候要跑機器學習的東西,但用自己的主機跑又很容易炸裂,每次遠端登入進去 lab 的電腦又很麻煩,我剛好看得到學長姐用 vscode 遠端回去 lab 電腦,我想說自己也來玩玩看,以下是客戶端的步驟,照著步驟操作後,就可以在本地端 vscode 做程式開發,實際算力是用 lab 的電腦。 Visual Studio Code 遠端開發設定下載 Remote SSH 插件在 VS Code IDE 側欄找到 Extensions,輸入 “Remote-SSH”,然後按下安裝鍵。 SSH 連線資訊設定在 VS Code IDE 左下角有一個 >< 的符號,找到後按下去,IDE 會跳出一個 prompt,輸入伺服器的 SSH 連線資訊,格式為 [帳號]@[IP位址 / 主機網域名稱] (不用打中括號)。 輸入完後,在在 VS Code IDE 右下角會挑出 Open Config,上一步的操作會在家目錄 ~/.ssh 中建立一個 config 檔案,裡面會紀錄我們的電腦跟其他主機的 SSH 連線資訊。 打開後長這樣,裡面可以自行更改 IP 位址、Port 號、SSH 連線用的私鑰,Host 預設是我剛剛輸入的 IP位址,為了方便我識別,我改成 LAB706,所以 Host 是可以自行更改成喜歡的別名,但 HostName 不行,要輸入正確的IP位址或主機網域名稱。 SSH 連線到遠端主機確認沒問題後,就可以關閉,再次按下 VS Code IDE 左下角有一個 >< 的符號,選擇 LAB706,就可以登入到遠端伺服器。 VS Code 將提示你輸入 SSH 密碼,輸入後即可連接到伺服器。 連進來後,沒有跳出錯誤訊息的 prompt,且右下角顯示 LAB706,代表連線成功,那要怎麼看到遠端主機上的檔案呢?按下 VS Code Explorer,再按 Open folder,就可以看得到遠端主機上的資料夾、跟檔案,接下來可以開始做事了。","link":"/2024/01/27/%E5%9C%A8-Visual-Studio-Code-%E9%81%A0%E7%AB%AF%E9%96%8B%E7%99%BC/"},{"title":"VS Code 帶參數 debug python程式","text":"問題描述學姊最近問我要怎麼在 VS Code 帶參數去debug python程式,什麼是帶參數執行 Python 程式呢?也就是在 Python 腳本檔依據執行指令的參數來決定程式要跑一個環境、哪一個資料集等等,是一個非常好用的功能。 學姊遇到的問題是如果在終端機上下 python main.py –e 1000 –d datasets,沒辦法在 VS Code 上 debug,原因是因為沒有使用 VS Code debugger,就算用了,也無法指定參數內容,當跑到要吃參數的程式片段的時候會因此拋錯。 解決方法首先,我先準備了一個範例程式碼 main.py,內容如下,在這個範例檔案會吃兩個參數,一個是 -e 迭代次數、另一個 -d 資料集,我使用了 argparse 套件來吃參數,用法在此不介紹。 12345678910111213141516171819202122232425import argparsedef parse_args(): parser = argparse.ArgumentParser(description='a simple demo of argparse') # epoch argument parser.add_argument('-e', type=int, required=True) # datasets path argument parser.add_argument('-d', type=str, required=True) # resolve arguments args = parser.parse_args() return argsif __name__ == '__main__': args = parse_args() epoch = args.e datasets_path = args.d print(epoch) print(datasets_path) 點開 VS Code 左側的 Run and debug,再點選 create a lauch.json file。 接著會跳出 Prompt,選擇 Python File。 點選後,VS Code 就會在當前的資料夾底下,建立一個 .vscode 的資料夾,裡面放 lauch.json,我會要在 configurations 裡面多新增一個 key 叫做 args,裡面填寫我們要執行的參數值,要注意的地方是,參數跟值要分開來寫,舉例來說,原本用終端機執行 python 的指令是 python -e 1000 -d datasets,args就要這樣寫 [“-e”, “1000”, “-d”, “~/test/datasets.csv”]。 1234567891011121314151617{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Python: Current File", "type": "python", "request": "launch", "program": "${file}", "console": "integratedTerminal", "args": ["-e", "1000", "-d", "~/test/datasets.csv"], "justMyCode": true } ]} 為何要這樣設定的原因是因為 VS Code 可以根據我們的參數設定來執行、除錯程式。 點開 VS Code 左側的 Run and debug,按下綠色 Play 按鈕就可以開始 debug,我是在程式碼的第 23 行下斷點,在 watch 監控 epoch、datasets_path 這兩個變數的內容,可以看到我們在 launch.json 設定的參數。 參考文件https://code.visualstudio.com/docs/cpp/launch-json-reference","link":"/2024/01/27/VS-Code-%E5%B8%B6%E5%8F%83%E6%95%B8-debug-python%E7%A8%8B%E5%BC%8F/"},{"title":"Snow Algorithm 雪花演算法","text":"什麼是雪花演算法?我們都知道在自然界當中每一片雪花都是獨一無二的。在目前提倡容器化、分散式的架構時代下,很可能會遇到 ID 重號的問題,我們對 ID 的期望就會希望雪花般一樣都不重複。 這個演算法最早是由 Twitter 所創建的,主要是拿來產推文的 ID,後來這套演算法被 Discord、Instagram採用,也衍伸許多不同格式的版本。 會想要使用這套演算法的原因是因為筆者想要拿它產生 token,快取在 redis 上,合法的使用者可以拿著這個 token 存取筆者在部署 Docker 上的微服務系統。 雪花演算法核心組成筆者先為大家科普一下雪花演算法的組成的成分,主要共分 4 個欄位組成 64 位元,頭一個位元我們不使用,其他欄位下方逐一跟各位說明。 時間戳記第二欄位是時間戳記,共 41 個位元,以 UNIX 時間(起始年是 1970 年)的毫秒為單位正好可以填滿41 bits 的空間,可表示最大數為 2,199,023,255,551,以毫秒計算約 69 年,這個數值是兩個時戳相減得出的。 如果是拿當下的時間戳填進去這個欄位,那麼數值很快就會到達天花板,我們身為生活智慧王,當然要善用空間,所以才會以產生 token 的時間戳減去一個開始時間戳。 由於前面有提到是微服務,為了保持一致,開始時間戳需要寫死,建議可以訂在系統上線前的日期,像筆者就選擇把開始時間的時戳訂成自己的生日。 工作站台 ID第三欄位是工作站台 ID,共 10 個位元,用來區分哪ㄧ台機器發出的 token。 流水號第四欄位是流水號 ID,共 12 個位元,意思是同一個時間下同一機器所配發出去的流水號,從 0 開始遞增,直到進入下一個毫秒,流水號再重新編號。 演算法說明位元分配器筆者建立一個類別叫作 BitsAllocator,主要做兩件工作,第一件事是初起化各個欄位位元數,第二件是配發雪花演算法的唯一 ID,以下筆者會逐一說明。 一開始在 BitsAllocator 的建構子決定好各欄位要多長,由參考 BitsAllocator 的類別去制定,假設我們按造預設的長度,那麼建構子內的三個參數分別為 41, 10, 12。 第二步就是要製作位元遮罩,所以我們要取得這三個欄位的最大值,舉例來說如果我們想要取得工作站 ID 的最大值 2¹⁰ — 1,也就是 10 個位元都是 1。 如何取得最大值呢?在 Java 的世界中整數採用二的補數,先將 — 1 向左位移 10 個位元的位置,再位元 NOT 運算,就可以得到囉。 第三步要把各個欄位擺放到正確的位置,像是時間戳記需要位元要向左位移 10 + 12 個位元,工作站站台 ID 要向左位移 12 個位元,流水號則不需要移動,所以我們需要紀錄一下到時候要位移的個數。 第四步就是把它們像積木一樣拼起來,筆者這邊設計一個方法,讓參考BitsAllocator 的類別呼叫 allocate,把當下的時間戳、機器的 ID、配發的流水號傳進來,該方法會把這三個欄位推到正確的位置,用到的是我們上面紀錄下來位移的各個去推,最後用 OR 運算合併。 唯一性 ID 取號器筆者設計了一個 SnowFlakeAlg 類別用來取號。 一開始建構 SnowFlakeAlg 類別,首先先注入 BitsAllocator,後續我們取號的時候會使用到。由於每一個放置在 Docker 容器內每一個微服務都會分配一組 IP,所以筆者是取其 IPv4 第四個欄位作為工作站站台 ID。 這組 IP 是怎麼得來的呢?Java 會在作業系統建立 Socket 連線,使用 InetAddress.getLocalHost().getHostAddress() ,可以得到本機的主機位址。 至於時戳怎麼產生呢?是呼叫 System.currentTimeMillis() 取得系統當下的時戳,記得前面我們有說雪花演算法時戳這個欄位是兩個毫秒相減得出的嗎?系統當下的時戳減去一個我們是先制定好的開始時戳,這邊我減去的是我去年的生日那天,如果結果值大於 2⁴¹ — 1,那就代表時戳佔用位元已耗盡,無法產製唯一性 ID。 會呼叫 nextMilliSecond() 方法原因在上一毫秒的流水號已經分配完,所以要循環等待到下一毫秒,為了避免因為機器硬體時鐘問題造成時戳還停留在過去,所以我們有寫了 while 迴圈,重新取時戳,同時判斷是否有大於上一個時戳的時間。 SnowFlakeAlg 類別主要功能也就是取號,第一先取得系統當下的時戳,同一個毫秒下,我們就讓遞增取流水號,在取號的過程中,我們會拿遞增後的流水號跟流水號的最大值做 AND 運算,看結果值是否為 0?如果是,那代表該時戳可分配的流水號全部分玩了,需要等到下一個毫秒,再重新分配。 最後我們拿系統時戳減開始時戳、工作站 ID、流水號當作參數呼叫bitsAllocator.allocate(),就可以拿到唯一性的 ID 當作 token 使用了。 完整程式碼SnowflakeAlgo","link":"/2024/01/29/Snow-Algorithm-%E9%9B%AA%E8%8A%B1%E6%BC%94%E7%AE%97%E6%B3%95/"},{"title":"Flask 創建一個二手書籍線上購物平台-系統需求與分析","text":"本系列文章是紀錄研究所軟體工程課程修課實作過程 系統需求1. 專案目的與動機現代教育體系中,學生每學期都需要購買大量教科書,然而,許多書籍僅在短時間內使用,之後就被束之高閣。為了解決這個問題,我們致力於建立一個二手書交流平台,讓學長姊能夠輕鬆販賣二手書,同時讓學生購書的負擔降低。這不僅有助於節省金錢,更能實現地球永續發展,減少資源浪費。 2. 系統特色a. 公開的平台,價格透明化 我們的平台注重透明度,讓購買者能夠清晰了解市場價格,避免不合理的高價販售。這種公開的交易環境有助於建立信任,提高整個系統的效能。 b. 第三方平台管理 為了確保平台的公正性和安全性,我們引入第三方平台管理機制。這將有助於處理潛在的糾紛,確保交易的順利進行,同時為使用者提供更多保障。 3. 使用者定位a. 不再需要用到該教科書的同學 透過平台,這些同學能夠迅速輕鬆地轉手自己不再需要的教科書,獲得一些額外的收入,同時也促進了書籍的再利用。 b. 需要教科書卻無法負擔高額費用的同學 平台使這些同學能夠以更經濟實惠的價格獲得需要的教材,減輕了他們的經濟負擔,提高了教育資源的可及性。 c. 想擁有學長姐精華筆記的同學 除了書籍,我們的平台還提供學長姐的筆記販售功能,這滿足了一部分同學對於高質量學習材料的需求。 系統分析 使用者案例圖找出誰是系統的參與人、歸納出領域、並劃分系統內的領域邊界 我們與同學深入討論後,整理出了這套系統可能的各種使用者案例,並繪製了相對應的 Use Case Diagram。系統的使用者案例主要分為四大模組,包括會員模組、商品模組、訂單模組、以及購物車模組。 1. 會員模組會員模組涵蓋了所有使用者在系統中的身份認證和相關功能。註冊、登入、修改個人資料等功能均屬於會員模組的範疇。 2. 商品模組商品模組提供了訪客和會員查詢、瀏覽商品的功能。即使在未登入的情況下,訪客可以方便地瀏覽並查詢系統中的商品。會員作為訪客的一種。 3. 訂單模組訂單模組與購物車模組之間存在高度相依性。這是因為結帳購物車這個使用者案例會延伸到新增訂單,因此兩者之間有著密切的關係。這一模組負責處理使用者的訂單相關操作,包括查看歷史訂單、取消訂單等功能。 4. 購物車模組購物車模組負責處理使用者在購物過程中的相關操作。這包括將商品加入購物車、管理購物車內商品、進行結帳等功能。購物車模組的操作直接影響到訂單模組的運作。 這套系統的構想是,訪客可以在不登入的情況下瀏覽、查詢商品,而會員則包含在訪客的範疇內,因此可以享有訪客使用的功能。特別值得一提的是,訂單模組與購物車模組之間存在相對高的相依性,這是因為結帳購物車的案例會直接連接到新增訂單的流程中。 資料建模分析 活動圖(泳道圖)可以拿來疏理各個使用者案例的動向,以及了解業務流程中參與人會是誰 我們透過活動圖以泳道的方式呈現了系統的業務流程。使用者以「訪客」或「會員」身份進入網站後,開始瀏覽並查詢商品。若在這過程中點擊網頁加入購物車,系統會先檢查會員是否已登入。若未登入,系統將引導至登入畫面;若已登入,則將商品成功加入購物車。 在查看購物車期間,會員擁有刪除商品或調整商品購買數量的權限。完成購物後,系統將自動生成訂單。會員隨後可在訂單列表中查看已下訂的訂單,並追蹤其進度。若欲取消訂單,會員需提交取消申請,經系統管理員審核後方可生效。 循序圖是用細部拆解使用者案例的流程 接著我們挑出比較核心的 Use case 來繪製成循序圖,方便我們梳理資料流、與業務流程,圖上的物件有會員、前端介面、後端模組、以及資料庫,整理流程為登入、瀏覽商品、加入購物,最後到結帳。 登入流程 會員或訪客進入前端介面。 前端介面發送登入請求給後端模組。 後端模組驗證登入請求,若為會員,則檢查會員身份;若為訪客,則要求註冊。 登入結果回傳給前端介面。 瀏覽商品流程 會員或訪客透過前端介面發送瀏覽商品請求。 後端模組從資料庫中搜尋商品資訊。 商品資訊回傳給前端介面。 加入購物車流程 會員或訪客透過前端介面發送加入購物車請求。 後端模組驗證使用者身份,確認是否為登入狀態。 若為登入狀態且有庫存狀態下,將商品加入購物車,並把加入結果回傳給前端介面。 如果沒有庫存,回傳「暫無庫存」給前端介面。 結帳流程 會員透過前端介面發送結帳請求。 後端模組從購物車中搜尋預購買商品,在庫存數足夠狀態下,扣除購買商品的庫存數,若不足,回傳「暫無庫存」給前端介面。 會員的購物車的商品移除,將購物商品的資訊儲存為訂單。 訂單成立後,將訂單資訊儲存到資料庫。 回傳訂單資訊給前端介面。 需求訪談(補充內容)怎麼需求獲取?在進行需求訪談之前,充分的事前準備是必要的。事前的準備包括深入了解相關背景知識。需求訪談通常應該針對真實的使用者進行,人數最好控制在 20 人以內。根據我們的案例,可以選擇不同系所的同學進行訪談,同時也應該尋求領域專家(例如老師或其他電商平台經營者)的意見。需要注意的是,並不是每位受訪者都能充分表達對於系統需求的看法,因此訪談者可能需要巧妙設計問題,引導受訪者表達他們對系統的需求。整體過程有點像摸象,透過了解需求來逐漸塑造系統的輪廓。 怎麼確認需求?在需求訪談結束後,我們將收集到各種意見和功能需求。基於這些資料,我們可以進行資料建模,而 UML(統一建模語言)是一個強大的工具,用來繪製系統使用者案例、設計業務流程等。接著,我們可以再次召集使用者和領域專家來檢視繪製的 UML 圖,請他們確認這是否是他們期望的系統樣貌,這是為了確保我們所想的與使用者需求是否一致。 在實務操作中,需求訪談往往需要進行多輪,且顧客的時間寶貴。因此,在召開會議之前,確保準備工作充足至關重要。需求文件要等到顧客簽署後方能確認完畢,這樣的確認流程是為了保證最終的系統需求與使用者的期望保持一致。","link":"/2024/02/03/Flask-%E5%89%B5%E5%BB%BA%E4%B8%80%E5%80%8B%E4%BA%8C%E6%89%8B%E6%9B%B8%E7%B1%8D%E7%B7%9A%E4%B8%8A%E8%B3%BC%E7%89%A9%E5%B9%B3%E5%8F%B0-%E7%B3%BB%E7%B5%B1%E9%9C%80%E6%B1%82%E8%88%87%E5%88%86%E6%9E%90/"},{"title":"Flask 創建一個二手書籍線上購物平台-資料庫設計","text":"本系列文章是紀錄研究所軟體工程課程修課實作過程 資料模型設計我們的資料庫包含8個實體,這些實體是根據使用者案例圖的設計而來。首先,我們從左上角開始進行說明。每位會員擁有一個「購物車」,而每個「購物車」可以包含多個「購物車品項」,這些「購物車品項」來自於「品項」實體,形成一對一的關係。 在二手平台中,我們從學長姐那裡收集書籍,將這些書籍的資訊登錄到「品項」實體中。由於這些品項有可能是同一本書,所以「品項」與「書籍」之間形成多對一的關係,這麼做的原因是為了區分提供者的身份。 回到前述,會員可以透過平台下單,因此每位會員可能擁有多張「訂單」,而每張訂單則包含多個「訂單商品」。「訂單商品」、「品項」、「書籍」這三個實體之間的關聯形成了訂單明細。 由於在會員模組中有一個「忘記密碼」的功能,我們計劃以系統分發token的方式來驗證會員身份,以便在後續進行密碼更改。因此,我們另外設計了一個實體,稱為「忘記密碼令牌」,以「會員的電子郵件地址」的屬性作為外部鍵,連接「會員」實體。由於我們限制系統中不允許相同的信箱重複註冊,這使得「忘記密碼令牌」和「會員」之間的實體關係保持為一對一。否則,在會員更改密碼時可能會影響到其他會員的密碼。 ERD ERD(雞爪圖) 資料庫權限設計 PostgreSQL 為本專案選用關聯式資料庫管理系統 RBAC 模型(Role-Based Access Control:基於角色的訪問控制在談論專案資料庫權限設計之前,我們不得不提及RBAC模型(基於角色的訪問控制)。RBAC模型是一種權限存取機制,由三個基本組成部分組成,即使用者、角色和許可權。這個模型強調誰(WHO)以什麼方式(HOW)可以對什麼(WHAT)進行存取。 以銀行貸款服務為例,承辦櫃員可以查詢顧客聯徵信用紀錄和初步審查貸款資料,最終由主管進行審核和撥款。因此,每個角色都擁有相對應的權責。我們的目標是使角色的權限最小化,以避免權限開放得太大。 同樣地,這種機制也可以應用在資料庫中,具有以下好處: 降低了潛在的安全風險: 如果受到SQL注入攻擊,這種機制可以防止系統使用的帳號擁有過大的權限,從而防止查詢到機敏資訊或竄改重要的資料,攻擊者僅能獲取到相應角色的權限。 細緻權限控制: 對於廠商或一般使用者,可以建立僅能讀取資料的帳號,而只有少數管理者能夠擁有寫入資料的權限,從而確保嚴格的讀寫權責的實施。 簡化權限管理:每個角色擁有特定的權限,無需針對每個使用者進行單獨的權限分配,只要分配對應的角色給使用者即可。 實作我們將 8 張資料表創建在同一個 schema 底下,建立 rl_sel、rl_del、rl_upd、rl_ins,分別是可以對這 8 張資料表篩選、刪除、更新、新增的權限角色,再來是建立 app_001、it_001 這兩個可登入角色,app_001 是給系統使用的帳號,it_001 是給開發人員使用的帳號,此外這組帳號可以在這個 schema 新增、刪除資料庫物件。 如果後台系統資料表在另一個 schema 存放,會因為這套機制,可以有效地保護資料,不讓角色享有過大的權限,設計上,就可以成立 app_002 角色,只能對後台系統資料表操作,it_001 則可以跨 schema 對資料表操作。 123456789101112131415161718192021222324252627282930313233343536-- 建立資料庫CREATE DATABASE bookstore;-- 建立 bookmazon schemaCREATE SCHEMA bookmazon;-- 可以選資料表內容的角色CREATE ROLE rl_sel;-- 可以刪除資料表內容的角色CREATE ROLE rl_del;-- 可以更新資料表內容的角色CREATE ROLE rl_upd;-- 可以新增資料表內容的角色CREATE ROLE rl_ins;-- 為新增、刪除、修改、查詢賦予角色GRANT SELECT ON TABLE bookmazon.book TO rl_sel;GRANT DELETE ON TABLE bookmazon.book TO rl_del;GRANT UPDATE ON TABLE bookmazon.book TO rl_upd;GRANT INSERT ON TABLE bookmazon.book TO rl_ins;-- 程式開發人員操作 db 的角色CREATE ROLE user_001 WITH LOGIN CREATEDB CREATEROLE PASSWORD 'user_001';GRANT rl_sel, rl_del, rl_upd, rl_ins to user_001;-- 賦予 bookmazon schema 的使用權限GRANT USAGE ON SCHEMA bookmazon to user_001;-- 賦予可以 bookmazon schema 建立物件(trigger、function、procedure)的權限GRANT CREATE ON SCHEMA bookmazon to user_001;GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA bookmazon TO user_001;-- 後端系統操作 db 的角色CREATE ROLE app_001 WITH LOGIN PASSWORD 'app_001';GRANT rl_sel, rl_del, rl_upd, rl_ins to app_001;-- 賦予 bookmazon schema 的使用權限GRANT USAGE ON SCHEMA bookmazon to app_001;GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA bookmazon TO app_001; 資料庫表空間管理什麼是資料庫表空間?在 PostgreSQL,表空間意思是指資料庫的管理者可以自行定義一段檔案系統的路經,讓資料庫的物件都儲存在這個資料夾底下,爾後凡有資料庫的物件,例如:資料表、索引、視圖要找地方儲存的時候,那就可以引用資料庫的管理者所定義好的路徑進行資料儲存。 為什麼要做資料庫表空間管理? 如果要資料表的資料要擴張到別的地方(新增硬碟),可以在不同的分割區上建立資料表空間。 資料庫管理者可以控制 PostgreSQL 安裝的磁碟規畫,假設資料不斷地增長,原有的檔案系統空間不足時,可以在新掛載的檔案系統建立新的表空間。 允許資料庫管理者依資料庫物件特性的知識來優化效能,比較常查詢的資料可以放在固態硬碟上,相對於不常查詢的資料,就可以放在傳統硬碟上。 實務上,如何進行?在 PostgreSQL,資料表會以檔案方式落地在預設目錄 $PGDATA/base/,檔案以資料庫物件 OID 命名,資料庫的大小取決於磁碟分割的大小。 為了做到資料庫表空間管理,我們選擇將資料改放在 /var/pgdata/bookmazon/,在下 PostgreSQL TABLESPACE 指令之前,我們先建立 /var/pgdata/bookmazon/ 資料夾出來,讓資料夾擁有者賦予給 postgres 角色(作業系統的角色),權限設為 700。 以上操作必須做,否則在下 PostgreSQL TABLESPACE 指令,會因為作業系統的檔案權限不足而失敗。 1234sudo mkdir /var/pgdatasudo mkdir /var/pgdata/bookmazon/sudo chown -R postgres:postgres /var/pgdatasudo chmod -R 700 /var/pgdata 我們用了 PostgreSQL TABLESPACE 指令在 /var/pgdata/bookmazon/ 建立了表空間。 1CREATE TABLESPACE OWNER postgres bookmazontbspace LOCATION '/var/pgdata/bookmazon/'; 以建立 password_reset_tokens 資料表為例,在下 CREATE TABLE 指令後面會加上 TABLESPACE [表空間名稱],來表示建立的資料表的資料要落地在哪一個磁區。 123456789CREATE TABLE bookmazon.password_reset_tokens ( id SERIAL NOT NULL , user_email VARCHAR(255) NOT NULL , token VARCHAR(255) NOT NULL , token_status VARCHAR(1) , update_datetime TIMESTAMP , create_datetime TIMESTAMP , CONSTRAINT pk_password_reset_tokens PRIMARY KEY (id)) TABLESPACE bookmazontbspace; 參考來源EnterproseDB Quickstart — 快速入門筆記 PostgreSQL 繁體中文手冊 - 23.6. Tablespaces PostgreSQL 表空間使用詳解","link":"/2024/02/04/Flask-%E5%89%B5%E5%BB%BA%E4%B8%80%E5%80%8B%E4%BA%8C%E6%89%8B%E6%9B%B8%E7%B1%8D%E7%B7%9A%E4%B8%8A%E8%B3%BC%E7%89%A9%E5%B9%B3%E5%8F%B0-%E8%B3%87%E6%96%99%E5%BA%AB%E8%A8%AD%E8%A8%88/"},{"title":"Flask 創建一個二手書籍線上購物平台-系統設計","text":"技術可行性評估 評估組員的技能樹,大部分組員有點 Python,且有使用過 Flask 框架的經驗,故使用Flask 框架。 考量到我們只有一個月的時間開發(不含測試),且同時要修課,我們決定使用 ORM ,原因是因為組員不需要額外寫 SQL,可以加快整體開發進度。 目前業界最新技術採用前後端分離,我們也是考量到時間,且組內的組員對 JavaScript 沒有到很熟悉,我們改由後端來渲染前端模板。 三層式架構雖然會增加開發時間,採用它的原因在於好測試,最主要這門課程著重在「測試」。 系統部署架構硬體資訊 硬體 作業系統 安裝模組 Client PC Windows / MacOS/ Linux Web Server AWS EC2 (Amazon Linux) nginx-1.23.4 Application Server AWS EC2 (Amazon Linux) Gunicorn 21.2.0 Database Server AWS RDS PostgreSQL 15 使用者會透過 PC 瀏覽我們二手書籍線上購物平台,期間發出的 Http request 會打到 Nginx 反向代理伺服器,再導向應用程式伺服器,在應用程式伺服器裡面運行 gunicorn 的網頁伺服器,如若涉及操作資料庫,會再到 PostgreSQL 做操作。 實際營運環境要用什麼樣的硬體規格,會在測試階段環節確認出來。 系統軟體架構設計系統使用 Python 的 Flask 網頁伺服器框架作為 MVC 框架,並且採用了三層式架構。首先是Controller層,負責接收和處理 Http request,同時處理與 View 的交互。第二層是Service層,專門負責處理商業邏輯,確保系統邏輯的一致性和有效性。最後一層是 ORM 層,主要負責操作 Model 並與 PostgreSQL 資料庫伺服器進行溝通。 選擇三層式架構的原因在於可以實現元件之間的解耦,提高模組的內聚力,同時為未來的開發工作提供方便,特別是在進行單元測試和整合測試時。這樣的結構使得不同層次的功能分明,開發人員能夠更容易地定位和處理相關模組的問題,同時也提升了代碼的可維護性和可擴展性。 模組版本資訊123451. Python version: 3.92. pytest: 7.4.33. Flask: 3.0.04. SQLAlchemy: 2.0.225. PostgreSQL version: 15 檔案結構在 Presentation layer 會放 Controller 模塊、跟網頁模板,在 Business layer 會放 Service 模塊,負責接收從 Controller 傳過來的資料,做完商業邏輯運算後,會使用 DBManager 模塊與 PostgreSQL 溝通操作。 類別圖設計Python 實務上不像 Java 會以類別包住 API,更常是直接在 py 檔上寫 function,所以我們 python 模塊視為類別,在類別圖上我們有特別標示 Module 字樣。 而這張類別圖是參考使用者案例圖所繪製出來的,我們將四大使用者案例轉換成 Python 模塊,細部使用者案例則設計 API 接口,承如上面所介紹的系統架構,以三層式設計,分別是 Controller、Service、DAO(ORM) 層。 大致上,負責開發的組員以這個架構下去做開發,更細部業務邏輯我們有記錄在系統設計書,提供可以組員開發做參考,在此不贅述。","link":"/2024/02/05/Flask-%E5%89%B5%E5%BB%BA%E4%B8%80%E5%80%8B%E4%BA%8C%E6%89%8B%E6%9B%B8%E7%B1%8D%E7%B7%9A%E4%B8%8A%E8%B3%BC%E7%89%A9%E5%B9%B3%E5%8F%B0-%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88/"},{"title":"Flask 創建一個二手書籍線上購物平台-開發階段規劃","text":"本系列文章是紀錄研究所軟體工程課程修課實作過程 專案時程規劃考慮到組員在 11 月和 12 月有許多課程報告需要完成,我們將系統程式的開發時間拉長了 5 到 6 週。預計在 12 月搭建測試環境,並開放給負責系統測試的團隊成員進行測試。他們將在測試期間收集所有未通過的案例問題。在最後兩週,開發人員將解決這些問題。以下是我們的專案時程表。 什麼是 WBS?工作分解結構(Work Breakdown Structure,簡稱 WBS)是在專案管理中常用的工具,把專案的工項分解成更小、更容易管理的部分。WBS基本上是一種樹狀結構,展示了專案的主要交付物,以及為了完成這些交付物所需的各種任務和活動。 我們使用這套架構來規劃分派給團隊成員的工作。在這個階段,我們的交付內容是開發程式。我們根據使用者案例圖和活動圖,規劃出大約 24 個 API 的開發任務。基於業務流程的順序性,我們討論了任務開發的優先順序,並在下面的 Level 和 WBS 圖表中標示出來,最後將這些任務分派給負責開發和 code review 的團隊成員。 程式碼提交與整合此專案使用 GitLab 做程式碼儲存、提交、整合,採 Git flow 方式整合程式碼,開發階段步驟如下: 首先開發人員要從 dev 分支拉出 feature 分支 完成程式實作後,抓最新版 dev 分支,與自己的 feature 分支,以此避免提交 merge request 會有版本衝突發生 推 local feature 分支到 remote feature 分支 於 GitLab 發 merge request,將 feature 分支合併至 dev 分支 由 code reviewer 做程式碼審閱,確認無誤後同意合併 循返往復","link":"/2024/02/07/Flask-%E5%89%B5%E5%BB%BA%E4%B8%80%E5%80%8B%E4%BA%8C%E6%89%8B%E6%9B%B8%E7%B1%8D%E7%B7%9A%E4%B8%8A%E8%B3%BC%E7%89%A9%E5%B9%B3%E5%8F%B0-%E9%96%8B%E7%99%BC%E9%9A%8E%E6%AE%B5%E8%A6%8F%E5%8A%83/"}],"tags":[{"name":"Azure DevOps","slug":"Azure-DevOps","link":"/tags/Azure-DevOps/"},{"name":"Java","slug":"Java","link":"/tags/Java/"},{"name":"Number","slug":"Number","link":"/tags/Number/"},{"name":"Heroku","slug":"Heroku","link":"/tags/Heroku/"},{"name":"Flask","slug":"Flask","link":"/tags/Flask/"},{"name":"Python","slug":"Python","link":"/tags/Python/"},{"name":"Git","slug":"Git","link":"/tags/Git/"},{"name":"GitHub","slug":"GitHub","link":"/tags/GitHub/"},{"name":"Hexo","slug":"Hexo","link":"/tags/Hexo/"},{"name":"Blog","slug":"Blog","link":"/tags/Blog/"},{"name":"PL/SQL","slug":"PL-SQL","link":"/tags/PL-SQL/"},{"name":"Tree","slug":"Tree","link":"/tags/Tree/"},{"name":"Queue","slug":"Queue","link":"/tags/Queue/"},{"name":"Array","slug":"Array","link":"/tags/Array/"},{"name":"C++","slug":"C","link":"/tags/C/"},{"name":"LinkedList","slug":"LinkedList","link":"/tags/LinkedList/"},{"name":"Spring Boot","slug":"Spring-Boot","link":"/tags/Spring-Boot/"},{"name":"Image Processing","slug":"Image-Processing","link":"/tags/Image-Processing/"},{"name":"LikeCoin","slug":"LikeCoin","link":"/tags/LikeCoin/"},{"name":"BlockChain","slug":"BlockChain","link":"/tags/BlockChain/"},{"name":"vscode","slug":"vscode","link":"/tags/vscode/"},{"name":"python","slug":"python","link":"/tags/python/"},{"name":"演算法","slug":"演算法","link":"/tags/%E6%BC%94%E7%AE%97%E6%B3%95/"},{"name":"軟體工程","slug":"軟體工程","link":"/tags/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B/"},{"name":"PostgreSQL","slug":"PostgreSQL","link":"/tags/PostgreSQL/"},{"name":"Database","slug":"Database","link":"/tags/Database/"}],"categories":[{"name":"Azure DevOps","slug":"Azure-DevOps","link":"/categories/Azure-DevOps/"},{"name":"Java","slug":"Java","link":"/categories/Java/"},{"name":"Heroku","slug":"Heroku","link":"/categories/Heroku/"},{"name":"Hexo","slug":"Hexo","link":"/categories/Hexo/"},{"name":"LeetCode","slug":"LeetCode","link":"/categories/LeetCode/"},{"name":"Oracle SQL","slug":"Oracle-SQL","link":"/categories/Oracle-SQL/"},{"name":"Spring Boot","slug":"Spring-Boot","link":"/categories/Spring-Boot/"},{"name":"Image Processing","slug":"Image-Processing","link":"/categories/Image-Processing/"},{"name":"vscode","slug":"vscode","link":"/categories/vscode/"},{"name":"演算法","slug":"演算法","link":"/categories/%E6%BC%94%E7%AE%97%E6%B3%95/"},{"name":"軟體工程","slug":"軟體工程","link":"/categories/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B/"}]}