下面更新
我有一个具有典型帐户/父帐户体系结构的帐户表来表示帐户层次结构(SQL Server 2012)。我使用 CTE 创建了一个 VIEW 来散列层次结构,总的来说它工作得很好,而且符合预期。我可以查询任何级别的层次结构,并轻松查看分支。
有一个业务逻辑字段需要作为层次结构的函数返回。每个帐户记录中的一个字段描述了企业的规模(我们将其称为 CustomerCount)。我需要报告的逻辑需要从整个分支汇总 CustomerCount。换句话说,给定一个帐户,我需要将该帐户的 customercount 值与层次结构中帐户下方每个分支中的每个子项相加。
我使用 CTE 中构建的层次结构字段成功计算了该字段,该字段看起来像 acct4.acct3.acct2.acct1。我遇到的问题只是让它运行得很快。如果没有这个计算字段,查询会在大约 3 秒内运行。当我添加计算字段时,它变成了一个 4 分钟的查询。
这是我能想出的最好的版本,它返回正确的结果。我正在寻找有关如何在不牺牲性能的情况下重新构建此视图的想法。
我理解这个变慢的原因(需要在 where 子句中计算一个谓词),但我想不出另一种方法来构造它并且仍然得到相同的结果。
下面是一些示例代码,用于构建表并执行 CTE,这与它在我的环境中的工作方式几乎完全一样。
Use Tempdb
go
CREATE TABLE dbo.Account
(
Acctid varchar(1) NOT NULL
, Name varchar(30) NULL
, ParentId varchar(1) NULL
, CustomerCount int NULL
);
INSERT Account
SELECT 'A','Best Bet',NULL,21 UNION ALL
SELECT 'B','eStore','A',30 UNION ALL
SELECT 'C','Big Bens','B',75 UNION ALL
SELECT 'D','Mr. Jimbo','B',50 UNION ALL
SELECT 'E','Dr. John','C',100 UNION ALL
SELECT 'F','Brick','A',222 UNION …Run Code Online (Sandbox Code Playgroud) 我需要从它的祖先计算后代的深度。当一个记录有 时object_id = parent_id = ancestor_id,它被认为是一个根节点(祖先)。我一直在尝试WITH RECURSIVE使用 PostgreSQL 9.4运行查询。
我不控制数据或列。数据和表架构来自外部源。该表正在不断增长。现在每天大约有 3 万条记录。树中的任何节点都可能丢失,并且它们将在某个时候从外部源中拉出。它们通常按created_at DESC顺序拉取,但数据是通过异步后台作业拉取的。
我们最初对这个问题有一个代码解决方案,但现在有 500 万行以上,几乎需要 30 分钟才能完成。
示例表定义和测试数据:
CREATE TABLE objects (
id serial NOT NULL PRIMARY KEY,
customer_id integer NOT NULL,
object_id integer NOT NULL,
parent_id integer,
ancestor_id integer,
generation integer NOT NULL DEFAULT 0
);
INSERT INTO objects(id, customer_id , object_id, parent_id, ancestor_id, generation)
VALUES (2, 1, 2, 1, 1, -1), --no parent yet
(3, 2, …Run Code Online (Sandbox Code Playgroud) 假设您有这样的nodes表:
CREATE TABLE nodes
(
node serial PRIMARY KEY,
parent integer NULL REFERENCES nodes(node),
ts timestamp NOT NULL DEFAULT now()
);
Run Code Online (Sandbox Code Playgroud)
它代表了一个标准的类似节点的树结构,根节点在顶部,几个子节点悬挂在根节点或其他子节点上。
让我们插入几个示例值:
INSERT INTO nodes (parent)
VALUES (NULL), (NULL), (NULL), (NULL), (1), (1), (1), (1), (6), (1)
, (6), (9), (6), (6), (3), (3), (3), (15);
Run Code Online (Sandbox Code Playgroud)
现在我想检索前 10 个根节点及其所有子节点,深度为 4:
WITH RECURSIVE node_rec AS
(
(SELECT 1 AS depth, * FROM nodes WHERE parent IS NULL LIMIT 10)
UNION ALL
SELECT depth + 1, n.*
FROM nodes …Run Code Online (Sandbox Code Playgroud) 如在 MSDN 上使用公用表表达式中所示,您可以将 CTE 定义为:
WITH expression_name [ ( column_name [,...n] ) ]
AS
( CTE_query_definition )
Run Code Online (Sandbox Code Playgroud)
并使用它:
SELECT <column_list> FROM expression_name;
Run Code Online (Sandbox Code Playgroud)
假设我有以下 2 个 CTE
with cte1 as(
select name from Table1
)
with cte2(name) as(
select name from Table1
)
Run Code Online (Sandbox Code Playgroud)
一个查询为两个 CTE 输出相同的结果,因为内部查询是相同的。这两者之间的唯一区别是cte2(name)在其声明中定义了列名( )。
当我执行两个 CTE 时,我看不到执行计划有任何不同。
我只是想知道:
我想通过一个查询将数据添加到至少三个表中。我想到了这样的事情:
WITH ins AS (
INSERT INTO core.adr
(street, "number", postal_code, city)
VALUES
('test 1', '25a', '00912', 'villageman')
RETURNING id)
INSERT INTO core.adr_information
(idref, info)
SELECT id, 'test data'
FROM ins;
Run Code Online (Sandbox Code Playgroud)
这完全适用于两个表。
这里的主要问题是,所有其他查询都需要id第一个INSERT查询的值,这似乎无法通过这种方式进行管理。我认为它可以通过存储过程或事务轻松完成,但我希望解决方案是一个简单而可靠的查询。
我是否忽略了什么?是否有可能以这种方式或另一种方式(没有存储过程*或事务)?
笔记
* Altough,严格来说,Postgres没有已存储过程,只有功能,@Erwin Brandstetter修改正确地通过评论中指出。有关相关信息,请参阅本系列帖子。
我有这个有效的 CTE 示例。
我可以选择所有祖父母和所有孩子。
但是如何在一个语句中选择所有祖父母和所有孩子?
在这个例子中,如果我给“父亲”作为输入,我想要祖父,父亲,儿子作为输出。
我使用 PostgreSQL。但我认为这个问题应该是标准的SQL。
如果我使用 PostgreSQL 特定的语法,请纠正我。
DROP table if exists tree;
CREATE TABLE tree (
id SERIAL PRIMARY KEY,
name character varying(64) NOT NULL,
parent_id integer REFERENCES tree NULL
);
insert into tree values (1, 'Grandfather', NULL);
insert into tree values (2, 'Father', 1);
insert into tree values (3, 'Son', 2);
-- -------------------------------------
-- Getting all children works
WITH RECURSIVE rec (id) as
(
SELECT tree.id, tree.name from tree where name='Father'
UNION ALL
SELECT tree.id, …Run Code Online (Sandbox Code Playgroud) 我们有一个 SQL Server 数据库,它有一个数据库审计规范,它审计对数据库的所有执行操作。
CREATE DATABASE AUDIT SPECIFICATION [dbAudit]
FOR SERVER AUDIT [servAudit]
ADD (EXECUTE ON DATABASE::[DatabaseName] BY [public])
Run Code Online (Sandbox Code Playgroud)
我们发现,某些查询会将结果集中的每一行的标量函数的使用写入审计日志。当这种情况发生时,日志在我们可以 ETL 到它的最终休息位置之前就被填满了,我们的日志记录出现了空白。
不幸的是,由于合规性原因,我们不能简单地停止审核每条EXECUTE语句。
我们解决这个问题的第一个想法是使用Server AuditWHERE上的子句来过滤掉活动。代码如下所示:
WHERE [object_id] not in (Select object_id from sys.objects where type = 'FN' )
Run Code Online (Sandbox Code Playgroud)
不幸的是,SQL Server 不允许关系 IN 运算符(可能是因为它不想在每次必须写入审核日志时进行查询)。
我们想避免编写一个存储过程,其硬代码object_id中WHERE条款,但是这是我们在解决这个问题的最好办法目前的想法。是否有我们应该考虑的替代方法?
我们注意到,当在递归 CTE 中使用标量函数时,它会导致查询为结果集中的每一行写入审计日志。
有一些标量值函数由供应商提供,我们无法删除或移动到备用数据库。
我不明白 Craig Ringer 在评论时的意思:
如果插入事务回滚,此解决方案可能会丢失更新;没有强制执行 UPDATE 影响任何行的检查。
在/sf/answers/609160401/ 上。请提供一个示例事件序列(例如,线程 1 执行 X,线程 2 执行 Y)以演示丢失更新是如何发生的。
在讨论这个问题的递归 CTE 解决方案时:
@ypercube偶然发现了一个令人惊讶的异常,这导致我们调查类型修饰符的处理。我们发现了令人惊讶的行为。
即使被指示不要这样做。最基本的例子:
SELECT 'vc8'::varchar(8)::varchar
Run Code Online (Sandbox Code Playgroud)
人们可能会期望varchar(没有修饰符),至少我会。但结果是varchar(8)(带修饰符)。下面的小提琴中有许多相关的案例。
不需要,所以这在相反的一面出错:
SELECT ARRAY['vc8']::varchar(8)[]
, ARRAY['vc8']::varchar(8)[] || 'vc8'::varchar(8)
Run Code Online (Sandbox Code Playgroud)
第一个表达式varchar(8)[]按预期产生。
但是第二个,在连接另一个之后varchar(8)被淡化到只是varchar[](没有修饰符)。来自array_append(),下面小提琴中的示例的类似行为。
在大多数情况下,所有这些都无关紧要。Postgres 不会丢失数据,并且当分配给列时,该值无论如何都会被强制转换为正确的类型。然而,相反方向的错误最终会导致一个令人惊讶的异常:
鉴于此简化表:
CREATE TABLE a (
vc8 varchar(8) -- with modifier
, vc varchar -- without
);
INSERT INTO a VALUES ('a', 'a'), ('bb', 'bb');
Run Code Online (Sandbox Code Playgroud)
虽然此 rCTE 对varchar列有效vc,但对 …
不久前,Brent Ozar 发表了一篇文章,详细介绍了 SQL Server 和 PostgreSQL 之间的一些差异:
SQL Server 和 PostgreSQL 的两个重要区别
第一点(“CTE 是优化栅栏”)引起了我的注意,因为很明显,在提供的示例中,SQL Server 将 CTE 和主查询组合在一起并将其优化为单个查询(而不是在PostgreSQL)。
但是,这种行为似乎与我在其他博客和培训课程中看到的示例相反,其中 SQL Server 确实将 CTE 视为优化栅栏,从而可以更好地使用索引、更好的性能等。例如:
因此,似乎 SQL Server 有时“尊重”CTE 作为优化栅栏。是否有任何好的资源可以记录 SQL Server 将可靠地将 CTE 作为优化栅栏(或相反的行为)的已知案例的特定列表?
cte ×10
postgresql ×6
recursive ×4
sql-server ×4
optimization ×2
audit ×1
cast ×1
concurrency ×1
datatypes ×1
debugging ×1
functions ×1
order-by ×1
performance ×1
update ×1
upsert ×1