Recursive join with SUM - sql

I have data in the following format:
FromStateID ToStateID Seconds
1 2 10
2 3 20
3 4 15
4 5 5
I need the following output
FromStateID ToStateID Seconds
1 2 10
2 3 20
3 4 15
4 5 5
1 3 10+20
1 4 10+20+15
1 5 10+20+15+5
2 4 20+15
2 5 20+15+5
3 5 15+5
This output shows the total time taken FromStateId to ToStateId in every combination in chronological order.
Please help.

I think this is a recursive CTE that follows the links:
with cte as (
select FromStateID, ToStateID, Seconds
from t
union all
select cte.FromStateId, t.ToStateId, cte.Seconds + t.Seconds
from cte join
t
on cte.toStateId = t.FromStateId
)
select *
from cte;
Here is a db<>fiddle.

#Gordon LinOff is the better solution. Below is another option to achieve the same.
You can achieve this using CROSS JOIN and GROUP BY
DECLARE #table table(FromStateId int, ToStateId int, seconds int)
insert into #table
values
(1 ,2 ,10),
(2 ,3 ,20),
(3 ,4 ,15),
(4 ,5 ,5 );
;with cte_fromToCombination as
(select f.fromStateId, t.tostateId
from
(select distinct fromStateId from #table) as f
cross join
(select distinct toStateId from #table) as t
)
select c.FromStateId, c.ToStateId, t.sumseconds as Total_seconds
from cte_fromToCombination as c
CROSS APPLY
(SELECT sum(t.seconds)
from
#table as t
WHERE t.ToStateId <= c.ToStateId
) as t(sumseconds)
where c.tostateId > c.fromStateId
order by FromStateId,ToStateId
+-------------+-----------+---------------+
| FromStateId | ToStateId | Total_seconds |
+-------------+-----------+---------------+
| 1 | 2 | 10 |
| 1 | 3 | 30 |
| 1 | 4 | 45 |
| 1 | 5 | 50 |
| 2 | 3 | 30 |
| 2 | 4 | 45 |
| 2 | 5 | 50 |
| 3 | 4 | 45 |
| 3 | 5 | 50 |
| 4 | 5 | 50 |
+-------------+-----------+---------------+

Related

How to get columns when using buckets (width_bucket)

I would like to know which row were moved to a bucket.
SELECT
width_bucket(s.score, sl.mins, sl.maxs, 9) as buckets,
COUNT(*)
FROM scores s
CROSS JOIN scores_limits sl
GROUP BY 1
ORDER BY 1;
My actual return:
buckets | count
---------+-------
1 | 182
2 | 37
3 | 46
4 | 15
5 | 29
7 | 18
8 | 22
10 | 11
| 20
What I expect to return:
SELECT buckets FROM buckets_table [...] WHERE scores.id = 1;
How can I get, for example, the column 'id' of table scores?
I believe you can include the id in an array with array_agg. If I recreate your case with
create table test (id serial, score int);
insert into test(score) values (10),(9),(5),(4),(10),(2),(5),(7),(8),(10);
The data is
id | score
----+-------
1 | 10
2 | 9
3 | 5
4 | 4
5 | 10
6 | 2
7 | 5
8 | 7
9 | 8
10 | 10
(10 rows)
Using the following and aggregating the id with array_agg
SELECT
width_bucket(score, 0, 10, 11) as buckets,
COUNT(*) nr_ids,
array_agg(id) agg_ids
FROM test s
GROUP BY 1
ORDER BY 1;
You get
buckets | nr_ids | agg_ids
---------+--------+----------
3 | 1 | {6}
5 | 1 | {4}
6 | 2 | {3,7}
8 | 1 | {8}
9 | 1 | {9}
10 | 1 | {2}
12 | 3 | {1,5,10}

How to create column for every single integer within a range in SQLite?

Here's some sample data from my table:
day_number daily_users_count
1 1
3 1
6 1
7 1
9 2
10 2
I need all day_number values, from 1 to max(day_number), and I want daily_users_count to be zero if it isn't mentioned in this table.
It should look something like this:
day_number daily_users_count
1 1
2 0
3 1
4 0
5 0
6 1
7 1
8 0
9 2
10 2
I think a left join with a table which has a number column with all integers from 1 to max(day_number) would work, if I put a default value for daily_users_count as 0.
What I don't get is how to create such a table where all integers within a certain range are present. Any alternate solutions or any ways to do this would be much appreciated.
You can do it with a recursive CTE which will return all the day_numbers including the missing ones and then a LEFT join to the table:
with cte as (
select min(day_number) day_number from tablename
union all
select day_number + 1 from cte
where day_number < (select max(day_number) from tablename)
)
select c.day_number,
coalesce(t.daily_users_count, 0) daily_users_count
from cte c left join tablename t
on t.day_number = c.day_number
See the demo.
Results:
| day_number | daily_users_count |
| ---------- | ----------------- |
| 1 | 1 |
| 2 | 0 |
| 3 | 1 |
| 4 | 0 |
| 5 | 0 |
| 6 | 1 |
| 7 | 1 |
| 8 | 0 |
| 9 | 2 |
| 10 | 2 |

select rows based on equal columns values

consider we have a table with this columns
Id
fk_newsId
fk_NewsGroupId
fk_NewsZoneId
I need to select all records with same fk_NewsGroup and fk_NewsZone
something like this
+----+-----------+--------------+-------------+
| Id | fk_NewsId | fk_NewsGroup | fk_NewsZone |
+----+-----------+--------------+-------------+
| 1 | 60 | 5 | 8 |
| 2 | 30 | 5 | 8 |
| 3 | 31 | 9 | 20 |
| 4 | 5 | 9 | 20 |
| 5 | 12 | 9 | 20 |
| 6 | 1000 | 20 | 11 |
| 7 | 21 | 20 | 11 |
| 8 | 6 | 20 | 11 |
+----+-----------+--------------+-------------+
how can do that?
I tride group by like this
but it dosnt give desired output
select fk_NewsId, fk_NewsGroup,fk_NewsZone from tbl_test
group by fk_NewsGroup,fk_NewsZone,fk_NewsId
You can try to use COUNT with window function, to get the count by fk_NewsGroup and fk_NewsZone columns.
then get count greater than one.
SELECT *
FROM (
SELECT *,COUNT(*) OVER(PARTITION BY fk_NewsGroup,fk_NewsZone ORDER BY fk_NewsZone) cnt
FROM tbl_test
)t1
where t1.cnt > 1
dbfiddle
Not absolutely clear as to what you mean, but something like so:
SELECT t.Id, t.fk_NewsId, t.fk_NewsGroup, t.fk_NewsZone FROM tbl_test t
INNER JOIN (
SELECT fk_NewsGroup,fk_NewsZone, COUNT(*) AS Counted FROM tbl_test
GROUP BY fk_NewsGroup,fk_NewsZone
HAVING COUNT(*) > 1) g
ON t.fk_NewsGroup = g.fk_NewsGroup
AND t.fk_NewsZone = g.fk_NewsZone
DBFiddle example
I would use Group by and do it like:
select max(id) as Id, Max(fk_NewsId) as fk_NewsId, fk_NewsGroup,fk_NewsZone from #temp
group by fk_NewsGroup,fk_NewsZone

Grouping by multiple ranges in SQL Server

I tried to search for a solution, but with no success.
How can I group my table from looking like this:
from | to | zone
1 | 1 | 1
1 | 2 | 1
1 | 3 | 1
1 | 4 | 2
1 | 5 | 2
1 | 6 | 2
1 | 7 | 1
1 | 8 | 1
1 | 9 | 1
1 | 10 | 9
2 | 1 | 7
2 | 2 | 7
2 | 3 | 7
2 | 4 | 2
2 | 5 | 2
2 | 6 | 2
2 | 7 | 7
2 | 8 | 7
2 | 9 | 7
To look like this :
from | to | zone
1 | 1-3 | 1
1 | 4-6 | 2
1 | 7-9 | 1
1 | 10 | 9
2 | 1-3 | 7
2 | 4-6 | 2
2 | 7-9 | 7
Thank you for your help
One approach here is to use the difference of row numbers method, using to to column as one row number, and a row number over a partition using from and zone as the other row number. It is a bit difficult to explain why this works in so many words. It might be best to view the demo link below to explore the query.
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY [from], zone ORDER BY [to]) rn
FROM yourTable
)
SELECT
t.[from],
CONVERT(varchar(10), MIN(t.[to])) + '-' + CONVERT(varchar(10), MAX([to])) AS [to],
t.zone
FROM cte t
GROUP BY
t.[from],
t.zone,
t.[to] - t.rn
ORDER BY
t.[from],
MIN(t.[to]);
Demo here:
Rextester
This is generally called as Gaps and Islands problem. If you are using SQL Server 2012+ then
;WITH cte
AS (SELECT *,
Sum(CASE WHEN zone = prev_zone THEN 0 ELSE 1 END)OVER(partition BY [from] ORDER BY [to]) AS grp
FROM (SELECT *,
Lag(zone)OVER(partition BY [from] ORDER BY [to]) AS prev_zone
FROM yourtable ) cs ([from], [to], zone)) a)
SELECT [from],
[to] = Concat(Min([to]), '-', Max([to])),
zone = Min(zone)
FROM cte
GROUP BY [from],grp
;with mycte
AS
(
select
,[from]
,min([to]) minto
,max([to]) maxto
,[zone]
from
mytable
group by
[from]
,[zone]
)
[from] AS [from]
,concat(minto, '-', maxto) AS [to]
,[zone] AS [zone]
from
mycte

Postgres width_bucket() not assigning values to buckets correctly

In postgresql 9.5.3 I can't get width_bucket() to work as expected, it appears to be assigning values to the wrong buckets.
Dataset:
1
2
4
32
43
82
104
143
232
295
422
477
Expected output (bucket ranges and zero-count rows added to help analysis):
bucket | bucketmin | bucketmax | Expect | Actual
--------+-----------+-----------+--------|--------
1 | 1 | 48.6 | 5 | 5
2 | 48.6 | 96.2 | 1 | 2
3 | 96.2 | 143.8 | 2 | 1
4 | 143.8 | 191.4 | 0 | 0
5 | 191.4 | 239 | 1 | 1
6 | 239 | 286.6 | 0 | 1
7 | 286.6 | 334.2 | 1 | 0
8 | 334.2 | 381.8 | 0 | 1
9 | 381.8 | 429.4 | 1 | 0
10 | 429.4 | 477 | 1 | 1
Actual output:
wb | count
----+-------
1 | 5
2 | 2
3 | 1
5 | 1
6 | 1
8 | 1
10 | 1
Code to generate actual output:
create temp table metrics (val int);
insert into metrics (val) values(1),(2),(4),(32),(43),(82),(104),(143),(232),(295),(422),(477);
with metric_stats as (
select
cast(min(val) as float) as minV,
cast(max(val) as float) as maxV
from metrics m
),
hist as (
select
width_bucket(val, s.minV, s.maxV, 9) wb,
count(*)
from metrics m, metric_stats s
group by 1 order by 1
)
select * from hist;
Your calculations appear to be off. The following query:
with metric_stats as (
select cast(min(val) as float) as minV,
cast(max(val) as float) as maxV
from metrics m
)
select g.n,
s.minV + ((s.maxV - s.minV) / 9) * (g.n - 1) as bucket_start,
s.minV + ((s.maxV - s.minV) / 9) * g.n as bucket_end
from generate_series(1, 9) g(n) cross join
metric_stats s
order by g.n
Yields the following bins:
1 1 53.8888888888889
2 53.8888888888889 106.777777777778
3 106.777777777778 159.666666666667
4 159.666666666667 212.555555555556
5 212.555555555556 265.444444444444
6 265.444444444444 318.333333333333
7 318.333333333333 371.222222222222
8 371.222222222222 424.111111111111
9 424.111111111111 477
I think you intend for the "9" to be a "10", if you want 10 buckets.