Using a recursive CTE in a view - sql

Having a typical parent/child hierarchy table it's a common thing to query it using Common Table Expression:
with CTE as (
select Id, ProviderId, ConsumerId
from T1
where ProviderId in (2, 3, 9)
union all
select T1.Id, T1.ProviderId, T1.ConsumerId
from T1
join CTE on C.ProviderId = CTE.ConsumerId
)
select * from CTE
Is it possible to create a view based on this query so that one can do:
select * from MagicView where ProviderId in (2,3,9)
In other words, can we somehow extract parameters from the anchor part of the CTE to create a generic view?

Create a TVF:
CREATE FUNCTION my_function (
#ProviderId int
)
RETURNS #ProviderTable TABLE
(
Id int NULL,
ProviderId int NULL,
ConsumerId int NULL
)
AS
BEGIN
WITH cte AS (
SELECT Id,
ProviderId,
ConsumerId
FROM T1
WHERE ProviderId in (#ProviderId)
UNION ALL
SELECT t.Id,
t.ProviderId,
t.ConsumerId
FROM T1 t
INNER JOIN cte c
ON t.ProviderId = c.ConsumerId
)
INSERT INTO #ProviderTable
SELECT * FROM cte;
RETURN;
END;
Than create a view:
CREATE VIEW my_view
AS
SELECT m.*
FROM Providers p
CROSS APPLY my_function (p.ProviderId) m
After that you can SELECT from view whatever you need:
SELECT *
FROM my_view
WHERE ProviderId in (2,3,9)
OPTION (MAXRECURSION 0)

One way to do Table Valued Route
CREATE FUNCTION [dbo].[MVParam_tvf](#ParameterString nvarchar(4000), #Delimiter char(1)= ',')
RETURNS #VALUES TABLE (Param nvarchar(4000))AS
Figure out your code preference to spilt delimited string to table
END
CREATE FUNCTION dbo.MagicView_tvf
(
#ParameterString NVARCHAR(4000)
,#Delimiter CHAR(1)
)
RETURNS TABLE
AS
RETURN
(
with CTE as (
select Id, ProviderId, ConsumerId
from
T1 t
INNER JOIN MVParam_tvf (#ParameterString, #Delimiter) p
ON CAST(t.ProviderId AS VARCHAR(10)) = p.OutPutColumn
union all
select T1.Id, T1.ProviderId, T1.ConsumerId
from T1
join CTE on C.ProviderId = CTE.ConsumerId
)
select * from CTE
)
GO
SELECT * FROM dbo.MagicView_tvf ('2,3,9')
If you want to go the view route create the view without the where line on anchor part of cte and then when you call it write your where statement on that query.

Related

Function to multiple tables

I have this function, but I wanted to pass a table so as to use the same function to get the job done for multiple tables. For example, I want this function work for table1, and table2. But it is just for table1 currently. I was trying to use a dynamic sql in vain; it doesn't pass the parameter selected.
Can someone help? Give me guide on how to pass table as a parameter.
Sample data, table1
CREATE TABLE table1 (id int identity (1,1), name varchar(60))
INSERT INTO table1
VALUES ('a1, a2, a9, a8')
Sample data, table2
CREATE TABLE table2 (id int identity (1,1), name varchar(60))
INSERT INTO table2
VALUES ('a1, a2, a9, a8')
The function:
CREATE FUNCTION f_split
(#id INT)
RETURNS #ab
TABLE (name VARCHAR(20),
ab1 VARCHAR(5)
)
AS
BEGIN
DECLARE #temp TABLE (rn INT, name VARCHAR(5))
INSERT INTO #temp(rn, name)
SELECT ROW_NUMBER() OVER(ORDER BY LTRIM(RTRIM(Split.a.value('.', 'NVARCHAR(MAX)'))) ASC) rn, LTRIM(RTRIM(Split.a.value('.', 'NVARCHAR(MAX)'))) Result
FROM
(
SELECT CAST('<X>'+REPLACE([name], ',', '</X><X>')+'</X>' AS XML) AS String
FROM table1 where id = #id
) AS A
CROSS APPLY String.nodes('/X') AS Split(a)
ORDER BY 1
INSERT INTO #ab
SELECT * FROM #temp
RETURN
END
This gives the result from table1.
SELECT * FROM F_SPLIT(1)
But I want the same function to work for table2 as well.
Any help is appreciated.
Use a partitioned view, which will allow you to specify the table name as a parameter in the where clause.
Start by creating a view that unions the two tables, plus an additional column to indicate which table the row comes from.
CREATE VIEW BothTables AS
SELECT 'Table1' TableName, * FROM Table1
UNION ALL
SELECT 'Table2' TableName, * FROM Table2
Then modify your function. When you pass the table name, use it to select a subset of rows from the view. So instead of
SELECT CAST('<X>'+REPLACE([name], ',', '</X><X>')+'</X>' AS XML) AS String
FROM table1
WHERE id = #id
Use
SELECT CAST('<X>'+REPLACE([name], ',', '</X><X>')+'</X>' AS XML) AS String
FROM BothTables
WHERE TableName = #TableName
AND id = #id

How to convert column into row with header sql

Note : i need the record like this
Scode value
BR 10
DL 7
One method is union all:
select 'br', br from t union all
select 'dl', dl from t union all
select 'AP', ap from t;
In a database that supports lateral joins, I would recommend something like this:
select v.*
from t cross apply
(values ('br', br), ('dl', dl), ('AP', ap)
) v(col, val);
use this query, it is dynamic and working.
CREATE TABLE [dbo].[temp](
[br] [int] NULL,
[dl] [int] NULL,
[ap] [int] NULL,
[hp] int null,
[kl] int null,
[mh] int null,
[py] int null
)
insert into temp values(10,7,7,7,8,8,0)
--Drop Table if exists #temp2
--drop table if exists #temp
create table #temp2(id int identity primary key,Scode varchar(10),value int)
declare #col varchar(max),#count int,#intcount int=1,#strquery varchar(max),#id int
select ROW_NUMBER() over (order by column_id) as RN,name into #temp from sys.columns where object_id=OBJECT_ID('temp')
select #count=count(1) from #temp
while(#intcount<=#count)
begin
set #strquery=''
set #col=''
set #id=0
select #col=name from #temp where RN=#intcount
set #strquery='select '+#col+' from temp'
insert into #temp2(value)
exec(#strquery)
select #id=SCOPE_IDENTITY()
update #temp2 set [Scode]=#col where id=#id
set #intcount=#intcount+1
end
select * from temp
select Scode,value from #temp2
Please go on this link and See the simple solution my problem is Solve.
https://www.codeproject.com/Answers/5259227/Convert-column-into-row-with-header-in-SQL#answer1
You could also use CROSS APPLY with UNION ALL to convert the columns:
select id, br,
dl,
ap
from yourtable
cross apply
(
select 'br', Indicator1 union all
select 'dl', Indicator2 union all
select 'ap', Indicator3
) c (indicatorname, indicatorvalue);

Select item type and its base type in SQL Server?

I have a simple table :
DECLARE #t TABLE(item INT, itemType INT )
insert INTO #t SELECT 1000,0
insert INTO #t SELECT 1000,3
insert INTO #t SELECT 1000,5
insert INTO #t SELECT 1000,6
insert INTO #t SELECT 2000,0
insert INTO #t SELECT 2000,3
insert INTO #t SELECT 2000,5
insert INTO #t SELECT 2000,6
insert INTO #t SELECT 3000,0
insert INTO #t SELECT 3000,10
insert INTO #t SELECT 4000,11
I want to select all items where itemtype = 3 but if there is a row , provide also its base itemtype (if it exists) which is itemType = 0.
For example :
for itemType = 3
1000,0 should be provided --why ? because table also has 1000 + itemType 0
1000,3 should be provided --why ? because we looked for itemType=3
2000,0 should be provided --why ? because table has found 2000,3 and this 2000 also has itemType=0
2000,3 should be provided --why ? because we looked for itemType=3
for itemType = 10
3000,0 should be provided --why ? because table has found 3000,10 and this 3000 also has itemType=0
3000,10 should be provided --why ? because we looked for itemType=10
for itemType = 11
4000,11 should be provided --why ? because we looked for itemType=11 ( notice , there isn't itemType 0 , so only itself).
I started doing :
;with cte as(
SELECT * FROM #t
)
select * from cte where itemType=3
In summary, if the itemType is found, provide itself + its zero type (if exists), and also for his siblings ( sample(#1) )
But I can't do union here cause CTE is not recognized there... rubbish. it is possible.
How can I solve it ?
SQL ONLINE
To avoid evaluating the itemType = 3 query twice you can self outer join then use CROSS APPLY ... VALUES to UNPIVOT
DECLARE #itemType INT = 3;
WITH T(item1, itemType1, item2, itemType2 )
AS (SELECT *
FROM #t T1
LEFT JOIN #t T2
ON T1.item = T2.item
AND T2.itemType = 0
AND T1.itemType <> 0
WHERE T1.itemType = #itemType)
SELECT item,
itemType
FROM T
CROSS APPLY ( VALUES (item1, itemType1),
(item2, itemType2) ) v(item, itemType)
WHERE item IS NOT NULL
SQL Fiddle
Execution Plans
DECLARE #findtype INT = 3;
WITH results AS
(
SELECT t.item, #findtype
FROM #t t
WHERE t.itemType = #findtype
UNION ALL
SELECT t.item, 0
FROM #t t
INNER JOIN results r on r.item = t.item
WHERE t.itemType = 0
)
SELECT *
FROM results;
WITH recordList
AS
(
SELECT item, itemType
FROM SampleTable
WHERE itemType = 11 -- change here
)
SELECT item, itemType FROM recordList
UNION
SELECT a.item, a.itemType
FROM SampleTable a
INNER JOIN recordList b
ON a.item = b.item
WHERE a.itemtype = 0
SQLFiddle Demo
SQLFiddle Demo (using IN clause for multiple values)

Union temporary tables to a final temporary table

I have like 10 diff temporary tables created in SQL server, what I am looking to do is union them all to a final temporary table holding them all on one table. All the tables have only one row and look pretty much exactly like the two temp tables below.
Here is what I have so far this is an example of just two of the temp tables as their all exactly like this one then #final is the table I want to union the all to:
create table #lo
(
mnbr bigint
)
insert into #login (mnbr)
select distinct (_ID)
FROM [KDB].[am_LOGS].[dbo].[_LOG]
WHERE time >= '2012-7-26 9:00:00
Select count(*) as countreject
from #lo
create table #pffblo
(
mber_br
)
insert into #pffblo (mber_br)
select distinct (mber_br)
from individ ip with (nolock)
join memb mp with (nolock)
on( ip.i_id=mp.i_id and mp.p_type=101)
where ip.times >= '2012-9-26 11:00:00.000'
select count(*) as countaccept
create table #final
(
countreject bigint
, Countacceptbigint
.....
)
insert into #final (Countreject, Countaccept....more rows here...)
select Countreject, Countaccept, ...more rows selected from temp tables.
from #final
union
(select * from #lo)
union
(select * from #pffblo)
select *
from #final
drop table #lo
drop table #pffblo
drop table #final
if this the form to union the rows form those temp tables to this final one. Then is this correct way to show all those rows that were thus unioned. When I do this union I get message number of columns in union need to match number of columns selected in union
I think you're using a union the wrong way. A union is used when you have to datasets that are the same structure and you want to put them into one dataset.
e.g.:
CREATE TABLE #Table1
(
col1 BIGINT
)
CREATE TABLE #Table2
(
col1 BIGINT
)
--populate the temporary tables
CREATE TABLE #Final
(
col1 BIGINT
)
INSERT INTO #Final (col1)
SELECT *
FROM #Table1
UNION
SELECT *
FROM #Table2
drop table #table1
drop table #table2
drop table #Final
I think what you're trying to do is get 1 data set with the count of all your tables in it. Union won't do this.
The easiest way (although not very performant) would be to do select statements like the following:
CREATE TABLE #Table1
(
col1 BIGINT
)
CREATE TABLE #Table2
(
col1 BIGINT
)
--populate the temporary tables
CREATE TABLE #Final
(
col1 BIGINT,
col2 BIGINT
)
INSERT INTO #Final (col1, col2)
select (SELECT Count(*) FROM #Table1) as a, (SELECT Count(*) FROM #Table2) as b
select * From #Final
drop table #table1
drop table #table2
drop table #Final
It appears that you want to take the values from each of temp tables and then place then into a single row of data. This is basically a PIVOT, you can use something like this:
create table #final
(
countreject bigint
, Countaccept bigint
.....
)
insert into #final (Countreject, Countaccept....more rows here...)
select
from
(
select count(*) value, 'Countreject' col -- your UNION ALL's here
from #lo
union all
select count(*) value, 'countaccept' col
from #pffblo
) x
pivot
(
max(value)
for col in ([Countreject], [countaccept])
) p
Explanation:
You will create a subquery similar to this that will contain the COUNT for each of your individual temp table. There are two columns in the subquery, one column contains the count(*) from the table and the other column is the name of the alias:
select count(*) value, 'Countreject' col
from #lo
union all
select count(*) value, 'countaccept' col
from #pffblo
You then PIVOT these values to insert into your final temp table.
If you do not want to use PIVOT, then you can use a CASE statement with an aggregate function:
insert into #final (Countreject, Countaccept....more rows here...)
select max(case when col = 'Countreject' then value end) Countreject,
max(case when col = 'countaccept' then value end) countaccept
from
(
select count(*) value, 'Countreject' col -- your UNION ALL's here
from #lo
union all
select count(*) value, 'countaccept' col
from #pffblo
) x
Or you might be able to JOIN all of the temp tables similar to this, where you create a row_number() for the one record in the table and then you join the tables with the row_number():
insert into #final (Countreject, Countaccept....more rows here...)
select isnull(lo.Countreject, 0) Countreject,
isnull(pffblo.Countaccept, 0) Countaccept
from
(
select count(*) Countreject,
row_number() over(order by (SELECT 0)) rn
from #lo
) lo
left join
(
select count(*) Countaccept,
row_number() over(order by (SELECT 0)) rn
from #pffblo
) pffblo
on lo.rn = pffblo.rn
SELECT *
INTO #1
FROM TABLE2
UNION
SELECT *
FROM TABLE3
UNION
SELECT *
FROM TABLE4
If you would like to get count for each temporary table in the resulting table, you will need just to calculate it for each column in subquery:
INSERT INTO result (col1, col2,...
SELECT
(SELECT COUNT() FROM tbl1) col1
,(SELECT COUNT() FROM tbl2) col2
..

Parent row missing in child parent relationship in with CTE

i have a temporary table in which i have the following data , i want to filter the rows of child with his parent categoryID untill its reaches at the top of that Parent in those hierarchy .
;with cte (rowid,ParentCategoryID,CategoryID,Status,Level,CategoryName,ISProduct) as
(
Select rowid,ParentCategoryID,CategoryID,Status,Level,CategoryName,ISProduct from #newtemp where ParentCategoryId!=0
union all
select cte.rowid,cte.ParentCategoryID,cte.CategoryID,cte.Status,cte.Level,cte.CategoryName,cte.ISProduct
from #newtemp inner join cte ON cte.CategoryId=#newtemp.ParentCategoryId
)
select * from cte
You need replace cte.CategoryId=#newtemp.ParentCategoryId on c.ParentCategoryId = #newtemp.CategoryID
;with cte (rowid,ParentCategoryID,CategoryID,Status,Level,CategoryName,ISProduct) as
(
Select rowid, ParentCategoryID, CategoryID, Status, Level, CategoryName, ISProduct
from #newtemp
where ParentCategoryId!=0
union all
select t.rowid, t.ParentCategoryID, t.CategoryID, t.Status, t.Level, t.CategoryName, t.ISProduct
from #newtemp t inner join cte c ON c.ParentCategoryId = t.CategoryID
)
select * from cte
Demo on SQLFiddle
If I understand you correct. You what something like this:
First some test data:
DECLARE #tbl TABLE
(
rowid INT,
parentCategoryID INT,
CategoryID INT,
[Status] INT,
[Level] INT,
CategoryName VARCHAR(100),
ISProduct BIT
)
INSERT INTO #tbl
SELECT 1,0,1,1,0,'jewellary',1 UNION ALL
SELECT 2,0,2,1,0,'f',0 UNION ALL
SELECT 11,2,4,1,10,'ghdf',1
Then the CTE like this:
;WITH cte_name (rowid,CategoryID,parentCategoryID,HasChildren)
AS
(
SELECT
tbl.rowid,
tbl.CategoryID,
tbl.parentCategoryID,
CASE WHEN EXISTS
(
SELECT
NULL
FROM
#tbl AS tblInner
WHERE
tblInner.parentCategoryID=tbl.CategoryID
)
THEN 1
ELSE 0
END
AS HasChildren
FROM
#tbl AS tbl
WHERE
tbl.parentCategoryID=0
UNION ALL
SELECT
tbl.rowid,
tbl.CategoryID,
tbl.parentCategoryID,
cte.HasChildren
FROM
#tbl AS tbl
JOIN cte_name AS cte
on cte.CategoryID=tbl.parentCategoryID
)
SELECT
tbl.*
FROM
cte_name
JOIN #tbl AS tbl
ON cte_name.rowid=tbl.rowid
WHERE
cte_name.HasChildren=1