Need to have subquery within subquery - postgresql

I have a stock table which holds for example
Partnumber | Depot | flag_redundant
------------+-------+----------------
1 | 1 | 5
1 | 2 | 0
1 | 3 | 0
1 | 4 | 5
2 | 1 | 0
2 | 2 | 0
2 | 3 | 0
2 | 4 | 0
I need to be able to see the depots in which the parts have not been flagged as redundant, but the flag_redundant has been at least been flagged once for that part, and I need to ignore any parts where there has not been a flag flagged.
Any help appreciated!
I'm thinking of something along the lines of ....
SELECT stock.part, stock.depot,
OrderCount = (SELECT CASE WHEN Stock.flag_redundant = 5 THEN 1 end as Countcolumn FROM stock C)
FROM stock
Partnumber | MissingDepots
------------+---------------
1 | Yes

You can group by partnumber and set the conditions in the HAVING clause:
select
partnumber, 'Yes' MissingDepots
from stock
group by partnumber
having
sum(flag_redundant) > 0 and
sum(case when flag_redundant = 0 then 1 end) > 0
Or:
select
partnumber, 'Yes' MissingDepots
from stock
group by partnumber
having sum(case when flag_redundant = 0 then 1 end) between 1 and count(*) - 1
See the demo.
Results:
> partnumber | missingdepots
> ---------: | :------------
> 1 | Yes

Assuming you want to get these partnumbers that contain data sets with flag_redundant = 5 AND 0:
demo:db<>fiddle
SELECT
partnumber,
'Yes' AS missing
FROM (
SELECT
partnumber,
COUNT(flag_redundant) FILTER (WHERE flag_redundant = 5) AS cnt_redundant, -- 2
COUNT(*) AS cnt -- 3
FROM
stock
GROUP BY partnumber -- 1
) s
WHERE cnt_redundant > 0 -- 4
AND cnt_redundant < cnt -- 5
Group by partnumber
Count all records with flag_redundant = 5
Count all records
Find all partnumbers that contain any element with 5 ...
... and which have more records than 5-element records

Related

Database only Running total with with conditions

There are a ton of questions about calculating running totals with Postgres but I am struggling to do something slightly different.
I have a table that looks like this
txn_id
amount
String
Integer
amounts can either be positive or negative.
I am trying to return a table that looks like this
txn_id
amount
running_total
overage_total
String
Integer
Integer
Integer
Where running total is running sum of the amount column as long as the amount is greater than zero and overage_total is the running sum of amounts that were lower than zero.
An example of would be
txn_id
amount
a
1
b
2
c
-4
d
2
e
-1
I have been using a window function for the running sum but it's not quite what we need.
The correct table would return
txn_id
amount
running_total
overage_total
a
1
1
0
b
2
3
0
c
-4
0
1
d
2
2
1
e
-1
1
1
Currently I have am doing this in code but it would be really incredible to do it in the database if it's possible.
The pattern here is running total with a cap. It could be achieved with recursive cte:
WITH RECURSIVE cte_r AS (
SELECT t.*, ROW_NUMBER() OVER(ORDER BY t.txn_id) AS rn FROM tab t
), cte AS (
SELECT rn,
txn_id,
amount,
CASE WHEN amount <= 0 THEN 0 ELSE amount END AS total,
CASE WHEN amount <= 0 THEN 1 ELSE 0 END AS overage_total
FROM cte_r
WHERE rn = 1
UNION ALL
SELECT cte_r.rn,
cte_r.txn_id,
cte_r.amount,
CASE WHEN cte.total + cte_r.amount <= 0 THEN 0
ELSE cte.total + cte_r.amount
END AS total,
cte.overage_total + CASE WHEN cte.total + cte_r.amount <= 0
THEN 1 ELSE 0 END AS overage_total
FROM cte
JOIN cte_r
ON cte.rn = cte_r.rn-1
)
SELECT txn_id, amount, total,overage_total
FROM cte
ORDER BY rn;
Output:
+---------+---------+--------+---------------+
| txn_id | amount | total | overage_total |
+---------+---------+--------+---------------+
| a | 1 | 1 | 0 |
| b | 2 | 3 | 0 |
| c | -4 | 0 | 1 |
| d | 2 | 2 | 1 |
| e | -1 | 1 | 1 |
| f | 2 | 3 | 1 |
| h | -4 | 0 | 2 |
+---------+---------+--------+---------------+
db<>fiddle demo
Related: Conditional SUM on Oracle and 7. Capping a running total
An option is to use a function to step through the rows and do calculations:
CREATE FUNCTION runningTotalWithCondition() RETURNS TABLE(txn_id char(1), amount int, running_total integer, overage_total integer) AS
$$
DECLARE
running_total integer := 0;
overage_total integer := 0;
c CURSOR FOR SELECT * FROM t ORDER BY txn_id ASC;
BEGIN
FOR recordvar IN c LOOP
IF (running_total + recordvar.amount) > 0 THEN
running_total = running_total + recordvar.amount;
overage_total = overage_total;
ELSE
overage_total = overage_total + abs(running_total + recordvar.amount);
running_total = 0;
END IF;
RETURN QUERY SELECT recordvar.txn_id, recordvar.amount, running_total, overage_total;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Calling the function:
SELECT * FROM runningTotalWithCondition();

Get different LIMIT on each group on postgresql rank

To get 2 rows from each group I can use ROW_NUMBER() with condition <= 2 at last but my question is what If I want to get different limits on each group e.g 3 rows for section_id 1, 1 rows for 2 and 1 rows for 3?
Given the following table:
db=# SELECT * FROM xxx;
id | section_id | name
----+------------+------
1 | 1 | A
2 | 1 | B
3 | 1 | C
4 | 1 | D
5 | 2 | E
6 | 2 | F
7 | 3 | G
8 | 2 | H
(8 rows)
I get the first 2 rows (ordered by name) for each section_id, i.e. a result similar to:
id | section_id | name
----+------------+------
1 | 1 | A
2 | 1 | B
5 | 2 | E
6 | 2 | F
7 | 3 | G
(5 rows)
Current Query:
SELECT
*
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY section_id ORDER BY name) AS r,
t.*
FROM
xxx t) x
WHERE
x.r <= 2;
Create a table to contain the section limits, then join. The big advantage being that as new sections are required or limits change maintenance is reduced to a single table update and comes at very little cost. See example.
select s.section_id, s.name
from (select section_id, name
, row_number() over (partition by section_id order by name) rn
from sections
) s
left join section_limits sl on (sl.section_id = s.section_id)
where
s.rn <= coalesce(sl.limit_to,2);
Just fix up your where clause:
with numbered as (
select row_number() over (partition by section_id
order by name) as r,
t.*
from xxx t
)
select *
from numbered
where (section_id = 1 and r <= 3)
or (section_id = 2 and r <= 1)
or (section_id = 3 and r <= 1);

indicator for increase over time

I'm trying to create an indicator for value increase over time within a group. In particular, I'm trying to flag certain grp if value ever increases by 50% over time.
I have a raw data that looks like:
id grp value_dt value
--------------------------------
1 1 11/20/20 1.4
1 1 11/21/20 0.8
1 1 11/24/20 2.8
1 1 11/25/20 2.5
1 2 11/29/20 1.5
1 2 12/1/20 1.6
2 1 11/21/20 0.8
2 2 11/26/20 0.9
2 3 12/1/20 0.9
2 3 12/3/20 2.8
You can see that for id = 1 and grp = 1, the value fluctuates as it increases and decreases over time, but because it had increase over time between 11/21/20 and 11/24/20 from 0.8 to 2.8 (greater than 50% increase), I want to flag the whole grp 1. I want my output to look like:
id grp val_ind
-----------------------
1 1 1
1 2 0
2 1 0
2 2 0
2 3 1
I can only think of using min and max (something like below), which doesn't include the 'over the time' factor in...
select id,
grp,
min(value) as min_grp,
max(value) as max_grp,
(max_grp - min_grp) as val_diff,
case when val_diff >= min_grp * 1.5 then 1 else 0 end as val_ind
If anyone can offer their advice, I will greatly appreciate it!
I think you want to know if at any point at time there is an increase of 50% , you flag that group. if yes , here is how you can do it,
you need to use cte and window functions :
; WITH cte AS (
SELECT *
, CASE WHEN COALESCE(LEAD(value) OVER (PARTITION BY id, grp ORDER BY value_dt),0) >= value* 2 THEN 1 ELSE 0 END val_ind
FROM ttt
)
SELECT
id , grp , MAX(val_ind) val_ind
FROM cte
GROUP BY
id , grp
id | grp | val_ind
-: | --: | ------:
1 | 1 | 1
1 | 2 | 0
2 | 1 | 0
2 | 2 | 0
2 | 3 | 1
db<>fiddle here

Increment Row_Number Only Where Distinct

I have the following table, which I've made very simple because I do not know how to format it as a table on here (side note if anyone could link me to an easy tutorial on that I would be forever grateful).
id
1
1
1
2
2
2
I'd like to add another column which increments in number only on distinct IDs so the outcome should be
Id
1
1
1
2
2
2
rowNum
1
1
1
2
2
2
Currently all I can manage to get is:
id
1
1
1
2
2
2
rowNum
1
2
3
4
5
6
I'm missing something very simple here as I'm confident I should be able to solve this issue using either row_number or rank and a window function but I cannot figure it out.
Use DENSE_RANK() instead of ROW_NUMBER():
SELECT
id,
DENSE_RANK() OVER (ORDER BY id) dr
FROM yourTable
Demo
You can do this with a subquery self join, as well.
mysql> select id,
> (select count(distinct id)
> from
> testtest b
> where b.id < a.id)
> from testtest a;
+------+---------------------------------------------------------------+
| id | (select count(distinct id) from testtest b where b.id < a.id) |
+------+---------------------------------------------------------------+
| 1 | 0 |
| 1 | 0 |
| 1 | 0 |
| 2 | 1 |
| 2 | 1 |
| 2 | 1 |
+------+---------------------------------------------------------------+
6 rows in set (0.01 sec)
And one more way:
select a.id, b.idRank
from testtest a,
(
select id,
rank() over
(order by id) as idRank
from (
select distinct id
from testtest
) testtest2
) b
where a.id = b.id

nber of rows within a group in oracle

In order to generate a report in ireport i need this query in oracle 10g.
SCHOOL:
SELECT STID,NAME,DEPT,SUM(CHARGE)
STID | PROG | DEPT | CHARGE
1 1 A 1
2 1 B 2
3 2 A 2
4 2 B 1
5 1 A 2
Desired OUTPUT:
DEPT | PROG | NBER_OF_STID | TOT_CHG
A 1 2 3
2 1 2
B 1 1 2
2 1 1
this is my query
SELECT DISTINCT DEPT, DISTINCT PROG, COUNT(STID), SUM (CHARGE) TOT_CHG
FROM SCHOOL
GROUP BY DEPT, PROG, STID, CHARGE
Help Thanks.
You need to group by only the columns that aren't going to be aggregated.
Try this:
SELECT DEPT, PROG, COUNT(STID) NBER_OF_STID, SUM (CHARGE) TOT_CHG
FROM SCHOOL
GROUP BY DEPT, PROG
Note: in your query you'll always get a tabular view, so results will be like this:
DEPT | PROG | NBER_OF_STID | TOT_CHG
A 1 2 3
A 2 1 2
B 1 1 2
B 2 1 1
IMHO, the visual formatting should be made in the report itself (ireport)