Temos uma situação em que efetivamente o mesmo relatório é solicitado por clientes diferentes, mas eles:
- Não quero todas as colunas
- Quer as colunas em uma ordem diferente da que naturalmente as temos
- Quer que eles sejam chamados de algo diferente do que temos armazenado ("Nº do cliente" vs "Número do cliente", por exemplo)
A intenção é facilitar o esforço necessário para acomodar essas solicitações de personalização. Atualmente, estamos em uma posição com algumas centenas de instâncias desses relatórios basicamente idênticos (excluindo essas diferenças superficiais). Eu estou olhando para ver se eu precisava de uma instância dessas Dynamic
consultas base por Parameter
conjunto ou se eu poderia lidar com todos os parameter
conjuntos possíveis por meio de 1 Stored Procedure
. A esperança é também não ter que ter um monte de instâncias específicas de algum tipo de SSRS
RDL
arquivo ou SSIS
DTSX
pacote para lidar com essas mudanças. Que os dados saiam da Stored Procedure
forma que precisamos que sejam exibidos/apresentados.
Vamos supor que eu construo um Dynamic SQL Command
onde a saída se parece com:
SELECT
Col1 AS 'Alias1',
Col2 AS 'Alias2',
Col3 AS 'Alias3'
FROM View
WHERE DateCol >= @StartDate
AND DateCol < @EndDate
Ele é construído a partir de algumas partes diferentes usando algumas tabelas. As estruturas de tabela abaixo são mais pseudocódigos para transmitir as ideias, então ignore coisas como não há chaves primárias declaradas, etc ...
CREATE TABLE [report].[ReportTemplate]
(
ID INT NOT NULL, --(Primary Key)
ReportName VarChar(100) NOT NULL,
ReportTypeID INT NOT NULL --(FK To report.ReportTemplateType.ID)
)
CREATE TABLE [report].[ReportTemplateType]
(
ID INT NOT NULL, --(Primary Key)
Name VarChar(50), --(Unique Constraint)
BaseCommand VarChar(2000), --Holds FROM and JOIN clauses
WhereCommand VarChar(2000), --Holds WHERE Clause
WhereCommandParameters VarChar(2000), --Holds declaration of the parameters
)
CREATE TABLE [report].[ReportTemplateColumnDetails]
(
ID INT NOT NULL, --(Primary Key)
ReportTemplateID INT NOT NULL, --(FK to report.ReportTemplate.ID)
ColumnName VarChar(256) NOT NULL,
ColumnAlias VarChar(256) NULL, --Have logic handle blank vs NULL values
ColumnOrder SmallInt NOT NULL
)
+----+-------------------+--------------+
| ID | ReportName | ReportTypeID |
+----+-------------------+--------------+
| 1 | Customer 1 Status | 1 |
| 2 | Customer 1 Sales | 2 |
+----+-------------------+--------------+
+----+--------+-----------------+------------------------------------------------------------------------------+-------------------------------------------------------------------+
| ID | Name | BaseCommand | WhereCondition | WhereConditionParameters |
+----+--------+-----------------+------------------------------------------------------------------------------+-------------------------------------------------------------------+
| 1 | Status | FROM StatusView | WHERE DateCol >= @StartDate AND DateCol < @EndDate | @StartDate DATEIME, @EndDate DateTime |
| 2 | Sales | FROM SalesView | WHERE DateCol >= @StartDate AND DateCol < @EndDate AND Col4 = @TypeParameter | @StartDate DATEIME, @EndDate DateTime, @TypeParameter VarChar(20) |
+----+--------+-----------------+------------------------------------------------------------------------------+-------------------------------------------------------------------+
+----+------------------+------------+-------------+-------------+
| ID | ReportTemplateID | ColumnName | ColumnAlias | ColumnOrder |
+----+------------------+------------+-------------+-------------+
| 1 | 1 | Col1 | Alias1 | 1 |
| 2 | 1 | Col2 | Alias2 | 2 |
| 3 | 1 | Col3 | Alias3 | 3 |
| 4 | 2 | Col4 | Alias1 | 1 |
| 5 | 2 | Col5 | Alias2 | 2 |
| 6 | 2 | Col6 | Alias3 | 3 |
+----+------------------+------------+-------------+-------------+
O comando é construído usando o código abaixo:
CREATE PROCEDURE [report].[ExecuteReportTemplate] (@ReportName VarChar(50))
AS
BEGIN
DECLARE @SQLCommand VarChar(MAX) = 'SELECT ',
@FirstColumnAdded BIT = 0,
@BaseCommand VarChar(2000),
@WhereCondition VarChar(2000),
@WhereConditionParameters VarChar(2000)
SELECT @BaseCommand = RTT.BaseCommand,
@WhereCondition = RTT.WhereCommand,
@WhereConditionParameters = RTT.WhereCommandParameters
FROM [report].[ReportTemplateType] RTT
INNER JOIN [report].[ReportTemplate] RT
ON RTT.ID = RT.ReportTypeID
WHERE RT.Name = @ReportName
DECLARE @ColumnName VarChar(256),
@ColumnAlias VarChar(256)
DECLARE ColumnCursor CURSOR FOR
SELECT ColumnName,
ColumnAlias
FROM [report].[ReportTemplateColumnDetails]
ORDER BY ColumnOrder
FETCH NEXT FROM ColumnCursor INTO @ColumnName, @ColumnAlias
WHILE (@@FETCH_STATUS = 0)
BEGIN
--Add a comma inbetween columns, does not happen on the first one
IF(@FirstColumnAdded = 1)
BEGIN
SET @SQLCommand = @SQLCommand + ', '
END
ELSE
BEGIN
SET @FirstColumnAdded = 1
END
--Adds the column into the list
SET @SQLCommand = @SQLCommand + @ColumnName
--If we have been provided an alias, set the alias
IF(@ColumnAlias IS NULL OR LTRIM(RTRIM(@ColumnAlias)) = '')
BEGIN
@SQLCommand = @SQLCommand + 'AS ''' + @ColumnAlias + ''' '
END
END
CLOSE ColumnCursor
DEALLOCATE ColumnCursor
--Now Add The Base Command
SELECT @SQLCommand = @SQLCommand + ' ' + @BaseCommand + ' ' + @WhereCommand
EXECUTE sp_executesql @sqlCommand, @WhereConditionParameters
@StartDate = '2019-01-01',
@EndDate = GETDATE()
END
Existe uma maneira de alterar dinamicamente os parâmetros configurados e passados sem ter que criar um comando separado?
Eu gostaria de ser capaz de preencher [report].[ReportTemplateType].[WhereCondition]
e [report].[ReportTemplateType].[WhereCondition]
com um diferente WHERE
e Parameters
. Por exemplo, adicionar um terceiro column
em WHERE condition
algo como Col4 = @TypeParameter
. A única maneira que conheço de resolver isso é criar um diferente Stored Procedure
onde tudo seja idêntico ao acima Stored Procedure
, mas mudaríamos a última peça para:
EXECUTE sp_executesql @sqlCommand, @WhereConditionParameters
@StartDate = '2019-01-01',
@EndDate = GETDATE(),
@TypeParameter = 'SomeStringValue'
Existe uma maneira de alterar dinamicamente os parâmetros configurados e passados sem ter que criar um comando separado?
Muito longo para um comentário, então despejei isso para uma resposta.
Minha preferência pessoal é que o SQL dinâmico (DSQL) nunca deve ser usado por código de produção que não seja de natureza administrativa . Eu uso o DSQL o tempo todo quando se trata de gerenciar ambientes, mas nenhum deles realmente exige um nível significativo de desempenho. No momento em que você começar a enviar o Dynamic SQL como código de produção , inevitavelmente terá problemas de desempenho . Nesse momento, as rodas saem porque o DSQL é notório para solucionar problemas. Obviamente, isso é apenas uma opinião e você é livre para fazer o que quiser, mas eu desencorajaria fortemente o uso de DSQL em qualquer código que você enviar para produção.
Antes de ir mais longe neste caminho, eu recomendo a leitura, o que eu sinto é o artigo definitivo sobre SQL dinâmico, por Erland Sommarskog: The Curse and Blessings of Dynamic SQL
É uma leitura muito boa; apenas esteja avisado que levará um pouco de tempo para digerir tudo.
Vou pular da minha caixa de sabão agora....
Quanto à sua pergunta:
Sim, você pode precisar aninhar seu DSQL ou adicionar tabelas de relatório adicionais de acordo, mas apenas seus requisitos e abordagem ditarão o caminho apropriado. Se você é a pergunta mais urgente, e obviamente eu diria que não.
Dos comentários:
Não há problema em usar
dynamic Sql
na medida do possível"Blessing Part of Dynamic Sql"
.Eu não estou entrando muito nisso.
Sua exigência em pequena escala,
Haverá UI onde você preencherá
UserPrefReport
. Não deve haver nenhum bug durante o preenchimento. Você preencherá o valor'FirstName as [FName],MiddleName as [MName]'
na mesma ordem que o usuário desejar.atualmente não há uso de
Reportid
.Ou você pode criar outra tabelaTableorView
e referenciarReportid
aqui.Não é grande coisa.Sua exigência em grande escala,
Isso significa que você também está dando ao usuário a facilidade de pesquisar nesses campos. E apenas esses campos devem ser preenchidos.
Nesse caso, o design do UserPrefReport será dessa maneira,
Em ambos os casos, a população de
UserPrefReport
é muito importante.Diga-me qual parte do seu requisito estou faltando no meu script ??