SQL Server CTE and recursion example misunderstanding - tsql

Could someone help me with cte expresion? I have a table:
old_card
new_card
dt
111
555
2020-01-09
222
223
2020-02-10
333
334
2020-03-11
444
222
2020-04-12
555
666
2020-05-12
666
777
2020-06-13
777
888
2020-07-14
888
0
2020-08-15
999
333
2020-09-16
223
111
2020-10-16
I need to get all the changes of old_card to a new_card, since old_card number 111 to a new_card number 0. So I must get 5 records from this table having only a new_card = 0 as input parameter
old_card
new_card
dt
111
555
2020-01-09
555
666
2020-05-12
666
777
2020-06-13
777
888
2020-07-14
888
0
2020-08-15
I think of to do it using cte, but I get all the records from the source table and can't understand why. Here is my cte:
;with cte as(
select
old_card,
new_card,
dt
from
cards_transfer
where
new_card = 0
union all
select
t1.old_card,
t1.new_card,
t1.dt
from
cards_transfer t1
inner join
cte on cte.old_card = t1.new_card)
But I get 8 rows instead. Can someone tell me please what I did wrong?

You said you wanted from 111 onwards. So you need to add that "stop" condition
where cte.old_card <> 111
;with cte as(
select
old_card,
new_card,
dt
from
cards_transfer
where
new_card = 0
union all
select
t1.old_card,
t1.new_card,
t1.dt
from
cards_transfer t1
inner join
cte on cte.old_card = t1.new_card
where cte.old_card <> 111
)

Related

Complex logic to create time series in Postgres

I have a sample dataset like below and I would like to create a report in such a format that the Value is updated for all the dates between the Start and End date.
Input Dataset
ID Start End Value
232 "2022-06-08 18:49:00" "2022-11-18 08:06:00" 55
456 "2022-10-17 10:24:00" "2022-12-16 12:52:00" 100
From the above Dataset I would like to create another dataset as below.
I need to generate the date series from the START and END date from the Input dataset and fill the same value to all of those value.
Any ideas or suggestions will be helpful.
Expected Output
ID Date Value
232 "2022-06-08" 55
232 "2022-06-09" 55
232 "2022-06-10" 55
232 "2022-06-11" 55
232 "2022-06-12" 55
.
.
232 "2022-11-17" 55
232 "2022-11-18" 55
456 "2022-10-17" 100
456 "2022-10-18" 100
456 "2022-10-19" 100
.
.
456 "2022-12-15" 100
456 "2022-12-16" 100
Database : Postgres 12
You can use generate_series()
select t.id,
g.dt::date as date,
t.value
from the_table t
cross join generate_series(t."Start"::date, t."End"::date, interval '1 day') as g(dt)
order by t.id, g.dt

Getting duplicate records from 2 sql tables

I have 2 SQL tables
Table #1
account
product
expiry-date
101
prod1
2021-01-30
102
prod2
2021-02-20
103
prod3
2021-03-09
103
prod3
2021-03-19
104
prod4
2021-03-15
105
prod5
2021-04-23
105
prod5
2021-04-24
106
prod6
2021-04-25
Table #2
account
101
106
From the above 2 tables I want to get only unmatched records from Table1 and avoid duplicate records.
Result:
account
product
expiry-date
102
prod2
2021-02-20
103
prod3
2021-03-09
104
prod4
2021-03-15
105
prod5
2021-04-23
Below query I tried but I am getting duplicate records, because expiry date is unique on account. I am getting below records in my output
SQL query I tried:
select distinct (a.account, a.product, a.expiry-date)
from table1 a
where a.account not in (select account from table2)
Result:
account
product
expiry-date
102
prod2
2021-02-20
103
prod3
2021-03-09
103
prod3
2021-03-19
104
prod4
2021-03-15
105
prod5
2021-04-23
105
prod5
2021-04-24
You can use the same query using aggregation:
SELECT a.account
,a.product
,MIN(a.expiry) expiry
FROM table1 a
WHERE a.account NOT IN (
SELECT account
FROM table2
)
GROUP BY a.account
,a.product
You can use an anti-join and then ROW_NUMBER() For example:
select *
from (
select a.*, row_number() over(partition by accoun order by expiry) as rn
from table1 a
left join table2 b on b.account = a.account
where b.account is null
) x
where rn = 1

selecting out duplicate records within the same table column and list them out

I've searched but so far don't find answer fits my situation.
How do you write select statement to selecting out duplicate records within the same table column and list them (so not group by it)??
example: to find duplicates for contract_id column and list them out
ID contract_id Sales1 Sales2
1 12345 100 200
2 54321 300 674
3 12345 343 435
4 09876 125 654
5 54321 374 233
6 22334 543 335
Result should look like this with order by contract_id as well:
ID contract_id Sales1 Sales2
1 12345 100 200
3 12345 343 435
2 54321 300 674
5 54321 374 233
You could use a subquery on the count >1
select * from my_table
where contract_id in (
select contract_id
from my_table
group by contract_id
having count(*) > 1
)

Postgresql Query for display of records every 45 days

I have a table that has data of user_id and the timestamp they joined.
If I need to display the data month-wise I could just use:
select
count(user_id),
date_trunc('month',(to_timestamp(users.timestamp))::timestamp)::date
from
users
group by 2
The date_trunc code allows to use 'second', 'day', 'week' etc. Hence I could get data grouped by such periods.
How do I get data grouped by "n-day" period say 45 days ?
Basically I need to display number users per 45 day period.
Any suggestion or guidance appreciated!
Currently I get:
Date Users
2015-03-01 47
2015-04-01 72
2015-05-01 123
2015-06-01 132
2015-07-01 136
2015-08-01 166
2015-09-01 129
2015-10-01 189
I would like the data to come in 45 days interval. Something like :-
Date Users
2015-03-01 85
2015-04-15 157
2015-05-30 192
2015-07-14 229
2015-08-28 210
2015-10-12 294
UPDATE:
I used the following to get the output, but one problem remains. I'm getting values that are offset.
with
new_window as (
select
generate_series as cohort
, lag(generate_series, 1) over () as cohort_lag
from
(
select
*
from
generate_series('2015-03-01'::date, '2016-01-01', '45 day')
)
t
)
select
--cohort
cohort_lag -- This worked. !!!
, count(*)
from
new_window
join users on
user_timestamp <= cohort
and user_timestamp > cohort_lag
group by 1
order by 1
But the output I am getting is:
Date Users
2015-04-15 85
2015-05-30 157
2015-07-14 193
2015-08-28 225
2015-10-12 210
Basically The users displayed at 2015-03-01 should be the users between 2015-03-01 and 2015-04-15 and so on.
But I seem to be getting values of users upto a date. ie: upto 2015-04-15 users 85. which is not the results I want.
Any help here ?
Try this query :
SELECT to_char(i::date,'YYYY-MM-DD') as date, 0 as users
FROM generate_series('2015-03-01', '2015-11-30','45 day'::interval) as i;
OUTPUT :
date users
2015-03-01 0
2015-04-15 0
2015-05-30 0
2015-07-14 0
2015-08-28 0
2015-10-12 0
2015-11-26 0
This looks like a hot mess, and it might be better wrapped in a function where you could use some variables, but would something like this work?
with number_of_intervals as (
select
min (timestamp)::date as first_date,
ceiling (extract (days from max (timestamp) - min (timestamp)) / 45)::int as num
from users
),
intervals as (
select
generate_series(0, num - 1, 1) int_start,
generate_series(1, num, 1) int_end
from number_of_intervals
),
date_spans as (
select
n.first_date + 45 * i.int_start as interval_start,
n.first_date + 45 * i.int_end as interval_end
from
number_of_intervals n
cross join intervals i
)
select
d.interval_start, count (*) as user_count
from
users u
join date_spans d on
u.timestamp >= d.interval_start and
u.timestamp < d.interval_end
group by
d.interval_start
order by
d.interval_start
With this sample data:
User Id timestamp derived range count
1 3/1/2015 3/1-4/15
2 3/26/2015 "
3 4/4/2015 "
4 4/6/2015 " (4)
5 5/6/2015 4/16-5/30
6 5/19/2015 " (2)
7 6/16/2015 5/31-7/14
8 6/27/2015 "
9 7/9/2015 " (3)
10 7/15/2015 7/15-8/28
11 8/8/2015 "
12 8/9/2015 "
13 8/22/2015 "
14 8/27/2015 " (5)
Here is the output:
2015-03-01 4
2015-04-15 2
2015-05-30 3
2015-07-14 5

SELECT table name that is inside UNION

I have two same tables.
I need to union them in such way:
SELECT f1,f2, xxx
FROM
(SELECT *
FROM tbl1
UNION ALL
SELECT *
FROM tbl2)
where xxx would query for a table name, where f1 and f2 fields are taken from.
Example output:
123 345 'tbl1' -- this rows are from the first table
121 345 'tbl1'
121 345 'tbl1'
123 345 'tbl1'
124 345 'tbl1'
125 345 'tbl2' -- this rows are from the second table
127 345 'tbl2'
Thank you in advance.
SELECT f1,f2, xxx
FROM
(SELECT *, 'tbl1' as xxx
FROM tbl1
UNION ALL
SELECT *, 'tbl2' as xxx
FROM tbl2)