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

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.

Related

TSQL - How to avoid UNION ALL

Sample Data:
DECLARE #Parent TABLE
(
[Id] INT
, [Misc_Val] VARCHAR(5)
) ;
DECLARE #Children TABLE
(
[Id] INT
, [P_ID] INT
) ;
INSERT INTO #Parent
VALUES
( 1, 'One' )
, ( 2, 'Two' )
, ( 3, 'Three' )
, ( 5, 'Four' ) ;
INSERT INTO #Children
VALUES
( 10, 1 )
, ( 11, 1 )
, ( 21, 2 )
, ( 23, 2 )
, ( 30, 3 )
, ( 40, 4 ) ;
Goal:
To efficiently output three fields ( [Id] and [IsChild], [Misc_Val] ). Output all records from #Parent table with [IsChild] = 0 and output all MATCHING records from #Child table (#Parent.Id = #Children.P_Id) with [IsChild] = 1.
Expected Output
Id IsChild Misc_Val
1 0 One
2 0 Two
3 0 Three
5 0 Four
10 1 One
11 1 One
21 1 Two
23 1 Two
30 1 Three
My try:
SELECT [P].[Id]
, 0 AS [IsChild]
, [P].[Misc_Val]
FROM #Parent AS [P]
UNION ALL
SELECT [C].[Id]
, 1
, [P].[Misc_Val]
FROM #Parent AS [P]
JOIN #Children AS [C]
ON [C].[P_ID] = [P].[Id] ;
Is there a better way to do this than using UNION ALL? #Parent and #Children tables are quite big and so am trying to avoid querying the #Parent table twice.
UPDATE: The below answer made me realized something I missed out when creating the post with mocked data. We do need some additional data from #Parent table regardless in the final output.
You can use CROSS APPLY to add the child table to the parent table.
This may or may not be faster, it can depend on indexing and so forth. You need to check the query plan.
SELECT v.Id
, v.IsChild
, P.Misc_Val
FROM #Parent AS P
CROSS APPLY (
SELECT
P.Id,
0 AS IsChild
UNION ALL
SELECT
C.Id,
1
FROM #Children AS C
WHERE C.P_ID = P.Id
) v;
Note that the first SELECT in the apply has no FROM and therefore does not do any table access.

SQL - "NOT IN" in WHERE clause using INNER JOIN not working

I need to filter a table based in a sub table data.
I'll exemplify with a hypnotic data to be easier to explain:
Master table: Cars
Sub table: Attributes (like Color, car type, accessories)
These attributes have an id (idOption) and the selected value (idList)
So, in an example, I need to filter all the cars with the color (idOption = 10) yellow (idList = 45). I can't filter this directly because the search need to consider the other option's results (which include the types, accessories.
When I use NOT IN for just one table, it works. But when I use merging the 2 tables with INNER JOIN, it does not work.
So in summary, I need to filter the 3 idOption (when is not NULL) with a given value, and this needs to reflect in the main table, grouped by product.
Table Cars:
idProduct | Description
1 Product A
2 Product B
3 Product C
Table Attributes:
idRow idProduct idOption idList
---------------------------------------
1 1 10 45
2 2 10 46
3 3 10 47
4 1 11 10
5 2 11 98
6 1 14 56
7 3 16 28
8 2 20 55
This is the stored procedure that I created which is not working:
ALTER PROCEDURE [dbo].[SP_GET_TestSearch]
(#Param1 BIGINT = NULL,
#PValue1 BIGINT = NULL,
#Param2 BIGINT = NULL,
#PValue2 BIGINT = NULL,
#Param3 BIGINT = NULL,
#PValue3 BIGINT = NULL)
AS
SET NOCOUNT ON;
SELECT
Cars.idProduct,
Cars.[Description]
FROM
Cars
INNER JOIN
Attributes ON Cars.idProduct = Attributes.idProduct
WHERE
((#Param1 IS NULL OR (idOption NOT IN (#Param1)))
AND
(#Param2 IS NULL OR (idOption NOT IN (#Param2)))
AND
(#Param3 IS NULL OR (idOption NOT IN (#Param3))))
OR
(idOption = ISNULL(#Param1, NULL)
AND idList = ISNULL(#PValue1, NULL))
OR
(idOption = ISNULL(#Param2, NULL)
AND idList = ISNULL(#PValue2, NULL))
OR
(idOption = ISNULL(#Param3, NULL)
AND idList = ISNULL(#PValue3, NULL))
GROUP BY
Cars.idProduct, Cars.[Description]
The following code demonstrates how to implement the logic of excluding vehicles from query results if they have any "bad" property values. The rejection is handled by ... where not exists ... which is used to check each car against the "bad" property values.
Rather than using an assortment of (hopefully) paired parameters to pass the undesirable properties, the values are passed in a table. The stored procedure to implement this ought to use a table-valued parameter (TVP) to pass the table.
-- Sample data.
declare #Cars as Table ( CarId Int Identity, Description VarChar(16) );
insert into #Cars ( Description ) values
( 'Esplanade' ), ( 'Tankigator' ), ( 'Land Yacht' );
select * from #Cars;
declare #Properties as Table ( PropertyId Int Identity, Description VarChar(16) );
insert into #Properties ( Description ) values
( 'Turbochargers' ), ( 'Superchargers' ), ( 'Hyperchargers' ), ( 'Color' ), ( 'Spare Tires' );
select * from #Properties;
declare #CarProperties as Table ( CarId Int, PropertyId Int, PropertyValue Int );
insert into #CarProperties ( CarId, PropertyId, PropertyValue ) values
( 1, 1, 1 ), ( 1, 4, 24 ), ( 1, 4, 42 ), -- Two tone!
( 2, 2, 1 ), ( 2, 4, 7 ),
( 3, 1, 2 ), ( 3, 4, 0 ), ( 3, 5, 6 );
select C.CarId, C.Description as CarDescription,
P.PropertyId, P.Description as PropertyDescription,
CP.PropertyValue
from #Cars as C inner join
#CarProperties as CP on CP.CarId = C.CarId inner join
#Properties as P on P.PropertyId = CP.PropertyId
order by C.CarId, P.PropertyId;
-- Test data: Avoid vehicles that have _any_ of these property values.
-- This should be passed to the stored procedure as a table-value parameter (TVP).
declare #BadProperties as Table ( PropertyId Int, PropertyValue Int );
insert into #BadProperties ( PropertyId, PropertyValue ) values
( 2, 1 ), ( 2, 2 ), ( 2, 4 ),
( 4, 62 ), ( 4, 666 );
select BP.PropertyId, BP.PropertyValue, P.Description
from #BadProperties as BP inner join
#Properties as P on P.PropertyId = BP.PropertyId;
-- Query the data.
select C.CarId, C.Description as CarDescription
from #Cars as C
where not exists (
select 42
from #CarProperties as CP inner join
#BadProperties as BP on BP.PropertyId = CP.PropertyId and BP.PropertyValue = CP.PropertyValue
where CP.CarId = C.CarId )
order by C.CarId;
A few things here.
Firstly, this kind of catch all procedure is a bit of an anti pattern for all sorts of reasons, see here for a full explanation:- https://sqlinthewild.co.za/index.php/2018/03/13/revisiting-catch-all-queries/
Secondly, you need to be very careful of using NOT IN with nullable values in a list: http://www.sqlbadpractices.com/using-not-in-operator-with-null-values/
I've added the DDL for the tables:-
IF OBJECT_ID('Attributes') IS NOT NULL
DROP TABLE Attributes;
IF OBJECT_ID('Cars') IS NOT NULL
DROP TABLE Cars;
IF OBJECT_ID('SP_GET_TestSearch') IS NOT NULL
DROP PROCEDURE SP_GET_TestSearch
CREATE TABLE Cars
(idProduct INT PRIMARY KEY
, Description VARCHAR(20) NOT NULL);
CREATE TABLE Attributes
(idRow INT PRIMARY KEY
, idProduct INT NOT NULL FOREIGN KEY REFERENCES dbo.Cars(idProduct)
, idOption INT NOT NULL
, idList INT NOT NULL);
INSERT INTO dbo.Cars
VALUES
(1, 'Product A')
,(2 , 'Product B')
,(3, 'Product C');
INSERT INTO dbo.Attributes
(
idRow,
idProduct,
idOption,
idList
)
VALUES (1,1,10,45)
,(2,2,10,46)
,(3,3,10,47)
,(4,1,11,10)
,(5,2,11,98)
,(6,1,14,56)
,(7,3,16,28)
,(8,2,20,55);
GO
The issue with your query, is that the first part of the block is always evaluated to TRUE for any idOption that you don't specify:-
((#Param1 IS NULL OR (idOption NOT IN (#Param1)))
AND
(#Param2 IS NULL OR (idOption NOT IN (#Param2)))
AND
(#Param3 IS NULL OR (idOption NOT IN (#Param3))))
To explain; if I pass in the following:-
DECLARE #Param1 BIGINT
, #Param2 BIGINT
, #Param3 BIGINT
, #PValue1 BIGINT
, #PValue2 BIGINT
, #PValue3 BIGINT;
SET #Param1 = 11
SET #Pvalue1 = 42
SET #Param2 = 11
SET #Pvalue2 = 10
SET #Param3 = 14
SET #PValue3= 56
EXEC dbo.SP_GET_TestSearch #Param1, #PValue1, #Param2, #PValue2, #Param3, #PValue3
Then you effectively have WHERE idOption NOT IN (11,14) as the evaluation for the first part of the clause, so all other rows are returned.
I suspect you really want the WHERE clause to be:-
WHERE
(#Param1 IS NULL AND #Param2 IS NULL AND #Param3 IS NULL)
OR
(idOption = #Param1
AND idList = #PValue1)
OR
(idOption = #Param2
AND idList = #PValue2)
OR
(idOption = #Param3
AND idList = #PValue3)

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.

Get hierarchical data is SQL SERVER with fallback logic

Consider the below schema
dbo.Cultures (Id, CultureCode, ParentId)
Culture table stores the data in the parent-child relationship.
Suppose we have below demo data
5 es-ES 3
Now I have another table which stores the multilingual data for the different cultures.
Schema for the table is as following
dbo.LangData(KeyName, CultureId, Value)
here cultureId is the foreign key of dbo.Cultures table.
Suppose this table has following data
Now I require to fetch the data for all the cultures which are in the Culture table and the corresponding value column in the LangData table.
The culture Ids which are not in the LangData table, for those the Value column will the value of the corresponding parent culture Id columns value. I.e. Data will be retrieved using fallback logic
E.g. For the above values the Result set will be following.
5 es-ES Colour_IN
Here for de-DE data is missing in LangData so it's value will be the data in it's parent culture i.e. en-IN, if in case data also not found in en-IN then it will pick the data of it's parent en-US.
Tried Soloution
First I fetch the culture hierarchy using CTE
CREATE FUNCTION [dbo].[ufnGetCultureHierarchyAll] ()
RETURNS #hierarchyResult TABLE(RowNo INT, CultureId INT, ParentCultureId INT)
AS
BEGIN
WITH CultureHierarchy_CTE(RowNo, CultureId, ParentCultureId)
AS (
SELECT 1,
Id,
ParentId
FROM [dbo].Cultures
UNION ALL
SELECT RowNo + 1,
ou.Id,
ou.ParentId
FROM [dbo].Cultures ou
JOIN CultureHierarchy_CTE cte
ON ou.Id = cte.ParentCultureId
)
-- inserting desired records into table and returning
INSERT INTO #hierarchyResult (RowNo,CultureId,ParentCultureId )
SELECT RowNo, CultureId , ParentCultureId FROM CultureHierarchy_CTE
RETURN;
END
This will return the hierarchy of the all the cultures
Now I attempted to apply join of the result set with the LangData table,
DECLARE #cultureHierarchy AS TABLE(
RowNumber INT,
CultureId INT,
ParentCultureId INT
)
--SELECT * FROM master.Cultures
----Get and store culture hierarchy
INSERT INTO #cultureHierarchy
SELECT RowNo, CultureId, ParentCultureId
FROM ufnGetCultureHierarchyAll()
SELECT c.Code AS [CultureCode],
c.CultureId AS [CultureId],
rv.Value
FROM dbo.LangData rv WITH (NOLOCK)
JOIN #cultureHierarchy c ON rv.CultureId = c.CultureId
END
but it is not working.
Is someone have any Idea regarding same.
Solution using Itzik Ben-Gan's hierarchy model. If you can extend the dbo.Cultures table with Hierarchy, Lvl and Root columns and index on Hierarchy, query will be faster. It has to be rewrited in that case though.
drop table if exists dbo.Cultures;
create table dbo.Cultures (
ID int
, Code varchar(50)
, ParentID int
);
insert into dbo.Cultures (ID, Code, ParentID)
values (1, 'en-US', null), (2, 'en-IN', 1), (3, 'de-DE', 2), (4, 'hi-HI', 2)
drop table if exists dbo.LangData;
create table dbo.LangData (
KeyName varchar(100)
, CultureID int
, Value varchar(100)
);
insert into dbo.LangData (KeyName, CultureID, Value)
values ('lblColourName', 1, 'Color'), ('lblColourName', 2, 'Colour-IN');
with cteCultures as (
select
c.ID, c.Code, c.ParentID, 0 as Lvl
, convert(varchar(max), '.' + CONVERT(varchar(50), c.ID) + '.') as Hierarchy
, c.ID as Root
from dbo.Cultures c
where c.ParentID is null
union all
select
c.ID, c.Code, c.ParentID, cc.Lvl + 1 as Lvl
, cc.Hierarchy + convert(varchar(50), c.ID) + '.' as Hierarchy
, cc.Root as Root
from dbo.Cultures c
inner join cteCultures cc on c.ParentID = cc.ID
)
select
ccr.ID
, ccr.Code
, coalesce(ld.Value, ld2.Value) as Value
from cteCultures ccr
left join dbo.LangData ld on ccr.ID = ld.CultureID
outer apply (
select
top (1) tcc.ID
from cteCultures tcc
inner join dbo.LangData tld on tcc.ID = tld.CultureID
where ld.KeyName is null
and ccr.Hierarchy like tcc.Hierarchy + '%'
and ccr.Hierarchy <> tcc.Hierarchy
order by tcc.Lvl desc
) tt
left join dbo.LangData ld2 on tt.ID = ld2.CultureID
If I understand your question:
We just build your hierarchy (SEQ and Lvl are optional) and then perform TWO left joins in concert with a Coalesce().
Example
Declare #Cultures table (id int,ParentId int,Code varchar(50))
Insert into #Cultures values
( 1, NULL,'en-US')
,( 2, 1 ,'en-IN')
,( 3, 2 ,'de-DE')
,( 4, 2 ,'hi-HI')
Declare #LangData table (keyName varchar(50),CultureId int,Value varchar(50))
Insert Into #LangData values
('lblColourName',1,'Color')
,('lblColourName',2,'Color_IN')
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by Code) as varchar(500))
,ID
,ParentId
,Lvl=1
,Code
From #Cultures
Where ParentId is null
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Code)) as varchar(500))
,r.ID
,r.ParentId
,p.Lvl+1
,r.Code
From #Cultures r
Join cteP p on r.ParentId = p.ID)
Select CultureId = A.ID
,A.Code
,Value = Coalesce(C.Value,B.Value)
From cteP A
Left Join #LangData B on (A.ParentId=B.CultureId)
Left Join #LangData C on (A.Id=C.CultureId)
Order By Seq
Returns
CultureId Code Value
1 en-US Color
2 en-IN Color_IN
3 de-DE Color_IN
4 hi-HI Color_IN

Query to select and combine uppermost and lowermost value from tables

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