Retrieve connected rows in SQL Server - sql-server

I have this table in SQL Server:
+--------------+---------------------+
| AccountId | AccountIdAssociated |
+--------------+---------------------+
| 2 | 3 |
| 3 | 15 |
| 1 | 30 |
| 3 | 12 |
| 12 | 10 |
| 10 | 50 |
| 19 | 32 |
| 18 | 33 |
+--------------+---------------------+
As you can see accounts 2, 3, 10, 12, 15, and 50 are connected to each other either directly or transitively how can I retrieve all these connected AccountIds by using only one number (let's say AccountId = 2)

What you need here are a couple of rCTEs to "traverse" the hierarchical data:
WITH VTE AS(
SELECT *
FROM (VALUES(2 ,3 ),
(3 ,15),
(1 ,30),
(3 ,12),
(12,10),
(19,32),
(18,33))V(AccountID,AccountIdAssociated)),
rCTEUp AS(
SELECT V.AccountID,
V.AccountIdAssociated
FROM VTE V
WHERE V.AccountID = 3
UNION ALL
SELECT V.AccountID,
V.AccountIdAssociated
FROM VTE V
JOIN rCTEUp r ON V.AccountIdAssociated = r.AccountID),
rCTEDown AS(
SELECT V.AccountID,
V.AccountIdAssociated
FROM VTE V
WHERE V.AccountID = 3
UNION ALL
SELECT V.AccountID,
V.AccountIdAssociated
FROM VTE V
JOIN rCTEDown r ON V.AccountID = r.AccountIdAssociated)
SELECT AccountID,
AccountIdAssociated
FROM rCTEUp
UNION ALL
SELECT AccountID,
AccountIdAssociated
FROM rCTEDown
WHERE AccountID != 3;

Related

STUFF in SQL Server with 2 tables on primary ID

I have 2 tables Contracts and Locations.
Contracts columns: CTX_ID, Parent_CTX_ID, Company_name
Locations columns: CTX_ID, State
I'm trying to create an out put that concatenates the STATE in one column for each Parent_CTX_ID.
Contracts table:
CTX_ID | Parent_CTX_ID | Company_Name
-------+---------------+-------------
1 | 100 | ABC
2 | 100 | ABC
3 | 100 | ABC
4 | 200 | DEF
5 | 200 | DEF
6 | 200 | DEF
Locations table:
CTX_ID | State
-------+------
1 | NJ
2 | PA
3 | DE
4 | NJ
5 | TX
6 | CA
Output I'm trying to get:
CTX_ID | Parent_CTX_ID | Company_Name | State | States
-------+---------------+--------------+-------+-----------
1 | 100 | ABC | NJ | NJ,PA,DE
2 | 100 | ABC | PA | NJ,PA,DE
3 | 100 | ABC | DE | NJ,PA,DE
4 | 200 | DEF | NJ | NJ,TX,CA
5 | 200 | DEF | TX | NJ,TX,CA
6 | 200 | DEF | CA | NJ,TX,CA
Current code:
(SELECT DISTINCT
c.parent_ctx_id,
STUFF((SELECT DISTINCT ',' + s.state
FROM Locations s
FOR XML PATH('')), 1, 1, '') as STATES
FROM
Contracts AS c
INNER JOIN
Locations AS s ON s.ctx_id = c.ctx_id)
Current output:
Parent_CTX_ID | States
--------------+---------------
100 | CA,DE,NJ,PA,TX
200 | CA,DE,NJ,PA,TX
SELECT
P.CTX_ID,
P.Parent_CTX_ID,
P.Company_Name,
L.State,
(
STUFF((SELECT
',' + s.state as [text()]
FROM
Contracts AS c
JOIN Locations AS s ON
s.ctx_id = c.ctx_id
WHERE
C.Parent_CTX_ID = P.Parent_CTX_ID
FOR XML PATH('')), 1, 1, '')
) States
FROM
Contracts P
LEFT JOIN Locations L ON
L.CTX_ID = P.CTX_ID
SQL Fiddle
You can create a CTE for the new locations (with parent_ctx_id) and use that in your query like below:
;WITH NEW_LOCATIONS AS
(
SELECT l.CTX_ID, l.[State], c.Parent_CTX_ID
FROM #locations l
JOIN #contracts c ON c.CTX_ID = l.CTX_ID
)
--SELECT * FROM new_locations
,cte AS
(
SELECT DISTINCT c.parent_ctx_id,
STUFF((SELECT DISTINCT ',' + s.state
FROM NEW_LOCATIONS s
WHERE s.Parent_CTX_ID = c.Parent_CTX_ID
FOR XML PATH('')), 1, 1, '') as STATES
FROM #contracts AS c
GROUP BY c.Parent_CTX_ID
)
--SELECT * FROM cte
SELECT c2.CTX_ID , c.Parent_CTX_ID , Company_Name , l.[State] , States
FROM cte c
JOIN #contracts c2 ON c.parent_ctx_id = c2.Parent_CTX_ID
JOIN #locations l ON l.CTX_ID = c2.CTX_ID
Please find the db<>fiddle here.

Summarizing a column in SQL Server after the creation of Pivot table

I cannot summarize numbers in the table (SQL-Server) after pivoting and I will be very grateful for your advice.
Better if I explain the problem on the example:
Existing table:
+-------+-----------+-----------+-------------------+
| # | $$$$$ | Fire | Water |
+-------+-----------+-----------+-------------------+
| 1 | 5 | 1 | 5 |
| 1 | 4 | 1 | 5 |
| 1 | 10 | 1 | 5 |
| 2 | 3 | 3 | 8 |
| 2 | 4 | 3 | 8 |
+-------+-----------+-----------+-------------------+
Desired output:
+-------+-----------+-----------+-------------------+
| # | $$$$$ | Fire | Water |
+-------+-----------+-----------+-------------------+
| 1 | 19 | 1 | 5 |
| 2 | 7 | 3 | 8 |
+-------+-----------+-----------+-------------------+
I tend to believe that I already tried all the solutions I found with summarizing and grouping by, but it was not solved, so I rely on you. Thanks in advance. The code I used to create the table:
WITH Enerc AS
(
SELECT
a1.[#],
a1.[$$$$$],
a2.[cause_of_loss]
FROM
data1 AS a1
LEFT JOIN
data2 AS a2 ON a1.[id] = a2.[id]
)
SELECT *
FROM Enerc
PIVOT
(SUM(gross_claim) FOR [cause_of_loss] IN ([Fire], [Water])) AS PivotTable;
No need to pivot. Your desired result should be got by grouping and using SUM:
SELECT
a1.[#],
SUM(a1.[$$$$$]),
a1.[Fire]
a1.[Water]
from data1 as a1
group by a1.[#], a1.[Fire], a1.[Water]
Let me show an example:
DECLARE #Hello TABLE
(
[#] INT,
[$$$$$] INT,
[Fire] INT,
[Water] INT
)
INSERT INTO #Hello
(
#,
[$$$$$],
Fire,
Water
)
VALUES
( 1, -- # - int
5, -- $$$$$ - int
1, -- Fire - int
5 -- Water - int
)
, (1, 4, 1, 5)
, (1, 10, 1, 5)
, (2, 3, 3, 8)
, (2, 4, 3, 8)
SELECT
h.#,
SUM(h.[$$$$$]),
h.Fire,
h.Water
FROM #Hello h
GROUP BY h.#, h.Fire, h.Water
try group by after the pivot.
With Enerc as
(SELECT
a1.[#],
a1.[$$$$$],
a2.[cause_of_loss]
from data1 as a1
left join data2 as a2
on a1.[id] = a2.[id]
)
select *
into tmp
from Enerc
PIVOT
(sum(gross_claim)
FOR [cause_of_loss] in (
[Fire], [Water]))
as PivotTable
select [#], sum([$$$$$])as [$$$$$], Fire, Water
from #tmp
group by [#],Fire, Water
EDIT: in case of permission denied:
With Enerc as
(SELECT
a1.[#],
a1.[$$$$$],
a2.[cause_of_loss]
from data1 as a1
left join data2 as a2
on a1.[id] = a2.[id]
),phase2 as(
select *
from Enerc
PIVOT
(sum(gross_claim)
FOR [cause_of_loss] in (
[Fire], [Water]))
as PivotTable)
select [#], sum([$$$$$])as [$$$$$], Fire, Water
from phase2
group by [#],Fire, Water

How to pull the average sales per visit of the top 10% of customers from 3 segments in T-SQL?

I'm newer to my job and i am not used to doing queries as complicated as the one i am being asked to do. Please help! I'm trying to pull the average sales per visit of the top 10% of customers from 3 customer segments. I'm stuck on pulling only the top 10% of customers. Here are two sample tables and what i have so far.
SELECT
cust_segment.segment,
AVG(cust_sales.sales) / cust_sales.visits AS "sales/vist"
FROM
cust_segment
INNER JOIN
cust_sales ON cust_segment.Customer = cust_sales.Customer
WHERE
cust_sales in (SELECT TOP 10 Percent cust_sales.sales, cust_segement.segment
FROM cust_segment
INNER JOIN cust_sales ON cust_segment.customer = cust_sales.customer)
GROUP BY
segment;
cust_segment
+-------------+---------+
| Customer | Segment |
+-------------+---------+
| 10000834678 | A |
| 10000467169 | A |
| 10000217202 | B |
| 10001562687 | C |
| 10000742574 | C |
| 10001577918 | A |
| 10000825179 | B |
| 10000019009 | B |
| 10001225606 | C |
| 10000473429 | A |
+-------------+---------+
cust_sales
+-------------+----------------+--------+
| Customer | Sales | Visits |
+-------------+----------------+--------+
| 10000834678 | $ 54.56 | 8 |
| 10000467169 | $ 27.61 | 7 |
| 10000217202 | $ 150.01 | 39 |
| 10001562687 | $ 39.59 | 8 |
| 10000742574 | $ 18.35 | 9 |
| 10001577918 | $ 23.72 | 4 |
| 10000825179 | $ 7.69 | 7 |
| 10000019009 | $ 94.41 | 47 |
| 10001225606 | $ 36.00 | 12 |
| 10000473429 | $ 5.76 | 6 |
+-------------+----------------+--------+
It should return:
+---------+-------------+
| Segment | Sales/Visit |
+---------+-------------+
| A | 6.82 |
| B | 3.846410256 |
| C | 4.94875 |
+---------+-------------+
This should do it:
SELECT
seg.Segment,
SUM( sales.Sales ) / SUM( sales.Visits ) AS [Sales/Visit]
FROM
cust_segment AS seg
LEFT OUTER JOIN cust_sales AS sales
ON seg.Customer = sales.Customer
AND seg.Customer IN (
SELECT TOP 10 PERCENT sa.Customer
FROM cust_sales AS sa
INNER JOIN cust_segment AS sg
ON sg.Segment = seg.Segment
AND sg.Customer = sa.Customer
ORDER BY sa.Sales DESC
)
GROUP BY
seg.Segment
;With cust_segment( Customer , Segment )
AS
(
SELECT 10000834678 , 'A' union all
SELECT 10000467169 , 'A' union all
SELECT 10000217202 , 'B' union all
SELECT 10001562687 , 'C' union all
SELECT 10000742574 , 'C' union all
SELECT 10001577918 , 'A' union all
SELECT 10000825179 , 'B' union all
SELECT 10000019009 , 'B' union all
SELECT 10001225606 , 'C' union all
SELECT 10000473429 , 'A'
)
,cust_sales(Customer,Sales,Visits)
AS
(
SELECT 10000834678 , 54.56 , 8 UNION ALL
SELECT 10000467169 , 27.61 , 7 UNION ALL
SELECT 10000217202 , 150.01 , 39 UNION ALL
SELECT 10001562687 , 39.59 , 8 UNION ALL
SELECT 10000742574 , 18.35 , 9 UNION ALL
SELECT 10001577918 , 23.72 , 4 UNION ALL
SELECT 10000825179 , 7.69 , 7 UNION ALL
SELECT 10000019009 , 94.41 , 47 UNION ALL
SELECT 10001225606 , 36.00 , 12 UNION ALL
SELECT 10000473429 , 5.76 , 6
)
SELECT Segment,seg AS [Sales/Visit] FROM
(
SELECT * ,ROW_NUMBER()OVER(PARTITION by seg ORDER BY Segment )Seq FROM
(
SELECT * ,MAX([Sales/Visit])OVER (PARTITION BY Segment ORDER BY Segment)seg FROM
(
SELECT C.Customer,c.Segment,SUM(Sales) /SUM(Visits) AS [Sales/Visit] FROM cust_sales s
INNER JOIN cust_segment c ON c.Customer=s.Customer
GROUP BY C.Customer,c.Segment
)dt
)dt2
)Final WHERE Final.Seq=1 ORDER BY 1
OutPut
Segment|Sales/Visit
------------------
A 6.820000
B 3.846410
C 4.948750

In SQL getting the Max() of a Count() for a specific Group by

My script
SELECT ans.Questions_Id,ans.Answer_Numeric,ans.Option_Id, opt.Description, count(ans.Option_Id) as [Count]
FROM Answers ans
LEFT OUTER JOIN Questions que
ON ans.Questions_Id = que.Id
LEFT OUTER JOIN Options opt
ON ans.Option_Id = opt.Id
WHERE que.Survey_Id = 1
and ans.Questions_Id = 1
GROUP By ans.Questions_Id,ans.Answer_Numeric,ans.Option_Id, opt.Description
ORDER BY 2, 5 desc
I am trying to get the top number responses (Description) for each Answer_Numeric. The result at the moment looks like this:
| Questions_Id | Answer_Numeric | Option_Id | Description | Count
-----------------------------------------------------------------------
| 1 | 1 | 27 | Technology | 183
| 1 | 1 | 24 | Personal Items | 1
| 1 | 2 | 28 | Wallet / Purse | 174
| 1 | 2 | 24 | Personal Items | 3
| 1 | 2 | 26 | Spiritual | 1
| 1 | 3 | 24 | Personal Items | 53
| 1 | 3 | 25 | Food / Fluids | 5
| 1 | 3 | 26 | Spiritual | 5
| 1 | 3 | 27 | Technology | 1
| 1 | 3 | 28 | Wallet / Purse | 1
As from the example data from above I need it to look like this:
| Questions_Id | Answer_Numeric | Option_Id | Description | Count
-----------------------------------------------------------------------
| 1 | 1 | 27 | Technology | 183
| 1 | 2 | 28 | Wallet / Purse | 174
| 1 | 3 | 24 | Personal Items | 53
I am pretty sure that I need to have a max or something in my Having clause but everything I have tried has not worked. Would really appreciate any help on this.
You can use ROW_NUMBER:
SELECT Questions_Id, Answer_Numeric, Option_Id, Description, [Count]
FROM (
SELECT ans.Questions_Id,ans.Answer_Numeric,ans.Option_Id,
opt.Description, count(ans.Option_Id) as [Count],
ROW_NUMBER() OVER (PARTITION BY ans.Questions_Id, ans.Answer_Numeric
ORDER BY count(ans.Option_Id) DESC) AS rn
FROM Answers ans
LEFT OUTER JOIN Questions que
ON ans.Questions_Id = que.Id
LEFT OUTER JOIN Options opt
ON ans.Option_Id = opt.Id
WHERE que.Survey_Id = 1
and ans.Questions_Id = 1
GROUP By ans.Questions_Id,
ans.Answer_Numeric,
ans.Option_Id,
opt.Description) AS t
WHERE t.rn = 1
ORDER BY 2, 5 desc
Alternatively you can use RANK so as to handle ties, i.e. more than one rows per Questions_Id, Answer_Numeric partition sharing the same maximum Count number.
Use row_number():
SELECT *
FROM (SELECT ans.Questions_Id, ans.Answer_Numeric, ans.Option_Id, opt.Description,
count(*) as cnt,
row_number() over (partition by ans.Questions_Id, ans.Answer_Numeric
order by count(*) desc) as seqnum
FROM Answers ans LEFT OUTER JOIN
Questions que
ON ans.Questions_Id = que.Id LEFT OUTER JOIN
Options opt
ON ans.Option_Id = opt.Id
WHERE que.Survey_Id = 1 and ans.Questions_Id = 1
GROUP By ans.Questions_Id, ans.Answer_Numeric, ans.Option_Id, opt.Description
) t
WHERE seqnum = 1
ORDER BY 2, 5 desc;
we can get the same result set in different ways and I have taken sample data set you just merge your joins in this code
declare #Table1 TABLE
(Id int, Answer int, OptionId int, Description varchar(14), Count int)
;
INSERT INTO #Table1
(Id, Answer, OptionId, Description, Count)
VALUES
(1, 1, 27, 'Technology', 183),
(1, 1, 24, 'Personal Items', 1),
(1, 2, 28, 'Wallet / Purse', 174),
(1, 2, 24, 'Personal Items', 3),
(1, 2, 26, 'Spiritual', 1),
(1, 3, 24, 'Personal Items', 53),
(1, 3, 25, 'Food / Fluids', 5),
(1, 3, 26, 'Spiritual', 5),
(1, 3, 27, 'Technology', 1),
(1, 3, 28, 'Wallet / Purse', 1)
;
SELECT tt.Id, tt.Answer, tt.OptionId, tt.Description, tt.Count
FROM #Table1 tt
INNER JOIN
(SELECT OptionId, MAX(Count)OVER(PARTITION BY OptionId ORDER BY OptionId)AS RN
FROM #Table1
GROUP BY OptionId,count) groupedtt
ON
tt.Count = groupedtt.RN
WHERE tt.Count <> 5
GROUP BY tt.Id, tt.Answer, tt.OptionId, tt.Description, tt.Count
OR
select distinct Count, Description , Id , Answer from #Table1 e where 1 =
(select count(distinct Count ) from #Table1 where
Count >= e.Count and (Description = e.Description))

Running "Group By" Ordinal Counter Based on a "Flip" Column

Usually I'm decent at set-based tsql problems. But this one is beating me.
I've been working 3 days on converting a while-loop procedure into a setbased one. I've gotten to the point below.......but can't make the final jump.
I have the following rows. MyOrdinal will be "in order" ... and a second column (MyMarker) will alternate between having a value and being null. Whenever this "flip" occurs on MyMarker, I would like to increment a "group by" ordinal counter by one. Whenever the "flip" values are non-null or null, these are grouped together as a set.
I've tried several things, but it was too ugly to post. That and since moving to ORM, I don't spend as much time in the tsql anymore.
declare #Holder table ( MyOrdinal int not null , MyMarker int , MyGroupNumber int )
INSERT INTO #Holder (MyOrdinal, MyMarker)
Select 1 , 1
union all Select 2, 2
union all Select 3, null
union all Select 4, 3
union all Select 5, 4
union all Select 6, 5
union all Select 7, 6
union all Select 8, 7
union all Select 9, 8
union all Select 10, 9
union all Select 11, 10
union all Select 12, 11
union all Select 13, 12
union all Select 14, 13
union all Select 15, 14
union all Select 16, 15
union all Select 17, null
union all Select 18, null
union all Select 19, null
union all Select 20, 16
union all Select 21, 17
union all Select 22, 18
union all Select 23, null
union all Select 24, null
union all Select 25, 19
union all Select 26, 20
union all Select 27, null
union all Select 28, 21
Select * from #Holder
Desired Output
| MyOrdinal | MyMarker | MyGroupNumber |
|-----------|----------|---------------|
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | null | 2 |
| 4 | 3 | 3 |
| 5 | 4 | 3 |
| 6 | 5 | 3 |
| 7 | 6 | 3 |
| 8 | 7 | 3 |
| 9 | 8 | 3 |
| 10 | 9 | 3 |
| 11 | 10 | 3 |
| 12 | 11 | 3 |
| 13 | 12 | 3 |
| 14 | 13 | 3 |
| 15 | 14 | 3 |
| 16 | 15 | 3 |
| 17 | null | 4 |
| 18 | null | 4 |
| 19 | null | 4 |
| 20 | 16 | 5 |
| 21 | 17 | 5 |
| 22 | 18 | 5 |
| 23 | null | 6 |
| 24 | null | 6 |
| 25 | 19 | 7 |
| 26 | 20 | 7 |
| 27 | null | 8 |
| 28 | 21 | 9 |
Try this one:
First, this assigns a same ROW_NUMBER for continuous Non-NULL MyMarker. ROW_NUMBER is NULL for NULL MyMarkers. After that, you want to add a ROW_NUMBER for NULL MyMarkers such that the value is between the previous NON-NULL and the next NON-NULL. Then use DENSE_RANK to finally assign MyGroupNumber:
SQL Fiddle
;WITH Cte AS(
SELECT *,
RN = ROW_NUMBER() OVER(ORDER BY MyOrdinal) - MyMarker + 1
FROM #Holder
),
CteApply AS(
SELECT
t.MyOrdinal,
t.MyMarker,
MyGroupNumber =
CASE
WHEN RN IS NULL THEN x.NewRN
ELSE RN
END
FROM Cte t
OUTER APPLY(
SELECT TOP 1 RN * 1.1 AS NewRN
FROM Cte
WHERE
t.MyOrdinal > MyOrdinal
AND MyMarker IS NOT NULL
ORDER BY MyOrdinal DESC
)x
)
SELECT
MyOrdinal,
MyMarker,
MyGroupNumber = DENSE_RANK() OVER(ORDER BY MyGroupNumber)
FROM CteApply
For Sql Server 2012:
select *, sum(b) over(order by myordinal)
from(select *,
case when (lag(mymarker) over(order by myordinal) is not null
and mymarker is null) or
(lag(mymarker) over(order by myordinal) is null
and mymarker is not null)
then 1 else 0 end as b
from #Holder) t
First you mark rows with 1 where there is a change from null to not null or from not null to null. Other columns are marked as 0. Then running sum of all rows till current.
Fiddle http://sqlfiddle.com/#!6/9eecb/5015
For Sql Server 2008:
with cte1 as (select *,
case when (select max(enddate) from t ti
where ti.ruleid = t.ruleid and ti.startdate < t.startdate) = startdate
then 0 else 1 end as b
from t),
cte2 as(select *, sum(b) over(partition by ruleid order by startdate) as s
from cte1)
select RuleID,
Name,
min(startdate),
case when count(*) = count(enddate)
then max(enddate) else null end from cte2
group by s, ruleid, name
Fiddle http://sqlfiddle.com/#!6/4191d/6

Resources