Counting Policy Ages with a Query in tsql - sql

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

Related

Filtering in SQL Server table on multiple columns - T-SQL

EDITED
We have a table like below where there are multiple rows with same Name with different Ops
Deal
Name
Ops
ABC
A
NULL
ABC
A
NULL
ABC
B
NULL
ABC
B
NULL
ABC
B
Default
ABC
B
Default
ABC
C
NULL
ABC
C
NULL
ABC
C
Default
ABC
C
Default
ABC
C
Aggr
ABC
C
Aggr
We need to get rows with Default when both NULL and Default is tagged and Aggr when NULL, Default and Aggr are tagged
Expected output:
Deal
Name
Ops
ABC
A
NULL
ABC
A
NULL
ABC
B
Default
ABC
B
Default
ABC
C
Aggr
ABC
C
Aggr
We need to get this using a SQL query.
I have tried this one here:
WITH PriorityRanking AS
(
SELECT
DENSE_RANK() OVER (PARTITION BY Deal, Name
ORDER BY
CASE COALESCE(Ops,NULL)
WHEN 'Aggr' THEN 1
WHEN 'Default' THEN 2
WHEN NULL THEN 3
END) AS Rnk,
*
FROM
Table_name
)
SELECT Deal, Name, Ops
FROM
(SELECT Deal, Name, Ops,
FROM PriorityRanking
WHERE Rnk = 1)
But the rank is set to 1 for NULL values and so the final select is not working properly.
Please suggest what is the best way to get the required data
I think you just need to use row_number and coalesce your NULL values to be ordered below the other values -
with r as (
select *, Row_Number() over(partition by deal, name order by IsNull(Ops,'x') ) rn
from t
)
select Deal, Name, Ops
from r
where rn = 1;
depending on your real data as mentioned you could also simply aggregate:
select Deal, Name, min(Ops) Ops
from t
group by Deal, Name;
The below logic can work
WITH cte1 as
(
Select deal,name,isnull(Ops,'Z') as Col from table
),
cte2 as
(
Select *,row_number() over(Partition by Deal,name order by col) as rnbr from cte1
)
Select deal,name,case when col='Z' then NUll else Col end as Ops from cte2 where rnbr=1
You can replace NULL with a text and sort after that
After changing your data, the basic concept stays valid
But now you need DENSE_RANK like you had it in your original question, as SQL SERVER can't handle NULL, so replacing will help
WITH PriorityRanking AS
(
SELECT DENSE_RANK() OVER (PARTITION BY Name ORDER BY CASE ISNULL(Ops,'NULL')
WHEN 'Aggr' THEN 1
WHEN 'Default' THEN 2
WHEN 'NULL' THEN 3
END) AS Rnk,*
FROM Table_name
)
SELECT [Deal], [Name], [Ops] FROM PriorityRanking WHERE Rnk = 1
Deal | Name | Ops
:--- | :--- | :------
ABC | A | null
ABC | A | null
ABC | B | Default
ABC | B | Default
ABC | C | Aggr
ABC | C | Aggr
db<>fiddle here
WITH PriorityRanking AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY Name ORDER BY CASE ISNULL(Ops,'NULL')
WHEN 'Aggr' THEN 1
WHEN 'Default' THEN 2
WHEN 'NULL' THEN 3
END) AS Rnk,*
FROM Table_name
)
SELECT Deal, Name, Ops FROM PriorityRanking where Rnk = 1
Deal | Name | Ops
:--- | :--- | :------
ABC | A | null
ABC | B | Default
ABC | C | Aggr
db<>fiddle here

Oracle SQL Get unique symbols from table

I have table with descriptions of smth. For example:
My_Table
id description
================
1 ABC
2 ABB
3 OPAC
4 APEЧ
I need to get all unique symbols from all "description" columns.
Result should look like that:
symbol
================
A
B
C
O
P
E
Ч
And it shoud work for all languages, so, as I see, regular expressions cant help.
Please help me. Thanks.
with cte (c,description_suffix) as
(
select substr(description,1,1)
,substr(description,2)
from mytable
where description is not null
union all
select substr(description_suffix,1,1)
,substr(description_suffix,2)
from cte
where description_suffix is not null
)
select c
,count(*) as cnt
from cte
group by c
order by c
or
with cte(n) as
(
select level
from dual
connect by level <= (select max(length(description)) from mytable)
)
select substr(t.description,c.n,1) as c
,count(*) as cnt
from mytable t
join cte c
on c.n <= length(description)
group by substr(t.description,c.n,1)
order by c
+---+-----+
| C | CNT |
+---+-----+
| A | 4 |
| B | 3 |
| C | 2 |
| E | 1 |
| O | 1 |
| P | 2 |
| Ч | 1 |
+---+-----+
Create a numbers table and populate it with all the relevant ids you'd need (in this case 1..maxlength of string)
SELECT DISTINCT
locate(your_table.description, numbers.id) AS symbol
FROM
your_table
INNER JOIN
numbers
ON numbers.id >= 1
AND numbers.id <= CHAR_LENGTH(your_table.description)
SELECT DISTINCT(SUBSTR(ll,LEVEL,1)) OP --Here DISTINCT(SUBSTR(ll,LEVEL,1)) is used to get all distinct character/numeric in vertical as per requirment
FROM
(
SELECT LISTAGG(DES,'')
WITHIN GROUP (ORDER BY ID) ll
FROM My_Table --Here listagg is used to convert all values under description(des) column into a single value and there is no space in between
)
CONNECT BY LEVEL <= LENGTH(ll);

How to create a condition for this case?

Sample Table:
Id |Acc_Code|Description |Balance | Acclevel| Acctype| Exttype|
--- -------- ----------------- |-------- |-------- | -------| -------|
1 |SA |Sales | 0.00 | 1 | SA | |
2 |CS |Cost of Sales | 0.00 | 1 | CS | |
3 |5000/001|Revenue | 94.34 | 2 | SA | |
4 |5000/090|Sales(Local) | 62.83 | 2 | SA | |
5 |7000/000|Manufacturing Acc |-250.80 | 2 | CS | MA |
6 |7000/200|Manufacturing Acc | 178.00 | 2 | CS | |
This is a sample data of a temporary table which would be used to be inserted into another temporary table that would calculate the data for Profit and Loss Statement (For Manufacturing related Accounts only).
In this case, the acc_code for Manufacturing accounts start from 7000/000 and separated/partitioned for each following Exttype.
Eg: We start from the exttype of MA and based on its acclevel (could be 2 or more) until the next exttype.
The idea is we get the manufacturing accounts by SELECT FROM tmp_acc_list WHERE acc_code BETWEEN #start_acc_code (7000/000 in this case) AND #end_acc_code (the data before the next exttype)
I don't know what the exttype is, I'm still learning the tables.
How do we create the #end_acc_code part out from this sample table?
So here is a all in one script.
I created Your table for test:
create table #tmp_acc_list(
Id numeric,
Acc_Code nvarchar(100),
Acclevel numeric,
Acctype nvarchar(100),
Exttype nvarchar(100));
GO
insert into #tmp_acc_list(Id, Acc_Code, Acclevel, Acctype, Exttype)
select 1 , 'SA', 1,'SA', null union all
select 2 , 'CS', 1,'CS', null union all
select 3 , '5000/001', 2,'SA', null union all
select 4 , '5000/090', 2,'SA', null union all
select 5 , '7000/000', 2,'CS', 'MA' union all
select 6 , '7000/200', 2,'CS', null
;
Then comes the query:
with OrderedTable as -- to order the table is Id is not an order
(
select
t.*, ROW_NUMBER() over (
order by id asc --use any ordering You need here
)
as RowNum
from
#tmp_acc_list as t
),
MarkedTable as -- mark with common number
(
select
t.*,
Max(case when t.Exttype is null then null else t.RowNum end)
over (order by t.RowNum) as GroupRownum
from OrderedTable as t
),
GroupedTable as -- add group Exttype
(
select
t.Id, t.Acc_Code, t.Acclevel, t.Acctype, t.Exttype,
max(t.Exttype) over (partition by t.GroupRownum) as GroupExttype
from MarkedTable as t
)
select * from GroupedTable where GroupExttype = 'MA'
Is this what You need?
select *
from
(
select Id, Acc_Code
from tmp_acc_list
where Acc_Code = '7000/000'
) s
cross join tmp_acc_list a
cross apply
(
select top 1 x.Id, x.Acc_Code
from tmp_acc_list x
where x.Id >= a.Id
and x.AccLevel = a.AccLevel
and x.Acctype = a.Acctype
and x.Exttype = ''
order by Id desc
) e
where a.Id between s.Id and e.Id

SQL Server - Select Distinct of two columns, where the distinct column selected has a maximum value based on two other columns

I have 2 tables - TC and T, with columns specified below. TC maps to T on column T_ID.
TC
----
T_ID,
TC_ID
T
-----
T_ID,
V_ID,
Datetime,
Count
My current result set is:
V_ID TC_ID Datetime Count
----|-----|------------|--------|
2 | 1 | 2013-09-26 | 450600 |
2 | 1 | 2013-12-09 | 14700 |
2 | 1 | 2014-01-22 | 15000 |
2 | 1 | 2014-01-22 | 15000 |
2 | 1 | 2014-01-22 | 7500 |
4 | 1 | 2014-01-22 | 1000 |
4 | 1 | 2013-12-05 | 0 |
4 | 2 | 2013-12-05 | 0 |
Using the following query:
select T.V_ID,
TC.TC_ID,
T.Datetime,
T.Count
from T
inner join TC
on TC.T_ID = T.T_ID
Result set I want:
V_ID TC_ID Datetime Count
----|-----|------------|--------|
2 | 1 | 2014-01-22 | 15000 |
4 | 1 | 2014-01-22 | 1000 |
4 | 2 | 2013-12-05 | 0 |
I want to write a query to select each distinct V_ID + TC_ID combination, but only with the maximum datetime, and for that datetime the maximum count. E.g. for the distinct combination of V_ID = 2 and TC_ID = 1, '2014-01-22' is the maximum datetime, and for that datetime, 15000 is the maximum count, so select this record for the new table. Any ideas? I don't know if this is too ambitious for a query and I should just handle the result set in code instead.
One method uses row_number():
select v_id, tc_id, datetime, count
from (select T.V_ID, TC.TC_ID, T.Datetime, T.Count,
row_number() over (partition by t.V_ID, tc.tc_id
order by datetime desc, count desc
) as seqnum
from t join
tc
on tc.t_id = t._id
) tt
where seqnum = 1;
The only issue is that some rows have the same maximum datetime value. SQL tables represent unordered sets, so there is no way to determine which is really the maximum -- unless the datetime really has a time component or another column specifies the ordering within a day.
It is possible to solve this using CTEs. First, extracting the data from your query. Second, get the maxdates. Third, get the highest count for each maxdate.:
;WITH Dataset AS
(
select T.V_ID,
TC.TC_ID,
T.[Datetime],
T.[Count]
from T
inner join TC
on TC.T_ID = T._ID
),
MaxDates AS
(
SELECT V_ID, TC_ID, MAX(t.[Datetime]) AS MaxDate
FROM Dataset t
GROUP BY t.V_ID, t.TC_ID
)
SELECT t.V_ID, t.TC_ID, t.[Datetime], MAX(t.[Count]) AS [Count]
FROM Dataset t
INNER JOIN MaxDates m ON t.V_ID = m.V_ID AND t.TC_ID = m.TC_ID AND m.MaxDate = t.[Datetime]
GROUP BY t.V_ID, t.TC_ID, t.[Datetime]
Just to keep it simple:
You need to group by T.V_ID,TC.TC_ID,
with selecting the max of date and then to get the maximum count, you must use a sub query as follows,
select T.V_ID,
TC.TC_ID,
max(T.Datetime) as Date_Time,
(select max(Count) from T as tb where v_ID = T.v_ID and DateTime = max(T.DateTime)) as Count
from T
inner join TC
on TC.T_ID = T._ID
group by T.V_ID,TC.TC_ID,

MSSQL: Only last entry in GROUP BY (with id)

Following / copying computhomas's question, but adding some twists...
I have the following table in MSSQL2008
id | business_key | result | date
1 | 1 | 0 | 9
2 | 1 | 1 | 8
3 | 2 | 1 | 7
4 | 3 | n | 6
5 | 4 | 1 | 5
6 | 4 | 0 | 4
And now i want to group based on the business_key returning the complete entry with the newest date.
So my expected result is:
id | business_key | result | date
1 | 1 | 0 | 9
3 | 2 | 1 | 7
4 | 3 | n | 6
5 | 4 | 1 | 5
I also bet that there is a way to achieve that, i just can't find / see / think of it at the moment.
edit: sorry about this, I actually meant something else from original question I did. I felt like editing this might be better than accepting a solution and making another question. my original problem was that I am not filtering by id.
SELECT t.*
FROM
(
SELECT *, ROW_NUMBER() OVER
(
PARTITION BY [business_key]
ORDER BY [date] DESC
) AS [RowNum]
FROM yourTable
) AS t
WHERE t.[RowNum] = 1
SELECT
*
FROM
mytable
WHERE
ID IN (SELECT MAX(ID) FROM mytable GROUP BY business_key)
SELECT
MAX(T1.id) AS [id],
T1.business_key,
T1.result
FROM
dbo.My_Table T1
LEFT OUTER JOIN dbo.My_Table T2 ON
T2.business_key = T1.business_key AND
T2.id > T1.id
WHERE
T2.id IS NULL
GROUP BY T1.business_key,
T1.result
ORDER BY MAX(T1.id)
Edited based on clarifications
SELECT M1.*
FROM My_Table M1
INNER JOIN
(
SELECT [business_key], MAX([date]) as MaxDate
FROM My_Table
GROUP BY [business_key]
) M2 ON M1.business_key = M2.business_key AND M1.[date] = M2.MaxDate
ORDER BY M1.[id]
Assuming the combination of business_key & date is unique then....
Working example (3rd time is a charm):
declare #src as table(id int, business_key int,result int,[date] int)
insert into #src
SELECT 1,1,0,9
UNION SELECT 2,1,1,8
UNION SELECT 3,2,1,7
UNION SELECT 4,3,1,6
UNION SELECT 5,4,1,5
UNION SELECT 6,4,0,4
;with bkdate(business_key,[date])
AS
(
select business_key,MAX([date])
from #src
group by business_key
)
select src.* from #src src
inner join bkdate
ON src.[date] = bkdate.date
and src.business_key = bkdate.business_key
order by id
How about (edited after question change):
with latestdate as (
select business_key, maxdate=max(date)
from the_table
group by business_key
), latest as (
select ID = max(id)
from the_table
inner join latestdate
on the_table.business_key=latestdate.business_key
and the_table.date=latestdate.maxdate
group by the_table.business_key
)
select the_table.*
from the_table
inner join latest
on latest.id=the_table.id