SQL Query to find streak with unique record types - sql

This is my example table data:
CustomerType Date Records
1 2018-01-01 233
2 2018-01-01 12
1 2018-01-02 0
2 2018-01-02 34
1 2018-01-03 0
2 2018-01-03 35
1 2018-01-04 0
2 2018-01-04 0
1 2018-01-05 5562
2 2018-01-05 3
I would like the output to show the maximum 0 Record streak, by CustomerType, to be fetched for each CustomerType. In other words, I would like the maximum number of zero records in a row for each particular CustomerType
Example output
CustomerType MaxZeroRecordsStreak
1 3
2 1
The farthest I've gotten so far is something like this:
SELECT CustomerType,
(SELECT COUNT(*)
FROM Table1 as t1
AND Records = '0'
AND t1.Date <= t2.Date) AS MaxZeroRecordsStreak
FROM Table2 As t2
ORDER BY CustomerType ASC;
But this is clearly not what I want.
Any help with this complex query would be appreciated.

Most databases support the ANSI-standard window functions. You can do this using the difference of row_numbers(). The following gets all sequences of zeros for a customer type:
select customertype, count(*) as num_zeros
from (select t.*,
row_number() over (partition by records, customertype order by date) as seqnum_rt,
row_number() over (partition by customertype order by date) as seqnum_t
from t
) t
where records = 0
group by records, customertype, seqnum_t - seqnum_rt;
You can get the maximum just by using this as a subquery:
select customertype, max(num_zeros)
from (select customertype, count(*) as num_zeros
from (select t.*,
row_number() over (partition by records, customertype order by date) as seqnum_rt,
row_number() over (partition by customertype order by date) as seqnum_t
from t
) t
where records = 0
group by records, customertype, seqnum_t - seqnum_rt
) t
group by customertype;

Related

PostgreSQL Pivot by Last Date

I need to make a PIVOT table from Source like this table
FactID UserID Date Product QTY
1 11 01/01/2020 A 600
2 11 02/01/2020 A 400
3 11 03/01/2020 B 500
4 11 04/01/2020 B 200
6 22 06/01/2020 A 1000
7 22 07/01/2020 A 200
8 22 08/01/2020 B 300
9 22 09/01/2020 B 100
Need Pivot Like this where Product QTY is QTY by Last Date
UserID A B
11 400 200
22 200 100
My try PostgreSQL
Select
UserID,
MAX(CASE WHEN Product='A' THEN 'QTY' END) AS 'A',
MAX(CASE WHEN Product='B' THEN 'QTY' END) AS 'B'
FROM table
GROUP BY UserID
And Result
UserID A B
11 600 500
22 1000 300
I mean I get a result by the maximum QTY and not by the maximum date!
What do I need to add to get results by the maximum (last) date ??
Postgres doesn't have "first" and "last" aggregation functions. One method for doing this (without a subquery) uses arrays:
select userid,
(array_agg(qty order by date desc) filter (where product = 'A'))[1] as a,
(array_agg(qty order by date desc) filter (where product = 'B'))[1] as b
from tab
group by userid;
Another method uses select distinct with first_value():
select distinct userid,
first_value(qty) over (partition by userid order by product = 'A' desc, date desc) as a,
first_value(qty) over (partition by userid order by product = 'B' desc, date desc) as b
from tab;
With the appropriate indexes, though, distinct on might be the fastest approach:
select userid,
max(qty) filter (where product = 'A') as a,
max(qty) filter (where product = 'B') as b
from (select distinct on (userid, product) t.*
from tab t
order by userid, product, date desc
) t
group by userid;
In particular, this can use an index on userid, product, date desc). The improvement in performance will be most notable if there are many dates for a given user.
You can use DENSE_RANK() window function in order to filter by the last date per each product and UserID before applying conditional aggregation such as
SELECT UserID,
MAX(CASE WHEN Product='A' THEN QTY END) AS "A",
MAX(CASE WHEN Product='B' THEN QTY END) AS "B"
FROM
(
SELECT t.*, DENSE_RANK() OVER (PARTITION BY Product,UserID ORDER BY Date DESC) AS rn
FROM tab t
) q
WHERE rn = 1
GROUP BY UserID
Demo
presuming all date values are distinct(no ties occur for dates)

Find subsequent occurrence of a value in a table

I have a table which looks like shown below
ID SubmittedValue ApprovedValue
1 25.9 0
1 29 29
1 25.9 25.9
1 50 0
1 45 0
1 10 0
1 10 10
Expected result
ID SubsequentlyApproved(CNT) Total_Amt_sub_aprvd
1 2 35.9
We get the above result because 25.9+10 since it is repeated in the subsequent rows.
How to perform VLOOKUP like functionality for this scenario. I tried the subquery but it didn't work.
SELECT a.id,
SUM(CASE WHEN a.ApprovedValue=0 THEN 1 ELSE 0 END) AS SUB_COUNT
FROM myTable a
join (select id, sum( case when SubmittedValue=ApprovedValue then 1 end) as check_value from myTable) b
on b.id=a.id and SUB_COUNT=check_value
but this is not giving me the expected result.
You seem to want to count rows where the values are the same and the first value appears more than once. If so, you can use window functions and aggregation:
select id, count(*), sum(ApprovedValue)
from (select t.*, count(*) over (partition by id, SubmittedValue) as cnt
from t
) t
where cnt > 1 and SubmittedValue = ApprovedValue
group by id
Without window functions using a semi-join
select id, count(*), sum(submittedvalue)
from test t1
where submittedvalue=approvedvalue
and exists (select 1
from test t2
where t1.id=t2.id and t1.submittedvalue=t2.submittedvalue
group by id, submittedvalue
having count(*)>1)
group by id;

SQL: count last equal values

I need to solve this problem in pure SQL:
I have to count all the records with a specific value:
In my table there is a column flag with values 0 or 1. I need to count all the 1 after last 0 and sum the amount column values of those records.
Example:
Flag | Amount
0 | 5
1 | 8
0 | 10
1 | 20
1 | 30
Output:
2 | 50
If last value is 0 I don't need to do anything.
I hasten that I need to perform a fast query (possibly accessing just one time).
I assumed that your example table is logically ordered by Amount. Then you can do this:
select
count(*) as cnt
,sum(Amount) as Amount
from yourTable
where Amount > (select max(Amount) from yourTable where Flag = 0)
If the biggest value is from a row where Flag = 0 then nothing will be returned.
If your table may not contain any zeros, then you are safer with:
select count(*) as cnt, sum(Amount) as Amount
from t
where Amount > all (select Amount from t where Flag = 0)
Or, using window functions:
select count(*) as cnt, sum(amount) as amount
from (select t.*, max(case when flag = 0 then amount end) as flag0_amount
from t
) t
where flag0_amount is null or amount > flag0_amount
I find the solution by myself:
select decode(lv,0,0,tot-prog) somma ,decode(lv,0,0,cnt-myrow) count
from(
select * from
(
select pan,dt,flag,am,
last_value(flag) over() lv,
row_number() OVER (order by dt) AS myrow,
count(*) over() cnt,
case when lead(flag) OVER (ORDER BY dt) != flag then rownum end AS change,
sum(am) over() tot,
sum(am) over(order by dt) prog
from test
where pan=:pan and dt > :dt and flag is not null
order by dt
) t
where change is not null
order by change desc
) where rownum =1

How to calculate unique rank in SQL Server (without any duplication)?

I want to calculate unique rankings but I get duplicate rankings
Here's my attempt:
SELECT
TG.EMPCODE,
DENSE_RANK() OVER (ORDER BY TS.COUNT_DEL DESC, TG.COUNT_TG DESC) AS YOUR_RANK
FROM
(SELECT
EmpCode,
SUM(CASE WHEN Tgenerate = 1 THEN 1 ELSE 0 END) AS COUNT_TG
FROM
TBLTGENERATE1
GROUP BY
EMPCODE) TG
INNER JOIN
(SELECT
EMP_CODE,
SUM(CASE WHEN STATUS = 'DELIVERED' THEN 1 ELSE 0 END) AS COUNT_DEL
FROM
TBLSTAT
GROUP BY
EMP_CODE) TS ON TG.EMPCODE = TS.EMP_CODE;
The output I get is like this:
EID Rank
---------
102 1
105 2
101 2
103 3
106 4
There is same rank for 105 and 101.
How do I calculate unique ranking?
Use ROW_NUMBER() instead of DENSE_RANK():
SELECT TG.EMPCODE,
ROW_NUMBER() OVER (ORDER BY TS.COUNT_DEL DESC, TG.COUNT_TG DESC) AS YOUR_RANK
Ties will then be given sequential rankings.

How do I select records where a value has not been exceeded

I wish to extract from a database a list of job Ids where the status for each job has not exceeded a given criteria.
For this example table I want to display all the jobid that have not exceeded status 200 and display only the latest status.
Job-Progress table
Jobid Status Date Time
1234 100 20131001 080000
1234 200 20131001 100000
1234 300 20131001 140000
9876 100 20131014 110000
5555 100 20131015 100000
5555 200 20131016 080000
The result i am looking for is
Jobid Status Date Time
9876 100 20131014 110000
5555 200 20131016 080000
Database is on AS400
This is a good use of window/analytic functions.
You can use row_number() to get the most recent status. You can use sum() over to count the number of times that status exceeds 200.
select jobid, Status, Date, Time
from (select jp.*,
row_number() over (partition by jobid order by date desc, time desc) as seqnum,
sum(case when status >= 200 then 1 else 0 end) over (partition by jobid) as status_200
from JobProgress jp
) jp
where status_200 = 0 and seqnum = 1;
The where clause then filters to the rows you are looking for.
select t1.*
from your_table t1
inner join
(
select Jobid, max(date*1000000+time) as maxdt
from your_table
group by jobid
having sum(case when status > 200 then 1 else 0 end) = 0
) t2 on t1.jobid = t2.jobid
and t1.date*100000+t1.time = maxdt
This is the simplest approach I can think of:
SELECT t.* FROM t
INNER JOIN (
SELECT jobid, max(date) date FROM t
GROUP BY jobid
HAVING COUNT(CASE WHEN status > 200 THEN 1 END) = 0
) s
ON t.jobid = s.jobid AND t.date = s.date
Fiddle here.
A common table expression will allow you to employ the ROW_NUMBER() function to mark the latest row of each job as 1. Then all you need to do is pick that row whenever its status is acceptable.
With x as
(SELECT *,
ROW_NUMBER() OVER(PARTITION BY Jobid ORDER BY Date, time DESC) AS pick
FROM JobProgress
)
SELECT jobid, status, date, time
FROM x
WHERE Status <= 200
And pick = 1
In SQL server you can use ROW_NUMBER:
SELECT *
FROM (SELECT
Jobid,
status,
ROW_NUMBER() OVER(PARTITION BY Jobid ORDER BY Date DESC) AS rn
FROM
Job-Progress
WHERE Status < 200) A
WHERE RowNum = 1