I have seen many examples of hierarchical ctes, but I wasn't able to get my sorting right yet.
To describe my problem, assume this Task table:
TaskId ParentTaskId Label
a10 null 10
a20 null 11
a30 a20 18
a50 a30 5
a40 a20 15
a60 null 12
The output of my query should be sorted by Label and the children's labels, like so:
Sequence TaskId Label
1 a10 10
2 a20 11
3 a40 15
4 a30 18
5 a50 5
6 a60 12
I added indentation to make it easier for you to notice the grouping. a40 and a30 are children of a20, and are ordered based on the label.
Please help. thanks!
Here's the answer:
drop table if exists #t
go
select
*
into
#t
from
(
values
('a10', null, '10'),
('a20', null, '11'),
('a30', 'a20', '18'),
('a50', 'a30', '5'),
('a40', 'a20', '15'),
('a60', null, '12')
) as T(TaskId, ParentTaskId, Label)
;
with cte as
(
select
l = 0,
p = cast('/' + Label as nvarchar(max)),
*
from
#t where ParentTaskId is null
union all
select
l = p.l + 1,
p = p.p + '/' + c.Label,
c.*
from
#t c
inner join
cte p on c.ParentTaskId = p.TaskId
)
select
*
from
cte
order by p, Label
You need to create a path from the root of your task to the current task and then use it to sort the final result.
On small dataset the above query will perform fine. On bigger (hundreds of thousands) I recommend to take a look at hierarchyid data type:
https://learn.microsoft.com/en-us/sql/t-sql/data-types/hierarchyid-data-type-method-reference?view=sql-server-2017
In T-SQL (MSSql 2008R2) I Would like to select certain rows from a table or set of results;
StoreId StoreName BrochureId PageId Rank Distance
43561 X 1627 11608 73 598.10
43561 X 1627 11591 68 598.10
43561 X 1627 11615 41 598.10
43827 Y 1727 21708 75 1414.69
43827 Y 1727 21591 62 1414.69
43827 Y 1727 21615 44 1414.69
43919 Z 1827 31809 77 2487.35
43919 Z 1827 31591 60 2487.35
43919 Z 1827 31615 39 2487.35
Would like to select only rows with lowest distance and with the highest rank, as such;
StoreId StoreName BrochureId PageId Rank Distance
43561 X 1627 11608 73 598.10
43827 Y 1727 21708 75 1414.69
43919 Z 1827 31809 77 2487.35
Thank you for your help.
You can use ROW_NUMBER for this.
SELECT * FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY StoreName ORDER BY Distance, [Rank] DESC) Rn
FROM
Table1
) t
WHERE Rn = 1
there are other ranking functions you can use as well.. for example, if you use RANK instead of ROW_NUMBER here, you can include ties in your result as well.
SQL Ranking Functions
Use a correlated subquery in your where clause... assuming your table name is MyTable, something like this should get what you want:
SELECT [StoreId], [StoreName], [BrochureId], [PageId], [Rank], [Distance]
FROM MyTable m
WHERE [Rank] = (SELECT MAX([Rank]) FROM MyTable x WHERE x.StoreId = m.StoreId)
OR [Distance] = (SELECT MIN([Distance]) FROM MyTable y WHERE y.StoreId = m.StoreId)
(note, I enclosed the column names in square brackets because "Rank" is a reserved SQL Keyword)
Please can you help me with the following query in MSSQL?
I will try and explain as best I can.
First keep in mind that all the columns in MyTable needs to be reported in the output report even though only three columns will be used to compare the data.
I have a table with MbrNo, DepCode and PracticeNo, now the Practice could have consulted with more than one MbrNo and with more than one DepCode from the same MbrNo. I require the Member/Dependent Count to be populated in an additional column whereby it reflects the amount of Members the doctor consulted. However the DepCode should also be considered. If the doctor consulted with 1 MbrNo but 3 different Dependent the count should then be 3, as the MbrNo is applicable to all 3 dependent and thus the practice consulted with three people.
MY TABLE
MbrNo | DepCode | PracticeNo | Adress | Email | TelNo
123 01 A XY XY XY
1234 00 B XY XY XY
1234 00 B XY XY XY
1245 00 C XY XY XY
123 02 A XY XY XY
1234 02 B XY XY XY
1233 01 A XY XY XY
1233 00 B XY XY XY
OUTPUT REPORT
MbrNo | DepCode | PracticeNo | Adress | Email | TelNo | MemberCount
123 01 A XY XY XY 3
1234 00 B XY XY XY 3
1234 00 B XY XY XY 3
1245 00 C XY XY XY 1
123 02 A XY XY XY 3
1234 02 B XY XY XY 3
1233 01 A XY XY XY 3
1233 00 B XY XY XY 3
Provider B = 3 MemberCount because the Provider Consulted the same member X 2 and the calculation it to confirm the People count. Also note that it should not be placed in order as my previous query that forms part of this query already orders the details accordingly.
I honestly have no idea how to spec this to get the desired results.
Hope the above makes sense and that someone can help me with it.
Thank you kindly.
Use CROSS APPLY to count the unique matches:
Test data
DECLARE #t table
( MbrNo int, DepCode int, PracticeNo char(1), Adress char(2),
Email char(2), TelNo char(2))
insert #t values
(123,'01','A','XY','XY','XY')
,(1234,'00','B','XY','XY','XY')
,(1234,'00','B','XY','XY','XY')
,(1245,'00','C','XY','XY','XY')
,(123,'02','A','XY','XY','XY')
,(1234,'02','B','XY','XY','XY')
,(1233,'01','A','XY','XY','XY')
,(1233,'00','B','XY','XY','XY')
Query for test data:
SELECT
t1.MbrNo,
t1.DepCode,
t1.PracticeNo,
t1.Adress,
t1.Email,
t1.TelNo,
z.MemberCount
FROM #t t1
CROSS APPLY
(SELECT count(*) MemberCount
FROM (SELECT null x
FROM #t
WHERE
t1.PracticeNo = PracticeNo
GROUP BY MbrNo, DepCode)y) z
Result:
MbrNo DepCode PracticeNo Adress Email TelNo MemberCount
123 1 A XY XY XY 3
1234 0 B XY XY XY 3
1234 0 B XY XY XY 3
1245 0 C XY XY XY 1
123 2 A XY XY XY 3
1234 2 B XY XY XY 3
1233 1 A XY XY XY 3
1233 0 B XY XY XY 3
If I understood, You want something like this:
SELECT MbrNo, DepCode, PracticeNo, Adress, Email, TelNo, c.total
FROM
table t
LEFT JOIN (
SELECT MbrNo, DepCode, PracticeNo, count(*) as total
FROM table
GROUP BY (MbrNo, DepCode, PracticeNo)
) c on (c.MbrNo = t.MbrNo and c.DepCode = t.DepCode and c.PracticeNo = t.PracticeNo)
Ok I've edited the answer to replace my previous answer, because this is the actual correct answer:
SELECT t.MbrNo, t.DepCode, t.PracticeNo, Adress, Email, TelNo, c.total
FROM
table t
LEFT JOIN (
SELECT PracticeNo, SUM(total) total
from
( SELECT MbrNo, DepCode, PracticeNo, count(distinct DepCode) as total
FROM table
GROUP BY MbrNo, DepCode, PracticeNo) a
GROUP BY PracticeNo
) c on (c.PracticeNo = t.PracticeNo)
I might be wrong, and there might be more clever people than me with SQL, but I'm not sure if there is a better way to get the Total per Practice No back without another view
This might be a better way though
;WITH aTable (MbrNo, DepCode, PracticeNo, total)
AS
(
SELECT MbrNo, DepCode, PracticeNo, count(distinct DepCode) as total
FROM table
GROUP BY MbrNo, DepCode, PracticeNo
)
SELECT t.MbrNo, t.DepCode, t.PracticeNo, Adress, Email, TelNo, (Select SUM(total) from aTable where PracticeNo = t.PracticeNo)
FROM
table t
Thanks so much for the help I really appreciate it. I am sure that all the above queries work but it is a bit high level for me as I have only been working on SQL for like 3 months here and there.
So below is the route I have taken that also gives me the required results. It might not be the best but for now it is what I at least understand.
I decided to create a unique number combining the MbrNo with the DepCode and Practice Number. Then did a count on the unique references and then joined the original table with the count onto a newer table again.
ALTER TABLE
Table1
ADD
UniqueMemberDep2 VARCHAR(MAX) NULL
UPDATE
Table1
SET UniqueMemberDep2 = CONCAT(MbrNo, DepNo, PracticeNo)
SELECT
PracticeNo, COUNT(Distinct UniqueMemberDep2) as ProviderMemberCount
INTO Temp
FROM Table1
GROUP BY PracticeNo
SELECT Table1.*, Temp.ProviderMemberCount
into Table2 FROM Table1 LEFT JOIN Temp on
Table1.PracticeNo = Temp.PracticeNo
Select * from Table2
DROP TABLE Table1
DROP TABLE Temp
I have one simple requirement. Below is my sql table.
ID Cname StartDate EndDate Value
1 x 01/15/2015 01/20/2015 50
2 x 01/17/2015 01/22/2015 60
3 y 02/15/2015 02/20/2015 40
4 y 02/17/2015 02/22/2015 80
I have date range and I want to convert this each date range into each day row. Along with that whenever there is a overlap of dates it adds the value.
Below is the sample output for more clarification.
Cname date value
x 1/15/2015 60
x 1/16/2015 60
x 1/17/2015 110
x 1/18/2015 110
x 1/19/2015 110
x 1/20/2015 110
x 1/21/2015 60
x 1/22/2015 60
y 2/15/2015 40
y 2/16/2015 40
y 2/17/2015 120
y 2/18/2015 120
y 2/19/2015 120
y 2/20/2015 120
y 2/21/2015 80
y 2/22/2015 80
Any help would be appreciated.
You can use the technique described here, in order to generate a date range for each interval of your table. Then simply group by Cname and date to get the desired result set:
;WITH natural AS
(
SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) - 1 AS val
FROM sys.all_objects
)
SELECT m.Cname, d = DATEADD(DAY, natural.val, m.StartDate),
SUM(value) AS value
FROM mytable AS m
INNER JOIN natural ON natural.val <= DATEDIFF(DAY, m.StartDate, m.EndDate)
GROUP BY Cname, DATEADD(DAY, natural.val, m.StartDate)
ORDER BY Cname, d
The CTE is used to create a tally table. The numbers of this table are then used to add 1,2,3, ... days to StartDate until EndDate is reached.
If you group by Cname, [Date], then SUM will return the required value since it will add any overlapping records within each Cname partition.
SQL Fiddle Demo
I have a table with data like this-
ID ParentID ProductTypeName
1 NULL Electronics
200 1 TV
300 200 Plasma
67 NULL KitchenAppliances
78 67 Stoves
82 78 Electric
99 78 Gas
23 200 LED
65 300 LG
66 300 Sony
I would like to get the data in the following format -
ID ParentID ProductTypeName Level Sort(Or Some kind of sort value)
1 NULL Electronics 0 1
200 1 TV 1 110
300 200 LED 2 120
65 300 LG 3 12010
66 300 Sony 3 12020
23 200 Plasma 2 100030
67 NULL KitchenAppliances 0 10000010
78 67 Stoves 1 1000001010
82 78 Electric 2 100000101020
99 78 Gas 2 100000101030
To display data in tree in this format. Here please note the children within each parent are sorted as well. Indentation is giving so as to give a better idea of the result -
Electronics
TV
LED
LG
Sony
Plasma
KitchenAppliances
Stoves
Electric
Gas
This is the query I wrote, but does not seem to be working.
The sort number logic seems to be broken.
Could someone help with this.
Any help appreciated. Thanks.
;WITH cte (ID, ParentID, [Level], [Name], Sort) AS(
SELECT sc1.ID,
NULL,
0,
sc1.Name,
cast(row_number()over(partition by sc1.ParentCategoryID order by sc1.Name) as varchar(max)) as Sort
FROM TableData sc1
WHERE sc1.ID is null
UNION ALL
SELECT sc2.ID,
sc2.ParentID,
g2.[level] + 1,
sc2.Name,
g2.Sort + cast(row_number()over(partition by sc2.ParentCategoryID order by sc2.Name) as varchar(max))Sort
FROM dbo.TableData sc2
INNER JOIN cte g2
ON sc2.ParentID = g2.ID
You are doing it almost right. The only things I changed are: condition WHERE sc1.ID is null in non-recursive part of the cte changed to WHERE sc1.ParentID is null, and the way sort key (path) is calculated:
;WITH cte (ID, ParentID, [Name], Level, SortPath, DisplayPath)
AS(
SELECT sc1.ID, NULL, sc1.Name, 0,
cast(row_number() over (partition by sc1.ParentCategoryID order by sc1.Name) as varbinary(max)),
cast(row_number() over (partition by sc1.ParentCategoryID order by sc1.Name) as varchar(max))
FROM dbo.TableData sc1
WHERE sc1.ParentID is null
UNION ALL
SELECT sc2.ID, sc2.ParentID, sc2.Name, g2.Level + 1
g2.SortPath + cast(row_number() over (partition by sc2.ParentCategoryID order by sc2.Name) as binary(4)),
g2.DisplayPath + '.' + cast(row_number() over (partition by sc1.ParentCategoryID order by sc1.Name) as varchar(10))
FROM dbo.TableData sc2
JOIN cte g2 ON sc2.ParentID = g2.ID
)
select ID, ParentID, Name, DisplayPath
from cte
order by SortPath
As you can see, there are two paths calculated, first one is for sorting of elements and second one is for viewing. In case if path of the tree element is not supposed to be shown anywhere, you may leave only SortPath.