Expand all view-queries in query - sql

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.

Related

Best way to join this into a temp table and inner join

Just wondering the best way to put this into a temp table and then join it.
IF EXISTS(SELECT LocId
FROM dbo.Locations WITH (NOLOCK)
WHERE SourceSystem = #SourceSystem
AND LocId IN (SELECT ListVal
FROM etopsuser.fnParseListToTable(#LocIdList, ';')) AND IsHot = 1)
BEGIN
Specifically trying to do it on this line of code
(SELECT ListVal
FROM etopsuser.fnParseListToTable(#LocIdList, ';')) AND IsHot = 1)
The NOLOCK is unrelated
You would create a temporary table just like any other table from a select:
SELECT ListVal
INTO #templist
FROM etopsuser.fnParseListToTable(#LocIdList, ';');
Then you would use it as:
SELECT l.LocId
FROM dbo.Locations l JOIN
#templist tl
ON l.LocId = tl.Listval
WHERE l.SourceSystem = #SourceSystem AND l.IsHot = 1
The best way to pass a list into a procedure is to use a Table Valued Parameter
CREATE TYPE dbo.List AS TABLE (ListVal varchar(255));
IF EXISTS(SELECT 1
FROM dbo.Locations l
WHERE l.SourceSystem = #SourceSystem
AND l.LocId IN (
SELECT ll.ListVal
FROM #LocIdList ll
) AND IsHot = 1)
Notes: Always use table references on every column, especially if subqueries are involved. Never use NOLOCK unless you are prepared for incorrect results. EXISTS ignores its SELECT, so SELECT 1 or SELECT NULL is standard.
Then you can pass in the table variable either from client code depending on language, or in T-SQL like this
DECLARE #list dbo.List;
INSERT #list (ListVal)
VALUES ('SomeValue');
EXEC YourProc #LocIdList = #list;

Conditional statement to determine inner join or left outer join?

I have a large millions+ rows of record that in a table that I want to do an inner join when a parameter is provided, if not do a filter based search. Long term solution is to split up the logic, but I need some assistance in the short term.
I'm trying to do something like below:
params:
#movie_ids int_table (optional passing in a table of only ints here)
#filter_name
#filter_genre
#filter_actor_name
DECLARE #id_count = sets movie id count here or 0;
SELECT
m.Id
m.Name
m.ShortName
m.Genre
m.ImageUrl
FROM Movies m WITH(NOLOCK)
IF movie_ids <> 0
BEGIN
INNER JOIN #movie_ids mids
ON mids.id = m.Id
END
ELSE
LEFT OUTER JOIN code_genres cg
ON m.GenreId = cg.Id
INNER JOIN Actors a
ON a.name LIKE '%#filter_actor_name%'
END
WHERE m.Name LIKE '%filter_name%'
OR m.ShortName LIKE '%filter_name%'
Sorry I couldn't provide the actual stored procedure since I'm not the original writer. The problem in short term I'm trying to figure out is to find out if I can do this conditional joins.
Currently it has a LEFT OUTER JOIN with a where clause that suppose to do the conditional, but the amount of data it is returning before the filter occurs is too much - too slow. Something like here
Let me know if I can provide more details.
(Long terms solution is done - split up the logic from sql, but I need something for now)
As Programnik mentioned, you can go for dynamic sql here. You can try something like this:
DECLARE #sql VARCHAR(1000) = '';
#sql = #sql + 'SELECT
m.Id
m.Name
m.ShortName
m.Genre
m.ImageUrl
FROM Movies m WITH(NOLOCK)';
IF movie_ids <> 0
BEGIN
#sql = #sql + ' INNER JOIN #movie_ids mids
ON mids.id = m.Id';
END
ELSE
#sql = #sql + ' LEFT OUTER JOIN code_genres cg
ON m.GenreId = cg.Id
INNER JOIN Actors a
ON a.name LIKE' +'%#filter_actor_name%';
END
#sql = #sql + ' WHERE m.Name LIKE' + '%filter_name%';
#sql = #sql + ' OR m.ShortName LIKE' + '%filter_name%';
EXEC sp_executesql #sql

Columns not showinng in DataSet for dynamic 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...

SQL syntax issue using "IN" keyword with dynamic data

I am using SQL Server 2000 and having a wierd issue with a SQL code block (below):
A user can enter "GM" as a possible parameter or "F". If the user enters "GM" as a parameter in the stored proc query string, I need the AutoDivision to include GMC, CAD, CHE,SAT
declare #AutoDivision as varchar(15)
set #AutoDivision = 'GM'
if #AutoDivision = 'GM'
Begin
Select #AutoDivision = '''Cad'', ''GMC'', ''Sat'', ''Che'''
End
SELECT
oh.OrderNumber, lg.[lgh_number]
FROM
[dbo].[OrderHeader] oh (NOLOCK)
INNER JOIN
[dbo].[DistrctHeader] lg (NOLOCK) ON oh.[inv_number] = lg.[inv_number]
INNER JOIN
[dbo].[DealerCompany] c (NOLOCK) ON c.cmp_id = LEFT(oh.[ordernumber],3)
INNER JOIN
[dbo].[divisionXREF] x (NOLOCK) ON x.Division = c.comp_revtype
WHERE
oh.ord_number = '113-889257'
AND x.AutoDivision IN (#AutoDivision)
--AND x.AutoDivision IN ('Cad', 'Sat', 'GMC', 'Che')
AND lg.[lgh_outstatus] IN ('AVAIL', 'PLAN', 'DISP', 'STRTD', 'PEND','COMP')
However, when I run the code below, I don't get back any records.
When I uncomment the code line
--AND x.AutoDivision IN ('Cad', 'Sat', 'GMC', 'Che')
it works (I get a record returned).
When I do a print 'AND x.AutoDivision IN (' + cast(#AutoDivision as varchar) + ')'
I get back AND x.AutoDivision IN ('Cad', 'GMC', 'Sat', 'Che')
What am I missing here?
You can't use a single variable to represent a comma separated list of IN parameters in SQL - Oracle, MySQL, SQL Server... doesn't matter.
To get this variable method to work, you need to use dynamic SQL so you are able to create the query as a string first (using concatenation to get the IN parameters from your variable), and the query statement is executed afterwards:
DECLARE #cmd VARCHAR(1000)
SET #cmd = 'SELECT oh.OrderNumber,
lg.[lgh_number]
FROM [dbo].[OrderHeader] oh (NOLOCK)
JOIN [dbo].[DistrctHeader] lg (NOLOCK) ON oh.[inv_number] = lg.[inv_number]
JOIN [dbo].[DealerCompany] c (NOLOCK) ON c.cmp_id = LEFT(oh.[ordernumber],3)
JOIN [dbo].[divisionXREF] x (NOLOCK) ON x.Division = c.comp_revtype
WHERE oh.ord_number = '113-889257'
AND x.AutoDivision IN ('+ #AutoDivision +')
AND lg.[lgh_outstatus] IN (''AVL'', ''PLN'', ''DSP'', ''STD'', ''PND'',''CMP'') '
EXEC(#cmd)
I recommend reading The Curse and Blessings of Dynamic SQL before implementing a dynamic SQL solution.
Table Valued Function
A table valued function would allow you do what you want without using dynamic SQL -- there's more info in this article.
To avoid this, you can create a temp table, fill it in, then use
IN (SELECT myField from #myTable)
Even though you concatenate what looks like a few different arguments your IN clause is actually testing it as a single string (you did declare it as a varchar) and it's no surprise that no records match that predicate.
Looks like you're trying to mix dynamic sql and a standard query. That won't work. Your query either has to be all dynamically created and then specially executed or your IN clause has to be inputed with individual arguments which can be done by subqery.
You could do something like:
... AND x.AutoDivision IN
(SELECT Division WHERE Corp = 'GM') ...
OR
... AND x.AutoDivision IN
(SELECT 'Cad' UNION SELECT 'GMC' UNION SELECT 'Sat' UNION SELECT 'Che') ...
Try this:
SELECT
oh.OrderNumber, lg.[lgh_number]
FROM
[dbo].[OrderHeader] oh (NOLOCK)
INNER JOIN
[dbo].[DistrctHeader] lg (NOLOCK) ON oh.[inv_number] = lg.[inv_number]
INNER JOIN
[dbo].[DealerCompany] c (NOLOCK) ON c.cmp_id = LEFT(oh.[ordernumber],3)
INNER JOIN
[dbo].[divisionXREF] x (NOLOCK) ON x.Division = c.comp_revtype
WHERE
oh.ord_number = '113-889257'
AND x.AutoDivision IN
(SELECT 'Cad' UNION SELECT 'GMC' UNION SELECT 'Sat' UNION SELECT 'Che')
AND lg.[lgh_outstatus] IN ('AVL', 'PLN', 'DSP', 'STD', 'PND','CMP')
That is an inline subquery that UNIONs arbitrary strings into a result set. (Take with a grain of salt. I am a long way from a Sql Server interface.)
AND x.AutoDivision IN (#AutoDivision)
--AND x.AutoDivision IN ('Cad', 'Sat', 'GMC', 'Che')
Can you clarify these two lines of code, are they not doing the same thing?
CREATE FUNCTION [dbo].[Split]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
GO
DECLARE #AutoDivision varchar(50) = NULL
SET #AutoDivision ='Cad, Sat, GMC, Che'
SELECT * FROM divisionXREF P INNER JOIN dbo.SPLIT(ISNULL(#AutoDivision,''),',') as T ON P.lgh_outstatus = CASE T.strval WHEN '' THEN P.PG_CODE ELSE T.strval END

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.