iT邦幫忙

0

C# 相同字串做合併(非合併處存格)

最近查資料學會了C# Read Excel,可是找不到如何將相同的名稱做合併(非合併儲存格)
以下是我的Read Excel

我不知道怎麼讓他變成只要號碼重複,重複的行就會變成相加後的行。(如上面的圖)

謝謝各位的回覆,我可能沒有講得很清楚,因為我要想要處理好幾百行、幾百列的資料,然後並不是複製到另一邊,是想要直接重複貼到新的LIST
https://ithelp.ithome.com.tw/upload/images/20210427/20136928QMruN0yuew.png

我更新一下問題,可能有點麻煩,我本來想要用List來做處理,如果有分配好各個名字,或者一組。
首先有兩個Excel,要做合併,然後是指定欄位,以及計算。
https://ithelp.ithome.com.tw/upload/images/20210428/20136928uiHnEvSnTB.png
https://ithelp.ithome.com.tw/upload/images/20210428/20136928VIkF9YPpVs.png

我想要將兩張表合再一起。

以有藍色的圖表"標題 im" 那個為主。
兩張表共通點是黃色。
第一張表只要黃色、藍色、紅色
第二張表只要黃色、綠色、紅色
特別注意是紅色,是相同im(名稱)做相加,兩張表的紅色欄位相加

https://ithelp.ithome.com.tw/upload/images/20210428/20136928Hzicqmxhm2.png

我之前的想法是將兩張表的資料都丟到LIST,處裡完在做輸出。學長也是叫我這樣做。
發現三張表欄位名稱有整個不同。

1
japhenchen
iT邦大師 1 級 ‧ 2021-04-26 08:16:15
最佳解答

想參考我的解法,請先去VS裡的nuget安裝epplus免費的Excel讀寫工具(效能比office快很多,因為是純xml讀寫,上千行的xlsx檔讀寫小於10秒,執行時期可免安裝office)

using OfficeOpenXml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
        private void Form1_Load(object sender, EventArgs e)
        {
            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
            using (ExcelPackage ep = new ExcelPackage(new System.IO.FileInfo(@"s:\test\test.xlsx")))
            {
                // 準備收集所有資料用
                var dataOrigin = new List<KeyValuePair<string, int>>();
                // 範例當然是放在第一個工作表
                ExcelWorksheet sh = ep.Workbook.Worksheets[0];
                // 先讀取這個xlsx檔有多少列
                int totalRows = sh.Dimension.Rows; 
                for (int r = 2; r <= totalRows;r++)
                {
                    dataOrigin.Add(new KeyValuePair<string, int>(sh.Cells[r, 1].Value.ToString(),Convert.ToInt32(sh.Cells[r, 2].Value)));
                }
                // 清空已經存在的轉換後結果
                sh.Cells[2, 4, totalRows, 5].Clear(); 

                // 把收集到的資料以編號做成群組(GroupBy)並按編號排序(OrderBy)
                var dataOutput = dataOrigin.GroupBy(g => g.Key).OrderBy(s => s.Key);

                int R = 2; //因為用foreach所以自己加index
                foreach(var d in dataOutput)
                {
                    sh.Cells[R, 4].Value = d.Key;  // 輸出編號
                    sh.Cells[R, 5].Value = d.Sum(s => s.Value);  // 輸出這個編號的加總
                    R++;
                }
                ep.Save();
            }
        }

輸出前https://ithelp.ithome.com.tw/upload/images/20210426/20117954tj1bthkN5P.jpg

輸出結果https://ithelp.ithome.com.tw/upload/images/20210426/20117954uPmlykSr2p.jpg

看更多先前的回應...收起先前的回應...

LINQ/LAMBDA很重要,LINQ/LAMBDA很重要,LINQ/LAMBDA很重要

一定要學好,不然資料一多一定會搞慘你

python也有免安裝OFFICE的OpenXYL可用,一樣簡單

好的,看了您的方式讓我學到一課,不過我可能沒講清楚題目,我是要一次做很多資料,可是想了兩天,還是無法,很多可以用到的指令還不是很熟。

分組的方法就一個 .GroupBy,我就依編號(key)來分群,每筆key裡有所有的成員,如編號1有2筆100,所以sum出來的結果就是200,以此類推,大量的資料也一樣,不會走鐘

恩,我後來看了KeyValuePair,如果我欄位想要好幾個,這個如何使用呢?

就只用List解決問題,只是讀寫office的方式比較與眾不同

那就不用KeyValuePair,需要自建Class,我就命名為ManyKeysValue

    class ManyKeysAndValue
    {
        public List<object> keys { get; set; }
        public object value { get; set; }
        public ManyKeysAndValue(object[] _keys, object v)
        {
            keys = new List<object>();
            keys.AddRange(_keys);
            value = v;
        }
    }

主程式要修改一下符合"完全可變長度陣列"當做KEY的的要求

        private void Form1_Load(object sender, EventArgs e)
        {
            goExcel();
        }

        private void goExcel()
        {
            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
            using (ExcelPackage ep = new ExcelPackage(new System.IO.FileInfo(@"s:\test\test.xlsx")))
            {
                // 準備收集所有資料用
                var dataOrigin = new List<ManyKeysAndValue>();
                // 範例當然是放在第一個工作表
                ExcelWorksheet sh = ep.Workbook.Worksheets[0];
                // 先讀取這個xlsx檔有多少列
                int totalRows = sh.Dimension.Rows;
                for (int r = 2; r <= totalRows; r++)
                {
                    dataOrigin.Add(
                        new ManyKeysAndValue(
                            new object[] {
                                sh.Cells[r,1].Value,
                                sh.Cells[r,2].Value,
                                sh.Cells[r,3].Value,
                                sh.Cells[r,4].Value,
                                sh.Cells[r,5].Value,
                                // 從第A列到第E列都是你的KEY組
                            },
                            sh.Cells[r, 6].Value)  // 你要加總的欄位
                        );
                }
                // 清空已經存在的轉換後結果
                sh.Cells[2, 12, totalRows, sh.Dimension.Columns].Clear();

                // 把收集到的資料以編號做成群組(GroupBy)並按編號排序(OrderBy)
                var dataOutput =
                    dataOrigin
                    .GroupBy(g => (string)g.keys[2]) // 0,1,2....2→居住地
                    .OrderBy(s => s.Key);

                int R = 2; //因為用foreach所以自己加index
                foreach (var d in dataOutput)
                {
                    sh.Cells[R, 12].Value = d.Key;  // 輸出編號
                    sh.Cells[R, 13].Value = d.Sum(s => Convert.ToInt32(s.value));  // 輸出這個編號的加總
                    R++;
                }
                ep.Save();
            }
        }

照你的想法改成多格資料的EXCEL,以下假設以行政區加總每個區的銷售營業額(單位為萬元),加總前...
https://ithelp.ithome.com.tw/upload/images/20210428/20117954HbceTqFFzU.jpg

加總後...https://ithelp.ithome.com.tw/upload/images/20210428/20117954qSSLSnBGNn.jpg

謝謝,又學到新的指令,我剛剛有更新的問題,請問如果是您會如何處理呢?

我自己的想法是一張一張做處理,比較笨的方法,先將想要的欄位,在做兩個(因為有兩張表)然後再做處理,在合併,學長說丟到LIST直接處理,直接產出第三張個EXCEL,可是當時卡在丟到LIST如何讓他做好一行一行分組,又要如何指定

建議另一個問題另開一篇,不要用更新的方法,這樣別人看板會亂

恩,好的。我了解了

0
小魚
iT邦大師 1 級 ‧ 2021-04-26 07:34:39

其實你要的是加總吧,
不懂你的問題在哪,
既然你都用List來存資料了,
就做完計算之後,
再一列一列把資料寫出來就好了.

我存到List是一個一個存進去,不知道該如何一行一行整個存進去,所以才不知道該怎麼做。

3
暐翰
iT邦大師 1 級 ‧ 2021-04-26 09:12:03

更新回答

我照著你的輸入,發現好像格子為NULL,會無法處理

我照你更新的 excel 做處理,沒問題

void Main()
{
	var path = @"C:\Users\Wei\Downloads\TestIssue232.xlsx";
	var rows = MiniExcel.Query(path,useHeaderRow:true);
	var newInput = (from s in rows
						group s by s.Number into g
						select new {
							Number=g.Key,
							Apr=g.Sum(_=>(decimal?)_.Apr),
							May=g.Sum(_=>(decimal?)_.May),
							Jun=g.Sum(_=>(decimal?)_.Jun),
						}
	);
	Console.WriteLine(newInput);
	
	MiniExcel.SaveAs("output.xlsx",newInput);
}

image


原回答

這樣嗎?
image

可以嘗試使用我寫的一個小工具 MiniExcel

安裝 nuget 包

Install-Package MiniExcel -Version 0.13.3

代碼

void Main()
{
	var path = @"C:\Users\Wei\Downloads\Book5.xlsx";
	var rows = MiniExcel.Query(path,true);

	var outputPath = "output2.xlsx";
	MiniExcel.SaveAs(outputPath, rows.GroupBy(g => g.號碼).Select(s => new {號碼=s.Key,金額=s.Sum(_=>(int)_.金額)}));
}

但牽扯到修改樣式建議還是使用 epplus

看更多先前的回應...收起先前的回應...
Homura iT邦高手 1 級 ‧ 2021-04-26 11:56:41 檢舉

看起來蠻好用的

小魚 iT邦大師 1 級 ‧ 2021-04-26 12:36:01 檢舉

這是甚麼套件?

暐翰 iT邦大師 1 級 ‧ 2021-04-26 13:00:18 檢舉

To Homura /images/emoticon/emoticon12.gif

To 小魚 這個是處理低規格伺服器,處理excel讀/寫/模板填充的套件

謝謝你的回答,您的工具我也有用過,只是不知道語法,以及做很多行列如何寫(範例只有兩列),我照著你的輸入,發現好像格子為NULL,會無法處理

暐翰 iT邦大師 1 級 ‧ 2021-04-28 08:59:54 檢舉

菜鳥獵人
我這邊測試沒有問題
過程我更新在內文了

好的,謝謝你,我其實是有一個比較大的問題,已經更新了,因為本來想說先慢慢處理,後來學長跟我說不用那麼麻煩,全部丟到List做處理(學長不是用C#)是其他語言。

0
海綿寶寶
iT邦大神 1 級 ‧ 2021-04-26 17:05:36

參考這篇拿去改
原始程式碼在此

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var transactions = new List<Transaction>{};

		transactions.Add(new Transaction { Category = "1", Amount = 100 });
		transactions.Add(new Transaction { Category = "2", Amount = 200 });
		transactions.Add(new Transaction { Category = "2", Amount = 100 });
		transactions.Add(new Transaction { Category = "3", Amount = 300 });
		transactions.Add(new Transaction { Category = "4", Amount = 100 });
		transactions.Add(new Transaction { Category = "5", Amount = 400 });
		transactions.Add(new Transaction { Category = "6", Amount = 100 });
		transactions.Add(new Transaction { Category = "5", Amount = 400 });
		transactions.Add(new Transaction { Category = "1", Amount = 100 });
		transactions.Add(new Transaction { Category = "2", Amount = 200 });
		
        var summaryApproach1 = transactions.GroupBy(t => t.Category)
                           .Select(t => new
                           {
                               Category = t.Key,
                               Amount = t.Sum(ta => ta.Amount),
                           }).ToList();

		Console.WriteLine("-- Summary: Approach 1 --");
        summaryApproach1.ForEach(
            row => Console.WriteLine($"Category: {row.Category}, Amount: {row.Amount}"));



        var summaryApproach2 = transactions.GroupBy(t => t.Category, (key, t) =>
        {
            var transactionArray = t as Transaction[] ?? t.ToArray();
            return new
            {
                Category = key,
                Amount = transactionArray.Sum(ta => ta.Amount),
            };
        }).ToList();

        Console.WriteLine("-- Summary: Approach 2 --");
        summaryApproach2.ForEach(
            row => Console.WriteLine($"Category: {row.Category}, Amount: {row.Amount}"));
    }
}

public class Transaction
{
    public string Category { get; set; }
    public decimal Amount { get; set; }
}
看更多先前的回應...收起先前的回應...

謝謝你的回答,因為我也看過這種類似的文章,只是不知道要怎麼讓他分好,而不是自己手動輸入分好組。

我改一個版本給你參考

我想問一下。
transactions.Add(new Transaction { Category = "1", Amount = 100 });
這些一定要手動打嗎?因為資料可能不是這些。
可能上白行。

這段你要自己改
把 "1" 和 100 改成你從 Excel 讀到的資料
大概像這樣

for (int i = 1; i <= rowCount;i++) {
	transactions.Add(new Transaction { Category = xlRange.Cells[i, 1], Amount = xlRange.Cells[i, 2] });
}

我等等試試看,謝謝你的回答

你好,剛剛成功了,原來是這樣加,我之前是連用雙迴圈,做你給的範例,難怪我都失敗了。因為這樣也是一個一個存進去

不過,我有個新問題,剛剛測試為 NULL 一樣會失敗,這個我該如何解決呢?

我要發表回答

立即登入回答