Extract the maximum value from the last number in a hierarchyID? - sql

I have a column with hierarchy IDs converted to strings in SQL Server. I need to add new hierarcyIDs for the new lines, but first I have to find the last child of the current ID. The hierarchyIDs are look like these:
/1/1/1/6/1/
/1/1/1/6/7/
/1/1/1/6/3/
/1/1/1/6/13/
/1/1/1/6/4/
As you can see, the maximum number is not equal with the count of the lines, so I can not use count()+1 unfortunately.
What I need to extract from this list is:
13
I only have experience in PL SQL, where it was easy to do this with regexp functions, but I can not find the solution in SQL Server.

You can use some STRING operation as below to get your desired output-
DEMO HERE
WITH your_table(your_column)
AS
(
SELECT '/1/1/1/6/1/' UNION ALL
SELECT '/1/1/1/6/7/' UNION ALL
SELECT '/1/1/1/6/3/' UNION ALL
SELECT '/1/1/1/6/13/' UNION ALL
SELECT '/1/1/1/6/4/'
)
SELECT
MAX(
CAST(
REVERSE(
LEFT(
REVERSE(LEFT(your_column,LEN(your_column)-1)),
CHARINDEX('/',REVERSE(LEFT(your_column,LEN(your_column)-1)) ,0) - 1
)
)
AS INT
)
)
FROM your_table
Note: Data has to be as your sample data

The creators of hierarchyid have anticipated your needs and have an officially supported solution for this.
declare #h table (h HIERARCHYID);
insert into #h (h)
values
('/1/1/1/6/1/'),
('/1/1/1/6/7/'),
('/1/1/1/6/3/'),
('/1/1/1/6/13/'),
('/1/1/1/6/4/');
declare #parent HIERARCHYID = '/1/1/1/6/';
declare #maxChild HIERARCHYID = (
select max(h)
from #h
where h.IsDescendantOf(#parent) = 1
);
-- ToString() added here for readability in the output;
-- it's not needed to be used as data.
select #parent.GetDescendant(#maxChild, null).ToString();
You can read more about this here.
Another way around this is to specify your own components to the hierarchyid yourself. I like to use the primary key values. For example, let's say that this data represents a company's org chart. If EmployeeID 1 is the CEO, 42 is the CFO (who reports to the CEO) and 306 is Accounting Manager (who reports to the CFO), the latter's hierarchyid would be /1/42/306/. Because the PK values are unique, the generated hierarchid is also unique.

To give some ideas, 2 solutions.
The first one is for Sql Server 2017 and beyond.
The second for most versions below that.
create table YourTable
(
ID int identity(101,1) primary key,
hierarcyIDs varchar(100)
)
GO
✓
insert into YourTable
(hierarcyIDs) values
('/1/2/3/4/5/')
,('/1/2/3/4/15/')
,('/1/2/3/4/10/')
,('/11/12/13/14/15/')
,('/11/12/13/14/42/')
;
GO
5 rows affected
SELECT
[1] as id1,
[2] as id2,
[3] as id3,
[4] as id4,
max([5]) as id5,
concat_ws('/','',[1],[2],[3],[4],max([5])+1,'') as nextHierarcyIDs
FROM YourTable
OUTER APPLY
(
select *
from
( select try_cast(value as int) as id
, row_number() over (order by (select 0)) rn
from string_split(trim('/' from hierarcyIDs),'/') s
) src
pivot (max(id)
for rn in ([1],[2],[3],[4],[5])
) pvt
) anarchy
group by [1],[2],[3],[4]
GO
id1 | id2 | id3 | id4 | id5 | nextHierarcyIDs
--: | --: | --: | --: | --: | :---------------
1 | 2 | 3 | 4 | 15 | /1/2/3/4/16/
11 | 12 | 13 | 14 | 42 | /11/12/13/14/43/
SELECT hierarcyIdLvl1to4
, MAX(hierarcyIDLvl5) AS maxHierarcyIDLvl5
FROM
(
SELECT id, hierarcyIDs
, substring(hierarcyIDs, 0, len(hierarcyIDs)-charindex('/',
reverse(hierarcyIDs),2)+2) AS hierarcyIdLvl1to4
, reverse(substring(reverse(hierarcyIDs),2,charindex('/',
reverse(hierarcyIDs),2)-2)) AS hierarcyIDLvl5
FROM YourTable
) q
GROUP BY hierarcyIdLvl1to4
GO
hierarcyIdLvl1to4 | maxHierarcyIDLvl5
:---------------- | :----------------
/1/2/3/4/ | 5
/11/12/13/14/ | 42
db<>fiddle here

Related

Sort an array of strings in SQL

I have a column of strings in SQL Server 2019 that I want to sort
Select * from ID
[7235, 6784]
[3235, 2334]
[9245, 2784]
[6235, 1284]
Trying to get the result below:
[6784, 7235]
[2334, 3235]
[2784, 9245]
[1284, 6235]
Given this sample data:
CREATE TABLE dbo.ID(ID int IDENTITY(1,1), SomeCol varchar(64));
INSERT dbo.ID(SomeCol) VALUES
('[7235, 6784]'),
('[3235, 2334]'),
('[9245, 2784]'),
('[6235, 1284]');
You can run this query:
;WITH cte AS
(
SELECT ID, SomeCol,
i = TRY_CONVERT(int, value),
s = LTRIM(value)
FROM dbo.ID CROSS APPLY
STRING_SPLIT(PARSENAME(SomeCol, 1), ',') AS s
)
SELECT ID, SomeCol,
Result = QUOTENAME(STRING_AGG(s, ', ')
WITHIN GROUP (ORDER BY i))
FROM cte
GROUP BY ID, SomeCol
ORDER BY ID;
Output:
ID
SomeCol
Result
1
[7235, 6784]
[6784, 7235]
2
[3235, 2334]
[2334, 3235]
3
[9245, 2784]
[2784, 9245]
4
[6235, 1284]
[1284, 6235]
Example db<>fiddle
The source table has a column with a JSON array.
That's why it is a perfect case to handle it via SQL Server JSON API.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID int IDENTITY PRIMARY KEY, jArray NVARCHAR(100));
INSERT #tbl (jArray) VALUES
('[7235, 6784]'),
('[3235, 2334]'),
('[9245, 2784]'),
('[6235, 1284]');
-- DDL and sample data population, end
SELECT t.*
, Result = QUOTENAME(STRING_AGG(j.value, ', ')
WITHIN GROUP (ORDER BY j.value ASC))
FROM #tbl AS t
CROSS APPLY OPENJSON(t.jArray) AS j
GROUP BY t.ID, t.jArray
ORDER BY t.ID;
Output
+----+--------------+--------------+
| ID | jArray | Result |
+----+--------------+--------------+
| 1 | [7235, 6784] | [6784, 7235] |
| 2 | [3235, 2334] | [2334, 3235] |
| 3 | [9245, 2784] | [2784, 9245] |
| 4 | [6235, 1284] | [1284, 6235] |
+----+--------------+--------------+

SQL: List/aggregate all items for one corresponding transaction id

I have the following table in a vertica db:
+-----+------+
| Tid | Item |
+-----+------+
| 1 | A |
| 1 | B |
| 1 | C |
| 2 | B |
| 2 | D |
+-----+------+
And I want to get this table:
+-----+-------+-------+-------+
| Tid | Item1 | Item2 | Item3 |
+-----+-------+-------+-------+
| 1 | A | B | C |
| 2 | B | D | |
+-----+-------+-------+-------+
Keep in mind that I don't know the maximum item number a transaction_id (Tid) can have, and the amount of items per Tid is not constant. I tried using join and where but could not get it to work properly. Thanks for the help.
There is no PIVOT ability in Vertica. Columns can not be defined on the fly as part of the query. You have to specify.
There are perhaps other options, such as concatenating them in an aggregate using a UDX, such as what you will find in this Stack Overflow answer. But this will put them into a single field.
The only other alternative would be to build the pivot on the client side using something like Python. Else you have to have a way to generate the column lists for your query.
For my example, I am assuming you are dealing with a unique (Tid, Item) set. You may need to modify to suite your needs.
First you would need to determine the max number if items you need to support:
with Tid_count as (
select Tid, count(*) cnt
from mytable
group by 1
)
select max(cnt)
from Tid_count;
And let's say the most Items you had to support was 4, you would then generate a sql to pivot:
with numbered_mytable as (
select Tid,
Item,
row_number() over (partition by Tid order by Item) rn
from mytable
)
select Tid,
MAX(decode(rn,1,Item)) Item1,
MAX(decode(rn,2,Item)) Item2,
MAX(decode(rn,3,Item)) Item3,
MAX(decode(rn,4,Item)) Item4
from numbered_mytable
group by 1
order by 1;
Or if you don't want to generate SQL, but know you'll never have more than X items, you can just create a static form that goes to X.
You can try this:
Create table #table(id int,Value varchar(1))
insert into #table
select 1,'A'
union
select 1,'B'
union
select 1,'C'
union
select 2,'B'
union
select 2,'D'
select id,[1] Item1,[2] Item2,[3] Item3 from
(
select id,Dense_rank()over(partition by id order by value)Rnak,Value from #table
)d
Pivot
(Min(value) for Rnak in ([1],[2],[3]))p
drop table #table

transform rows into one row based on unique ID

Reason | ID
---------------------------------------
Sales - Agent Attitude | 2
---------------------------------------
Billing - Process | 2
---------------------------------------
Technical - Outages | 1005
---------------------------------------
Technical - knowledge | 1005
---------------------------------------
Others | 1005
---------------------------------------
i have the above table and i want a result like the below by using SQL server i can combine rows into one row by separating them by , by using STUFF() function but i want the format as the below so any help
ID | Reason 1 | Reason 2 | Reason 3
---------------------------------------------------------------------
2 | Sales - Agent Attitude | Billing - Process | NULL
---------------------------------------------------------------------
1005 | Technical - Outages | Technical - knowledge |Others
---------------------------------------------------------------------
Try below query..
select distinct
ID , [1] as 'Reason 1' , [2] as 'Reason 2' , [3] as 'Reason 3'
from
(
select *, ROW_NUMBER() OVER ( PARTITION BY id ORDER BY reason DESC ) as RID from #temp
) src
pivot
(
max(Reason)
for rid in ([1], [2],[3])
) piv
plz let us know if you have any que. or concerns
This is the pivot solution:
;with
t as (
SELECT 'Reason ' + cast(ROW_NUMBER() over (partition by id order by id) as varchar(2)) n, *
from YourTable
)
select *
FROM t
pivot (min(reason) for n in ([Reason 1],[Reason 2],[Reason 3],[Reason 4],[Reason 5])) p

Counting Policy Ages with a Query in tsql

I have a table containing insurance policies (let's call it POLICIES) in one field, along with the policies off of which they were renewed in another field:
POLICY_ID | PRIOR_POLICY_ID
===========================
ABC |
ABD | ABC
AFP |
ANR | ABD
BRC | AFP
FKZ |
I would like to write a query to count the total number of prior policies for each policy, with the result looking like this:
POLICY_ID | NUM_PRIOR_POLICIES
==============================
ABC | 0
ABD | 1
AFP | 0
ANR | 2
BRC | 1
FKZ | 0
Any suggestions would be appreciated.
You need a recursive CTE for this:
with cte as (
select p.policy_id, 0 as num_priors
from policies p
where prior_policy_id is null
union all
select p.policy_id, 1 + cte.num_priors
from cte join
policies p
on p.prior_policy_id = cte.policy_id
)
select *
from cte;
Here is a SQL Fiddle showing it working.
DECLARE #data TABLE ( POLICY_ID char(3), PRIOR_POLICY_ID char(3) );
INSERT #data VALUES
('ABC',NULL ),('ABD','ABC'),('AFP',NULL ),
('ANR','ABD'),('BRC','AFP'),('FKZ',NULL );
WITH cte AS (
SELECT POLICY_ID, 0 AS NUM_PRIOR_POLICIES
FROM #data
WHERE PRIOR_POLICY_ID IS NULL
UNION ALL
SELECT d.POLICY_ID, NUM_PRIOR_POLICIES + 1
FROM cte c
INNER JOIN #data d
ON (c.POLICY_ID = d.PRIOR_POLICY_ID)
)
SELECT POLICY_ID, NUM_PRIOR_POLICIES
FROM cte
ORDER BY POLICY_ID

SQL: Putting an individuals distinct diagnosis into one horizontal row

I'm using Microsoft SQL Server 2008 for a mental health organization.
I have a table that lists all of out clients and their diagnoses, but each diagnoses that a client has is in a new row. I want them all to be in a single row listed out horizontally with the date for each diagnosis. Some people have just one diagnosis, some have 20, some have none.
Here's an example of how my data sort of looks now (only with a lot few clients, we have thousands):
And Here's the format I'd like it to end up:
Any solutions you could offer or hints in the right direction would be great, thanks!
In order to get the result, I would first unpivot and then pivot your data. The unpivot will take your date and diagnosis columns and convert them into rows. Once the data is in rows, then you can apply the pivot.
If you have a known number of values, then you can hard-code your query similar to this:
select *
from
(
select person, [case#], age,
col+'_'+cast(rn as varchar(10)) col,
value
from
(
select person,
[case#],
age,
diagnosis,
convert(varchar(10), diagnosisdate, 101) diagnosisDate,
row_number() over(partition by person, [case#]
order by DiagnosisDate) rn
from yourtable
) d
cross apply
(
values ('diagnosis', diagnosis), ('diagnosisDate', diagnosisDate)
) c (col, value)
) t
pivot
(
max(value)
for col in (diagnosis_1, diagnosisDate_1,
diagnosis_2, diagnosisDate_2,
diagnosis_3, diagnosisDate_3,
diagnosis_4, diagnosisDate_4)
) piv;
See SQL Fiddle with Demo.
I am going to assume that you will have an unknown number of diagnosis values for each case. If that is the case, then you will need to use dynamic sql to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+'_'+cast(rn as varchar(10)))
from
(
select row_number() over(partition by person, [case#]
order by DiagnosisDate) rn
from yourtable
) t
cross join
(
select 'Diagnosis' col union all
select 'DiagnosisDate'
) c
group by col, rn
order by rn, col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT person,
[case#],
age,' + #cols + '
from
(
select person, [case#], age,
col+''_''+cast(rn as varchar(10)) col,
value
from
(
select person,
[case#],
age,
diagnosis,
convert(varchar(10), diagnosisdate, 101) diagnosisDate,
row_number() over(partition by person, [case#]
order by DiagnosisDate) rn
from yourtable
) d
cross apply
(
values (''diagnosis'', diagnosis), (''diagnosisDate'', diagnosisDate)
) c (col, value)
) t
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. Both queries give the result:
| PERSON | CASE# | AGE | DIAGNOSIS_1 | DIAGNOSISDATE_1 | DIAGNOSIS_2 | DIAGNOSISDATE_2 | DIAGNOSIS_3 | DIAGNOSISDATE_3 | DIAGNOSIS_4 | DIAGNOSISDATE_4 |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| John | 13784 | 56 | Depression | 03/13/2012 | Brain Injury | 03/14/2012 | Spinal Cord Injury | 03/15/2012 | Hypertension | 03/16/2012 |
| Kate | 2643 | 37 | Bipolar | 03/11/2012 | Hypertension | 03/12/2012 | (null) | (null) | (null) | (null) |
| Kevin | 500934 | 25 | Down Syndrome | 03/18/2012 | Clinical Obesity | 03/19/2012 | (null) | (null) | (null) | (null) |
| Pete | 803342 | 34 | Schizophenia | 03/17/2012 | (null) | (null) | (null) | (null) | (null) | (null) |
For this type of pivoting, I think the aggregate/group method is feasible:
select d.case, d.person,
max(case when seqnum = 1 then diagnosis end) as d1,
max(case when seqnum = 1 then diagnosisdate end) as d1date,
max(case when seqnum = 2 then diagnosis end) as d2,
max(case when seqnum = 2 then diagnosisdate end) as d2date,
. . . -- and so on, for as many groups that you want
from (select d.*, row_number() over (partition by case order by diagnosisdate) as seqnum
from diagnoses d
) d
group by d.case, d.person
Since you are dealing with sensitive medical information, identifyiable information (name age etc) shouldn't be stored in the same table as the medical information. Also, if you extract out the person info into its own table and a Diagnosis table that has the personID foreign key you can establish the 1 to many relationship you want.
Unless you use Dynamic SQL, the PIVOT operator will not work here. I assume that patients can come in on any date. The PIVOT operator works with a finite and predefined number of columns. Your options are to use Dynamic SQL to create the PIVOT table, or to use Excel or a reporting tool like SSRS to do a Pivot report.
I think the Dynamic SQL option would not be practical here, since, you could end up having hundreds of columns for each of the patient visit dates.
If you want to explore the Dynamic SQL option anyway, have a look here:
https://www.simple-talk.com/blogs/2007/09/14/pivots-with-dynamic-columns-in-sql-server-2005/