本文来自社区伙伴对《DAX 权威指南(第二版)》的学习笔记,有问题可以留言或联系BI佐罗修改,感谢你的支持。
DAX 即数据分析表达式,是 Microsoft Power BI、Microsoft Analysis Services 和 Microsoft Power Pivot for Excel 的编程语言。它创建于 2010 年,第一次发布了针对 Microsoft Excel 2010 的 PowerPivot。2010 年,PowerPivot 的拼写没有空格。该空格于 2013 年以 Power Pivot 的名称引入。从那时起,DAX 在 Excel 社区(使用 DAX 在 Excel 中创建 Power Pivot 数据模型)和商业智能(BI)社区(使用 DAX 在 Power BI 和 Analysis Services 中构建模型)中变得越来越流行。DAX 存在于许多不同的工具中,它们共享同一个名为 Tabular 的内部引擎。由于这个原因,我们经常引用 Tabular 模型来指代以上这些不同的工具。
DAX 是一种简单的语言。也就是说,DAX 与大多数编程语言不同,因此熟悉它的一些新概念可能需要一些时间。根据我们的经验,在向成千上万的人教授了 DAX 之后,学习 DAX 的基础知识非常简单,您将能够在几小时内开始使用它。当涉及到理解高级概念,例如计算上下文、迭代和上下文转换时,一切就会变得复杂,但不要放弃,要有耐心。当您的大脑开始消化这些概念时,您会发现 DAX 确实是一种简单的语言,只是需要一些时间来适应。
本章首先概述了数据模型在表和关系方面是什么。我们建议所有经验水平的读者阅读本节,以熟悉本书中涉及表、模型和各种关系时使用的术语。 在本章后续的部分中,还将为那些已经具有 Microsoft Excel、SQL 和 MDX 等编程语言经验的读者提供相应的建议。每个部分都聚焦于某种特定的语言,以便感兴趣的读者快速地将 DAX 与其进行比较。您只需要按你知道的语言的比较即可,然后阅读最后一节“DAX 之于 Power BI 用户”,然后再在下一章正式开始学习 DAX 语言。
[!NOTE] 对于初学者来说,你可能不会 SQL,更不会 MDX,那你完全可以跳过本章中的这些描述,因为这些描述的就在说明一件事:用 DAX 才是最合适的。
DAX 是专为针对数据模型中进行业务计算而设计的。读者可能已经知道什么是数据模型。如果不知道,我们从数据模型和关系的概念出发,构建 DAX 知识体系的基础。
数据模型,是若干个由关系连接的表构成。
我们都知道表是怎样的,即包含数据的若干行,每一行都被分成若干列。每一列都符合一种数据类型,并包含一个信息。我们通常将表中的一行称为记录。表是整合数据的一种方便的方法。很明显,单表是最简单的形式,它本身也算是一个数据模型。因此,当我们在 Excel 工作簿中写入名称和数字时,我们就已经在创建数据模型了。
如果一个数据模型包含许多表,那么它们可能通过关系进行连接。关系是两个表之间的连接。如果两个表之间有关系连接,我们会说这两个表是相关的。从图形上看,关系由连接两个表的线表示。图 1-1 显示了一个数据模型示例。
图 1-1 该数据模型由 6 个表组成。
以下是关系的几个重要方面:
每个关系都可以有一个单向或双向的交叉筛选。筛选总是从关系的一端进行到多端。如果交叉筛选是双向的,也就是说,如果它有两个箭头,筛选也发生在从多端到一端。
一个例子会帮助理解这种行为。如果报表基于图 1-1 所示的数据模型,年份在行上,并且 Quantity 和 Count of Product Name 在值区域,则生成如图 1-2 所示的结果。
[!NOTE] 这里的报表一词,和透视表同义。指的是具体的一个图表,而非整个页面。
图 1-2 此报告显示的是跨越多表进行筛选的效果。
Calendar Year(日历年份)位于 Date 表的列。Date 日期表的关系是一端到 Sales 销售表的多端,引擎会根据年份对 Sales 表进行相应的年份筛选。因此上图 Quantity 数量按年度显示。
然而对于产品表而言,情况又有些不一样。因为 Sales 表和 Product 表之间是双向关系。若将产品名称的数量放入报表,可获得每年销售的产品数量,因为通过 Sales 销售表的关系传递,Product 产品被相应的年份所筛选。如果 Sales 表和 Product 表之间是单向关系,那么结果会有所不同,如下一小节所示。
如果我们调整报表,将 Color(颜色)放入行标签,将 Count of Date(日期数量)放入值区域,结果发生变化,如图 1-3 所示:
图 1-3 如报表所示,若表的关系不是双向筛选关系,表无法显示筛选结果
如图位于行上的 Color 字段对应 Product 产品表的 Color 颜色列,因为 Product 产品表单向关系对应 Sales 销售表,Quantity 数量正确地显示了筛选后的结果。Count of Product Name(产品名称计数)显示的是筛选后结果,因为它和 Color 来自同一个表(即产品表)。Count of Date(日期计数)可能出乎初学者意料,每一行显示的都是相同的数值,实际上,这个相同的数值是日期表的总行数。
因为日期表和销售表是单向关系,颜色列的筛选并没有传递到日期表。因此,尽管销售表已经被筛选,单向关系类型导致该筛选不能传递至 Date 表。
如果我们将日期表和销售表之间的关系调整为双向关系,结果图如 1-4 所示:
图 1-4 如果我们启用双向关系,日期表的筛选结果可以正确显示
您可能已经知道 DAX 和 Excel 函数有些相似。毕竟,Excel 的 Power Pivot 是在 DAX 起源时便开始使用,开发团队也试图让 Excel 函数和 DAX 相似。这种相似性让我们更加容易学习 DAX。不过,DAX 和 Excel 函数还是有一些根本性区别的。
Excel 在单元格中执行计算。使用坐标引用单元格,编写如下公式
= (A1 * 1.25) - B2
在 DAX 中,没有像单元格和坐标这样的概念。单元格不适用于 DAX,而表和列可以。因此,DAX 表达式引用表和列,意味着全新的编写代码方式。然而,引用表和列在 Excel 中已经出现过。如果我们通过“表格”功能将 Excel 范围定义为表格,Excel 就可以引用表格和列来编写公式。在图 1-5 中,SalesAmount 列中计算的表达式没有引用工作簿中的单元格,而是引用该表的列:
图 1-5 Excel 可以引用表格的列
在 Excel 中,我们使用[@列名称]的格式来引用表的列,列名称是需要引用的列的名称,@符号表示”获取当前行的值“,因为语法不直观,我们通常不会这样写,单击单元格时,Excel 会插入正确的代码。
你可能认为 Excel 有两种不同的计算方式,我们可以标准引用单元格,单元格 F4 的公式显示为 E4*D4,或者在表中引用列。引用列有如下优点,列的所有单元格都是相同的表达式,Excel 会根据每一行不同的值来计算公式。
和 Excel 不一样,DAX 只能够在表中运行,所有的公式必须引用表内的列,举一个例子,在 DAX 里我们会这样写之前的乘法:
Sales[SalesAmount] = Sales[ProductPrice] * Sales[ProductQuantity]
可以发现每一列名都以表名称为前缀。在 Excel 中,公式仅在这个表中运行,我们不需要添加表前缀。但是,DAX 需要对数据模型里的许多表进行操作,因此,我们必须特别指明表名(可能不同表中的两列的列名相同)。
DAX 的很多功能的运行方式和 Excel 一模一样,比如,DAX 里 If 函数的读法和 Excel 一样:
IF ( [@SalesAmount] > 10, 1, 0)
IF ( Sales[SalesAmount] > 10, 1, 0)
Excel 和 DAX 的重要区别在于两者引用整列的语法方式不同,在[@产品数量]里,@表示“当前行的值”,而 DAX 不需要特别指出值来自于该行,因为逻辑默认就是这样的。Excel 可以用所有列的概念,也就是该列的所有行,如果将“@”符号删除,你会发现结果如图 1-6 所示:
图 1-6 Excel 可以通过在列名称前省略@符号,从而引用此列中的所有行
AllSales 列是销售额列的所有值的总计,所以 AllSales 列的所有行都是同一个值。换句话说,所在列的当前行的值,与将所在列的所有行的总计值的语法是不一样的。
DAX 是不一样的,图 1-6 的销售总计在 DAX 中是这样写的:
AllSales := SUM ( Sales[SalesAmount] )
在特定某行取值与将整列视为整体取值,两者的语法没有区别。因为我们在聚合函数中用了列名称(此例聚合函数为 SUM 函数),这让列名变成一个整体参数,导致 DAX 认为我们要这一列的所有值的总和。因此,尽管 Excel 需要一个清晰的语法,来区分要检索的两种类型的数据,DAX 会自动消除语法歧义。至少刚开始接触,这种差异可能会被混淆。
Excel 和 DAX 的写法是类似的,两者都是函数式语言。函数式语言由函数调用构成的表达式组成。尽管语句,循环和跳转这些概念经常在许多编程语言中看到,Excel 和 DAX 是没有这些概念的。DAX 的世界里,所有都是表达式。这对于掌握其他编程语言的程序猿来说是一个挑战,但对所有 Excel 用户来说,这一点都不奇怪。
迭代的概念对你而言可能是陌生的。Excel 里,一次只能执行一次计算(没有迭代)。前面的例子展示计算销售总额,我们新建一个价格乘以数量的列,随后我们对其求和,计算销售总额。得出的数字可作为分母,用来计算每种产品的销售百分比。
在 DAX 里,你可以使用迭代器在一个步骤中执行相同的操作,迭代器的工作方式正如其名:迭代表,并对表的每一行进行计算,将结果予以汇总,返回需要的单个值。
[!NOTE] 迭代是一个动词,指对某集合的元素依次访问,通常在迭代访问时,会进行一些操作。在 DAX 中,这个集合就是表,而集合的元素就是表中的行。而迭代器是一个名词,指可以进行迭代动作的函数。
通过之前的例子,我们现在能够用 SUMX 迭代器来计算销售额:
AllSales :=
SUMX (
Sales,
Sales[ProductQuantity] * Sales[ProductPrice]
)
这种方法既有优点也有缺点,优点是你不用担心添加辅助列,一个步骤中就可以执行许多复杂的运算。缺点是,与用 Excel 函数编写相比,DAX 编写的视觉效果不够直观。实际上,你看不到计算价格乘以数量的列,它仅在计算的中间过程中存在。
这个我们稍后会解释,我们可以创建一个计算列,来计算价格和数量的乘积。然而,这样做不是一个好方法,因为这会占用内存,降低计算效率,这个我们会在第 18 章”优化 VertiPaq”中提到。
我们需要明确的事实:DAX 首先需要学习的,不是它和其他编程语言有何不同,而是思维模式的转变。遇到一些待解决的问题时,你可能已经习惯在网上找复杂的公式和解决方案去解决。在 Excel,你可能会找到一个几乎满足你需求的公式。复制,根据需求微调,不用想这个公式的运行原理就可以直接用了。
这个方法适用于 Excel,但不适用于 DAX。你需要研究 DAX,真正理解什么是计算上下文后,才会写出好的 DAX 代码。如果没有好的理论基础,你会感觉 DAX 要么像变魔术一样来计算值,要么计算出一堆不知所云的奇怪数字。这不是 DAX 的问题,而是你完全不知道 DAX 是怎么运行的。
幸运的是,DAX 的理论仅有两个重要概念,我们会在第四章“理解计算上下文”中解释,在读第四章时,做好吃透的准备,等你完全掌握这个内容,也就了解 DAX 了,DAX 主要是通过经验的积累来进行学习,记住:掌握是成功的一半。因此,真正掌握计算上下文后,再继续深入学习。
如果你已经熟悉 SQL 语言并做了很多表,在列与列之间创建连接来建立关系。从这点来看,DAX 的世界对你来说驾轻就熟。的确,DAX 的计算是一个在很多建立关系的表中进行查询,将其汇总的过程。
SQL 和 DAX 第一个不同之处是模型里关系的工作方式。在 SQL 中,我们可以在表之间设置外键来声明关系,但如果我们不明确声明,在查询里,引擎不会使用这些外键。举一个例子,如果我们有一个销售表一个客户表,客户键是在客户表是主键,在销售表是外键,写出如下查询:
SELECT
Customers.CustomerName, SUM ( Sales.SalesAmount ) AS SumOfSalesFROM
Sales INNER JOIN Customers ON Sales.CustomerKey = Customers.CustomerKeyGROUP BY
Customers.CustomerName
尽管在模型里,我们用外键来声明关系,查询中依旧需要明确声明并说明连接条件。虽然这样做会把查询变的冗长,但这样做很有用,因为你可以在不同的查询中使用不同的连接条件,表达查询的方式拥有更大的自由度。
DAX 中,关系是模型的一部分,所有的关系都是左外连接。模型里定义关系的时候,你不再需要在查询中指定查询类型:在查询中只要你使用与主表相关的列,DAX 都会自动使用左外连接。因此,在 DAX 里,之前的 SQL 查询你会这样写:
EVALUATE
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", SUM ( Sales[SalesAmount] )
)
因为 DAX 知道“销售”和“客户”之间的现有关系,所以它会按照模型自动进行关联。最后,SUMMARIZECOLUMNS 函数需要按 Customers [CustomerName]执行分组。
SQL 是一种声明语言。你不用想引擎是如何返回信息,把需要的数据集进行声明,将其定义,用 SELECT 语句检索返回。
然而,DAX 是一个函数语言,DAX 的每一个表达式都是一系列函数调用。一个函数参数又可以是其他函数调用,这样的参数会将查询变的复杂,DAX 执行查询后获得计算结果。
举一个例子,如果我们想知道哪些客户住在欧洲,SQL 的查询会这样写:
SELECT
Customers.CustomerName, SUM ( Sales.SalesAmount ) AS SumOfSalesFROM
Sales INNER JOIN Customers ON Sales.CustomerKey = Customers.CustomerKeyWHERE
Customers.Continent = 'Europe'GROUP BY
Customers.CustomerName
用 DAX 的话,我们不用在查询中声明“在哪里”的条件,而我们需要一个特殊的函数: Filter,来获得筛选后的结果:
EVALUATE
SUMMARIZECOLUMNS (
Customers[CustomerName],
FILTER (
Customers,
Customers[Continent] = "Europe"
),
"SumOfSales", SUM ( Sales[SalesAmount] )
)
你会发现 Filter 是一个函数:它返回了只住在欧洲的客户,获得了我们希望的结果。函数嵌套的顺序和函数的选用,对 DAX 的运行结果都有影响。SQL 也有这样的情况。SQL 的查询优化器会找到查询更优解,DAX 的话,尽管 DAX 的查询优化器也做的不错,而你,作为编写者,最好承担更多的责任,而不要指望 DAX 引擎对此的自动优化能力。
SQL 作为查询语言和作为编程语言,其表现存在一个清晰的分界线——在数据库中创建存储过程,视图和其他代码段的指令集,SQL语句的体现方式不同,程序员用代码来完善数据模型。然而,DAX的查询和编程在形式上是没有区别的。各种各样的函数将表进行转换后,再以表的形式返回。前面查询中的Filter函数就是一个很好的例子。
在这一点上,可以看出 DAX 比 SQL 的形式更加简单,若你将其作为一个编程语言(它最开始的用途)来学习,你会发现,其中所有相关的知识也适用于查询语言。
作为查询语言,子查询是 SQL 最强大的功能之一。DAX 也有类似的概念。然而 DAX 的子查询是通过语句来表现的。
举一个例子,如果想知道购买总额超过 100 美元的顾客与其具体的购买金额,在 SQL 的查询中这样写:
SELECT
CustomerName,
SumOfSalesFROM ( SELECT
Customers.CustomerName, SUM ( Sales.SalesAmount ) AS SumOfSales FROM
Sales INNER JOIN Customers ON Sales.CustomerKey = Customers.CustomerKey GROUP BY
Customers.CustomerName
) AS SubQueryWHERE
SubQuery.SumOfSales > 100
通过调用嵌套函数,在 DAX 中我们得到相同的结果:
EVALUATE
FILTER (
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", SUM ( Sales[SalesAmount] )
),
[SumOfSales] > 100
)
如上这段代码中,子查询将 CustomerName 和 SumOfSales 所返回的值,再次赋入 Filter 函数,Filter 函数保留其中销售总额大于 100 的值。现在,你可能看不懂这段代码,不过你学习 DAX 后,你会发现 DAX 的子查询比 SQL 的简单的多, 而且因为 DAX 是函数式语言,读起来也更顺畅。
因为 DAX 是 Tabular 的新语言,许多专业商业智能用户开始学习它。以前,分析服务多维模型(Analysis Services Multidemensional)是用 MDX 语言构建查询的。如果你以前用的是 MDX,做好从头学 DAX 的准备,因为 DAX 和 MDX 几乎没有相似的地方。更糟糕的是,DAX 的一些概念会让你联想到 MDX 的一些概念(但两者完全不同)。
以往经验来看,掌握 MDX 的情况下再学 DAX 是一件非常难的事情。你需要将 MDX 的已有知识清空,再去学 DAX。把之前你知道的多维空间知识都忘掉,重新去学习这门新语言。
[!NOTE] 就像学习两个门派的武功,有武功的人总会自己认为再掌握一门武功是容易的,然后对于 DAX 来说,忘记以前所学的,才是入门的第一要务。
MDX 在模型定义的多维空间里运行。多维空间的形状取决于数据模型定义的层次结构和数据结构,反过来,层次结构和数据结构又定义了多维空间的坐标集。不同维度中,成员集的交集定义多维空间的点。可能你需要点时间去理解:任何属性层次结构的[all]成员,实际上是多维空间的一个点。
DAX 就没那么复杂了。DAX 没有维度,没有成员,没有多维空间的点。也就是说,DAX 压根没有多维空间这个东西。DAX 的层次在模型里定义,但是 DAX 的模型和 MDX 的不一样。DAX 空间建立在表,列和关系上。每个 Tabular 模型里的表,既不是度量组也不是维度,它是一个可以计算值,扫描,筛选,对里面的值进行求和的表。DAX 的一切都基于两个简单的概念:表和关系。
你很快就发现,在建模这一块,Tabular 的选择比多维空间少。然而,选项少并不意味着功能少,因为你可以用 DAX 的编程语句来补充模型。Tabular 建模的真正能力在于 DAX 的速度。你可能在模型中,需要尽可能避免过度使用 MDX,因为优化 MDX 的速度是一件很难的事。然而 DAX 就不一样了,它的速度惊人的快。所以,在 DAX 公式中进行大多数的复杂计算,而不是在模型。
DAX 和 MDX 是编程语言,也是查询语言。MDX 可以通过脚本,来区分编程语言和查询语言。在 MDX 脚本中使用的是 MDX 语言,有几个特殊语句,比如 SCOPE 语句,只能在脚本中使用。用 MDX 检索时,用 SELECT 语句来返回数据。DAX 的话,就有些不一样。
DAX 可以作为编程语言来定义计算列,计算表和度量值。DAX 新提出的计算列和计算表的概念,MDX 里面没有。DAX 的度量值和 MDX 的计算集合类似。
DAX 也可以作为查询语言,举一个例子——用报表服务来返回Tabular模型的值。
因此,DAX 对于编程或查询没有区别,它在查询和编程里使用是完全一致的。更进一步的是,你还可以用 MDX 查询由 DAX 构建的 Tabular 模型。当然,构建 Tabular 模型时,只能使用 DAX。
[!NOTE] 这里是对 MDX 更深层应用的描述,DAX 初学者根本不需要了解。
MDX 中,你依靠层次来进行大部分的运算。如果想知道前一年的销售,你需要在 Year 的层次结构检索位于 CurrentMember 的 PrevMember, 检索出 PrevMember 后,覆盖 MDX 过滤器。比如,在 MDX 里定义前年的计算,会像如下这样写:
CREATE MEMBER CURRENTCUBE.[Measures].[SamePeriodPreviousYearSales] AS
(
[Measures].[Sales Amount],
ParallelPeriod (
[Date].[Calendar].[Calendar Year],
1,
[Date].[Calendar].CurrentMember
)
);
度量值使用 ParallelPeriod 函数,函数返回 Calendar 层次结构上 CurrentMember 的表亲。因此,它基于模型中定义的层次结构。
同样的计算用 DAX 的筛选上下文和时间智能函数来写,如下:
SamePeriodPreviousYearSales :=
CALCULATE (
SUM ( Sales[Sales Amount] ),
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)
通过 Filter 函数和其他的函数,还有很多其他的方法来达到相同的计算效果,但是核心是一样的:用筛选表的方法,而不是用层次的方法。这两者的差异很大,在你还没习惯 DAX 时,你可能会想用层次结构计算。
另外重要的一点差异是:MDX 引用[Messures]. [Sales Amount],模型定义了你需要使用的聚合函数。DAX 不会提前定义聚合。你可能已经注意到了,计算的表达式是 SUM(Sales[SalesAmount]),模型不会有提前聚合,因为你需要的时候就会及时定义,我们永远可以随时创建计算销售额的度量值,不过这个内容已经超出本章范围,我们会在书的后面内容讲到。
还有一个 DAX 和 MDX 的差异,很重要:MDX 过多的使用 SCOPE 语句来实现业务逻辑(同样,需要使用层次结构)。而 DAX 用的完全是另外一个套路,DAX 语言里,压根没有层次结构这一说。
比如说,如果我们要清除 Year 级别的度量值,MDX 里需要这样写:
SCOPE ( [Measures].[SamePeriodPreviousYearSales], [Date].[Month].[All] )
THIS = NULL;
END SCOPE;
DAX 没有像 SCOPE 语句这样的东西,为了获得同样的结果,我们需要确认筛选上下文中的筛选器,语句则变的更复杂:
SamePeriodPreviousYearSales :=
IF (
ISINSCOPE ( 'Date'[Month] ),
CALCULATE (
SUM ( Sales[Sales Amount] ),
SAMEPERIODLASTYEAR ( 'Date'[Date] )
),
BLANK ()
)
上面的式子表示,只有客户在浏览月份级别或更低的界别的日历层次结构时,这个公式才返回值。否则,返回空值。稍后你会详细的学习这个公式。和等效作用的 MDX 相比,DAX 更容易出错。老实说,层次结构处理是 DAX 真正缺少的功能之一。
最后,用 MDX 的时候,你可能已经习惯于避免叶级计算。你习惯提前计算值,将得出的值进行聚合返回结果,因为 MDX 的叶级计算很慢。而 DAX 的叶级计算速度非常快,不过 DAX 的聚合有其他的用途,且仅对大型数据集有效。因此,在搭建数据模型时,需要一些观念的转换。大多数情况下,适用于 SSAS 多维的数据模型,不适用于表格模型,反之亦然。
如果您跳过前面的部分直接来到这里,欢迎!DAX 是 Power BI 的原生语言,如果您没有 Excel,SQL 或 MDX 的经验,Power BI 将是您接触 DAX 的第一个地方。如果您以前没有使用其他工具构建模型的经验,您将了解到 Power BI 是一个功能强大的分析和建模工具,DAX 是非常棒的伙伴。
如果您刚刚开始使用 Power BI,并想进一步学习与了解,那么 DAX 将助您事半功倍。
以下是我们给您的建议:不要期望能在几天内编写复杂的 DAX 代码。掌握 DAX 需要一定练习,因此您需要投入时间和精力。根据我们的经验,一开始当您掌握了一些简单的计算时,您可能会学习热情高涨,但一旦开始学习计算上下文和 CALCULATE(DAX 语言中最复杂的主题),一切看起来都很复杂,您的学习热情可能会减退,但请不要放弃,这是大多数 DAX 学习者必经的阶段,当您学到这些章节,您已经非常接近完全理解 DAX 语言了,放弃将非常可惜。您需要反复阅读和练习,因为一日不练十日空。您可以快速学完本书,达到 DAX 大师级别。
计算上下文是 DAX 语言的核心,需要您花时间理解和掌握,鲜有人能在几天内掌握所有关于 DAX 的知识。此外,与任何复杂的主题一样,随着时间的推移,您将发现本书中诸多细节之美。当你认为自己掌握了所有东西时,请再次阅读本书。您会发现当您对内容有了更为深入的理解时,之前许多看似不那么重要的细节将会有更深刻的内涵。
好好享受本书的其他部分吧!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有