Can't put CTE result to temporary table - sql-server

I got a SP where I need to put CTE result to a temporary table so I can use the temporary table later on in SP. Since I am new so I am experiencing difficulty. Please help me out.
CREATE TABLE #TempTable1 (
tmp_id INT NULL,
tmp_parent_id INT NULL,
temp_level VARCHAR(50) NULL,
temp_order VARCHAR(50) NULL,
temp_promoter_ID INT NULL
);
DECLARE #promoterid INT = (
SELECT
p.promoterid
FROM dbo.Promoters p
INNER JOIN dbo.UserProfile u
ON u.UserId = p.UserProfileId
WHERE u.UserName = #Username
);
;WITH Empl_Tab (Id, ParentId, LEVEL, [Order], promoterid) AS (
SELECT
promoters.UserProfileId AS ID,
promoters.Level1 AS ParentID,
0 AS LEVEL,
CONVERT([VARCHAR](MAX), promoters.PromoterId) AS [Order],
promoters.PromoterId
FROM promoters
WHERE Promoters.PromoterId = #promoterid
UNION ALL
SELECT
p.UserProfileId AS ID,
p.Level1 AS ParentID,
Empl_Tab.LEVEL + 1 AS LEVEL,
Empl_Tab.[Order] + CONVERT([VARCHAR](30), p.PromoterId) AS [Order],
p.PromoterId
FROM Promoters p
INNER JOIN Empl_Tab
--inner join dbo.UserProfile u on u.UserId= Promoters.UserProfileId
ON Empl_Tab.promoterid = p.Level1
--where p.Active!=2
)
--select Id, ParentId, LEVEL,[Order],promoterid from Empl_Tab
INSERT INTO #TempTable1 --(tmp_id, tmp_parent_id, temp_level, temp_order, temp_promoter_ID )
SELECT *
FROM Empl_Tab;
Now I like to put the Emp1_Tab result to the temporary table and like to use the temporary table data later on in this same SP.

Procedure TeamCustomersListNew, Line 42 String or binary data would be truncated.
The above error message states that one of the values you're inserting exceeds the max length. The temp_order column of the temp table only allows for 50 characters. You may want to increase it or use VARCHAR(MAX) instead:
CREATE TABLE #TempTable1 (
tmp_id INT NULL,
tmp_parent_id INT NULL,
temp_level INT NULL,
temp_order VARCHAR(MAX) NULL,
temp_promoter_ID INT NULL
);
Additionally, temp_level should be INT.

Related

How to get records which has more than one entries on another table

An example scenario for my question would be:
How to get all persons who has multiple address types?
Now here's my sample data:
CREATE TABLE #tmp_1 (
ID uniqueidentifier PRIMARY KEY
, FirstName nvarchar(max)
, LastName nvarchar(max)
)
CREATE TABLE #tmp_2 (
SeedID uniqueidentifier PRIMARY KEY
, SomeIrrelevantCol nvarchar(max)
)
CREATE TABLE #tmp_3 (
KeyID uniqueidentifier PRIMARY KEY
, ID uniqueidentifier REFERENCES #tmp_1(ID)
, SeedID uniqueidentifier REFERENCES #tmp_2(SeedID)
, SomeIrrelevantCol nvarchar(max)
)
INSERT INTO #tmp_1
VALUES
('08781F73-A06B-4316-B6A5-802ED58E54BE', 'AAAAAAA', 'aaaaaaa'),
('4EC71FCE-997C-46AA-B119-6C5A2545DDC2', 'BBBBBBB', 'bbbbbbb'),
('B0726ABF-738E-48BC-95CB-091C9D731A0E', 'CCCCCCC', 'ccccccc'),
('6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', 'DDDDDDD', 'ddddddd')
INSERT INTO #tmp_2
VALUES
('4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'Value1'),
('4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'Value2'),
('6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'Value3')
INSERT INTO #tmp_3
VALUES
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdgdfbgcv'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'asdfadsas'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'xxxxxeeeeee'),
(NEWID(), '4EC71FCE-997C-46AA-B119-6C5A2545DDC2', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdfsd'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'zxczxcz'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'eerwerwe'),
(NEWID(), '6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'vbcvbcvbcv')
Which gives you:
This is my attempt:
SELECT
t1.*
, Cnt -- not really needed. Just added for visual purposes
FROM #tmp_1 t1
LEFT JOIN (
SELECT
xt.ID
, COUNT(1) Cnt
FROM (
SELECT
#tmp_3.ID
, COUNT(1) as Cnt
FROM #tmp_3
GROUP BY ID, SeedID
) xt
GROUP BY ID
) t2
ON t1.ID = t2.ID
WHERE t2.Cnt > 1
Which gives:
ID FirstName LastName Cnt
B0726ABF-738E-48BC-95CB-091C9D731A0E CCCCCCC ccccccc 2
08781F73-A06B-4316-B6A5-802ED58E54BE AAAAAAA aaaaaaa 2
Although this gives me the correct results, I'm afraid that this query is not the right way to do this performance-wise because of the inner queries. Any input is very much appreciated.
NOTE:
A person can have multiple address of the same address types.
"Person-Address" is not the exact use-case. This is just an example.
The Cnt column is not really needed in the result set.
The way you have named your sample tables and data help little in understanding the problem.
I think you want all IDs which have 2 or more SomeIrrelevantCol values in the last table?
This can be done by:
select * from #tmp_1
where ID in
(
select ID
from #tmp_3
group by ID
having count(distinct SomeIrrelevantCol)>=2
)

Sql server subquery to be rewritten using Joins

I have this pasted subquery and i need to rewrite it using only joins ( no subquery).
Tried multiple times for close to a month but in vain.
Request you to help me out.
SELECT * FROM wp_user WHERE userId NOT IN
(SELECT u.userId FROM wp_user as u, wp_luncher as i, wp_subscription as s
WHERE u.userId = i.luncherId
and i.luncherId = s.luncherid)
and CreationDate between '20181001' and '20181015';
Tables involved :-
CREATE TABLE wp_user (
userId int identity(1,1) PRIMARY KEY NOT NULL,
userName varchar(20) NOT NULL,
CreationDate date NOT NULL
);
CREATE TABLE wp_luncher (
luncherId int PRIMARY KEY NOT NULL,
parentId int FOREIGN KEY REFERENCES wp_user(userId)
);
CREATE TABLE wp_subscription (
SubId int PRIMARY KEY NOT NULL,
luncherId int FOREIGN KEY REFERENCES wp_luncher(luncherId)
);
Try this query.
You can use OUTER APPLY.
SELECT *
FROM wp_user wp
OUTER APPLY
(
SELECT i.luncherId
FROM wp_luncher as i, wp_subscription as s
WHERE i.luncherId = wp.userId
and i.luncherId = s.luncherid
) I
WHERE I.luncherId IS NOT NULL
and CreationDate between '20181001' and '20181015';
Another option inserts the subquery's data into a table variable.
DECLARE #TempTable AS TABLE (UserId INT)
INSERT INTO #TempTable
SELECT i.luncherId
FROM wp_luncher as i, wp_subscription as s
WHERE i.luncherId = s.luncherid
SELECT *
FROM wp_user wp
LEFT JOIN #TempTable I ON I.UserId = wp.userId
WHERE I.UserId IS NOT NULL
and CreationDate between '20181001' and '20181015';

When inserting a row, can the `ParentId` be set as the `Id` from a previous inserted row within the same query?

Suppose I have a table called #tblTemp like this:
DECLARE #tblTemp TABLE
(
Id INT NOT NULL IDENTITY,
Name VARCHAR(MAX) NOT NULL,
ParentId INT NULL
)
and my XML structure (assigned to #Xml) was:
<Data>
<MyRow Name="I am the Parent"/>
<MyRow Name="I am the child" ParentName="I am the Parent"/>
</Data>
Question: would it be possible to insert into the ParentId column within the same query?
SQL Script
INSERT INTO #tblTemp ([Name], [ParentId])
SELECT
Rw.value('#Name','VARCHAR(MAX)'), -- Name
(SELECT [Id] -- Select ID From Parent Name
FROM #tblTemp AS [TT]
WHERE [TT].[Name] = Rw.value('#ParentName', 'VARCHAR(MAX)'))
FROM
#Xml.nodes('Data/MyRow') AS Data(Rw)
SELECT *
FROM #tblTemp AS [TT]
The script inserts NULL into the ParentId column as I suspect the previous inserts haven't been committed yet so the table will be empty.
Alternative: if it isn't possible to insert into the ParentId column within the same query, then my alternative would be to do the insert then update the table where required.
Try it like this:
DECLARE #tblTemp TABLE
(
Id INT NOT NULL,
Name VARCHAR(MAX) NOT NULL,
ParentId INT NULL
)
DECLARE #xml XML=
N'<Data>
<MyRow Name="I am the Parent"/>
<MyRow Name="I am the child" ParentName="I am the Parent"/>
<MyRow Name="another child" ParentName="I am the Parent"/>
<MyRow Name="baby" ParentName="I am the child"/>
</Data>';
WITH DerivedTable AS
(
SELECT r.value(N'#Name',N'nvarchar(max)') AS [Name]
,r.value(N'#ParentName',N'nvarchar(max)') AS [ParentName]
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowNmbr
FROM #xml.nodes(N'/Data/MyRow') AS A(r)
)
,recCTE AS
(
SELECT 1 AS Lvl
,[Name]
,[ParentName]
,RowNmbr
,CAST(NULL AS BIGINT) AS ParentRowNmbr
,CAST(N'' AS NVARCHAR(MAX)) AS [ParentPath]
FROM DerivedTable
WHERE ParentName IS NULL
UNION ALL
SELECT r.Lvl+1
,t.[Name]
,t.[ParentName]
,t.RowNmbr
,r.RowNmbr
,r.[ParentPath]+t.[ParentName]+N'|'
FROM DerivedTable AS t
INNER JOIN recCTE AS r ON r.[Name]=t.[ParentName]
)
--Use this SELECT to see all columns returned by the recursive CTE
--SELECT * FROM recCTE
INSERT INTO #tblTemp(ID,[Name],ParentId)
SELECT RowNmbr, [Name],ParentRowNmbr
FROM recCTE;
SELECT * FROM #tblTemp;
The result
Id Name ParentId
1 I am the Parent NULL
2 I am the child 1
3 another child 1
4 baby 2
Short explanation:
The first CTE will read the values as derived table and use ROW_NUMBER() to give a running number to each row as ID.
The second CTE is recursively travelling down the road.
The result can be inserted directly into your table.
Attention
I changed your table from ID is IDENTITY to a normal INT column. You can use SELECT MAX(ID) first to get the highest existing ID and add this to ROW_NUMBER() in the first CTE. Otherwise it might happen, that the IDs given by ROW_NUMBER() are not the same as the ID given by IDENTITY.

Stored Procedure Syntax with CTE

This is probably trivial but I am just learning about CTE (thanks to help here).
I have a procedure that is used to determine totals.
The first part is the totals are the sum of a position at their level an below. So I needed a way to retrieve records that (1) determined the level of the record (hierarchy) and (2) returned all records at and below. That was asked and answered here.
Now want to take the CTE table from the answer above and use it in the second part of my procedure (get the totals)
CREATE PROCEDURE [dbo].[GetProgramTotals]
#programId nvarchar(10) = null,
#owner int = null,
#totalAmount money OUT,
#usedAmount money OUT,
#remainingAmount money OUT
AS
BEGIN
WITH rCTE AS
(
SELECT
*, 0 AS Level
FROM Forecasting.dbo.Addressbook
WHERE Addressbook = #owner
UNION ALL
SELECT
t.*, r.Level + 1 AS Level
FROM Addressbook t
INNER JOIN rCTE r ON t.ParentAddressbook = r.Addressbook
)
Select #totalAmount = (Select Sum(Amount) from dbo.Budget where
(#programId IS NULL or (ProgramId = #programId)) and (#owner IS NULL or (BudgetOwner in (SELECT Addressbook from rCTE))))
Select #usedAmount = (Select Sum(SubTotal) from dbo.OrderLine where
(#programId IS NULL or (ProgramId = #programId) and (#owner IS NULL) or (Budget in (SELECT Addressbook from rCTE))))
if (#totalAmount is null)
set #totalAmount = 0
if (#usedAmount is null)
set #usedAmount = 0
Set #remainingAmount = (#totalAmount - #usedAmount)
END
The idea of this procedure is the dynamically calculate an individual (or all) programs based an a users position in a hierarchy.
So a regional managers totals would be the sum of all districts and district reps.
UPDATE: I updated this based on squillman (thank you) comment below.
Now I have a different problem. When I execute the proc - I get 'Invalid object name rCTE'.
You can't use SET in the middle of a query like that. Change it to a SELECT and it should remedy your syntax error.
CREATE PROCEDURE [dbo].[GetProgramTotals]
#programId nvarchar(10) = null,
#owner int = null,
#totalAmount money OUT,
#usedAmount money OUT,
#remainingAmount money OUT
AS
BEGIN
WITH rCTE AS(
SELECT *, 0 AS Level FROM Forecasting.dbo.Addressbook WHERE Addressbook = #owner
UNION ALL
SELECT t.*, r.Level + 1 AS Level
FROM Addressbook t
INNER JOIN rCTE r ON t.ParentAddressbook = r.Addressbook)
SELECT #totalAmount = (Select Sum(Amount) from dbo.Budget where
(#programId IS NULL or (ProgramId = #programId)) and (#owner IS NULL or (BudgetOwner in (SELECT Addressbook from rCTE))))
, #usedAmount = (Select Sum(SubTotal) from dbo.OrderLine where
(#programId IS NULL or (ProgramId = #programId) and (#owner IS NULL) or (Budget in (SELECT Addressbook from rCTE))))
if (#totalAmount is null)
set #totalAmount = 0
if (#usedAmount is null)
set #usedAmount = 0
Set #remainingAmount = (#totalAmount - #usedAmount)
END
CTE's can be a bit confusing at first, but they are really quite simple once they make sense. For me it clicked when I began thinking of them as just another temp table syntax (pro-tip: they're not in reality, just conceptually). So basically:
Create one or more "temp tables". These are your CTE expressions, and there can be more than one.
Perform a standard operation using one or more of the CTE expressions in the statement immediately following your CTE(s).
As Martin mentioned in comments below, the CTE(s) are only scoped for the next immediate statement and fall out of scope after that.
So,
;WITH cte1 AS
(
SELECT Col1 FROM Table1
),
cte2 AS
(
SELECT Col1 FROM Table2
)
SELECT Col1 FROM cte1 //In scope here
UNION
SELECT Col1 FROM cte1; //Still in scope since we're still in the first statement
SELECT Col1 FROM cte1; //Fails. cte1 is now out of scope (as is cte2)
In your case you're using the recursive CTE to form a parent/child hierarchy and then setting variables based on the results. Your CTE syntax is pretty close after the edit, you just need the comma to bring things back together into one statement.
//Variable assignment example
;WITH cte1 AS
(
SELECT Col1 FROM Table1
),
cte2 AS
(
SELECT Col1 FROM Table2
)
SELECT #var1 = (SELECT TOP 1 Col1 FROM cte1)
,#var2 = (SELECT TOP 1 Col1 FROM cte2) //You're missing the comma at the start of this line
Change Select #usedAmount=... to , #usedAmount=...

How to flatten tree leaves sequence in SQL

I have a table S which has an 2 columns: Name for ID, and an XML column.
This table represents a tree data.
S
-----+----------------
Name | XmlCol
-----+----------------
A | <E><B Id='b1' Type=0 /><B Id='D' Type=1 /><B Id='b2' Type=0 /></E>
D | <E><B Id='b3' Type=0 /><B Id='G' Type=1 /></E>
F | <E><B Id='b4' Type=0 /></E>
G | <E><B Id='b5' Type=0 /></E>
The data appears in given order. Order matters here.
Notice the XML structure.
Type = 0 means that the entry is of type leaf.
Type = 1 means that there is a row in the table with same Name, hence a node and not a leaf.
There are 5 leaves, b1, b2, b3, b4, b5.
What I want is to get a table of all leaves in the sequence presented, like this:
Leaves
--------
b1
b3
b5
b2
b4
when the start node is 'A'
This is the XML parsing snippet, but it is just the begining.
SELECT [Id] = xTree.b.value('#Id', 'varchar(10)')
FROM [S]
CROSS APPLY [XmlCol].nodes('/E/B') AS xTree(b)
Can anyone suggest how to do this in SQL?
Try something like this:
DECLARE #Source TABLE (
Name VARCHAR(10) PRIMARY KEY,
XmlCol XML NOT NULL
)
DECLARE #Root VARCHAR(10)='A'
INSERT INTO #Source VALUES
('A',CONVERT(XML,'<E><B Id="b1" Type="0" /><B Id="D" Type="1" /><B Id="b2" Type="0" /></E>')),
('D',N'<E><B Id="b3" Type="0" /><B Id="G" Type="1" /></E>'),
('F',N'<E><B Id="b4" Type="0" /></E>'),
('G',N'<E><B Id="b5" Type="0" /></E>')
DECLARE #Temp1 TABLE (
ID INT IDENTITY PRIMARY KEY,
Name VARCHAR(10) NOT NULL,
Type INT NOT NULL,
Value VARCHAR(10) NOT NULL
)
INSERT INTO #Temp1
SELECT Name, xTree.b.value('#Type', 'int') AS Type, xTree.b.value('#Id', 'varchar(10)') AS Value
FROM #Source
CROSS APPLY [XmlCol].nodes('/E/B') AS xTree(b)
DECLARE #Temp2 TABLE (
ID INT PRIMARY KEY,
Name VARCHAR(10) NOT NULL,
Type INT NOT NULL,
Value VARCHAR(10) NOT NULL,
Position INT NOT NULL
)
INSERT INTO #Temp2
SELECT *, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY ID) AS Position
FROM #Temp1
DECLARE #Temp3 TABLE (
Name VARCHAR(10) NOT NULL,
Type INT NOT NULL,
Value VARCHAR(10) NOT NULL,
Position FLOAT NOT NULL,
Level INT NOT NULL,
PRIMARY KEY (Name, Position)
)
;WITH CTE AS (
SELECT Name, Type, Value, CONVERT(FLOAT,Position) AS Position, 0 AS Level
FROM #Temp2 WHERE Type=0
UNION ALL
SELECT t1.Name, t2.Type, t2.Value,
t1.Position+t2.Position*POWER(CONVERT(FLOAT,0.1),1+t2.Level),
t2.Level+1 AS Level
FROM #Temp2 t1
INNER JOIN CTE t2 ON t2.Name=t1.Value
WHERE t1.Type=1
)
INSERT INTO #Temp3
SELECT * FROM CTE
SELECT Value
FROM (
SELECT Value, 0 AS Extra, Position
FROM #Temp3 WHERE Name=#Root
UNION ALL
SELECT Value, 1 AS Extra, Position
FROM #Temp3 WHERE Value NOT IN (
SELECT Value
FROM #Temp3 WHERE Name=#Root
)
) u
ORDER BY Extra, Position
A few observations:
in XML, the value of the attributes should always be quoted
it's not very clear in which order you want the values that are outside the tree of the given root (in this example, only b4 is not part of the tree starting from A; if there were multiple values like this, in multiple other trees, it's not clear which is the desired order)
the use of table variables may be avoided by using a more complex CTE, but I think they are helpful for performance
I assumed a maximum of 10 sub-nodes per level; if there are more sub-nodes, you can change 0.1 to 0.01, for example

Resources