星期二, 10月 20, 2026

[GAS] 拆活動時段

報名活動使用 Google Forms 時,發現時段是以下列方式呈現,直覺是資料這樣儲存,是要怎麼統計,練習一下在 Google Sheet 內把時段拆開並進行統計

Google Form 上的選單選項
各時段示意圖
設計流程圖
拆解活動時段 gs Code

重點紀錄
  • 該 GAS 是綁定在 Google Sheets 上,而不是 Google Forms
  • gs Code 抓取 Google Sheet 部分,為了方便手動執行測試,會判斷是從 Google Form 還是 GAS 內直接執行的
  • Range.insertCheckboxes() 是指把指定範圍內的 cell 變成 checkbox 並設定勾選值
/**
 * Google Forms 提交後,自動拆解 A、B、C 時段。
 *
 * C 欄:原始報名選項
 * D 欄:A 時段
 * E 欄:B 時段
 * F 欄:C 時段
 */
function onFormSubmit(e) {

  // 從試算表取得工作表
  const sheet = e && e.range
    ? e.range.getSheet()
    : SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form_Responses");

  if (!sheet) {
    throw new Error("找不到 Form_Responses 工作表。");
  }

  // 表單提交:使用實際提交列
  // 手動執行:使用最後一列
  const row = e && e.range
    ? e.range.getRow()
    : sheet.getLastRow();

  if (row <= 1) {
    throw new Error("目前沒有可處理的報名資料。");
  }

  // 讀取 C 欄報名選項
  const selectedValue = sheet.getRange(row, 3).getDisplayValue();

  // 解析報名選項
  // 例如 A + C (08:00 - 13:00) → ["A", "C"]
  const selectedGroups = selectedValue
    .split("(")[0]
    .split("+")
    .map(group => group.trim());

  // 基本時段
  const groups = ["A", "B", "C"];

  // 產生 CheckBox 勾選狀態
  const checkboxValues = groups.map(group =>
    selectedGroups.includes(group)
  );

  // D、E、F 欄
  const range = sheet.getRange(row, 4, 1, 3);

  // 建立 CheckBox
  range.insertCheckboxes();

  // 寫入勾選狀態
  range.setValues([checkboxValues]);
}
設定觸發條件

活動類型:提取表單時,在 以事件為準的觸發條件 內說明
An installable form submit trigger runs when a user responds to a form,There are two versions of the form-submit trigger
  • one for Google Forms itself
  • one for Sheets if the form submits to a spreadsheet
該筆記是屬於後者,意思是當 Google Sheets 接收到從 Google Forms 送來資料時會觸發,而該 Blog 筆記 - [GAS] 自行定義回覆 Email,則是屬於前者

因為 function 名稱都慣性取名為 onFormSubmit,所以誤會兩件事情
  • 直覺 GAS 是綁定在 Goole Forms 上,但就該筆記並不是
  • 以為跟簡單觸發條件 onOpen、doGet 一樣會自動觸發,不需要手動建立
統計公式設定

時段拆解開後,在 Google Sheets 內另外開一個 Sheet 來當成統計頁面,透過設定公式 CountIf() 來進行統計,小活動用最簡單方式來達成就好
=COUNTIF(Data!D2:D,TRUE)
=COUNTIF(Data!E2:E,TRUE)
=COUNTIF(Data!F2:F,TRUE)


實際執行

星期六, 10月 17, 2026

[SQL] 資料表值參數 - DataTable 參數欄位順序

使用 [SQL] 資料表值參數 (Table Value Parameters) 時拋出字串要轉型為整數 SQL Exception,這才發現原來 DataTable 參數欄位順序要搭配 TVP 參數順序,兩者必須手動維持順序一致,沒有辦法自動對應
使用者定義資料表類型 (User-defined Table Type)
CREATE TYPE [dbo].[UserDefinedTableType] AS TABLE(
	Col1 char(4) NOT NULL,
	Col2 char(16) NOT NULL,
	Col3 int NOT NULL
)
該 case 是使用 FastMember ObjectReader 把 List<T> 轉成 DataTable,所以有兩種處理方式
  1. 事先定義欄位順序:在 class 上指定 FastMember.OrdinalAttribute 定義轉換後欄位順序,詳見 [C#] FastMember-ObjectReader
  2. 事後調整欄位順序:產生 DataTable 後,透過 DataColumn.SetOrdinal() 調整 DataTable 欄位順序
// ToDataTable() 則是封裝好的 FastMember ObjectReader 轉換功能
DataTable dt = List<T>.ToDataTable();
dt.Columns[Col1].SetOrdinal(0);
dt.Columns[Col2].SetOrdinal(1);
dt.Columns[Col3].SetOrdinal(2);

星期五, 10月 16, 2026

[SQL] SQL Profile 側錄 Exception

C# AP 端開發時發生 SQL Exception,在本地端開啟 Profile 側錄語法時沒有看見 SQL Exception 資訊,發現使用標準範本的話要特別去開啟 Errors and Warmings 內相關事件才會記錄

C# AP 端拋出 SQL Excpetion
該 case 在 Profile 內開啟 Exception 和 User Error Message 就行
Profile 側錄結果

星期二, 10月 13, 2026

[LINQ] ToLookup

ToLookup 用來將集合轉成一個「一對多」的字典結構(ILookup<TKey, TElement>)

主要特點
  • 一對多對應(One-to-Many):一個鍵(Key)可以對應到多個值(Collection of values),與 ToDictionary(一對一)不同
  • 立即執行(Eager Execution):呼叫 ToLookup() 時會立即計算並產生結果,這點跟 GroupBy()延遲執行 (Deffered Execution) 不同
  • 唯讀結構:產生的 ILookup 是唯讀的,無法修改
  • 安全存取:如果用 Key 去取值,就算該 Key 不存在,它也會回傳一個空的集合而不是丟出例外錯誤(KeyNotFoundException)
簡易範例
namespace ToLookupSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var books = new List<Book>
            {
                new Book { Title = "C# 實戰", Category = "CSharp", PublishYear = 2025 },
                new Book { Title = "設計模式", Category = "CSharp", PublishYear = 2023 },
                new Book { Title = "MS SQL TSQL 學習手冊", Category = "SQL", PublishYear = 2020 },
                new Book { Title = "MS SQL 效能調校", Category = "SQL", PublishYear = 2025 },
                new Book { Title = "MS SQL 索引深入淺出", Category = "SQL", PublishYear = 2025 },
                new Book { Title = "世界歷史", Category = "History", PublishYear = 1981 }
            };

            // 1. 建立 ILookup:以 Category 為 Key
            ILookup<string, Book> bookLookup = books.ToLookup(b => b.Category);

            // 2. 透過索引子 [Key] 查詢存在的書籍分類
            IEnumerable<Book> sql = bookLookup["SQL"];
            Console.WriteLine($"SQL 書籍:{sql.Count()}");
            foreach (Book book in sql)
                Console.WriteLine($"     ----- {book.Title}");

            // 3. 查詢不存在的書籍分類:回傳空集合,不會拋出 Exception
            IEnumerable<Book> ai = bookLookup["AI"];
            Console.WriteLine($"是否有 AI 分類書籍:{ai.Any()}");

            // 4. 檢查某個 Key 是否存在
            bool hasHistory = bookLookup.Contains("History");
            Console.WriteLine($"是否有 History 分類書籍:{hasHistory}");

            // 5. 忽略大小寫
            ILookup<string, string> books4OrdinalIgnoreCase = books.ToLookup(
                keySelector: b => b.Category,
                elementSelector: b => b.Title,
                comparer: StringComparer.OrdinalIgnoreCase // 忽略大小寫
            );

            IEnumerable<string> csharp = books4OrdinalIgnoreCase["csharp"];
            Console.WriteLine($"C# 書籍 - 不區分大小寫");
            foreach (string title in csharp)
                Console.WriteLine($"     ----- {title}");

            // 6. Key 使用 ValueTuple (Category, PublishYear) 作為複合 Key
            var booksValueTuple = books.ToLookup(b => (
                Category: b.Category,
                Year: b.PublishYear
            ));

            var targetKey = ("SQL", 2025);
            var matchedBooks = booksValueTuple[targetKey].ToList();

            Console.WriteLine($"2005 出版 SQL 書籍 - 複合 Key");
            foreach (Book book in matchedBooks)
                Console.WriteLine($"     ----- {book.Title}");
        }
    }

    public class Book
    {
        public string Title { get; set; }
        public string Category { get; set; }
        public int PublishYear { get; set; }
    }
}

星期一, 10月 05, 2026

[SSMS] 匯入、匯出資料

在 SSMS V22 上發現沒有辦法使用內建匯出資料功能,查發現原來 SSMS V22 的商業智慧功能並不是預設安裝的,安裝起來就可以使用

SSMS V22 上匯入、匯出資料反灰無法使用
在 Visual Studio Installer 內把 [商業智慧] 安裝起來,內含 SQL Server Integration Services (SSIS),就可以正常使用