How to Retrieve Column Headers of a Select Query? - sql

How it is possible to retrieve column headers of a select query as a single column in SQL Server ? (it is preferred to retrieve data type of columns )
Query example:
select a.PartId, a.PartName, b.GroupName
from Parts as a
inner join Groups as b on a.GroupRef = b.GroupId
Expected result:
Columns
--------
PartId
PartName
GroupName

Starting from SQL Server 2012+ you can use sys.dm_exec_describe_first_result_set to get all metadata about result set:
DBFiddle Demo
DECLARE #tsql NVARCHAR(MAX) =
N'select a.PartId , a.PartName , b.GroupName
from Parts as a inner join Groups as b
on a.GroupRef = b.GroupId';
SELECT name AS [Columns]
FROM sys.dm_exec_describe_first_result_set(#tsql, NULL, 1)

One way is to create a temporary table with the schema of resultset and then query tempdb's schema table to get the column names and details. You can get all needed details.
select a.PartId , a.PartName , b.GroupName into #yourtable
from Parts as a inner join Groups as b
on a.GroupRef = b.GroupId
where 1=2
SELECT c.name as columnname,t.name as datatype
FROM tempdb.sys.columns c
inner join tempdb.sys.systypes as t on t.xtype = c.system_type_id
WHERE [object_id] = OBJECT_ID(N'tempdb..#yourtable');

SELECT 'PartId', 'PartName', 'GroupName'
UNION ALL
select a.PartId , a.PartName , b.GroupName
from Parts as a inner join Groups as b
on a.GroupRef = b.GroupId

Related

List the tables used by the sql script, and the nature of access to the table

I have a script with thousand of lines and I want to extract all the tables (& temp tables) the script references, and also the nature of access to the table such as select, insert, update or delete. Is there a tool we can use for this purpose?
Sample script (input):
SELECT * FROM Table1
UPDATE Table1 SET Col1 = 1
DELETE FROM Table2
EXEC 'INSERT INTO #Table3 SELECT ''Test'''
Sample output:
1. Table1 - select, update
2. Table2 - delete
3. #Table3 - insert
You may find the following query useful, however, one thing it doesn't give you is the references from dynamic SQL statements (and perhaps also table variables and temp tables).
I think to get the level of information you're looking for you'd probably need to write your own script/program. Previously I wrote something that gave me the results from the below query, including any dynamic SQL references, in C# using SQL Server Management Objects (SMO).
SELECT DISTINCT
OBJECT_SCHEMA_NAME(D.referencing_id) referencing_object_schema
, OBJECT_NAME(D.referencing_id) referencing_object_name
, O.[type_desc] referencing_object_type
, D.referenced_schema_name
, D.referenced_entity_name
, D.referenced_id
,
CASE
WHEN O_REF.[object_id] IS NOT NULL THEN O_REF.[type_desc]
WHEN T.user_type_id IS NOT NULL THEN 'USER_DEFINED_TYPE'
END referenced_object_type
FROM
sys.sql_expression_dependencies D
LEFT JOIN sys.objects O ON D.referencing_id = O.[object_id]
LEFT JOIN sys.objects O_REF ON D.referenced_id = O_REF.[object_id]
LEFT JOIN sys.types T ON D.referenced_id = T.user_type_id
There is a system function called: fn_dblog. You could use it as below:
IF OBJECT_ID('tempdb..#tempLog') IS NOT NULL DROP TABLE #tempLog
-- Raw Data
SELECT DISTINCT
[AllocUnitName],
Operation
INTO #tempLog
FROM sys.fn_dblog(NULL,NULL)
WHERE Operation IN ('LOP_INSERT_ROWS','LOP_MODIFY_ROW',
'LOP_DELETE_ROWS','LOP_BEGIN_XACT','LOP_COMMIT_XACT')
AND [AllocUnitName] is not null
ORDER By [AllocUnitName], Operation
-- SELECT * FROM #tempLog
-- Display Enhancement and Grouping
SELECT [AllocUnitName], Operations = STUFF((
SELECT N', ' + REPLACE(operation,'LOP_', '')
FROM #tempLog
WHERE [AllocUnitName] = x.[AllocUnitName]
FOR XML PATH(''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 2, N'')
FROM #tempLog AS x
WHERE [AllocUnitName] is not null
GROUP BY [AllocUnitName]
ORDER BY [AllocUnitName]

Stored procedure that takes in a dynamic number of joins

I am trying to build a stored procedure that takes in a dynamic number of tables and joins them.
SELECT a.Account
FROM Database.Accounts a
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 1) b ON a.Account = b.Account
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 2) c ON a.Account = c.Account
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 3) d ON a.Account = d.Account
...
/*Where the number of joins is equal to COUNT(Table_Name) FROM Database.TableStrings*/
or
first = SELECT a.Account
FROM Database.Accounts a
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 1) b ON a.Account = b.Account
second = SELECT a.Account
FROM first a
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 2) b ON a.Account = b.Account
...
Below is an example of the tables and the expected output:
---Database.Accounts---
Account (Integer)
------------------------
111
222
333
444
555
---Database.TableStrings---
Table_Name (String):
------------------------
'Database.HasPhoneNumber'
'Database.HasEmail'
'Database.HasBankAccount'
---Database.HasPhoneNumber---
Account (Integer)
------------------------
111
444
---Database.HasEmail---
Account (Integer)
------------------------
111
222
---Database.HasBankAccount---
Account (Integer)
------------------------
111
222
555
With the example tables, the expected output would be Account 111.
I am having a hard time visualizing how to do this in a stored procedure, if it is even possible.
you may consider using recursive queries (cte) to handle the string concatenation. and run sp_executesql to execute generated sql string query
lets generate also your table aliases using char()
declare #strSQL nvarchar(max), #strSQLjoin nvarchar(max)
set #strSQL = 'SELECT a.Account
FROM Database.Accounts a ';
set #strSQLjoin = '';
with cte as (
select 'Database.PhoneNumbers' as TableStrings
union all select 'Database.Emails'
union all select 'Database.BankAccounts'
), cte2 as (
select TableStrings, (char(row_number() over (order by TableStrings) + 97)) as tbl
from cte)
select #strSQLjoin = coalesce(#strSQLjoin
+ ' JOIN (SELECT Table_Name FROM '+TableStrings+') '+tbl+' ON a.Account = '+tbl+'.Account '
,' JOIN (SELECT Table_Name FROM '+TableStrings+') '+tbl+' ON a.Account = '+tbl+'.Account')
from cte2;
set #strSQL=#strSQL + #strSQLjoin;
print #strSQL
exec sp_executesql #strSQL
I understand that this is not exactly what you asked for. But since adding new rows to the TableStrings table already implies schema changes (creating new tables), at this point you might as well update your stored procedure accordingly instead of using dynamic SQL
If that's an option then for example your query may look like
SELECT a.Account
FROM Database.Accounts a
where exists (select 1 from Database.HasPhoneNumber e where e.Account = a.Account)
and exists (select 1 from Database.HasEmaile e where e.Account = a.Account)
and exists (select 1 from Database.HasBankAccount e where e.Account = a.Account)
...
based on your sample data and your sample query, you can achieve what you want without using dynamic query
-- sample table
declare #Accounts table
(
Account int
)
declare #TableStrings table
(
Table_Name varchar(100),
Account int
)
-- sample data
insert into #Accounts
values (111), (222), (333), (444), (555)
insert into #TableStrings
values ('Database.HasPhoneNumber', 111), ('Database.HasPhoneNumber', 444),
('Database.HasEmail', 111), ('Database.HasEmail', 222),
('Database.HasBankAccount', 111), ('Database.HasBankAccount', 222),
('Database.HasBankAccount', 555)
select a.Account
from #Accounts a
inner join #TableStrings t on a.Account = t.Account
group by a.Account
having count(*) = (select count(distinct Table_Name) from #TableStrings)

Accessing table returning by table-valued function from SP

I have table valued function
ALTER FUNCTION [dbo].[fn_Functiont]()
RETURNS TABLE
AS
RETURN
(
SELECT d.*, b.Name AS Name, ps.Name AS PaymentSystemName, c.UserName AS UserName, c.FirstName AS ClientFirstName, c.LastName AS LastName, c.Number AS DocumentNumber, c.Id
FROM Document AS d
JOIN System AS ps ON d.SystemId = ps.Id
JOIN Client AS c ON c.Id = d.ClientId
LEFT JOIN Shop AS b ON b.Id = d.ShopId
WHERE d.OperationTypeId IN (2, 4, 5) AND c.Type = 1
)
And SP. In that SP i have declared temporary table like this
DECLARE #tempTable AS TABLE
(
.. columns here ...
)
after declaring i just inserting info
INSERT INTO #tempTable
SELECT * FROM [dbo].[fn_Functiont]()
Select #column1,colum2...,from #tempTable
The problem is that i have to declare a lot of columns in #temptable and code looks like ugly.So is there a better way to reading rows in SP from table valued function?
Instead of table Variable #tempTable Use Temp Table and Try This
SELECT * INTO #tempTable FROM [dbo].[fn_Functiont]()

Existing query optimization

We have 5 tables and we are trying to create a view to get the results.
Below is the view which is working fine.
I need suggestions. Is it a good practice to write this query in this way or it can be optimized in a better way.
SELECT p.Pid, hc.hcid, hc.Accomodation, ghc.ghcid, ghc.ProductFeatures, wp.existing, wp.acute, mc.cardiaccover, mc.cardiaclimitationperiod
FROM TableA p
LEFT JOIN TableB hc
ON p.pid = hc.pid
LEFT JOIN TableC ghc
ON p.pid = ghc.pid
LEFT JOIN (SELECT *
FROM (SELECT hcid,
title,
wperiodvalue + '-' + CASE WHEN
wperiodvalue > 1 THEN
unit +
's' ELSE
unit END wperiod
FROM TableD) d
PIVOT ( Max(wperiod)
FOR title IN (acute,
existing
) ) piv1) wp
ON hc.hcid = wp.hcid
LEFT JOIN (SELECT *
FROM (SELECT hcid,
title + col new_col,
value
FROM TableE
CROSS apply ( VALUES (cover,
'Cover'),
(Cast(limitationperiod AS
VARCHAR
(10)),
'LimitationPeriod') ) x (value, col
)) d
PIVOT ( Max(value)
FOR new_col IN (cardiaccover,
cardiaclimitationperiod,
cataracteyelenscover,
cataracteyelenslimitationperiod
) ) piv2) mc
ON hc.hcid = mc.hcid
Any suggestions would be appreciated.
Thanks
My suggestion is to break down the query using temporary table, create stored procedure then dump data in the one new table and with the help of that table you can create view:
Store both PIVOT result in tow seperate temp tables as
SELECT * INTO #pvtInfo FROM ( --first PIVOT query
SELECT * INTO #pvtInfoTwo FROM ( --second PIVOT query
Then your final query will be as :
SELECT p.Pid,
hc.hcid,
hc.Accomodation,
ghc.ghcid,
ghc.ProductFeatures,
wp.existing,
wp.acute,
mc.cardiaccover,
mc.cardiaclimitationperiod
FROM TableA p
LEFT JOIN TableB hc ON p.pid = hc.pid
LEFT JOIN TableC ghc ON p.pid = ghc.pid
LEFT JOIN #pvtInfo wp ON hc.hcid = wp.hcid
LEFT JOIN #pvtInfoTwo mc ON hc.hcid = mc.hcid
First you can try then only go with SP and VIEW.
Hope, It will help.

How to check intersection of subqueries in query?

I have the next query:
SELECT c.client_code, a.account_num, m.account_close_date, u.uso, m.product_name
FROM accounts a INNER JOIN Clients c ON c.id = a.client_id INNER JOIN
Uso u ON c.uso_id = u.uso_id INNER JOIN Magazine m ON a.account_id = m.account_id
and I need to compare product_name with input parameter.
product_name and input parameter #s are comma-delimited strings.
I use next split function:
ALTER FUNCTION [dbo].[Split]
(
#s VARCHAR(max),
#split CHAR(1)
)
RETURNS #temptable TABLE (items VARCHAR(MAX))
AS
BEGIN
DECLARE #x XML
SELECT #x = CONVERT(xml,'<root><s>' + REPLACE(#s,#split,'</s><s>') + '</s></root>');
INSERT INTO #temptable
SELECT [Value] = T.c.value('.','varchar(20)')
FROM #X.nodes('/root/s') T(c);
RETURN
END;
I think that I need to check the intersection of tables, which I will receive after split of product_name and after split of input parameter. I trid to do this:
WHERE (select * from dbo.Split(m.product_name, ';')
INTERSECT select * from dbo.Split('product1;product2',';'))
is not null
But it does not work quite right. Please, help me.
INTERSECT requires the same column output and is used like UNION or EXCEPT: not in the WHERE clause
Just JOIN onto the udf
...
INNER JOIN
Magazine m ON a.account_id = m.account_id
INNER JOIN
dbo.Split(#parameter, ';') CSV ON m.productname = CSV.items
If you need to split m.productname, if you can't fix the design, use CROSS APPLY
...
INNER JOIN
Magazine m ON a.account_id = m.account_id
CROSS APPLY
dbo.Split(m.productname, ';') WTF
INNER JOIN
dbo.Split(#parameter, ';') CSV ON WTF.items = CSV.items
However, JOIN and INTERSECT give different results if #parameter has duplicated values. Add a DISTINCT to the UDF for example to get around this. Or change the udf JOIN into EXISTS