Columns not showinng in DataSet for dynamic SQL - sql

I have the following SP which works correctly when ran on its own:
ALTER PROCEDURE [dbo].[sgetInvoiceHeaderDetails]
#InvoiceNo varchar(max)
AS
BEGIN
SET FMTONLY ON;
declare #sql varchar(max)
set #sql = 'SELECT IH.InvoiceNo, IH.InvoiceDate, IH.InvoiceTime, C.Name, R.Name AS Customer, IH.NetAmount,
IM.Name AS Item, ID.UnitPrice, ID.Qty, ID.Total, ID.BatchNo
FROM InvoiceHeader AS IH INNER JOIN
InvoiceDetail AS ID ON IH.InvoiceNo = ID.InvoiceNo INNER JOIN
Customer AS C ON IH.CustomerId = C.Id INNER JOIN
Route AS R ON IH.RouteId = R.Id INNER JOIN
ItemMaster AS IM ON ID.ItemMasterId = IM.Id
WHERE IH.InvoiceNo IN ('+#InvoiceNo+')'
print #sql
exec (#sql)
END
The problem I'm having is that when I add a DataSet for a report, it pulls no fields/columns in the Fields section. I'm guessing it's due to the dynamic SQL?
How can I resolve that?

As statet in my comment you should avoid the dynamic approach.
Just to offer you a pure inline solution in SQL have a look at this:
DECLARE #tbl TABLE(ID INT, Caption VARCHAR(100));
INSERT INTO #tbl VALUES(1,'Test 1'),(2,'Test 2'),(3,'Test 3'),(4,'Test 4'),(5,'Test 5');
DECLARE #WantToGet VARCHAR(100)='1,3,4';
WITH Splitted AS
(
SELECT CAST('<x>' + REPLACE(#WantToGet,',','</x><x>') + '</x>' AS XML) AS AsXml
)
,SplittedAsList AS
(
SELECT The.Node.value('.','int') As ID
FROM Splitted
CROSS APPLY AsXml.nodes('/x') AS The(Node)
)
SELECT Caption
FROM #tbl AS tbl
INNER JOIN SplittedAsList sal ON sal.ID = tbl.ID;
The string 1,3,4 is splitted as a list. The INNER JOIN at the end is exactly the same as you wanted to achieve with the IN-clause.
This approach you can plcae within a table valued function (make sure to keep this as inline function!). This function is much better reusable everywhere.
The second recommandable approach would be the CREATE TYPE, bute this needs more action on application side...

Related

Simple query that should be working is not

I have a simple query:
declare #manual varchar(80) = '''Discount'',''Misc Charges'''
select *
from #Final
where charge_type in (#manual)
Now I've gone as far as verifying my declared variable is setup correctly by using the PRINT command as follows: PRINT '''Discount'',''Misc Charges''' and it in fact returns as I would expect: 'Discount','Misc Charges'.
However, when I run this query, I get no results.
If I instead simply use:
select *
from #Final
where charge_type in ('Discount','Misc Charges')
Then no problem, I get my results. I'm sure I'll kick myself once I get the answer, but as of right now, this is just not making sense. No errors, it's just not giving me my columns without any rows as if there's no data. What am I missing?
Because
IN ('''Discount'',''Misc Charges''')
is the same as
= '''Discount'',''Misc Charges'''
In other words, that is one single string that contains a bunch of escaped string delimiters, not a comma-separated list of individual string values. Which is why you can do this without SQL Server barfing:
PRINT '''Discount'',''Misc Charges''';
What you want is:
declare #manual varchar(80) = 'Discount,Misc Charges';
select f.*
from #Final AS f
INNER JOIN STRING_SPLIT(#manual, ',') AS s
ON f.charge_type = s.value;
However that can fail on compatibility_level < 130, in which case:
declare #manual varchar(80) = 'Discount,Misc Charges';
select f.*
from #Final AS f
INNER JOIN
OPENJSON('["' + REPLACE(#manual, ',', '","') + '"]') AS s
ON f.charge_type = s.value;
In the latter case you can make the query itself a little nicer by using slightly different jacked-up strings in the variable declaration:
declare #manual varchar(80) = '["Discount","Misc Charges"]';
select f.*
from #Final AS f
INNER JOIN
OPENJSON(#manual) AS s ON f.charge_type = s.value;
Or if you are on an older version and you really are hand-crafting these strings inline, you can use a table variable or CTE like #SMor suggested.
Table variable:
DECLARE #d table(str varchar(32));
INSERT #d VALUES('Discount'),('Misc Charges');
SELECT f.*
from #Final AS f
INNER JOIN #d AS d
ON f.charge_type = d.str;
CTE:
;WITH cte AS
(
SELECT str = 'Discount'
UNION ALL
SELECT str = 'Misc Charges'
)
SELECT f.*
from #Final AS f
INNER JOIN cte
ON f.charge_type = cte.str;
If you'll have more values at some point, it tips to writing a table constructor instead of multiple UNION ALLs, e.g.
;WITH cte AS
(
SELECT str FROM
(
VALUES('Discount','Misc Charges')
) AS s(str)
)
SELECT f.*
from #Final AS f
INNER JOIN cte
ON f.charge_type = cte.str;
You can use just use your list of values as comma seperated string & then use STRING_SPLIT.
declare #manual varchar(80) = 'Discount,Misc Charges'
select *from #Final
where charge_type in (SELECT * from STRING_SPLIT(#manual,',))
Here is to to do in SQL Server 2016 onwards.
SQL
DECLARE #manual VARCHAR(80) = 'Discount,Misc Charges';
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, charge_type VARCHAR(30));
INSERT INTO #tbl (charge_type) VALUES
('Discount'),
('No Discount'),
('Misc Charges');
SELECT *
FROM #tbl
WHERE charge_type in (SELECT value FROM STRING_SPLIT(#manual, ','))

Replacing Is Null Or Exist Inner Query Logic to Reduce Stored Procedure Execution Time

My stored procedure is currently using Is Null Or Exist logic combined with an inner query to filter out the records. The stored procedure is converting multiple comma-separated input values to temp tables (in the production scenario, the input record count will be much higher). And the inner query is using these temp tables for filter conditions. Due to the concern over query execution time would like to change the existing inner-query with an alternate (like left join). But need to retain the same Is Null Or Exist logic. Any suggestions?
DECLARE #SelectedOfferes varchar(1000) = 'FLT10,SPL20'
DECLARE #SelectedBrandCode varchar(1000) = '208,406'
DECLARE #CategoryCode varchar(1000) = 'GMOVN2,CELSMR,LCDTV38IN'
CREATE TABLE #SelectedOfferes
(
DiscountCode VARCHAR(20)
)
CREATE TABLE #BrandCode
(
BrandCode VARCHAR(20)
)
CREATE TABLE #CategoryCode
(
CategoryCode VARCHAR(20)
)
IF #SelectedOfferes IS NOT NULL
BEGIN
INSERT INTO #SelectedOfferes
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedOfferes, ',')
END
IF #SelectedBrandCode IS NOT NULL
BEGIN
INSERT INTO #BrandCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedBrandCode, ',')
END
IF #CategoryCode IS NOT NULL
BEGIN
INSERT INTO #CategoryCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#CategoryCode, ',')
END
SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode
WHERE (#SelectedOfferes IS NULL
OR (EXISTS (SELECT 1 FROM #SelectedOfferes OFR
WHERE OFR.DiscountCode = P.DiscountCode)))
AND (#SelectedBrandCode IS NULL
OR (EXISTS (SELECT 1 FROM #BrandCode BC
WHERE BC.BrandCode = P.BrandCode)))
AND (#CategoryCode IS NULL
OR (EXISTS (SELECT 1 FROM #CategoryCode CAT
WHERE CAT.CategoryCode = P.CategoryCode)))
Dynamic SQL version
I have some questions about your string split function, is it set-based or a looping query? If it's not set-based then you should probably replace it with Jeff Moden's DelimitedSplit8K available at http://www.sqlservercentral.com/articles/Tally+Table/72993/ .
The below example should work the same as what you supplied but should be faster since it removes the ORs and the correlated subqueries from the WHERE clause. I'm not a fan of using dynamic SQL but sometimes it is the best way to get the job done. Maybe someone else can come up with a non-dynamic solution that works as well or better.
DECLARE #SelectedOfferes varchar(1000) = 'FLT10,SPL20'
DECLARE #SelectedBrandCode varchar(1000) = '208,406'
DECLARE #CategoryCode varchar(1000) = 'GMOVN2,CELSMR,LCDTV38IN'
CREATE TABLE #SelectedOfferes
(
DiscountCode VARCHAR(20)
)
CREATE TABLE #BrandCode
(
BrandCode VARCHAR(20)
)
CREATE TABLE #CategoryCode
(
CategoryCode VARCHAR(20)
)
IF #SelectedOfferes IS NOT NULL
BEGIN
INSERT INTO #SelectedOfferes
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedOfferes, ',')
END
IF #SelectedBrandCode IS NOT NULL
BEGIN
INSERT INTO #BrandCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedBrandCode, ',')
END
IF #CategoryCode IS NOT NULL
BEGIN
INSERT INTO #CategoryCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#CategoryCode, ',')
END
DECLARE #SQL NVarchar(4000);
SET #SQL = N'SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode'
IF #SelectedOfferes IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #SelectedOfferes OFR ON OFR.DiscountCode = P.DiscountCode';
IF #SelectedBrandCode IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #BrandCode BC ON BC.BrandCode = P.BrandCode';
IF #CategoryCode IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #CategoryCode CAT ON CAT.CategoryCode = P.CategoryCode';
EXEC sys.sp_executesql #stmt = #SQL;
This method doesn't quite do what the OP wanted but is valid in many other cases
I have some questions about your string split function, is it set-based or a looping query? If it's not set-based then you should probably replace it with Jeff Moden's DelimitedSplit8K available at http://www.sqlservercentral.com/articles/Tally+Table/72993/ .
But either way the below change to your last query should help quite a bit. The IS NULL parts aren't needed since it is a LEFT JOIN and the table will be empty if the variable it is built with is NULL, so you get the same result with less work for the engine.
SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode
LEFT JOIN #SelectedOfferes OFR ON OFR.DiscountCode = P.DiscountCode
LEFT JOIN #BrandCode BC ON BC.BrandCode = P.BrandCode
LEFT JOIN #CategoryCode CAT ON CAT.CategoryCode = P.CategoryCode

Expand all view-queries in query

I have queries like this:
SELECT
*
FROM vwUsers
LEFT JOIN tblLocations
ON vwUsers.id = tblLocations.owner
Is there an automated way to expand out this query to include the view statement (and any nested view statements)? I'd like to end up with something like:
SELECT * FROM
(SELECT id,name, -- vwUsers expanded into it's defining statement
FROM tblUsers
LEFT JOIN tblNames
ON tblUsers.id = tblNames.id) AS vwUsers
LEFT JOIN tblLocations
ON vwUsers.id = tblLocations.owner
The solution seems clear:
Create a list with all refereed views
Get their definitions
Replace the definitions in your query
Something like this in my environment:
DECLARE #Objects TABLE
(
[ObjectName] SYSNAME
,[Definition] NVARCHAR(MAX)
);
INSERT INTO #Objects ([ObjectName])
VALUES ('[dbo].[SurveyInstancesHistory]')
,('dbo.vw_MystClients');
UPDATE #Objects
SET [Definition] = M.[definition]
FROM #Objects O
INNER JOIN [sys].[objects] OB
ON OBJECT_ID([ObjectName]) = OB.[object_id]
INNER JOIN [sys].[sql_modules] M
ON OBJECT_ID([ObjectName]) = M.[object_id]
WHERE OB.[type] = 'V';
DECLARE #DynamicTSQLStatement NVARCHAR(MAX) = N'
SELECT *
FROM SurveyInstances SI
INNER JOIN [dbo].[SurveyInstancesHistory] SIH
ON SI.[SurveyInstanceID] = SIH.[SurveyInstanceID]
INNER JOIN ProtoSurveys PS
ON SI.[ProtoSurveyID] = PS.[ProtoSurveyID]
INNER JOIN dbo.vw_MystClients MC
ON PS.[ClientID] = MC.[Client<br>ID];'
SELECT #DynamicTSQLStatement = REPLACE(#DynamicTSQLStatement, [ObjectName], '(' + [Definition] + ') AS ' + [ObjectName])
FROM #Objects;
SELECT #DynamicTSQLStatement;
In order this to be automated the following cases must be handled:
we can automatically get the referred objects using regex matches
we can automatically replace the CREATE VIEW .... AS statement using regex
In you need a script that's always working in your example you need to write the regex expressions by yourself as people are creating views with different syntax. As the above can be used and edited by hand in order to run, a very complex script can be created depending on your needs.
Strongly recommenced to implement the String Utility Functions Sample in order to get regex support in T-SQL.

Could some explain this TSQL code for me?

I got the following stored procedure, but I am new to T-SQL and not quite sure about its syntax. Could some explain a little to me?
Question:
What does the #tmp syntax stand for?
Where is the c in c.RootCategoryId defined?
Here's the code:
ALTER PROCEDURE [dbo].[GetAllTopCategories]
#prewin7 bit
AS
SELECT * INTO #tmp FROM dbo.fnGetCategories(#prewin7)
SELECT
c.RootCategoryId CategoryId,
c.CategoryName + ' [' + CAST(g.DiagnosticCount AS nvarchar(max)) + ']' CategoryName,
#prewin7 as PreWin7
FROM (
SELECT
c.RootCategoryId CategoryId,
SUM(c.DiagnosticCount) DiagnosticCount
FROM #tmp c
GROUP BY c.RootCategoryId
) g
INNER JOIN #tmp c ON c.CategoryId = g.CategoryId
ORDER BY c.CategoryName
DROP TABLE #tmp
The #tmp is a local temporary table, e.g. it is only ever visible to the connection that created it, and once that connection is gone, so is the temporary table. So that DROP TABLE #tmp at the end is really not necessary - the table would be dropped automatically by SQL Server.
This temporary table was created here:
SELECT * INTO #tmp FROM dbo.fnGetCategories(#prewin7)
so it will contain any of the rows that the result set from the dbo.fnGetCategories stored function will return.
SELECT
c.RootCategoryId CategoryId,
SUM(c.DiagnosticCount) DiagnosticCount
FROM #tmp c
Here, data is selected from that temporary table, which has a table alias of c - so therefore, that c.RootCategoryId must be a column of that temporary table, and thus it must be one of the rows that is returned from the stored function called above.
Do you really need a temp table?
ALTER PROCEDURE [dbo].[GetAllTopCategories]
#prewin7 bit
AS
/*SELECT * INTO #tmp FROM dbo.fnGetCategories(#prewin7)*/
SELECT
c.RootCategoryId CategoryId,
c.CategoryName + ' [' + CAST(g.DiagnosticCount AS nvarchar(max)) + ']' CategoryName,
#prewin7 as PreWin7
FROM (
SELECT
c.RootCategoryId CategoryId,
SUM(c.DiagnosticCount) DiagnosticCount
FROM (select * FROM dbo.fnGetCategories(#prewin7)) c
GROUP BY c.RootCategoryId
) g
INNER JOIN (select * FROM dbo.fnGetCategories(#prewin7)) c ON c.CategoryId = g.CategoryId
ORDER BY c.CategoryName

How to join dynamic sql statement in variable with normal statement

I have a quite complicated query which will by built up dynamically and is saved in a variable.
As second part i have another normal query and i'd like to make an inner join between these both.
To make it a little more easier here is a little example to illustrate my problem.
For this little example i used the AdventureWorks database.
Some query built up dynamically
(Yes, i know there is nothing dynamic here, cause it's just an example.)
DECLARE #query AS varchar(max) ;
set #query = '
select
HumanResources.Employee.EmployeeID
,HumanResources.Employee.LoginID
,HumanResources.Employee.Title
,HumanResources.EmployeeAddress.AddressID
from
HumanResources.Employee
inner join HumanResources.EmployeeAddress
on HumanResources.Employee.EmployeeID = HumanResources.EmployeeAddress.EmployeeID
;';
EXEC (#query);
The normal query i have
select
Person.Address.AddressID
,Person.Address.City
from
Person.Address
Maybe what i'd like to have but doesn't work
select
#query.*
,Addresses.City
from
#query as Employees
inner join
(
select
Person.Address.AddressID
,Person.Address.City
from
Person.Address
) as Addresses
on Employees.AddressID = Addresses.AddressID
Use temp tables & have the records dumped into it (from the dynamic query) & use the temp table to join with the static query that you have.
set #query = 'CREATE table #myTempTable AS
select
HumanResources.Employee.EmployeeID
,HumanResources.Employee.LoginID
,HumanResources.Employee.Title
,HumanResources.EmployeeAddress.AddressID
from
HumanResources.Employee
inner join HumanResources.EmployeeAddress
on HumanResources.Employee.EmployeeID = HumanResources.EmployeeAddress.EmployeeID
;';
EXEC (#query);
And then
select
Employees.*
,Addresses.City
from
#myTempTable as Employees
inner join
(
select
Person.Address.AddressID
,Person.Address.City
from
Person.Address
) as Addresses
on Employees.AddressID = Addresses.AddressID
You might be on the right track, but as long as you trust the source of the parameter and not exposed for SQL-Injection, you might just have to alter your select when building your #query like:
parameter to your function '#YourAlternateTableParm'
DECLARE #query AS varchar(max) ;
set #query = 'select ' + #YourAlternateTableParm
+ '.*, Addresses.City
from ' + #YourAlternateTableParm
+ ' as Employees
inner join
( ..... '
This way, like you were building your original string, you are building the actual value of the parameter to a function/procedure call with the table name desired to represent your "Employees" file into the string, then you execute that. SQL is not dynamically interpretting the #query inline the way you were trying to do.