如何在Excel表格上运行SQL查询?

103
我正在尝试从另一个表格中创建一个子表格,其中包含所有按照姓氏字段A-Z排序且具有不为空的电话号码字段。我可以使用SQL很容易地完成这个任务,但是我不知道如何在Excel中运行SQL查询。我想将数据导入postgresql并在那里进行查询,但这似乎有点过度了。
为了实现我的目标,SQL查询SELECT lastname, firstname, phonenumber WHERE phonenumber IS NOT NULL ORDER BY lastname会做到这一点。这似乎太简单了,以至于Excel不能本地支持它。我怎么能在Excel中运行这样的SQL查询?

你想在SQL本身中完成这个操作,还是在应用程序中完成? - John Bingham
4
我已经彻底调查了这个问题,并在 https://exceldevelopmentplatform.blogspot.com/2018/10/vba-microsoftaceoledb120-details.html 上撰写了发现的结果。 - S Meaden
@S Meaden:优秀的、直截了当的代码示例,非常有用,可以帮助我们理解复杂的一般性理论。非常感谢! - pstraton
12个回答

79

有很多种方法可以完成这个任务,其他人已经提出了一些建议。沿着“通过SQL获取Excel数据”的轨迹,以下是一些要点。

  1. Excel具有"数据连接向导",允许您从另一个数据源或甚至在同一个Excel文件中导入或链接。

  2. 作为Microsoft Office(和操作系统)的一部分,存在两个感兴趣的提供程序:旧的"Microsoft.Jet.OLEDB"和最新的"Microsoft.ACE.OLEDB"。在设置连接时(例如使用数据连接向导),请查找它们。

  3. 连接到Excel工作簿后,工作表或范围相当于表或视图。工作表的表名是将工作表名称附加了美元符号("$")并用方括号("["和"]")括起来;对于范围,则只是范围的名称。要将未命名的单元格范围指定为记录源,请将标准Excel行/列表示法附加到方括号中的工作表名称末尾。

  4. 本机SQL将是Microsoft Access的SQL。(过去称为JET SQL;但是Access SQL已经发展,我认为JET是不推荐使用的旧技术。)

  5. 例如,读取工作表:SELECT * FROM [Sheet1$]

  6. 例如,读取范围:SELECT * FROM MyRange

  7. 例如,读取未命名的单元格范围:SELECT * FROM [Sheet1$A1:B10]

  8. 有很多很多很多书籍和网站可帮助您解决具体问题。

进一步说明

默认情况下,假定您的Excel数据源的第一行包含列标题,可以用作字段名称。如果不是这种情况,您必须关闭此设置,否则您的第一行数据“消失”以用作字段名称。这是通过向连接字符串的扩展属性添加可选的HDR = setting来完成的。默认值为HDR = Yes,不需要指定。如果没有列标题,则需要指定HDR = No;提供程序将命名您的字段F1、F2等。
关于指定工作表的警告:提供程序假定您的数据表从指定工作表的最上方、最左边、非空单元格开始。换句话说,在行3、列C中开始数据表没有问题。但是,例如,在单元格A1中输入工作表标题并位于数据左上方是不被允许的。
关于指定范围的警告:当您将工作表指定为记录源时,提供程序在有空间的情况下在现有记录下方添加新记录。当您指定一个范围(具名或未命名)时,Jet也会在范围内现有记录下方添加新记录,只要有空间。但是,如果重新查询原始范围,则生成的记录集不包括范围外新添加的记录。
CREATE TABLE的数据类型(值得尝试):短整型、长整型、单精度型、双精度型、货币型、日期时间型、位型、字节型、全局唯一标识符型、大二进制型、长二进制型、可变二进制型、长文本型、可变字符型、十进制型。
连接到“旧技术”Excel(扩展名为xls的文件):Provider = Microsoft.Jet.OLEDB.4.0; Data Source = C:\MyFolder\MyWorkbook.xls; Extended Properties = Excel 8.0;。对于Microsoft Excel 5.0和7.0(95)工作簿,请使用Excel 5.0源数据库类型,对于Microsoft Excel 8.0(97),9.0(2000)和10.0(2002)工作簿,请使用Excel 8.0源数据库类型。

连接到“最新”的 Excel 文件(扩展名为 xlsx):Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Excel2007file.xlsx;Extended Properties="Excel 12.0 Xml;HDR=YES;"

将数据视为文本:IMEX 设置将所有数据视为文本。 Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Excel2007file.xlsx;Extended Properties="Excel 12.0 Xml;HDR=YES;IMEX=1";

(更多详细信息请参见http://www.connectionstrings.com/excel)

更多信息,请参见http://msdn.microsoft.com/en-US/library/ms141683(v=sql.90).aspxhttp://support.microsoft.com/kb/316934

通过 VBA 详细说明了使用 ADODB 连接到 Excel,详情请查看http://support.microsoft.com/kb/257819

Microsoft JET 4 的详细信息请参见http://support.microsoft.com/kb/275561


6
数据连接需要一个文件名,这是查询当前文件时的障碍。此外,查询不能像公式一样使用,您只能使用固定的查询来填充范围;而且它不会自动更新。因此,它无法用于查询当前文件,也无法作为公式的替代品。所以,它对于查询当前文件和作为公式的即插即用替换是无用的。 - ivan_pozdeev
2
@ivan_pozdeev 我刚刚在使用Excel 2010时确认了可以查询当前文件;我不知道后来的Excel/Office版本是否不再支持此功能。我同意通过数据连接向导创建自引用表格是笨拙的 - 主要是因为连接是使用工作簿的完整路径进行的,因此重命名/复制/移动工作簿将导致断开连接或混淆结果。但是,对于不需要使用VBA的工作簿,自引用查询非常可管理。 - rskar
@ivan_pozdeev 我也同意Excel并不是针对自引用表进行优化的;假设总是外部数据源。可以通过连接属性的使用选项卡实现自动刷新(例如每隔几分钟重新加载),而且通过VBA,可以利用重新计算事件。尽管如此,我认为我从未过分宣传它作为公式的即插即用替代品。 - rskar
2
有许多不错的方法可以完成这个任务,但如果这些“不错”的方式在实际使用中存在重大缺陷(这些缺陷正是阻碍它们广泛应用的原因),那么就不能掩盖这些问题。 - ivan_pozdeev
Excel 365 中的数据连接向导已被删除。 :( - Hans Schulze

9

我建议你试试QueryStorm - 它是Excel的一个插件,使在Excel中使用SQL非常方便。

Gif showing QueryStorm in action in Excel

在SQL脚本中,Excel表格可以像普通的数据库表格一样显示。
支持所有四种SQL数据操作:select/update/insert/delete
执行查询的引擎是SQLite,因此您可以使用连接、公共表达式、窗口函数等功能。您还可以享受代码完成、自动格式化、符号工具提示等高级功能。
它有一个完全免费的社区版,供个人和小公司使用。如果您所在的公司有超过5名员工或年收入超过1百万美元,您将需要付费许可证,但可以使用免费试用密钥进行评估。 这篇博客文章以更详细的方式描述了插件的SQL功能。
免责声明:我是作者。

2
不幸的是,尽管它是一个很棒的工具,但现在似乎只能付费使用,除了提供30天的试用期。 - Marc
1
遗憾的是,我太早点点赞了它,现在无法取消点赞,虽然有一个免费版本但是没有付费密钥就无法运行查询...正在卸载。 - Flying Turtle
1
@FlyingTurtle,你在使用免费的Community许可证时是否遇到了许可提示?如果是这样,那就是一个bug。如果是,请告诉我,我会调查一下。 - anakic
1
多么惊人的工具!非常感谢!目前完全免费的社区版本对我的使用情况来说已经完全足够了。 :) - Tobias Hermann
1
这是一个了不起的工具!非常感谢!目前完全免费的社区版本对我的使用场景来说完全够用。 :) - undefined
谢谢,这总是很好的阅读:) 你激励我更新一下这个答案,因为人们仍然在寻找它。 - anakic

7

tl;dr; Excel原生支持这些功能 - 使用筛选器和/或表格

(http://office.microsoft.com/en-gb/excel-help/filter-data-in-an-excel-table-HA102840028.aspx)

您可以通过OleDb连接以编程方式打开Excel,并在工作表中的表上执行SQL。但是,您可以通过过滤器而不是公式完成您要求的所有操作。
1. 在所查看的数据中单击任何位置 2. 转到选项卡中的数据 3. 选择“筛选器”,它大约位于中间并且看起来像漏斗 - 现在,您的表的第一行的每个单元格右侧都会有箭头 4. 单击电话号码上的箭头,取消选择空白(最后一个选项) 5. 单击姓氏上的箭头,选择A-Z排序(顶部选项)
尝试一下..请注意以下几点:
  1. 你可以选择筛选出的行并将它们粘贴到其他地方
  2. 在左侧状态栏中,您将看到满足筛选条件的行数与总行数相比(例如,在 313 条记录中找到 308 条)
  3. 您可以在 Excel 2010 及以上版本中按颜色筛选
  4. 有时我会创建计算列,以给出数据的状态或经过清理的版本,然后您也可以按这些列进行筛选或排序(例如,像其他答案中的公式一样)

除非您要经常执行此操作或者您想自动导入数据到其他地方,否则请使用筛选器..但为了完整起见:

C# 选项:

 OleDbConnection ExcelFile = new OleDbConnection( String.Format( "Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=\"Excel 12.0;HDR=YES\"", filename));
 ExcelFile.Open();

一个很好的开始是查看模式,因为那里可能有比你想象的更多内容:
List<String> excelSheets = new List<string>();

// Add the sheet name to the string array.
foreach (DataRow row in dt.Rows) {
    string temp = row["TABLE_NAME"].ToString();
    if (temp[temp.Length - 1] == '$') {
         excelSheets.Add(row["TABLE_NAME"].ToString());
    }
}

然后当您想要查询表格时:

 OleDbDataAdapter da = new OleDbDataAdapter("select * from [" + sheet + "]", ExcelFile);
 dt = new DataTable();
  da.Fill(dt);

注意 - 在Excel中使用表格!:

Excel具有“表格”功能,使数据更像一个表格.. 这给您带来了一些巨大的好处,但不会让您执行每种类型的查询。

http://office.microsoft.com/en-gb/excel-help/overview-of-excel-tables-HA010048546.aspx

对于Excel中的表格数据,这是我的默认方式。我首先点击数据,然后从功能区的主页部分选择“表格格式”。这会为您提供默认的筛选和排序,并允许您通过名称访问表格和字段(例如,table[fieldname])。这还允许对列进行聚合函数,例如最大值和平均值。

如果你想要减少列数,我个人会将筛选后的行复制到一个新的工作表中,然后删除不需要的列。虽然你也可以将它们隐藏起来,但这很少有意义。 - user359135
1
使用 System.Data.OleDb; 使用 System.Data; - user359135
1
我每天都使用筛选器,并且每周会用C#访问电子表格。当使用C#时,通常是为了将数据导入数据库以便进行更深入的操作。对于实际的查询,一旦数据在SQL Server中,就不值得在中间的SQL到Excel级别上进行操作,而是使用筛选器或SQL。 - user359135

6
您可以按照以下本地方法实现此操作:
  1. 选择表并使用Excel对其按姓氏排序
  2. 创建一个2行1列的高级过滤器条件,例如在E1和E2中,其中E1为空,E2包含公式=C6="",C6是电话号码列的第一个数据单元格。
  3. 选择表并使用高级筛选器复制到范围中,使用E1:E2中的条件范围,并指定要将输出复制到的位置

如果您想以编程方式执行此操作,建议使用宏记录器记录上述步骤并查看代码。


10
确认需求为翻译SQL相关内容。 - S Meaden

5

这里被接受的答案是旧技术,不应再尝试使用。

当这个问题被写出时,Power Query还不是一个众所周知的选项,除非你使用最新版本的Office并将其作为单独的Add-in安装,否则无法使用。

现在,Power Query已经包含在Excel中,并默认用于获取数据。这是正确的方法。它简单、快速且有效。

以下是使用Power Query回答此问题的方法。如果需要帮助复制此操作,请搜索“Power Query入门”。一旦您开始使用Power Query,您会发现这是非常基本和易于操作的高级编辑器:

let
    Source = Excel.CurrentWorkbook(){[Name="Names"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"lastname", type text}, {"firstname", type text}, {"phonenumber", type text}}),
    #"Filtered Rows" = Table.SelectRows(#"Changed Type", each ([phonenumber] <> null)),
    #"Removed Other Columns" = Table.SelectColumns(#"Filtered Rows",{"lastname", "firstname", "phonenumber"}),
    #"Sorted Rows" = Table.Sort(#"Removed Other Columns",{{"lastname", Order.Ascending}})
in
    #"Sorted Rows"

Power Query非常完美。谢谢。 - jjcf89
这个教程可以完成所有操作,而无需使用高级编辑器。https://support.microsoft.com/zh-cn/office/%E5%90%88%E5%B9%B6%E6%9F%A5%E8%AF%A2%E5%92%8C%E5%8A%A0%E5%85%A5%E8%A1%A8-cbd17828-7a50-4dc6-9aac-20af4ef6d8a6 - jjcf89
2
我不想学习一门复杂的新语言,即使它更好。我已经了解 SQL。 - ttulinsky
2
我不想学习一门复杂的新语言,即使它更好。我已经了解SQL了。 - undefined
@ttulinsky 首先将Excel数据加载到真实的数据库中。如果您尝试使用SQL对Excel进行操作,这可能会导致奇怪的兼容性问题。 - TheRizza
@ttulinsky 首先将Excel数据加载到一个真实的数据库中。如果你试图对Excel使用SQL,那么你就会遇到奇怪的兼容性问题。 - undefined

4

4
看起来它在使用SQL选择数据导入到Excel,而不是对当前电子表格运行查询? - Rup
你只需要在Excel中的名称管理器中为每个表创建一个名称,或者只需选择该表并在显示单元格地址的框中输入名称。然后您就可以使用它来针对工作表进行查询。在查询中,您已经获得了工作表的完整地址,因此如果您将电子表格移动到磁盘的其他位置,则查询将无法正常工作。 - Petrik

3

如果您只需要执行一次,请按照Charles的说明进行操作,但是如果您想要使筛选器具有动态性,则也可以使用Excel公式和辅助列来完成此操作。

假设您的数据位于名为DataSheet的工作表中,并从以下列的第2行开始:

  • A:姓氏
  • B:名字
  • C:电话号码

在该工作表上,您需要两个辅助列。

  • D2:=IF(A2="",1,0),这是筛选器列,对应您的where条件
  • E2:=IF(D2<>1,"",SUMIFS(D$2:D$1048576,A$2:A$1048576,"<"&A2)+SUMIFS(D$2:D2,A$2:A2,A2)),这对应于order by

将这些公式复制到数据所达到的范围。

在应显示结果的工作表上,创建以下列。

  • A:从第2行开始的数字序列,这限制了您可以获得的最大行数(类似于sequel中的limit)
  • B2:=MATCH(A2,DataSheet!$E$2:$E$1048576,0),这是相应数据的行
  • C2:=IFERROR(INDEX(DataSheet!A$2:A$1048576,$B2),""),这是实际数据或为空(如果没有数据存在)

将B2和C2中的公式复制下来,并将列C复制到列D和E。


0
如果您已经使用Expat库编译了GDAL/OGR,则可以使用XLSX驱动程序读取.xlsx文件,并从命令提示符中运行SQL表达式。例如,在与电子表格相同的目录中从osgeo4w shell中使用ogrinfo实用程序:
ogrinfo -dialect sqlite -sql "SELECT name, count(*) FROM sheet1 GROUP BY name" Book1.xlsx

将在sheet1上运行一个SQLite查询,并以不寻常的形式输出查询结果:

INFO: Open of `Book1.xlsx'
      using driver `XLSX' successful.

Layer name: SELECT
Geometry: None
Feature Count: 36
Layer SRS WKT:
(unknown)
name: String (0.0)
count(*): Integer (0.0)
OGRFeature(SELECT):0
  name (String) = Red
  count(*) (Integer) = 849

OGRFeature(SELECT):1
  name (String) = Green
  count(*) (Integer) = 265
...

或者使用ogr2ogr运行相同查询,生成一个简单的CSV文件:

$ ogr2ogr -f CSV out.csv -dialect sqlite \
          -sql "SELECT name, count(*) FROM sheet1 GROUP BY name" Book1.xlsx

$ cat out.csv
name,count(*)
Red,849
Green,265
...

要处理旧的 .xls 文件,您需要使用针对 FreeXL 库构建的 XLS 驱动程序,这不是很常见(例如不包括在 OSGeo4w 中)。

0

这可以通过使用SQLite和"XLite"扩展来实现:

  1. https://github.com/x2bool/xlite下载到你的工作目录中
  2. 运行sqlite:sqlite3
  3. 加载扩展:.load libxlite(在Linux和macOS上),或者.load xlite(在Windows上)

然后你可以创建一个虚拟表,并对其进行查询:

CREATE VIRTUAL TABLE test_data USING xlite (
    FILENAME './tests/abcdef_colnames.xlsx', -- path to to your excel file
    WORKSHEET 'Sheet1', -- sheet name
    RANGE 'A2:F', -- optional range
    COLNAMES '1' -- optional param for column names
);

SELECT A, B, C, D, E, F FROM test_data;

SELECT
    COUNT(*),
    D
FROM test_data
GROUP BY D
ORDER BY COUNT(*);

-1
你可以在你选择的语言和平台中尝试使用本机Excel数据库驱动程序进行实验。在Java世界中,你可以尝试http://code.google.com/p/sqlsheet/,它提供了一个JDBC驱动程序,用于直接处理Excel表格。同样,在其他平台上也可以获取该DB技术的驱动程序。
然而,我可以保证,你很快就会遇到这些包装库所提供功能数量的瓶颈。更好的方法是使用Apache HSSF/POI或类似级别的库,但需要更多的编码工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接