Query to select and combine uppermost and lowermost value from tables - sql

We have the following tables on our SQL Server 2012.
Table A (data):
ID, Description
---------------
1 , Bla 1
2 , Bla 2
3 , Bla 3
Table P (data):
ID, ParentID, Name
------------------
1 , NULL , AAA
2 , 3 , CCC
3 , 1 , XXX
Table X (foreign keys A_ID to A.ID and P_ID to P.ID):
ID, A_ID, P_ID
--------------
1 , 1 , 1
2 , 1 , 2
3 , 2 , 1
4 , 2 , 2
5 , 2 , 3
6 , 3 , 1
Question:
We need a query something like:
SELECT ...
WHERE A_ID = 1
which should return this result:
ID, Name, Subname
-----------------
2 , AAA , CCC
Name needs to contain the upper most Name from Table P, i.e. the one that has no ParentID.
Subname needs to contain the bottom most Name from the Table P for which the ID still exists in Table X.
ID needs to contain the ID from Table X where P_ID is the ID of the bottom most child.
Another example:
SELECT ...
WHERE A_ID = 2
should return this result:
ID, Name, Subname
-----------------
4 , AAA , CCC
And
SELECT ...
WHERE A_ID = 3
should return this result:
ID, Name, Subname
-----------------
6 , AAA , NULL
We've tried various queries, but some work only for 'where A_ID = 1' and not for 'where A_ID = 2'. In order to select the lowest level child from P, we've looked at the 'How to select lowest level in hierarchy form table post' which probably comes in handy for the query we're looking for.
A single query would be nice, but we will accept a stored procedure as well.
Thanks in advance!
Information
The ID columns in all tables are primary keys
The ID columns in any given table can be changed to any other value in the sample data, while taking into account the primary and foreign key constraints. (E.g. changing P.ID '2' to '4' also results in the change of X.P_ID's '2' to '4'.) This is to show that ID's are not necessarily in order.
Values in the column P.Name can be any non-null value.
Table P can have multiple rows with ParentId set to null.
Sample Data
Taken from #NEER
DECLARE #A TABLE (ID INT, DESCRIPTION NVARCHAR(10))
INSERT INTO #A
VALUES
(1 , 'Bla 1'),
(2 , 'Bla 2'),
(3 , 'Bla 3')
DECLARE #P TABLE (ID INT, ParentID INT, Name NVARCHAR(10))
INSERT INTO #P
VALUES
(1 , NULL , 'AAA'),
(2 , 3 , 'CCC'),
(3 , 1 , 'XXX')
DECLARE #X TABLE (ID INT,A_ID INT,P_ID INT)
INSERT INTO #X
VALUES
(1 , 1 , 1),
(2 , 1 , 2),
(3 , 2 , 1),
(4 , 2 , 2),
(5 , 2 , 3),
(6 , 3 , 1)

Try with the below query. I think Table A is not required for getting the desired result.
SELECT TOP 1 First_VALUE(x.ID) OVER(ORDER BY x.ID desc) ID
,First_VALUE(Name) OVER(ORDER BY p.ID) Name
,CASE WHEN First_VALUE(Name) OVER(ORDER BY p.ID) = First_VALUE(Name) OVER(ORDER BY p.ID desc) THEN NULL
ELSE First_VALUE(Name) OVER(ORDER BY p.ID desc) END SubName
FROM [table P] p
JOIN [table X] x
ON p.ID=x.[P_ID]
WHERE x.[A_ID]=3

Try following query
DECLARE #TableA AS TABLE (ID INT,Des NVARCHAR(MAX));
Insert Into #TableA VALUES(1,'Bal 1'); Insert Into #TableA VALUES(2,'Bal 2'); Insert Into #TableA VALUES(3,'Bal 3');
DECLARE #TableP AS TABLE (ID INT,ParentID INT,Name NVARCHAR(MAX));
Insert Into #TableP VALUES(1,Null,'AAA'); Insert Into #TableP VALUES(2,1,'BBB'); Insert Into #TableP VALUES(3,2,'CCC');
DECLARE #TableX AS TABLE (ID INT,A_ID INT,P_ID INT);
Insert Into #TableX Values(1,1,1); Insert Into #TableX Values(2,1,2); Insert Into #TableX Values(3,2,1); Insert Into #TableX Values(4,2,3); Insert Into #TableX Values(5,3,1);
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Inner Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=1
Order by X.ID Desc
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Left Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=2
Order by X.ID Desc
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Left Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=3
Order by X.ID Desc

You can try the following
with report as (
select max(x.ID) as ID, min(x.P_ID) as MinP, max(x.P_ID) as MaxP
from X x
where x.A_ID = 1 -- <-- here you can change the value
)
select r.ID,
mn.Name as Name,
case when r.MinP = r.MaxP then null else mx.Name end as Subname
from report r
inner join P mn on mn.ID = r.MinP
inner join P mx on mx.ID = r.MaxP
Hope this will help you

Try it with a GROUP BY:
SELECT x.a_id, max(x.id) AS id, min(p.name) AS name,
CASE WHEN max(p.name) = min(p.name) THEN NULL
ELSE max(p.name) END AS subname
FROM p INNER JOIN x
ON p.id = x.p_id
GROUP BY x.a_id
HAVING x.a_id = 1
Still works with your updated sample data. Tested here: http://sqlfiddle.com/#!9/99597f/1

You can use Recursive CTE as the below:
DECLARE #A TABLE (ID INT, DESCRIPTION NVARCHAR(10))
INSERT INTO #A
VALUES
(1 , 'Bla 1'),
(2 , 'Bla 2'),
(3 , 'Bla 3')
DECLARE #P TABLE (ID INT, ParentID INT, Name NVARCHAR(10))
INSERT INTO #P
VALUES
(1 , NULL , 'AAA'),
(2 , 3 , 'CCC'),
(3 , 1 , 'XXX')
DECLARE #X TABLE (ID INT,A_ID INT,P_ID INT)
INSERT INTO #X
VALUES
(1 , 1 , 1),
(2 , 1 , 2),
(3 , 2 , 1),
(4 , 2 , 2),
(5 , 2 , 3),
(6 , 3 , 1)
DECLARE #A_ID INT = 2
;WITH Parents
AS
(
SELECT
P.ID, P.ParentID, P.Name
FROM #P P WHERE P.ParentID IS NULL
UNION ALL
SELECT
P.ID, Parent.ID, Parent.Name
FROM
#P P INNER JOIN
Parents Parent ON P.ParentID = Parent.ID
), Temp
AS
(
SELECT
X.ID,
Parent.Name Name,
IIF(P.ParentID IS NULL, NULL, P.Name) SubName
FROM
#A A INNER JOIN
#X X ON X.A_ID = A.ID INNER JOIN
#P P ON X.P_ID = P.ID LEFT JOIN
Parents Parent ON P.ParentID = Parent.ID OR (P.ParentID IS NULL AND P.ID = Parent.ID)
WHERE
A.ID = #A_ID
), MainTable
AS
(
SELECT
Temp.ID ,
Temp.Name ,
Temp.SubName,
COUNT(Temp.ID) OVER (PARTITION BY Temp.Name ORDER BY (SELECT NULL)) CountOfRowByParent
FROM
Temp
)
SELECT
MainTable.ID ,
MainTable.Name ,
MainTable.SubName
FROM
MainTable
WHERE
(
MainTable.CountOfRowByParent > 1 AND
MainTable.SubName IS NOT NULL
) OR
MainTable.CountOfRowByParent = 1
Result for 2:
ID Name SubName
4 AAA CCC
5 AAA XXX

Related

Nested while loop in SQL Server is not showing the expected result

I am trying to connect records from two different tables so I can display the data in a tabular format in an SSRS tablix.
The code below does not return the expected results.
As is, for each item in Temp_A the loop updates everything with the last item in Temp_C. Here is the code:
CREATE TABLE #Temp_A
(
[ID] INT,
[Name] VARCHAR(255)
)
INSERT INTO #Temp_A ([ID], [Name])
VALUES (1, 'A'), (2, 'B')
CREATE TABLE #Temp_C
(
[ID] INT,
[Name] VARCHAR(255)
)
INSERT INTO #Temp_C ([ID], [Name])
VALUES (1, 'C'), (2, 'D')
CREATE TABLE #Temp_Main
(
[Temp_A_ID] INT,
[Temp_A_Name] VARCHAR(255),
[Temp_C_ID] INT,
[Temp_C_Name] VARCHAR(255),
)
DECLARE #MIN_AID int = (SELECT MIN(ID) FROM #Temp_A)
DECLARE #MAX_AID int = (SELECT MAX(ID) FROM #Temp_A)
DECLARE #MIN_DID int = (SELECT MIN(ID) FROM #Temp_C)
DECLARE #MAX_DID int = (SELECT MAX(ID) FROM #Temp_C)
WHILE #MIN_AID <= #MAX_AID
BEGIN
WHILE #MIN_DID <= #MAX_DID
BEGIN
INSERT INTO #Temp_Main([Temp_A_ID], [Temp_A_Name])
SELECT ID, [Name]
FROM #Temp_A
WHERE ID = #MIN_AID
UPDATE #Temp_Main
SET [Temp_C_ID] = ID, [Temp_C_Name] = [Name]
FROM #Temp_C
WHERE ID = #MIN_DID
SET #MIN_DID = #MIN_DID + 1
END
SET #MIN_AID = #MIN_AID + 1
SET #MIN_DID = 1
END
SELECT * FROM #Temp_Main
DROP TABLE #Temp_A
DROP TABLE #Temp_C
DROP TABLE #Temp_Main
Incorrect result:
Temp_A_ID | Temp_A_Name | Temp_C_ID | Temp_C_Name
----------+-------------+-----------+---------------
1 A 2 D
1 A 2 D
2 B 2 D
2 B 2 D
Expected results:
Temp_A_ID | Temp_A_Name | Temp_C_ID | Temp_C_Name
----------+-------------+-----------+---------------
1 A 1 C
1 A 2 D
2 B 1 C
2 B 2 D
What am I missing?
You seem to want a cross join:
select a.*, c.*
from #Temp_A a cross join
#Temp_C c
order by a.id, c.id;
Here is a db<>fiddle.
There is no need to write a WHILE loop to do this.
You can use insert to insert this into #TempMain, but I don't se a need to have a temporary table for storing the results of this query.

SQL Server - from two rows, one column to one row, two columns?

if object_id( 'tempdb.dbo.#ctp', 'u' ) is not null
drop table #ctp ;
create table #ctp( id int, mastername varchar( 16 ) ) ;
insert into #ctp values( 1, 'Big Boy' ) ;
if object_id( 'tempdb.dbo.#client', 'u' ) is not null
drop table #client ;
create table #client( id int, name varchar(16 ), type int ) ;
insert into #client values( 1, 'ABC', 5 ) ;
insert into #client values( 2, 'XYZ', 6 ) ;
if object_id( 'tempdb.dbo.#ctpclient', 'u' ) is not null
drop table #ctpclient ;
create table #ctpclient( id int, ctpfk int, clientfk int ) ;
insert into #ctpclient values( 1, 1, 1 ) ;
insert into #ctpclient values( 2, 1, 2 ) ;
select tp.mastername
, c.name
, c.type
, cc.ctpfk
, cc.clientfk
from #ctp tp
join #ctpclient cc
on tp.id = cc.ctpfk
join #client c
on c.id = cc.clientfk
;
current output
mastername|name|type
Big Boy|ABC|5
Big Boy|XYZ|6
Instead of two rows of output, I would like the output to be as follows:
mastername|nameone|nametwo
Big Boy | ABC | XYZ
What is the optimal way to do this given that I have a many to many table such as #ctpclient?
Assuming you always have 2 rows you can use a crosstab (aka conditional aggregation). It would look something like this.
with SortedValues as
(
select tp.mastername
, c.name
, ROW_NUMBER() over (partition by mastername order by clientfk) as RowNum
from #ctp tp
join #ctpclient cc on tp.id = cc.ctpfk
join #client c on c.id = cc.clientfk
)
select mastername
, MAX(case when RowNum = 1 then name end) as NameOne
, MAX(case when RowNum = 2 then name end) as NameTwo
from SortedValues
group by mastername
If you have a varying numbers you can still accomplish but it is bit more complex.

Recursive select in SQL

I have an issue I just can't get my head around. I know what I want, just simply can't get it out on the screen.
What I have is a table looking like this:
Id, PK UniqueIdentifier, NotNull
Name, nvarchar(255), NotNull
ParentId, UniqueIdentifier, Null
ParentId have a FK to Id.
What I want to accomplish is to get a flat list of all the id's below the Id I pass in.
example:
1 TestName1 NULL
2 TestName2 1
3 TestName3 2
4 TestName4 NULL
5 TestName5 1
The tree would look like this:
-1
-> -2
-> -3
-> -5
-4
If I now ask for 4, I would only get 4 back, but if I ask for 1 I would get 1, 2, 3 and 5.
If I ask for 2, I would get 2 and 3 and so on.
Is there anyone who can point me in the right direction. My brain is fried so I appreciate all help I can get.
declare #T table(
Id int primary key,
Name nvarchar(255) not null,
ParentId int)
insert into #T values
(1, 'TestName1', NULL),
(2, 'TestName2', 1),
(3, 'TestName3', 2),
(4, 'TestName4', NULL),
(5, 'TestName5', 1)
declare #Id int = 1
;with cte as
(
select T.*
from #T as T
where T.Id = #Id
union all
select T.*
from #T as T
inner join cte as C
on T.ParentId = C.Id
)
select *
from cte
Result
Id Name ParentId
----------- -------------------- -----------
1 TestName1 NULL
2 TestName2 1
5 TestName5 1
3 TestName3 2
Here's a working example:
declare #t table (id int, name nvarchar(255), ParentID int)
insert #t values
(1, 'TestName1', NULL),
(2, 'TestName2', 1 ),
(3, 'TestName3', 2 ),
(4, 'TestName4', NULL),
(5, 'TestName5', 1 );
; with rec as
(
select t.name
, t.id as baseid
, t.id
, t.parentid
from #t t
union all
select t.name
, r.baseid
, t.id
, t.parentid
from rec r
join #t t
on t.ParentID = r.id
)
select *
from rec
where baseid = 1
You can filter on baseid, which contains the start of the tree you're querying for.
Try this:
WITH RecQry AS
(
SELECT *
FROM MyTable
UNION ALL
SELECT a.*
FROM MyTable a INNER JOIN RecQry b
ON a.ParentID = b.Id
)
SELECT *
FROM RecQry
Here is a good article about Hierarchy ID models. It goes right from the start of the data right through to the query designs.
Also, you could use a Recursive Query using a Common Table Expression.
I'm guessing that the easiest way to accomplish what you're looking for would be to write a recursive query using a Common Table Expression:
MSDN - Recursive Queries Using Common Table Expressions

"Distinct" column in SQL query

SELECT id, EmpNo
FROM EmployeesTable
EmpNo can be the same for 1 or more records in the results of the above query. I now want to add another column derived from EmpNo(lets call it EmpNo2) but only returning distinct values of EmpNo.
For example if the above query returns 100 records but there are 69 distinct EmpNo values and i modify the query to
SELECT id, EmpNo, Distinct EmpNo2
FROM EmployeesTable EmpNo
,
i want all the 100 rows to be returned but the last column EmpNo2 should return 69 distinct values of EmpNo field.
But as already know, using distinct in that way results into an error but i want to implement such functionality - and a subquery is not helping.
SAMPLE REQUIRED RESULTS
ID EmpNo EmpNo2
1 0T4/HR 0T4/HR
1 0T4/HR 2VP/E
1 0T4/HR xT9/67
1 0T4/HR
1 0T4/HR
2 2VP/E
2 2VP/E
2 2VP/E
2 2VP/E
2 2VP/E
3 XT9/67
3 XT9/67
3 xT9/67
3 XT9/67
How about:
Select id, empno, empno2
from employeestable left outer join (
SELECT min([id]) as minid
,[empno] empno2
FROM [EmployeesTable]
group by empno) etab2 on employeestable.id = etab2.minid
You're saying a subquery won't work, though - why not?
Your requirement is not clear and I also have very little information. Following is what you need. This can be even better but it is just a try.
declare #temp table
(
uniqueid int identity(1, 1),
id int,
empno varchar(50),
empno2 varchar(50)
)
insert into #temp select 1, '0T4/HR', null
insert into #temp select 1, '0T4/HR' , null
insert into #temp select 1 , '0T4/HR' , null
insert into #temp select 1, '0T4/HR' , null
insert into #temp select 1, '0T4/HR' , null
insert into #temp select 2, '2VP/E' , null
insert into #temp select 2, '2VP/E' , null
insert into #temp select 2, '2VP/E' , null
insert into #temp select 2, '2VP/E' , null
insert into #temp select 2, '2VP/E' , null
insert into #temp select 3, 'XT9/67' , null
insert into #temp select 3, 'XT9/67' , null
insert into #temp select 3, 'xT9/67' , null
insert into #temp select 3, 'XT9/67' , null
SELECT ROW_NUMBER() OVER (ORDER BY id) AS id, empno into #temp FROM #temp group by empno, id
update #temp set empno2 = t2.empno
from #temp t inner join #temp t2 on t.uniqueid = t2.id
select * from #temp
drop table #temp

t-sql recursive query

Based on an existing table I used CTE recursive query to come up with following data. But failing to apply it a level further.
Data is as below
id name parentid
--------------------------
1 project 0
2 structure 1
3 path_1 2
4 path_2 2
5 path_3 2
6 path_4 3
7 path_5 4
8 path_6 5
I want to recursively form full paths from the above data. Means the recursion will give the following output.
FullPaths
-------------
Project
Project\Structure
Project\Structure\Path_1
Project\Structure\Path_2
Project\Structure\Path_3
Project\Structure\Path_1\path_4
Project\Structure\Path_2\path_5
Project\Structure\Path_3\path_6
Thanks
Here's an example CTE to do that:
declare #t table (id int, name varchar(max), parentid int)
insert into #t select 1, 'project' , 0
union all select 2, 'structure' , 1
union all select 3, 'path_1' , 2
union all select 4, 'path_2' , 2
union all select 5, 'path_3' , 2
union all select 6, 'path_4' , 3
union all select 7, 'path_5' , 4
union all select 8, 'path_6' , 5
; with CteAlias as (
select id, name, parentid
from #t t
where t.parentid = 0
union all
select t.id, parent.name + '\' + t.name, t.parentid
from #t t
inner join CteAlias parent on t.parentid = parent.id
)
select *
from CteAlias
Try something like this:
WITH Recursive AS
(
SELECT
ID,
CAST(PathName AS VARCHAR(500)) AS 'FullPaths',
1 AS 'Level'
FROM
dbo.YourTable
WHERE
ParentID = 0
UNION ALL
SELECT
tbl.ID,
CAST(r.FullPaths + '\' + tbl.PathName AS VARCHAR(500)) AS 'FullPaths',
r.Level + 1 AS 'Level'
FROM
dbo.YourTable tbl
INNER JOIN
Recursive r ON tbl.ParentID = r.ID
)
SELECT * FROM Recursive
ORDER BY Level, ID
Output:
ID FullPaths Level
1 project 1
2 project\structure 2
3 project\structure\path_1 3
4 project\structure\path_2 3
5 project\structure\path_3 3
6 project\structure\path_1\path_4 4
7 project\structure\path_2\path_5 4
8 project\structure\path_3\path_6 4
try this:
DECLARE #YourTable table (id int, nameof varchar(25), parentid int)
INSERT #YourTable VALUES (1,'project',0)
INSERT #YourTable VALUES (2,'structure',1)
INSERT #YourTable VALUES (3,'path_1',2)
INSERT #YourTable VALUES (4,'path_2',2)
INSERT #YourTable VALUES (5,'path_3',2)
INSERT #YourTable VALUES (6,'path_4',3)
INSERT #YourTable VALUES (7,'path_5',4)
INSERT #YourTable VALUES (8,'path_6',5)
;WITH Rec AS
(
SELECT
CONVERT(varchar(max),nameof) as nameof,id
FROM #YourTable
WHERE parentid=0
UNION ALL
SELECT
CONVERT(varchar(max),r.nameof+'\'+y.nameof), y.id
FROM #yourTable y
INNER jOIN Rec r ON y.parentid=r.id
)
select * from rec
output:
nameof
-----------------------------------------------
project
project\structure
project\structure\path_1
project\structure\path_2
project\structure\path_3
project\structure\path_3\path_6
project\structure\path_2\path_5
project\structure\path_1\path_4
(8 row(s) affected)
Something like
;WITH MyCTE AS
(
SELECT
name AS FullPaths, id
FROM
MyTable
WHERE
parentid = 0 /*Normally it'd be IS NULL with an FK linking the 2 columns*/
UNION ALL
SELECT
C.FullPaths + '\' + M.name, M.id
FROM
MyCTE C
JOIN
MyTable M ON M.parentid = C.id
)
SELECT FullPaths FROM MyCTE
You'll have to change the name of #test table I was using.
WITH cte(id, name, parentid) AS
(
SELECT id, convert(varchar(128), name), parentid
FROM #test
WHERE parentid = 0
UNION ALL
SELECT t.id, convert(varchar(128), c.name +'\'+t.name), t.parentid
FROM #test t
INNER JOIN cte c
ON c.id = t.parentid
)
SELECT name as FullPaths
FROM cte
order by id