前言
最近剛好有要在SQL Server中Left Join N筆結果排序後只取得第一筆的需求。
發現Left Join資料結果會有問題,所以發現了Outer Apply作法能夠達到,因此筆記一下。
實作
需求:
撈出每位使用者還沒完成的最早一筆任務。
並以Priority做排序,如果遇到相同層級則以最早新建的資料為主。
Todos資料表:
Users資料表:
sql:
1 | SELECT |
執行結果:
總結
SQL Server有些特殊語法,有時真的得在實作時才會真的碰到。(坑)
最近剛好有要在SQL Server中Left Join N筆結果排序後只取得第一筆的需求。
發現Left Join資料結果會有問題,所以發現了Outer Apply作法能夠達到,因此筆記一下。
需求:
撈出每位使用者還沒完成的最早一筆任務。
並以Priority做排序,如果遇到相同層級則以最早新建的資料為主。
Todos資料表:
Users資料表:
sql:
1 | SELECT |
執行結果:
SQL Server有些特殊語法,有時真的得在實作時才會真的碰到。(坑)
因為最近有碰PostgreSQL的需要,因此來整理儲存型別。
此外,發現PostgreSQL的欄位型態具有Alias別名,所以有些型別找不到就要參考文件的Alias。
在以下只列常見的型別,更多請自己參閱參考資料。
| 名稱 | 描述 | 備註 |
| character varying(n), varchar(n) | 可變長度,但有限制 | 其中 n 是正整數。可以長度最多為 n 個字元(不是位元組)的字串。 |
| character(n), char(n) | 固定長度,空白填充 | 其中 n 是正整數。可以長度最多為 n 個字元(不是位元組)的字串。 |
| text | 可變且無限長度 |
| 名稱 | 描述 | 備註 |
| smallint | 2 bytes ,-32768 to +32767 | |
| integer | 4 bytes,-2147483648 to +2147483647 | |
| bigint | 8 bytes,-9223372036854775808 to +9223372036854775807 | |
| numeric | up to 131072 digits before the decimal point; up to 16383 digits after the decimal point。可調式精確度數值型別 | Alias decimal |
| real | 4Bytes,6 decimal digits precision 非精度浮點數型別 | |
| double precision | 8Bytes,15 decimal digits precision 非精度浮點數型別 |
| 名稱 | 描述 | 備註 |
| boolean | 1Bytes |
| 名稱 | 描述 | 備註 |
| timestamp [ (p) ] [ without time zone ] | 8 bytes, both date and time (no time zone), 4713 BC ~ 294276 AD | |
| timestamp [ (p) ] with time zone | 8 bytes, both date and time, with time zone, 4713 BC ~ 294276 AD |
在這篇中以一個熟悉SQL Server的角度,列出了平常可能會見到的PostgreSQL資料型態。
因為後者的資料型別彈性高(能夠自己建立物件存進、支援儲存JSON等等),所以等後面實際碰到再額外補充。
而完整的型別請自行參考網址的參考資料吧!
可能會有人有疑問怎沒SQL Server的NVARCHAR?
Generally you just use a UTF-8 encoded DB, and everything works with no hassle.
在開資料時在存字串常會有選擇上的問題,到底該存哪種型別?
而在此就稍微介紹NVARCHAR、VARCHAR的差異。
以SQL Server 2019資料庫為準。
n表示可變大小字串資料。 n 會定義字串大小 (單位為位元組配對),且可以是 1 到 4,000 之間的值。max 表示儲存大小上限是 2^30-1 個字元 (2 GB)。
使用 n 以位元組為單位來定義字串大小,該值可以介於 1 到 8,000,或使用 max 來表示資料行大小限制最多為 2^31-1 個位元組 (2 GB) 的儲存體。
| NVARCHAR | VARCHAR | |
| Unicode支援 | 有 | 無 |
| 每個字元儲存容量 | 英文字元2 Byte;非英文字元2 Byte | 英文字元1 Byte;非英文字元2 Byte |
| 儲存長度 | 不定長,依照資料內容 | 不定長,依照資料內容 |
| n的長度限制 | 1~4000(表示可以用寫4000個中、英文字元) | 1~8000(表示可以用寫8000個英文字元) |
| max | 2GB | 2GB |
| 結論 | 要儲存英文以外的用NVARCHAR,可以避免亂碼問題,但儲存空間相較VARCHAR會多一些。 | 只存英文VARCHAR沒問題,存其他可能會有亂碼問題。 |
使用NVARCHAR來儲存字串會比較好,因為可避免字串因為中英混雜亂碼。
https://docs.microsoft.com/zh-tw/sql/t-sql/data-types/char-and-varchar-transact-sql?view=sql-server-ver15
https://docs.microsoft.com/zh-tw/sql/t-sql/data-types/nchar-and-nvarchar-transact-sql?view=sql-server-ver15
https://ithelp.ithome.com.tw/articles/10213922
在這篇文章中介紹如何用C#產生PDF檔案。
使用套件為IText7。
IText7:https://itextpdf.com/en/products/itext-7/itext-7-core
程式碼範例:https://github.com/yuhsiang237/csharp-notes/tree/master/IText7Sample
先用Nuget安裝:
1 | Install-Package itext7 |
如下,以下為產生一個PDF檔案,並加入標題。
其中標題為中英文混雜。
Program.cs
1 | using iText.Kernel.Font; |
可以使用此套件來快速產生PDF,如果有中文需求可以像我上面那樣自己去補中文字型即可。
在這篇文中紀錄如何擷取中英混雜的子字串。
以下是我想到的作法,就是利用big5中文兩字元、英文一字元的特性去做。
20220217,使用Linq重構部分程式碼。
1 | using System; |
專案存庫:https://github.com/yuhsiang237/csharp-notes/tree/master/SubEnglishChineseString
撰寫中…
在這回中主要介紹C#的單元測試。
將會以AspNetCore中的xUnit、Moq套件進行範例測試。
至於為什麼要單元測試?
因為能夠快速的驗證結果、在撰寫時也只要執行片段程式碼。
對於有些程式碼容易有改A壞B的問題,能夠透過測試直接幫我們一次測完。
讓我們只需要針對錯誤案例進行程式碼錯誤區塊修改,加快整體專案除錯的時間。
完整測試專案連結:
https://github.com/yuhsiang237/csharp-unit-test
使用Moq套件的Mock建立假的物件,並且設定函式、屬性、返回的值,再使用xUnit提供的驗證方法進行測試。
首先建立我們要測試的介面與類別,一個是IFoo.cs、Bar.cs。
csharp-unit-test/UnitTestProject/Bar.cs
1 | using System; |
csharp-unit-test/UnitTestProject/IFoo.cs
1 | using System; |
再來建立測試,使用Moq、Xunit。
我們可以對函式返回值、假物件、屬性進行測試。
以下為測試範例:
/csharp-unit-test/UnitTestProjectTests/UnitTest.cs
1 | using System; |
再來開啟VisualStudio的測試視窗運行,可以得到以下結果:
表示測試成功。
撰寫測試能夠改善平常除錯的速度,特別當專案大起來時。
此外有些平台有測試的功能(如Azure DevOps Server的TFS),可以在CI自動整合時跑測試。
撰寫中…
如果在C#中遇到一些重複的String字串變數,是由變數的命名而來,就可以使用nameof直接提取出自串,避免再打一次造成打錯的問題。
nameof運算式會產生變數、類型或成員的名稱做為字串常數
1 | Console.WriteLine(nameof(System.Collections.Generic)); // output: Generic |
https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/nameof#code-try-0
在這篇中主要筆記LINQ 延遲執行的特性。
一般我們如果使用純SQL語言去與資料庫查詢通常一下指令就會被馬上執行,而LINQ卻不一樣,是直到某個觸發點他才會開始運作。
即寫Select時並不會馬上運作。
1.foreach(var item in items)
2.轉為ToList(), ToArray(), ToDictionary(),因為需巡覽才能轉型,所以會直接執行。
3.取得彙總結果的聚合函数 Count(), Max(), Sum(), First(), Last()。
延遲執行的基礎 - Iterator、yield
關鍵:使用 yield 關鍵字,C# 會自動將該 block 實作為 iterator 方式執行。
1 | using System; |
實作IEnumerable介面,並且撰寫方法GetEnumerator,yield會把每個區塊視為iterator(迭代器)。
而foreach中取得每個item在逆向工程可以見到其實是呼叫GetEnumerator取得一個IEnumerator(即iterator)。
有這觀念再拉回來看LINQ的方法,可以發現是IEnumerable。
Where:
1 | public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) |
Select:
1 | public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) |
想知道是不是延遲執行可以看返回值是否為以下3種物件:
1 | IEnumerable<TSource> |
1 | IEnumerable<IGrouping<TKey, TSource>> |
1 | IOrderedEnumerable<TSource> |
1 | using System; |
在這範例中使用泛型(Generic)的IEnumerable
1 | IEnumerable<T> |
因為Select、Where方法是IEnumerable,所以會在foreach觸發GetEnumerator時執行,進而達到延遲執行的效果。
1.可以再添加額外的表達式,如主要的查詢表達式後面可以添加子表達式過濾作為最終結果。
2.取得資料是最新的,因為當下才執行
可以得知C#的LINQ為什麼能夠延遲執行是因為採用IEnumerable介面,裡面會有yield區塊形成迭代器,所以直到foreach執行時才會開始觸發。
https://dotblogs.com.tw/hatelove/2013/09/10/csharp-linq-deferred-execution-introduction
https://dotblogs.com.tw/hatelove/2012/05/10/introducing-foreach-ienumerable-ienumerator-yield-iterator
https://dotblogs.com.tw/regionbbs/2012/01/12/linq_deferred_execution