Find Missing multiple numbers sequence in SQL server - sql

My table is like this:
DrawDate num DrawName etc...
2015-08-01 2 Draw 2
2015-08-01 3 Draw 3
2015-08-02 1 Draw 1
2015-08-02 2 Draw 2
2015-08-02 4 Draw 4
2015-08-03 3 Draw 3
2015-08-04 1 Draw 1
2015-08-04 2 Draw 2
2015-08-04 3 Draw 3
2015-08-04 4 Draw 4
i would like to get the missing sequence number (num column) in the Table.
How could i achieve this?
I had find so many solutions but there are drawbacks of not including start number or the hardcoding the first and last number for sequence. But i could not hardcode there any value. Start value is specified by 1 but no end sequence is defined. it could be 4 as in the table above or in some cases may be 18 or 20 but i need to find the duplicates by drawdate maximum value (the last one).
Edit
The Final Result would be
DrawDate missingnum
2015-08-01 1
2015-08-02 3
2015-08-03 1
2015-08-03 2

Let me guess:
declare #t table
(
DrawDate date,
Num int
)
insert into #t values
('2015-08-01' , 2 ),
('2015-08-01' , 3 ),
('2015-08-02' , 1 ),
('2015-08-02' , 2 ),
('2015-08-02' , 4 ),
('2015-08-03' , 3 ),
('2015-08-04' , 1 ),
('2015-08-04' , 2 ),
('2015-08-04' , 3 ),
('2015-08-04' , 4 );
with AvailableDates as
(
select distinct DrawDate from #t
),
AvailableNumbers as
(
select distinct Num from #t
),
CrossJoined as
(
select
DrawDate,
Num
from
AvailableDates
cross join AvailableNumbers
)
select
*
from
CrossJoined cj
left join #t t on t.DrawDate = cj.DrawDate and t.Num = cj.Num
where
t.Num is null;
Edit
The second guess (after the comment reading):
with MaxNumberPerDate as
(
select DrawDate, MaxNum = max(Num) from #t group by DrawDate
),
N as
(
select n from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)
),
Multiplied as
(
select
DrawDate,
Num = N.n
from
MaxNumberPerDate md
inner join N on N.n <= md.MaxNum
)
select
m.*
from
Multiplied m
left join #t t on t.DrawDate = m.DrawDate and t.Num = m.Num
where
t.Num is null;

Related

Break up running sum into maximum group size / length

I am trying to break up a running (ordered) sum into groups of a max value. When I implement the following example logic...
IF OBJECT_ID(N'tempdb..#t') IS NOT NULL DROP TABLE #t
SELECT TOP (ABS(CHECKSUM(NewId())) % 1000) ROW_NUMBER() OVER (ORDER BY name) AS ID,
LEFT(CAST(NEWID() AS NVARCHAR(100)),ABS(CHECKSUM(NewId())) % 30) AS Description
INTO #t
FROM sys.objects
DECLARE #maxGroupSize INT
SET #maxGroupSize = 100
;WITH t AS (
SELECT
*,
LEN(Description) AS DescriptionLength,
SUM(LEN(Description)) OVER (/*PARTITION BY N/A */ ORDER BY ID) AS [RunningLength],
SUM(LEN(Description)) OVER (/*PARTITION BY N/A */ ORDER BY ID)/#maxGroupSize AS GroupID
FROM #t
)
SELECT *, SUM(DescriptionLength) OVER (PARTITION BY GroupID) AS SumOfGroup
FROM t
ORDER BY GroupID, ID
I am getting groups that are larger than the maximum group size (length) of 100.
A recusive common table expression (rcte) would be one way to resolve this.
Sample data
Limited set of fixed sample data.
create table data
(
id int,
description nvarchar(20)
);
insert into data (id, description) values
( 1, 'qmlsdkjfqmsldk'),
( 2, 'mldskjf'),
( 3, 'qmsdlfkqjsdm'),
( 4, 'fmqlsdkfq'),
( 5, 'qdsfqsdfqq'),
( 6, 'mds'),
( 7, 'qmsldfkqsjdmfqlkj'),
( 8, 'qdmsl'),
( 9, 'mqlskfjqmlkd'),
(10, 'qsdqfdddffd');
Solution
For every recursion step evaluate (r.group_running_length + len(d.description) <= #group_max_length) if the previous group must be extended or a new group must be started in a case expression.
Set group target size to 40 to better fit the sample data.
declare #group_max_length int = 40;
with rcte as
(
select d.id,
d.description,
len(d.description) as description_length,
len(d.description) as running_length,
1 as group_id,
len(d.description) as group_running_length
from data d
where d.id = 1
union all
select d.id,
d.description,
len(d.description),
r.running_length + len(d.description),
case
when r.group_running_length + len(d.description) <= #group_max_length
then r.group_id
else r.group_id + 1
end,
case
when r.group_running_length + len(d.description) <= #group_max_length
then r.group_running_length + len(d.description)
else len(d.description)
end
from rcte r
join data d
on d.id = r.id + 1
)
select r.id,
r.description,
r.description_length,
r.running_length,
r.group_id,
r.group_running_length,
gs.group_sum
from rcte r
cross apply ( select max(r2.group_running_length) as group_sum
from rcte r2
where r2.group_id = r.group_id ) gs -- group sum
order by r.id;
Result
Contains both the running group length as well as the group sum for every row.
id description description_length running_length group_id group_running_length group_sum
-- ---------------- ------------------ -------------- -------- -------------------- ---------
1 qmlsdkjfqmsldk 14 14 1 14 33
2 mldskjf 7 21 1 21 33
3 qmsdlfkqjsdm 12 33 1 33 33
4 fmqlsdkfq 9 42 2 9 39
5 qdsfqsdfqq 10 52 2 19 39
6 mds 3 55 2 22 39
7 qmsldfkqsjdmfqlkj 17 72 2 39 39
8 qdmsl 5 77 3 5 28
9 mqlskfjqmlkd 12 89 3 17 28
10 qsdqfdddffd 11 100 3 28 28
Fiddle to see things in action (includes random data version).

SQL Join table to itself

I have a table where there are two columns like below.
value1 DerivedFrom
1 0
2 1
3 2
4 3
5 4
Basically, what it is saying is 1 was new, 2 was derived from 1, 3 was derived from 2 and so on.
I want the out put with 1 as the master key and 2,3,4 and 5 as children.
value1 DerivedFrom
1 0
1 1
1 2
1 3
1 4
Is it achiveble in SQL ? Thanks in advance
As mentioned in the comment, the simplest way is with an rCTE (recursive Common Table Expression):
--Sample Data
WITH YourTable AS(
SELECT *
FROM (VALUES(1,0),
(2,1),
(3,2),
(4,3),
(5,4))V(value1,DerivedFrom)),
--Solution
rCTE AS(
SELECT YT.value1 as rootValue,
YT.value1,
YT.DerivedFrom
FROM YourTable YT
WHERE YT.DerivedFrom = 0
UNION ALL
SELECT r.rootValue,
YT.value1,
YT.DerivedFrom
FROM YourTable YT
JOIN rCTE r ON YT.DerivedFrom = r.value1)
SELECT r.rootValue AS value1,
r.DerivedFrom
FROM rCTE r;

How to find multiple pairs in one oracle table

I have a problem with finding matches in one Oracle-Table and I hope you can help me.
I have one bill-table containing booking data looking like this:
ID GROUP Bill-Number Value Partner-Number
1 1 111 10,90
2 1 751 40,28
3 1 438 125,60
4 1 659 -10,90 987
5 1 387 -165,88 755
6 1 774 -100,10
7 1 664 -80,12
8 1 259 180,22 999
9 2 774 -200,10
10 2 664 -80,12
11 2 259 280,22 777
As you can see, we have some bills that are containing costs.
Some time later, there is coming the counter-bill that is summarizing the previous costs. The sum of the bills and the associated counter-bill is creating a Value of 0.
Example: value of (id 2 + id 3 = id 5*-1)
or in numbers: 40,28 + 125,60 + (-165,88) = 0
The counter-bills are containing a "Partner-Number". I need to add this information to the associated bills.
The solution should look like this:
ID GROUP Bill-Number Value Partner-Number
1 1 111 10,90 987
2 1 751 40,28 755
3 1 438 125,60 755
4 1 659 -10,90 987
5 1 387 -165,88 755
6 1 774 -100,10 999
7 1 664 -80,12 999
8 1 259 180,22 999
9 2 774 -200,10 777
10 2 664 -80,12 777
11 2 259 280,22 777
I have to match the bills only inside a group. (ID is my primary key)
As long as the group is containing one counter-bill with a 1:1 relation to a bill it is doable for me.
But how can I find the matches like in group 1 where the relation is 1:N? (the group contains multiple counter-bills)
I hope you can help me - thank you in advance :)
The following SQL code, has been tested with Oracle 12c and 18c, respectively. Ideas/steps:
{1} Split the original table into a MINUSES and PLUSES table, containing just positive numbers, saving us a few function calls later.
{2} Create 2 views that will find combinations of pluses that fit a particular minus (and vice versa).
{3} List all components, in "comma-separated" form in a table called ALLCOMPONENTS.
{4} Table GAPFILLERS: Expand the (comma separated) IDs of all components, thereby obtaining all necessary values to fill the gaps in the original table.
{5} LEFT JOIN the original table to the GAPFILLERS.
Original table/data
create table bills ( id primary key, bgroup, bnumber, bvalue, partner )
as
select 1, 1, 111, 10.90, null from dual union all
select 2, 1, 751, 40.28, null from dual union all
select 3, 1, 438, 125.60, null from dual union all
select 4, 1, 659, -10.90, 987 from dual union all
select 5, 1, 387, -165.88, 755 from dual union all
select 6, 1, 774, -100.10, null from dual union all
select 7, 1, 664, -80.12, null from dual union all
select 8, 1, 259, 180.22, 999 from dual union all
select 9, 2, 774, -200.10, null from dual union all
select 10, 2, 664, -80.12, null from dual union all
select 11, 2, 259, 280.22, 777 from dual ;
{1} split the table into a PLUS and a MINUS table
-- MINUSes
create table minuses as
select id
, bgroup as mgroup
, bnumber as mnumber
, bvalue * -1 as mvalue
, partner as mpartner
from bills where bvalue < 0 ;
-- PLUSes
create table pluses as
select id
, bgroup as pgroup
, bnumber as pnumber
, bvalue as pvalue
, partner as ppartner
from bills where bvalue >= 0 ;
{2} View: find components of PLUSvalues
-- used here: "recursive subquery factoring"
-- and LATERAL join (needs Oracle 12c or later)
create or replace view splitpluses
as
with recursiveclause ( nextid, mgroup, tvalue, componentid )
as (
select -- anchor member
id as nextid
, mgroup as mgroup
, mvalue as tvalue -- total value
, to_char( id ) as componentid
from minuses
union all
select -- recursive member
M.id
, R.mgroup
, R.tvalue + M.mvalue
, R.componentid || ',' || to_char( M.id )
from recursiveclause R
join minuses M
on M.id > R.nextid and M.mgroup = R.mgroup -- only look at values in the same group
)
--
select
mgroup
, tvalue as plusvalue
, componentid as minusids
, ppartner
from
recursiveclause R
, lateral ( select ppartner from pluses P where R.tvalue = P.pvalue ) -- fetch the partner id
where
tvalue in ( select pvalue from pluses where ppartner is not null ) -- get all relevant pvalues that must be broken down into components
and ppartner is not null -- do this for all pluses that have a partner id
;
{2b} View: find components of MINUSvalues
create or replace view splitminuses
as
with recursiveclause ( nextid, pgroup, tvalue, componentid )
as (
select -- anchor member
id as nextid
, pgroup as pgroup
, pvalue as tvalue -- total value
, to_char( id ) as componentid
from pluses
union all
select -- recursive member
P.id
, R.pgroup
, R.tvalue + P.pvalue
, R.componentid || ',' || to_char( P.id )
from recursiveclause R
join pluses P
on P.id > R.nextid and P.pgroup = R.pgroup
)
--
select
pgroup
, tvalue as minusvalue
, componentid as plusids
, mpartner
from
recursiveclause R
, lateral ( select mpartner from minuses M where R.tvalue = M.mvalue )
where
tvalue in ( select mvalue from minuses where mpartner is not null )
and mpartner is not null
;
The views give us the following result sets:
SQL> select * from splitpluses;
MGROUP PLUSVALUE MINUSIDS PPARTNER
1 180.22 6,7 999
2 280.22 9,10 777
SQL> select * from splitminuses ;
PGROUP MINUSVALUE PLUSIDS MPARTNER
1 10.9 1 987
1 165.88 2,3 755
{3} Table ALLCOMPONENTS: list of all "components"
create table allcomponents ( type_, group_, value_, cids_, partner_ )
as
select 'components of PLUS' as type_, M.* from splitminuses M
union all
select 'components of MINUS', P.* from splitpluses P
;
SQL> select * from allcomponents ;
TYPE_ GROUP_ VALUE_ CIDS_ PARTNER_
components of PLUS 1 10.9 1 987
components of PLUS 1 165.88 2,3 755
components of MINUS 1 180.22 6,7 999
components of MINUS 2 280.22 9,10 777
{4} Table GAPFILLERS: derived from ALLCOMPONENTS, contains all values we need to fill the "gaps" in the original table.
-- One row for each CSV (comma-separated value) of ALLCOMPONENTS
create table gapfillers
as
select unique type_, group_, value_
, trim( regexp_substr( cids_, '[^,]+', 1, level ) ) cids_
, partner_
from (
select type_, group_, value_, cids_, partner_
from allcomponents
) AC
connect by instr( cids_, ',', 1, level - 1 ) > 0
order by group_, partner_ ;
SQL> select * from gapfillers ;
TYPE_ GROUP_ VALUE_ CIDS_ PARTNER_
components of PLUS 1 165.88 2 755
components of PLUS 1 165.88 3 755
components of PLUS 1 10.9 1 987
components of MINUS 1 180.22 6 999
components of MINUS 1 180.22 7 999
components of MINUS 2 280.22 10 777
components of MINUS 2 280.22 9 777
7 rows selected.
{5} The final LEFT JOIN
select
B.id, bgroup, bnumber, bvalue
, case
when B.partner is null then G.partner_
else B.partner
end as partner
from bills B
left join gapfillers G on B.id = G.cids_
order by 1 ;
-- result
ID BGROUP BNUMBER BVALUE PARTNER
1 1 111 10.9 987
2 1 751 40.28 755
3 1 438 125.6 755
4 1 659 -10.9 987
5 1 387 -165.88 755
6 1 774 -100.1 999
7 1 664 -80.12 999
8 1 259 180.22 999
9 2 774 -200.1 777
10 2 664 -80.12 777
11 2 259 280.22 777
11 rows selected.
DBFIDDLE here.
SQLis idealy designe for a brute force approach to solve the problems (the only problm is, that for large data the query would hang forever).
Here a possible step by step approach, considering only one counter-bill in teh first step, than two in the second step etc.
I'm showing queries for the first two steps, you should get the idea how to proceed - most probably with dynamic SQL in a loop.
The first step is trivial self-joining joining the table and constraining the GROUP and value. A result table is created, whichis used further to limit already matched rows.
create table tab_match as
-- 1 row match
select b.ID, b.GROUP_ID, b.BILL_NUMBER, b.VALUE, a.partner_number from tab a
join tab b
on a.group_id = b.group_id and /* same group */
-1 * a.value = b.value /* oposite value */
where a.partner_number is not NULL /* consider group row only */
In the second step you repeats the same, only adding one join (we investigate two sub.bills) with an additional constraint on the total value -1 * a.value = (b.value + c.value)
Also we suppress all partner_numbers and bills already assigned. The result is inserted in the temporary table.
insert into tab_match (ID, GROUP_ID, BILL_NUMBER, VALUE, PARTNER_NUMBER)
select b.ID, b.GROUP_ID, b.BILL_NUMBER, b.VALUE, a.partner_number partner_number_match from tab a
join tab b
on a.group_id = b.group_id and /* same group */
sign(a.value) * sign(b.value) < 0 and /* values must have oposite signs */
abs(a.value) > abs(b.value) /* the partial value is lower than the sum */
join tab c /* join to 2nd table */
on a.group_id = c.group_id and
sign(a.value) * sign(c.value) < 0 and
abs(a.value) > abs(c.value) and
-1 * a.value = (b.value + c.value)
where a.partner_number is not NULL and /* consider open group row only */
a.partner_number not in (select partner_number from tab_match) and
a.id not in (select id from tab_match) /* ignore matched rows */
;
You must proceed with processing of 3,4 etc. rows until all partner_numbers and bills are assigned.
Add a next join
join tab d
on a.group_id = d.group_id and
sign(a.value) * sign(d.value) < 0 and
abs(a.value) > abs(d.value)
and adjust the total sum predicate in each step
-1 * a.value = (b.value + c.value + d.value)
Good Luck;)

get count of reference id and it is their child recursively

I have the below table( I need them for oracle and sql server):
id id_reference
1 0
2 1
3 1
4 1
6 2
7 2
8 3
9 8
10 0
11 10
12 10
13 12
I want to get the count of the id_reference for each id.
the result
id count(1)
1 7 -- because id 1 2 3 4 and the child 6 7 8 9 are referring to the id
2 2 -- because id 6 and 7 are referring to it
3 2 -- because id 8 and the child 9 referring to it
4 0 -- non are referring to them
6 0 -- non are referring to them
7 0 -- non are referring to them
8 1 -- because 9 is referring to the id
10 3 -- because 11 , 12 and 13 are referring
11 0 -- none are referring
12 1 -- 13 is referring to id
13 0 -- none is referring to id
this what I tried but I need it to be recursive.
select count(1),
id,
(select count(1) from tab e2 where e2.id <=e1.id and id_ref in ( select id from tab e3 where e3.id_ref= e2.id )
from tab e1
group by id
order by id desc
Oracle version:
dbfiddle demo
select distinct id, nvl(cnt, 0)
from tab
left join (
select root, count(1) cnt
from (
select tab.*, connect_by_root(id) root
from tab
where level > 1
connect by id_reference = prior id)
group by root) r on root = tab.id
order by id
In SQL Server (2016+), this is how I'd achieve the above result set:
USE Sandbox;
GO
WITH VTE AS(
SELECT *
FROM (VALUES (1 ,0 ),
(2 ,1 ),
(3 ,1 ),
(4 ,1 ),
(6 ,2 ),
(7 ,2 ),
(8 ,3 ),
(9 ,8 ),
(10,0 ),
(11,10),
(12,10),
(13,12)) V(ID, ID_ref)),
CTE AS (
SELECT ID,
CONVERT(varchar(30),CONVERT(varchar(4),ID)) AS Delimited
FROM VTE V
WHERE V.ID_ref = 0
UNION ALL
SELECT V.ID,
CONVERT(varchar(30),CONCAT(C.Delimited,',' + CONVERT(varchar(4),V.ID)))
FROM CTE C
JOIN VTE V ON V.ID_ref = C.ID),
Splits AS(
SELECT C.ID,
SS.value
FROM CTE C
CROSS APPLY STRING_SPLIT(C.Delimited,',') SS)
SELECT V.ID,
COUNT(S.ID) - 1 AS [Count]
FROM VTE V
JOIN Splits S ON S.[value] = V.ID
GROUP BY V.ID;
This firstly creates a delimited list of each ID at each layer. It then splits them out and finally does a Count -1.
If you aren't on SQL Server 2016+, then you can use a XML Splitter or delimitedsplit8k(_lead).
Note that a rCTe will stop recursing at 100 loops. You'll need to use OPTION (MAXRECURSION N) to increase the loops (where N is a suitable number of the maximum layer you might have).

SQL: first row of group by after join and order

Assuming we got two below tables:
TravelTimes
OriginId DestinationId TotalJourneyTime
1 1 10
1 2 20
2 2 30
2 3 40
1 3 50
Destinations
DestinationId Name
1 Destination 1
2 Destination 2
3 Destination 3
How do I find the quickest journey between each origin and destination?
I want to join TravelTimes with Destinations by DestinationId and then group them by OriginId and sort each group by TotalJourneyTime and select the first row of each group.
I did try joining and grouping, but it seems group by is not the solution for my case as I don't have any aggregation column in the output.
Expected output
OriginId DestinationId DestinationName TotalJourneyTime
1 1 Destination 1 10
2 3 Destination 3 40
Use a RANK to rank each journey partitioned by the origin and destination and ordered by the travel time
WITH RankedTravelTimes
AS
(
select originid,
destinationId,
totaljourneytime,
rank() over (partition by originid,destinationid order by totaljourneytime ) as r
from traveltimes
)
SELECT rtt.*, d.name
FROM RankedTravelTimes rtt
INNER JOIN Destinations d
ON rtt.destinationId = d.id
WHERE rtt.r=1
The above will include both the journey from 1-2 and 2-2 as separate. If you're only interested in the destination you can remove originId out of the partition.
Not sure I see the problem here with just joining and grouping the data with a MIN on the journey time:
CREATE TABLE #Traveltimes
(
[OriginId] INT ,
[DestinationId] INT ,
[TotalJourneyTime] INT
);
INSERT INTO #Traveltimes
( [OriginId], [DestinationId], [TotalJourneyTime] )
VALUES ( 1, 1, 10 ),
( 1, 2, 20 ),
( 2, 2, 30 ),
( 2, 3, 40 ),
( 2, 3, 50 );
CREATE TABLE #Destinations
(
[DestinationId] INT ,
[Name] VARCHAR(13)
);
INSERT INTO #Destinations
( [DestinationId], [Name] )
VALUES ( 1, 'Destination 1' ),
( 2, 'Destination 2' ),
( 3, 'Destination 3' );
SELECT d.DestinationId ,
d.Name ,
tt.OriginId ,
MIN(tt.TotalJourneyTime) MinTime
FROM #Destinations d
INNER JOIN #Traveltimes tt ON tt.DestinationId = d.DestinationId
GROUP BY tt.OriginId ,
d.DestinationId ,
d.Name
DROP TABLE #Destinations
DROP TABLE #Traveltimes
Gives you:
DestinationId Name OriginId MinTime
1 Destination 1 1 10
2 Destination 2 1 20
2 Destination 2 2 30
3 Destination 3 2 40
Note: why do you travel from destination 1 to itself?
I think you want the following:
;with cte as(select *, row_number() over(partition by DestinationId order by TotalJourneyTime) rn
from TravelTimes)
select * from cte c
join Destinations d on c.DestinationId = d.DestinationId
where c.rn = 1