Sequence without the row number - sql-server

I have below data set
Create table #table(
Message varchar(10),
ID varchar(5),
ParentID varchar(5))
Insert into #table
select 'Parent','123',''
UNION
select 'Child','234','123'
UNION
select 'Child','345','123'
UNION
select 'Child','145','123'
UNION
select 'Parent','333',''
UNION
select 'Child','567','333'
UNION
select 'Child','789','333'
UNION
select 'Child','100','333'
UNION
select 'Child','111','333'
select * from #table
when I select the data , data looks random. but i would like to have in below sequence
Message ID ParentID
Parent 123
Child 234 123
Child 345 123
Child 145 123
Parent 333
Child 567 333
Child 789 333
Child 100 333
Child 111 333
I tried with row number , it is not working somehow for the below sequence.
Can someone please help me ?

Use a CASE statement in ORDER BY, The following query should do what you want:
select * from #table
order by case when Message = 'Parent' then ID else ParentID end, ParentID

use this:
select message,id,parentid
from #table
order by case when parentid = '' then convert(int,id)-1 else parentid end
this solution is not based of message column and will correct with all data sets.

Use conditional sorting:
select * from #table
order by case when parentid = '' then id else parentid end, parentid
See the demo.
Results:
> Message | ID | ParentID
> :------ | :-- | :-------
> Parent | 123 |
> Child | 145 | 123
> Child | 234 | 123
> Child | 345 | 123
> Parent | 333 |
> Child | 100 | 333
> Child | 111 | 333
> Child | 567 | 333
> Child | 789 | 333
If the parents contain NULL at the parentid column instead of '', then the ORDER BY clause must be changed to:
order by isnull(parentid, id), parentid

Related

How to group parent/child records by flag and collapse to a single record for the combo where the flag is true if one or more expanded was true?

Sorry about the long title it was difficult to describe what I am trying to do. It is simple to understand as you will see below, but difficult to summarise into a title.
I have a query that returns the following data:
| ParentID | ChildID | Flag |
| 100 | 1 | 0 |
| 100 | 1 | 1 |
| 100 | 2 | 0 |
| 100 | 2 | 0 |
| 200 | 1 | 1 |
| 200 | 1 | 1 |
The flag column will only have 1 or 0 in it.
I need to filter the results so that there is only a single row for each parent/child combination.
The flag should be 1 if there were one or more records in the full result set above where it was 1 for the parent/child combo, otherwise it should be zero.
So the result if applied to the above would be:
| ParentID | ChildID | Flag |
| 100 | 1 | 1 |
| 100 | 2 | 0 |
| 200 | 1 | 1 |
I have this working with just the ChildID and Flag columns:
DECLARE #InMemoryResultsFirstPass AS TABLE (ChildID Integer, Flag Integer)
DECLARE #InMemoryResultsRecs AS TABLE (ChildID Integer, Flag Integer)
INSERT INTO #InMemoryResultsFirstPass
SELECT 1 ChildID, 0 Flag
UNION SELECT 1 ChildID, 1 Flag
UNION SELECT 2 ChildID, 0 Flag
UNION SELECT 2 ChildID, 0 Flag
UNION SELECT 1 ChildID, 1 Flag
UNION SELECT 1 ChildID, 1 Flag
select * from #InMemoryResultsFirstPass
INSERT INTO #InMemoryResultsRecs
SELECT DISTINCT
result.*
FROM #InMemoryResultsFirstPass imr
CROSS APPLY (
SELECT TOP 1
*
FROM #InMemoryResultsFirstPass imrfp
WHERE imrfp.ChildID = imr.ChildID
ORDER BY imrfp.Flag DESC
) result
select * from #InMemoryResultsRecs
But have been unable to work out how to do this once I add it in the ParentID column. I have tried a few different approaches trying to do a nested query in the CROSS APPLY with GROUP BY on the ParentID but no matter what I try I lose the ParentID = 200 records:
DECLARE #InMemoryResultsFirstPass AS TABLE (ParentID Integer, ChildID Integer, Flag Integer)
DECLARE #InMemoryResultsRecs AS TABLE (ParentID Integer, ChildID Integer, Flag Integer)
INSERT INTO #InMemoryResultsFirstPass
SELECT 100 ParentID, 1 ChildID, 0 Flag
UNION SELECT 100 ParentID, 1 ChildID, 1 Flag
UNION SELECT 100 ParentID, 2 ChildID, 0 Flag
UNION SELECT 100 ParentID, 2 ChildID, 0 Flag
UNION SELECT 200 ParentID, 1 ChildID, 1 Flag
UNION SELECT 200 ParentID, 1 ChildID, 1 Flag
select * from #InMemoryResultsFirstPass
INSERT INTO #InMemoryResultsRecs
SELECT DISTINCT
result.*
FROM #InMemoryResultsFirstPass imr
CROSS APPLY (
SELECT TOP 1
*
FROM #InMemoryResultsFirstPass imrfp
WHERE imrfp.ChildID = imr.ChildID
ORDER BY imrfp.Flag DESC
) result
select * from #InMemoryResultsRecs
Any assistance would be greatly appreciated.
Thank you for your time.
Actually, I just worked it out:
SELECT ParentID, ChildID, MAX(Flag) AS Flag
FROM #InMemoryResultsFirstPass
GROUP BY ParentID, ChildID
ORDER BY ParentID, ChildID
Hopefully this helps someone else :-)

How to use last_value with group by with count in SQL Server?

I have table like:
name | timeStamp | previousValue | newValue
--------+---------------+-------------------+------------
Mark | 13.12.2020 | 123 | 155
Mark | 12.12.2020 | 123 | 12
Tom | 14.12.2020 | 123 | 534
Mark | 12.12.2020 | 123 | 31
Tom | 11.12.2020 | 123 | 84
Mark | 19.12.2020 | 123 | 33
Mark | 17.12.2020 | 123 | 96
John | 22.12.2020 | 123 | 69
John | 19.12.2020 | 123 | 33
I'd like to mix last_value, count (*) and group to get this result:
name | count | lastValue
--------+-----------+-------------
Mark | 5 | 33
Tom | 2 | 534
John | 2 | 69
This part:
select name, count(*)
from table
group by name
returns table:
name | count
--------+---------
Mark | 5
Tom | 2
John | 2
but I have to add the last value for each name.
How to do it?
Best regards!
LAST_VALUE is a windowed function, so you'll need to get that value first, and then aggregate:
WITH CTE AS(
SELECT [name],
[timeStamp], --This is a poor choice for a column's name. timestamp is a (deprecated) synonym of rowversion, and a rowversion is not a date and time value
previousValue,
newValue,
LAST_VALUE(newValue) OVER (PARTITION BY [name] ORDER BY [timeStamp] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lastValue
FROM dbo.YourTable)
SELECT [Name],
COUNT(*) AS [count],
lastValue
FROM CTE
GROUP BY [Name],
lastValue;
I got a solution that works, but here's another one:
SELECT
[name], COUNT([name]), [lastValue]
FROM (
SELECT
[name], FIRST_VALUE([newValue]) OVER (PARTITION BY [name] ORDER BY TimeStamp DESC ROWS UNBOUNDED PRECEDING) AS [lastValue]
FROM [table]
) xyz GROUP BY [name], [lastValue]
Keep well!

Ordering SQL query by hierarchy and it's random code

I've tried to search for something related and similar, but, couldn't find it.
This is a table that I need to get as result:
+-----+-----------+------+-------------------+
| ID | PARENT_ID | CODE | NAME |
+-----+-----------+------+-------------------+
| 218 | NULL | 1445 | First One |
| 235 | 218 | 2 | First Child |
| 247 | 235 | 45 | First Grandchild |
| 246 | 235 | 55 | Second Grandchild |
| 230 | 218 | 3 | Second Child |
| 238 | 230 | 12 | Third Grandchild |
| 231 | 230 | 20 | Fourth Grandchild |
+-----+-----------+------+-------------------+
The order must be by it's hierarchy followed by it's code.
I need this to make an assertion. And, if it's possible, I would like to get this only doing a query, without a method to sort this list.
This is a sample of what I'm trying to assert:
Tree Hierarchy
What I've done so far, it's the following recursive query:
WITH CTE (ID, PARENT_ID, CODE, NAME)
AS
-- Anchor:
(SELECT
ID,
PARENT_ID,
CODE,
NAME
FROM WAREHOUSE
WHERE PARENT_ID IS NULL
UNION ALL
-- Level:
SELECT
W.ID,
W.PARENT_ID,
W.CODE,
W.NAME
FROM WAREHOUSE AS W
INNER JOIN CTE
ON R.PARENT_ID = CTE.ID)
SELECT *
FROM CTE
I appreciate any help on this!
Thanks in advance!
Looks like you can use the [CODE] sequence in a hierarchyid path
Example
Declare #YourTable Table ([ID] int,[PARENT_ID] int,[CODE] varchar(50),[NAME] varchar(50))
Insert Into #YourTable Values
(218,NULL,1445,'First One')
,(235,218,2,'First Child')
,(247,235,45,'First Grandchild')
,(246,235,55,'Second Grandchild')
,(230,218,3,'Second Child')
,(238,230,12,'Third Grandchild')
,(231,230,20,'Fourth Grandchild')
;with cteP as (
Select ID
,PARENT_ID
,[Code]
,Name
,HierID = convert(hierarchyid,concat('/',[Code],'/'))
From #YourTable
Where Parent_ID is null
Union All
Select ID = r.ID
,PARENT_ID = r.PARENT_ID
,r.[Code]
,Name = r.Name
,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.[Code],'/'))
From #YourTable r
Join cteP p on r.PARENT_ID = p.ID)
Select Lvl = HierID.GetLevel()
,ID
,PARENT_ID
,[Code]
,Name
From cteP A
Order By A.HierID
Returns
Lvl ID PARENT_ID Code Name
1 218 NULL 1445 First One
2 235 218 2 First Child
3 247 235 45 First Grandchild
3 246 235 55 Second Grandchild
2 230 218 3 Second Child
3 238 230 12 Third Grandchild
3 231 230 20 Fourth Grandchild

Building data sets from multiple multivalue records using applys with missing values

I have a SQL Server 2012 database which was imported from a multi-value environment which causes me more headaches than I care to count, however it is what it is and I have to work with it.
I am trying to build a data set using these multi value records but have hit a stumbling bock. This is my scenario
I have a custom split string TVF that splits a string of "Test,String" into
Rowno | Item
------+---------
1 | Test
2 | String
I have the following data:
Clients Table
Ref | Names | Surname | DOB | IdNo
----+-----------+-----------+---------------------+------
123 |John,Sally |Smith | DOB1,DoB2 | 45,56
456 |Dave,Paul |Jones,Dann| DOB1,DOB2 | 98
789 |Mary,Moe,Al|Lee | DOB1 | NULL
What I need to create is a data set that looks like this:
Ref | Names | Surname | DOB | IdNo
----+-----------+-----------+---------------------+------
123 | John | Smith | DOB1 | 45
123 | Sally | Smith | DOB2 | 56
456 | Dave | Jones | DOB1 | 98
456 | Paul | Dann | DOB2 |
789 | Mary | Lee | DOB1 |
789 | Moe | Lee | |
789 | Al | Lee | |
In the past, to solve similar issues, I would tackle this using a query like this:
SELECT
Ref
, SplitForenames.ITEM names
, SplitSurname.ITEM Surname
, SplitDOB.ITEM dob
, SplitNI.ITEM ID
FROM
Clients
CROSS APPLY
dbo.udf_SplitString(Names, ',') SplitForenames
OUTER APPLY
dbo.udf_SplitString(Surname, ',') SplitSurname
OUTER APPLY
dbo.udf_SplitString(DOB, ',') SplitDOB
OUTER APPLY
dbo.udf_SplitString(ID, ',') SplitNI
WHERE
SplitSurname.RowNo = SplitForenames.RowNo
AND SplitDOB.RowNo = SplitForenames.RowNo
AND SplitNI.RowNo = SplitForenames.RowNo
ORDER BY
REF;
However due to there being examples of differences between the number of surnames to forenames and missing DOB and ID fields i cannot match them in this way.
I need to match where there is a match then otherwise be blank for DOB and ID and use the first instance of the surname. I am just stuck as to how to achieve this.
Anyone have any suggestions as to how i can create my required data-set from the original source.
Thanks in advance
I cant find what are the condition of DOB column to be split or not.
However: with the Split function SpliF as below:
CREATE FUNCTION SplitF(#str AS NVARCHAR(max))
RETURNS #People TABLE
(Rowno INT,Item NVARCHAR(10))
AS
BEGIN
DECLARE #i INT, #pos INT
DECLARE #subname NVARCHAR(max)
SET #I = 0;
WHILE(LEN(#str)>0)
BEGIN
SET #pos = CHARINDEX(',',#str)
IF #pos = 0 SET #pos = LEN(#str)+1
SET #subname = SUBSTRING(#str,1,#pos-1)
SET #str = SUBSTRING(#str, #pos+1, len(#str))
SET #i = #i + 1
INSERT INTO #People VALUES (#i, #subname)
END
RETURN
END
GO
select * from SplitF('test,my,function')
Rowno Item
----------- ----------
1 test
2 my
3 function
and basic data:
select Ref, Names, Surname, DOB, IdNo into #clients
from ( select 123 as Ref, 'John,Sally' as Names, 'Smith' as Surname,
'DOB1,DOB2' as DOB, '45,56' as IdNo
union all select 456, 'Dave,Paul','Jones,Dann','DOB1,DOB2', '98'
union all select 789, 'Mary,Moe,Al', 'Lee', 'DOB1', NULL) A
select * from #clients
Ref Names Surname DOB IdNo
----------- ----------- ---------- --------- -----
123 John,Sally Smith DOB1,DOB2 45,56
456 Dave,Paul Jones,Dann DOB1,DOB2 98
789 Mary,Moe,Al Lee DOB1 NULL
using below code you will get such results:
select
Ref,
RTrim(S_NAM.Item) as Names,
coalesce(S_SURNAM.Item,S_SURNAM_LAST.Item) AS Surname,
coalesce(split_dob.Item, '') as DOB,
coalesce(split_IdNo.Item,'') as IdNo
from
#clients MAIN
outer apply(select Rowno, Item from SplitF(MAIN.Names)) as S_NAM
outer apply(select top 1 Item from SplitF(MAIN.Surname) where Rowno = S_NAM.Rowno) as S_SURNAM
outer apply(select top 1 Item from SplitF(MAIN.Surname) order by Rowno desc) as S_SURNAM_LAST
outer apply(select top 1 Item from SplitF(MAIN.IdNo) where Rowno = S_NAM.Rowno) as split_IdNo
outer apply(select top 1 Item from SplitF(MAIN.DOB) where Rowno = S_NAM.Rowno) as split_dob
order by MAIN.Ref, S_NAM.Rowno
Ref Names Surname DOB IdNo
----------- ---------- ---------- ---------- ----------
123 John Smith DOB1 45
123 Sally Smith DOB2 56
456 Dave Jones DOB1 98
456 Paul Dann DOB2
789 Mary Lee DOB1
789 Moe Lee
789 Al Lee
I think you can handle this using subqueries and doing the RowNo comparison before the OUTER APPLY:
FROM Clients c CROSS APPLY
dbo.udf_SplitString(Names, ',') SplitForenames OUTER APPLY
(SELECT . . .
FROM dbo.udf_SplitString(Surname, ',') SplitSurname
WHERE SplitSurname.RowNo = SplitForenames.RowNo
) SplitSurname OUTER APPLY
(SELECT . . .
FROM dbo.udf_SplitString(DOB, ',') SplitDOB
WHERE SplitDOB.RowNo = SplitForenames.RowNo
) SplitDOB OUTER APPLY
(SELECT . . .
FROM dbo.udf_SplitString(DOB, ',') SplitNI
WHERE SplitNI.RowNo = SplitForenames.RowNo
) SplitNI

SQL recursion with full hierarchy

This is what my query looks like right now:
with allmembers (objectid, parentid, name, parentname, recursion) as
(
-- anchor elements: where parentid = 25
select objectid, parentid, name, name as parentname, 0 as recursion
from orgs as orgs1
where parentid = 25
-- recursion begins here
union all
select orgs2.objectid, orgs2.parentid, orgs2.name, orgs3.name as parentname, recursion + 1
from orgs as orgs2
join allmembers as orgs3 on orgs2.parentid = orgs3.objectid
)
-- we select all the results
select *
from allmembers
It selects the orgs (organizations) from a list, where the parentid is 25 (these are the "root organizations") and joins them with all their child organizations, recursively, until there are none left. So we get a list of organizations and their parents.
My problem is that I get only the direct child/parents relationsships:
name | parentname
Sales | All_Employees
Direct Sales | Sales
What is lost in this process is that "Direct Sales" is also a member of "All_Employees", indirectly, through "Sales".
So I would rather have the following result added:
name | parentname
Sales | All_Employees
Direct Sales | Sales
Direct Sales | All_Employees
How to achieve this?
Without going too far and getting functions involved, would using a materialized path suffice for your needs?
create table orgs (objectid int, name varchar(128), parentid int);
insert into orgs values
(26,'All Employees', 25)
,(27,'Sales', 26)
,(28,'Direct Sales',27);
with allmembers as (
-- anchor elements: where parentid = 25
select
objectid
, parentid
, name
, parentname = convert(varchar(128),'')
, rootname = name
, recursion = convert(int,0)
, name_path = convert(varchar(256),name)
from orgs
where parentid = 25
-- recursion begins here
union all
select
c.objectid
, c.parentid
, c.name
, parentname = p.name
, rootname = p.rootname
, recursion = p.recursion + 1
, name_path = convert(varchar(256),p.name_path + ' > ' + c.name)
from orgs as c
join allmembers as p on c.parentid = p.objectid
)
-- we select all the results
select *
from allmembers
returns:
+----------+----------+---------------+---------------+---------------+-----------+--------------------------------------+
| objectid | parentid | name | parentname | rootname | recursion | name_path |
+----------+----------+---------------+---------------+---------------+-----------+--------------------------------------+
| 26 | 25 | All Employees | | All Employees | 0 | All Employees |
| 27 | 26 | Sales | All Employees | All Employees | 1 | All Employees > Sales |
| 28 | 27 | Direct Sales | Sales | All Employees | 2 | All Employees > Sales > Direct Sales |
+----------+----------+---------------+---------------+---------------+-----------+--------------------------------------+

Resources