英文:
"Simulate" Variables in a View
问题
背景
再次提到,我正在一个非常受限制的T-SQL环境中工作,这是一个应用程序,其中只能定义“视图”的“主体”:可能是下面这个SQL语句中的 ...
:
CREATE VIEW My_View AS ...
这个 ...
必须在应用程序的GUI中的自己的文本字段中(以SQL形式)编写,这会带来下面注意事项中描述的某些不灵活性。
这是我的 @@VERSION
:
Microsoft SQL Server 2019 (RTM-CU19) (KB5023049) - 15.0.4298.1 (X64)
Jan 27 2023 16:44:09
Copyright (C) 2019 Microsoft Corporation
Web Edition (64-bit) on Linux (Amazon Linux 2) <X64>
注意事项
主体 (...
) 限制为一个单独的SQL语句!
此外,存储的 PROCEDURE
无法使用。
目标
我希望“模拟”“本地”变量的存在,以便可以动态更新某些标准,而不是在查询中硬编码为字面值。具体来说,我希望这些“变量”位于顶部,以便可以轻松调整它们。
问题
不幸的是,形式为 @x
的变量在视图中不可用。
方法
我提出了这种方法,使用一个名为 vars
的CTE,它的“字段”是这些变量。它们的值可以作为子查询的形式访问,如 (SELECT x FROM vars)
:
/***********************
** 变量标量 **
***********************/
WITH vars AS (SELECT
'Greg' AS c_name, -- 一个名叫“Greg”的客户。
GETDATE() AS t_date -- 今天发生的交易。
/**********
** 查询 **
**********/
) SELECT
*
FROM
Transactions
WHERE
-- |-------- 'Greg' -------|
Client_Name = (SELECT c_name FROM vars) AND
Transaction_Date = (SELECT t_date FROM vars)
-- |--------- 今天 -------|
我进一步建议可以通过 STRING_SPLIT()
派生出值的变量列表:
/***********************
** 变量标量 **
***********************/
WITH vars AS (SELECT
'Greg' AS c_name
/******************
** 变量列表 **
******************/
), vars_dates AS (SELECT DISTINCT CAST([Value] AS DATE) AS vals FROM STRING_SPLIT('
2023-06-28
2023-06-01
2023-01-01
', Char(10)) WHERE [Value] <> ''
/**********
** 查询 **
**********/
) SELECT
*
FROM
Transactions
WHERE
-- |----------- 'Greg' ----------|
Client_Name = (SELECT c_name FROM vars ) AND
Transaction_Date IN (SELECT vals FROM vars_dates)
-- |-----------------------------|
-- {1/1/23, 6/1/23, 6/28/23}
请注意,现在 vars_dates
中的值都在单独的行上列出,因此用户不需要担心标点符号(逗号等)与任何分隔符发生冲突。
问题
到目前为止,我已经能够在需要 x
的值的任何地方调用 (SELECT x FROM vars)
。事实上,在我的使用案例中,vars
和 vars_dates
都完美地工作。
但是是否有任何情况下这些表达式不起作用,无法代替字面值和列表?更一般地说,这种方法的限制是什么?
英文:
Background
Once again, I am working in a very restricted T-SQL environment, an application where one may only define the "body" of a VIEW
: presumably the ...
in
CREATE VIEW My_View AS ...
under the hood. The ...
must be written (as SQL) within its own text field in the application's GUI, which imposes certain inflexibilities described under Note.
Here is my @@VERSION
:
Microsoft SQL Server 2019 (RTM-CU19) (KB5023049) - 15.0.4298.1 (X64)
Jan 27 2023 16:44:09
Copyright (C) 2019 Microsoft Corporation
Web Edition (64-bit) on Linux (Amazon Linux 2) <X64>
Note
The body (...
) is limited to a single SQL statement!
Furthermore, stored PROCEDURE
s are out of play.
Goal
I wish to "simulate" the presence of "local" variables, so certain criteria can be dynamically updated, rather than hardcoded as literals throughout the query. Specifically, I want these "variables" to be at the top, so they can be easily adjusted.
Issue
Unfortunately, variables of the form @x
are unavailable in views.
Approach
I have postulated this approach, with a vars
CTE whose "fields" are the variables. Their values can be accessed as a subquery of the form (SELECT x FROM vars)
:
/*********************
** Variable Scalars **
**********************/
WITH vars AS (SELECT
'Greg' AS c_name, -- A client named "Greg".
GETDATE() AS t_date -- Transactions occurring today.
/**********
** Query **
**********/
) SELECT
*
FROM
Transactions
WHERE
-- |-------- 'Greg' -------|
Client_Name = (SELECT c_name FROM vars) AND
Transaction_Date = (SELECT t_date FROM vars)
-- |--------- Today -------|
I further suggest that variable lists of values can be specified as further CTEs, derived via STRING_SPLIT()
:
/*********************
** Variable Scalars **
**********************/
WITH vars AS (SELECT
'Greg' AS c_name
/******************
** Variable List **
*******************/
), vars_dates AS (SELECT DISTINCT CAST([Value] AS DATE) AS vals FROM STRING_SPLIT('
2023-06-28
2023-06-01
2023-01-01
', Char(10)) WHERE [Value] <> ''
/**********
** Query **
**********/
) SELECT
*
FROM
Transactions
WHERE
-- |----------- 'Greg' ----------|
Client_Name = (SELECT c_name FROM vars ) AND
Transaction_Date IN (SELECT vals FROM vars_dates)
-- |-----------------------------|
-- {1/1/23, 6/1/23, 6/28/23}
Notice now the values in vars_dates
are listed each on a separate line, so the user need not worry about punctuation (commas, etc.) clashing with any delimiter.
Question
So far, I have been able to call (SELECT x FROM vars)
wherever I need the value of x
. Indeed, both vars
and vars_dates
have worked perfectly throughout my use cases.
But are there any situations where these expressions will not work, in place of literal values and lists? More generally, what are the limitations of this approach?
答案1
得分: 1
你只需在顶部定义一些值,然后在视图中的多个重复点中使用它们,这样将来只需在一个地方更改它们即可。
考虑到你正在使用的约束条件,你目前的方法似乎是可以的(如果这些值不需要在其他视图中共享)。
这基本上是这里的"伪常量"想法的更局部范围版本,至少对于标量值,SQL Server应该能够使用基础的文字值进行基数估计(所以在这方面比实际的TSQL @variable要好)。
对于标量值,而不是需要子查询,比如 SELECT c_name FROM vars
,我更倾向于只是使用单行 vars
进行 CROSS JOIN
,这样列就更容易使用。参见这里的示例:
-- 一个示例表,代替 'Transactions'。
CREATE TABLE #T(X INT);
INSERT INTO #T VALUES (1),(2),(2),(3),(3),(3);
-- CROSS JOIN 技巧本身。
WITH vars AS
(
SELECT 3 AS foo
)
SELECT #T.*
FROM vars
CROSS JOIN #T
WHERE X = vars.foo
-- 清理。
DROP TABLE #T
下面的示例演示了基数估计是使用值 3
进行的,并且正确估计了3行。
对于列表,我可能会使用表值构造函数而不是字符串拆分。这应该让SQL Server更容易看到列表中的行数(如果它查看了这些实际值,可能还有其他基数估计的好处)。
-- ...
/******************
** 变量列表 **
******************/
), vars_dates AS (SELECT vals FROM (VALUES
(CAST('2023-06-28' AS date)), /*使来自常量扫描的列的类型为日期*/
('2023-06-01'),
('2023-01-01')
) tbl(vals)
-- ...
与实际变量不同,如果你使用非确定性函数,这种方法不会保证值在查询中始终保持不变,但是根据你当前的约束条件,你无法保证这一点。
英文:
You just want to define some values once at the top and then use them in repeated points throughout the view so you can change them in one place in the future.
Given the constraints you are working with the approach you have seems fine (if these values are not required to be shared in other views).
It is basically a more locally scoped version of the "Pseudo-Constant" idea here and at least for scalar values SQL Server should be able to use the underlying literal value for cardinality estimation (so better than an actual TSQL @variable in that respect).
For the scalar values rather than needing sub queries such as SELECT c_name FROM vars
I'd be minded to just CROSS JOIN
the single row vars
so then the columns are available for easier use. See this example here:
-- An example table, in place of 'Transactions'.
CREATE TABLE #T(X INT);
INSERT INTO #T VALUES (1),(2),(2),(3),(3),(3);
-- The CROSS technique itself.
WITH vars AS
(
SELECT 3 AS foo
)
SELECT #T.*
FROM vars
CROSS JOIN #T
WHERE X = vars.foo
-- Cleanup.
DROP TABLE #T
The below demonstrates the cardinality estimation was done using the value 3
and it correctly estimates 3 rows.
For the lists I'd likely use a Table Value Constructor rather than string splitting. It should allow SQL Server to see the number of rows in the lists easier (and maybe have other benefits for cardinality estimation if it looks at the actual values in these).
-- ...
/******************
** Variable List **
******************/
), vars_dates AS (SELECT vals FROM (VALUES
(CAST('2023-06-28' AS date)), /*So the column from the constant scan is typed as date*/
('2023-06-01'),
('2023-01-01')
) tbl(vals)
-- ...
Unlike an actual variable this approach won't guarantee that the value will stay the same throughout the query if you use a non deterministic function but you don't have any way of actually guaranteeing that with your current constraints.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论