Sum inventory item groups - sql

I'm trying to sum all starting inventories and cap inventory amounts of product groups. Initial output will give starting inventories and caps of specific products, but I'd like to roll up the total starting inventory into the product group level.
This is in Oracle SQL. I've tried removing the product category and adding the SUM() function to the starting inventory, but it still is not rolling up the sum to the group level. There is a SELECT statement in the FROM clause... maybe there is an issue there???
SELECT OPRVIEW_OBSERVATION_DATE,
OPRVIEW.LOOKUP_CODE OPRVIEW_LOOKUP_CODE,
HSALE001V.GROUPBUD HSALE001V_GROUPBUD,
sum(HSALE001V.CAP) HSALE001V_CAP,
sum(OPRVIEW.START_INV) OPRVIEW_START_INV
FROM (SELECT h.observation_date,
loc.lookup_code,
inv.start_inv,
inv.product
FROM apps.nlas_inventory inv,
apps.nlas_header h,
apps.nlas_location loc
WHERE inv.header_id = h.id
AND loc.id = h.location_id) OPRVIEW
INNER JOIN (select rtrim(ltrim(gnmcu)) as siteBUD,
rtrim(ltrim(GNALPH1)) as itemBUD,
GNDC as StatusBUD,
GNAN25 as CAP,
GNALPH2 as GroupBUD
from hsale001) HSALE001V
ON OPRVIEW.LOOKUP_CODE = HSALE001V.SITEBUD
AND OPRVIEW.PRODUCT = HSALE001V.ITEMBUD
WHERE ( ( ( OPRVIEW.OBSERVATION_DATE BETWEEN TO_DATE('2019-08-07 00 00 00', 'yyyy-MM-dd hh24:mi:ss') AND TO_DATE('2019-08-07 23 59 59', 'yyyy-MM-dd hh24:mi:ss') )
AND ( OPRVIEW.LOOKUP_CODE IN ( '123' ) ) )
AND ( HSALE001V.STATUSBUD IN ( 'CA' ) ) )
GROUP BY FLOOR(TO_NUMBER(TO_CHAR(OPRVIEW.OBSERVATION_DATE, 'yyyyddd'))) - 1900000,
OPRVIEW.LOOKUP_CODE,
HSALE001V.GROUPBUD,
HSALE001V.CAP,
OPRVIEW.START_INV
Current output (lists out each product per line... product is not displayed):
DATE SITE GROUP CAP INVENTORY
119219 123 2 0 3778
119219 123 2 24000 23165
Desired output:
DATE SITE GROUP CAP INVENTORY
119219 123 2 0 26943

As also mentioned in the comment, there is problem with group by. But there are last 2 columns which do not require in group by.
SELECT OPRVIEW_OBSERVATION_DATE,
OPRVIEW.LOOKUP_CODE OPRVIEW_LOOKUP_CODE,
HSALE001V.GROUPBUD HSALE001V_GROUPBUD,
sum(HSALE001V.CAP) HSALE001V_CAP,
sum(OPRVIEW.START_INV) OPRVIEW_START_INV
FROM (SELECT h.observation_date,
loc.lookup_code,
inv.start_inv,
inv.product
FROM apps.nlas_inventory inv,
apps.nlas_header h,
apps.nlas_location loc
WHERE inv.header_id = h.id
AND loc.id = h.location_id) OPRVIEW
INNER JOIN (select rtrim(ltrim(gnmcu)) as siteBUD,
rtrim(ltrim(GNALPH1)) as itemBUD,
GNDC as StatusBUD,
GNAN25 as CAP,
GNALPH2 as GroupBUD
from hsale001) HSALE001V
ON OPRVIEW.LOOKUP_CODE = HSALE001V.SITEBUD
AND OPRVIEW.PRODUCT = HSALE001V.ITEMBUD
WHERE ( ( ( OPRVIEW.OBSERVATION_DATE BETWEEN TO_DATE('2019-08-07 00 00 00', 'yyyy-MM-dd hh24:mi:ss') AND TO_DATE('2019-08-07 23 59 59', 'yyyy-MM-dd hh24:mi:ss') )
AND ( OPRVIEW.LOOKUP_CODE IN ( '123' ) ) )
AND ( HSALE001V.STATUSBUD IN ( 'CA' ) ) )
GROUP BY FLOOR(TO_NUMBER(TO_CHAR(OPRVIEW.OBSERVATION_DATE, 'yyyyddd'))) - 1900000,
OPRVIEW.LOOKUP_CODE,
HSALE001V.GROUPBUD;
--HSALE001V.CAP, -- not required
--OPRVIEW.START_INV -- not required
You need only columns in the group by using which you need your result to be grouped as also its name suggests.
Aggregate functions are mostly used on non grouped columns.
Note: CAP will be 24000 using this query.
Cheers!!

Related

Oracle SQL Hierarchy Summation

I have a table TRANS that contains the following records:
TRANS_ID TRANS_DT QTY
1 01-Aug-2020 5
1 01-Aug-2020 1
1 03-Aug-2020 2
2 02-Aug-2020 1
The expected output:
TRANS_ID TRANS_DT BEGBAL TOTAL END_BAL
1 01-Aug-2020 0 6 6
1 02-Aug-2020 6 0 6
1 03-Aug-2020 6 2 8
2 01-Aug-2020 0 0 0
2 02-Aug-2020 0 1 1
2 03-Aug-2020 1 0 1
Each trans_id starts with a beginning balance of 0 (01-Aug-2020). For succeeding days, the beginning balance is the ending balance of the previous day and so on.
I can create PL/SQL block to create the output. Is it possible to get the output in 1 SQL statement?
Thanks.
Try this following script using CTE-
Demo Here
WITH CTE
AS
(
SELECT DISTINCT A.TRANS_ID,B.TRANS_DT
FROM your_table A
CROSS JOIN (SELECT DISTINCT TRANS_DT FROM your_table) B
),
CTE2
AS
(
SELECT C.TRANS_ID,C.TRANS_DT,SUM(D.QTY) QTY
FROM CTE C
LEFT JOIN your_table D
ON C.TRANS_ID = D.TRANS_ID
AND C.TRANS_DT = D.TRANS_DT
GROUP BY C.TRANS_ID,C.TRANS_DT
ORDER BY C.TRANS_ID,C.TRANS_DT
)
SELECT F.TRANS_ID,F.TRANS_DT,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT < F.TRANS_DT
) BEGBAL,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT = F.TRANS_DT
) TOTAL ,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT <= F.TRANS_DT
) END_BAL
FROM CTE2 F
You can as well do like this (I would assume it's a bit faster): Demo
with
dt_between as (
select mindt + level - 1 as trans_dt
from (select min(trans_dt) as mindt, max(trans_dt) as maxdt from t)
connect by level <= maxdt - mindt + 1
),
dt_for_trans_id as (
select *
from dt_between, (select distinct trans_id from t)
),
qty_change as (
select distinct trans_id, trans_dt,
sum(qty) over (partition by trans_id, trans_dt) as total,
sum(qty) over (partition by trans_id order by trans_dt) as end_bal
from t
right outer join dt_for_trans_id using (trans_id, trans_dt)
)
select
trans_id,
to_char(trans_dt, 'DD-Mon-YYYY') as trans_dt,
nvl(lag(end_bal) over (partition by trans_id order by trans_dt), 0) as beg_bal,
nvl(total, 0) as total,
nvl(end_bal, 0) as end_bal
from qty_change q
order by trans_id, trans_dt
dt_between returns all the days between min(trans_dt) and max(trans_dt) in your data.
dt_for_trans_id returns all these days for each trans_id in your data.
qty_change finds difference for each day (which is TOTAL in your example) and cumulative sum over all the days (which is END_BAL in your example).
The main select takes END_BAL from previous day and calls it BEG_BAL, it also does some formatting of final output.
First of all, you need to generate dates, then you need to aggregate your values by TRANS_DT, and then left join your aggregated data to dates. The easiest way to get required sums is to use analitic window functions:
with dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
select min(trans_dt) from trans
union all
select dt+1 from dates
where dt+1<=(select max(trans_dt) from trans)
)
,trans_agg as ( -- aggregating QTY in TRANS
select TRANS_ID,TRANS_DT,sum(QTY) as QTY
from trans
group by TRANS_ID,TRANS_DT
)
select -- using left join partition by to get data on daily basis for each trans_id:
dt,
trans_id,
nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
nvl(qty,0) as TOTAL,
nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL
from dates
left join trans_agg tr
partition by (trans_id)
on tr.trans_dt=dates.dt;
Full example with sample data:
alter session set nls_date_format='dd-mon-yyyy';
with trans(TRANS_ID,TRANS_DT,QTY) as (
select 1,to_date('01-Aug-2020'), 5 from dual union all
select 1,to_date('01-Aug-2020'), 1 from dual union all
select 1,to_date('03-Aug-2020'), 2 from dual union all
select 2,to_date('02-Aug-2020'), 1 from dual
)
,dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
select min(trans_dt) from trans
union all
select dt+1 from dates
where dt+1<=(select max(trans_dt) from trans)
)
,trans_agg as ( -- aggregating QTY in TRANS
select TRANS_ID,TRANS_DT,sum(QTY) as QTY
from trans
group by TRANS_ID,TRANS_DT
)
select
dt,
trans_id,
nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
nvl(qty,0) as TOTAL,
nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL
from dates
left join trans_agg tr
partition by (trans_id)
on tr.trans_dt=dates.dt;
You can use a recursive query to generate the overall date range, cross join it with the list of distinct tran_id, then bring the table with a left join. The last step is aggregation and window functions:
with all_dates (trans_dt, max_dt) as (
select min(trans_dt), max(trans_dt) from trans group by trans_id
union all
select trans_dt + interval '1' day, max_dt from all_dates where trans_dt < max_dt
)
select
i.trans_id,
d.trans_dt,
coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) - coalesce(sum(t.qty), 0) begbal,
coalesce(sum(t.qty), 0) total,
coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) endbal
from all_dates d
cross join (select distinct trans_id from trans) i
left join trans t on t.trans_id = i.trans_id and t.trans_dt = d.trans_dt
group by i.trans_id, d.trans_dt
order by i.trans_id, d.trans_dt

Filtered last transaction

first: I have two tables with a primary key((Agent_ID). I want to join both tables, filter Agent_Type =1 and status =1
Second: get the last active year total transaction value monthly wise who is not done any transaction for the last three months.
Agent table
Agent_ID Agent_Type
234 1
456 1
567 1
678 0
Agent_Transaction table
Agent_ID Amount Transaction_Date status
234 70 23/7/2019 1
234 54 11/6/2019 0
234 30 23/5/2019 1
456 56 12/1/2019 1
456 80 15/3/2019 1
456 99 20/2/2019 1
456 76 23/12/2018 1
567 56 10/10/2018 0
567 60 30/6/2018 1
456
select Agent_ID,CONCAT(Extract(MONTH from Agent_Transaction.Transaction_Date),
EXTRACT (YEAR FROM Agent_Transaction.Transaction_Date))as MONTH_YEAR,
SUM(Agent_Transaction.Amount)AS TOTAL
from Agent
inner join Agent_Transaction
on Agent_Transaction.Agent_ID = Agent.Agent_ID
where Agent.Agent_Type='1' AND Agent_Transaction.status='1' AND
(Agent_Transaction.Transaction_Date between ADD_MONTHS(SYSDATE,-3) and SYSDATE)
GROUP BY Agent.Agent_ID,
CONCAT(Extract(MONTH from Agent_Transaction.Transaction_Date),EXTRACT (YEAR FROM Agent_Transaction.Transaction_Date)),
Agent_Transaction.Amount
But I didn't get what I expected.
As far as I understood the requirement, you can use the following query:
SELECT
A.AGENT_ID,
TO_CHAR(TRUNC(ATR.TRANSACTION_DATE, 'MONTH'), 'MONYYYY'), -- YOU CAN USE DIFFERENT FORMAT ACCORDING TO REQUIREMENT
SUM(AMOUNT) AS TOTAL_MONTHWISE_AMOUNT -- MONTHWISE TRANSACTION TOTAL
FROM
AGENT A
JOIN AGENT_TRANSACTION ATR ON ( A.AGENT_ID = ATR.AGENT_ID )
WHERE
-- EXCLUDING THE AGENTS WHICH HAVE DONE NO TRANSACTION IN LAST THREE MONTHS USING FOLLOWING NOT IN
ATR.AGENT_ID NOT IN (
SELECT
DISTINCT ATR_IN1.AGENT_ID
FROM
AGENT_TRANSACTION ATR_IN1
WHERE
ATR_IN1.TRANSACTION_DATE > ADD_MONTHS(SYSDATE, - 3)
AND ATR_IN1.STATUS = 1 -- YOU CAN USE IT ACCORDING TO REQUIREMENT
)
-- FETCHING LAST YEAR DATA
AND EXTRACT(YEAR FROM ATR.TRANSACTION_DATE) = EXTRACT(YEAR FROM ADD_MONTHS(SYSDATE, - 12))
AND A.AGENT_TYPE = 1
AND ATR.STATUS = 1
GROUP BY
A.AGENT_ID,
TRUNC(ATR.TRANSACTION_DATE, 'MONTH');
Please comment if minor changes are required or you need different logic.
Cheers!!
-- Update --
Updated the query after OP described the original issue:
SELECT
AGENT_ID,
TO_CHAR(TRUNC(TRANSACTION_DATE, 'MONTH'), 'MONYYYY'), -- YOU CAN USE DIFFERENT FORMAT ACCORDING TO REQUIREMENT
SUM(AMOUNT) AS TOTAL_MONTHWISE_AMOUNT -- MONTHWISE TRANSACTION TOTAL
FROM
(
SELECT
A.AGENT_ID,
TRUNC(ATR.TRANSACTION_DATE, 'MONTH') AS TRANSACTION_DATE,
MAX(TRUNC(ATR.TRANSACTION_DATE, 'MONTH')) OVER(
PARTITION BY A.AGENT_ID
) AS LAST_TR_DATE,
AMOUNT,
AGENT_TYPE,
STATUS
FROM
AGENT A
JOIN AGENT_TRANSACTION ATR ON ( A.AGENT_ID = ATR.AGENT_ID )
WHERE
A.AGENT_TYPE = 1
AND ATR.STATUS = 1
)
WHERE
-- EXCLUDING THE AGENTS WHICH HAVE DONE NO TRANSACTION IN LAST THREE MONTHS USING FOLLOWING NOT IN
LAST_TR_DATE > ADD_MONTHS(SYSDATE, - 3)
-- FETCHING LAST YEAR DATA
AND TRANSACTION_DATE BETWEEN ADD_MONTHS(LAST_TR_DATE, - 12) AND LAST_TR_DATE
GROUP BY
AGENT_ID,
TRANSACTION_DATE;
Cheers!!
-- Update --
Your exact query should look like this:
SELECT
AGENT_ID,
TO_CHAR(TRUNC(TX_TIME, 'MONTH'), 'MONYYYY') AS MONTHYEAR,
SUM(TX_VALUE) AS TOTALMONTHWISE
FROM
(
SELECT
A.AGENT_ID,
TRUNC(ATR.TX_TIME, 'MONTH') AS TX_TIME, -- changed this alias name
MAX(TRUNC(ATR.TX_TIME, 'MONTH')) OVER(
PARTITION BY A.AGENT_ID
) AS LAST_TR_DATE,
ATR.TX_VALUE,
A.AGENT_TYPE_ID
FROM
TBLEZ_AGENT A
JOIN TBLEZ_TRANSACTION ATR ON ( A.AGENT_ID = ATR.SRC_AGENT_ID )
WHERE
A.AGENT_TYPE_ID = '3'
AND ATR.STATUS = '0'
AND ATR.TX_TYPE_ID = '5'
)
WHERE
LAST_TR_DATE < ADD_MONTHS(SYSDATE, - 3)
AND ( TX_TIME BETWEEN ADD_MONTHS(LAST_TR_DATE, - 12) AND LAST_TR_DATE )
GROUP BY
AGENT_ID,
TX_TIME;
-- UPDATE --
In response to this comment -- **Hi Tejash, How to get the total day-wise to above my scenario? **
SELECT
AGENT_ID,
TX_TIME,
SUM(TX_VALUE) AS TOTALDAYWISE
FROM
(
SELECT
A.AGENT_ID,
TRUNC(ATR.TX_TIME) AS TX_TIME, -- changed TRUNC INPUT PARAMETER -- REMOVED MONTH IN TRUNC
MAX(TRUNC(ATR.TX_TIME)) OVER( -- changed TRUNC INPUT PARAMETER -- REMOVED MONTH IN TRUNC
PARTITION BY A.AGENT_ID
) AS LAST_TR_DATE,
ATR.TX_VALUE,
A.AGENT_TYPE_ID
FROM
TBLEZ_AGENT A
JOIN TBLEZ_TRANSACTION ATR ON ( A.AGENT_ID = ATR.SRC_AGENT_ID )
WHERE
A.AGENT_TYPE_ID = '3'
AND ATR.STATUS = '0'
AND ATR.TX_TYPE_ID = '5'
)
WHERE
LAST_TR_DATE < ADD_MONTHS(SYSDATE, - 3)
AND ( TRUNC(TX_TIME, 'MONTH') BETWEEN ADD_MONTHS(LAST_TR_DATE, - 12) AND LAST_TR_DATE )
-- changed TRUNC INPUT PARAMETER -- ADDED MONTH IN TRUNC
GROUP BY
AGENT_ID,
TX_TIME;
Cheers!!
SELECT
A.AGENT_ID,
TO_CHAR(TRUNC(ATR.TX_TIME, 'MONTH'), 'MONYYYY') AS MONTHYEAR,
SUM(ATR.TX_VALUE) AS TOTALMONTHWISE
FROM
(
SELECT
A.AGENT_ID,
TRUNC(ATR.TX_TIME, 'MONTH') AS TRANSC_DATE,
MAX(TRUNC(ATR.TX_TIME, 'MONTH')) OVER(
PARTITION BY A.AGENT_ID
) AS LAST_TR_DATE,
ATR.TX_VALUE,
A.AGENT_TYPE_ID
FROM
TBLEZ_AGENT A
JOIN TBLEZ_TRANSACTION ATR ON ( A.AGENT_ID = ATR.SRC_AGENT_ID )
WHERE
A.AGENT_TYPE_ID = '3'
AND ATR.STATUS = '0'
AND ATR.TX_TYPE_ID = '5'
)
WHERE
LAST_TR_DATE > ADD_MONTHS(SYSDATE, - 3)
AND ( TX_TIME BETWEEN ADD_MONTHS(LAST_TR_DATE, - 12) AND LAST_TR_DATE )
GROUP BY
A.AGENT_ID,TRUNC(ATR.TX_TIME, 'MONTH')

Group by in columns and rows, counts and percentages per day

I have a table that has data like following.
attr |time
----------------|--------------------------
abc |2018-08-06 10:17:25.282546
def |2018-08-06 10:17:25.325676
pqr |2018-08-05 10:17:25.366823
abc |2018-08-06 10:17:25.407941
def |2018-08-05 10:17:25.449249
I want to group them and count by attr column row wise and also create additional columns in to show their counts per day and percentages as shown below.
attr |day1_count| day1_%| day2_count| day2_%
----------------|----------|-------|-----------|-------
abc |2 |66.6% | 0 | 0.0%
def |1 |33.3% | 1 | 50.0%
pqr |0 |0.0% | 1 | 50.0%
I'm able to display one count by using group by but unable to find out how to even seperate them to multiple columns. I tried to generate day1 percentage with
SELECT attr, count(attr), count(attr) / sum(sub.day1_count) * 100 as percentage from (
SELECT attr, count(*) as day1_count FROM my_table WHERE DATEPART(week, time) = DATEPART(day, GETDate()) GROUP BY attr) as sub
GROUP BY attr;
But this also is not giving me correct answer, I'm getting all zeroes for percentage and count as 1. Any help is appreciated. I'm trying to do this in Redshift which follows postgresql syntax.
Let's nail the logic before presenting:
with CTE1 as
(
select attr, DATEPART(day, time) as theday, count(*) as thecount
from MyTable
)
, CTE2 as
(
select theday, sum(thecount) as daytotal
from CTE1
group by theday
)
select t1.attr, t1.theday, t1.thecount, t1.thecount/t2.daytotal as percentofday
from CTE1 t1
inner join CTE2 t2
on t1.theday = t2.theday
From here you can pivot to create a day by day if you feel the need
I am trying to enhance the query #johnHC btw if you needs for 7days then you have to those days in case when
with CTE1 as
(
select attr, time::date as theday, count(*) as thecount
from t group by attr,time::date
)
, CTE2 as
(
select theday, sum(thecount) as daytotal
from CTE1
group by theday
)
,
CTE3 as
(
select t1.attr, EXTRACT(DOW FROM t1.theday) as day_nmbr,t1.theday, t1.thecount, t1.thecount/t2.daytotal as percentofday
from CTE1 t1
inner join CTE2 t2
on t1.theday = t2.theday
)
select CTE3.attr,
max(case when day_nmbr=0 then CTE3.thecount end) as day1Cnt,
max(case when day_nmbr=0 then percentofday end) as day1,
max(case when day_nmbr=1 then CTE3.thecount end) as day2Cnt,
max( case when day_nmbr=1 then percentofday end) day2
from CTE3 group by CTE3.attr
http://sqlfiddle.com/#!17/54ace/20
In case that you have only 2 days:
http://sqlfiddle.com/#!17/3bdad/3 (days descending as in your example from left to right)
http://sqlfiddle.com/#!17/3bdad/5 (days ascending)
The main idea is already mentioned in the other answers. Instead of joining the CTEs for calculating the values I am using window functions which is a bit shorter and more readable I think. The pivot is done the same way.
SELECT
attr,
COALESCE(max(count) FILTER (WHERE day_number = 0), 0) as day1_count, -- D
COALESCE(max(percent) FILTER (WHERE day_number = 0), 0) as day1_percent,
COALESCE(max(count) FILTER (WHERE day_number = 1), 0) as day2_count,
COALESCE(max(percent) FILTER (WHERE day_number = 1), 0) as day2_percent
/*
Add more days here
*/
FROM(
SELECT *, (count::float/count_per_day)::decimal(5, 2) as percent -- C
FROM (
SELECT DISTINCT
attr,
MAX(time::date) OVER () - time::date as day_number, -- B
count(*) OVER (partition by time::date, attr) as count, -- A
count(*) OVER (partition by time::date) as count_per_day
FROM test_table
)s
)s
GROUP BY attr
ORDER BY attr
A counting the rows per day and counting the rows per day AND attr
B for more readability I convert the date into numbers. Here I take the difference between current date of the row and the maximum date available in the table. So I get a counter from 0 (first day) up to n - 1 (last day)
C calculating the percentage and rounding
D pivot by filter the day numbers. The COALESCE avoids the NULL values and switched them into 0. To add more days you can multiply these columns.
Edit: Made the day counter more flexible for more days; new SQL Fiddle
Basically, I see this as conditional aggregation. But you need to get an enumerator for the date for the pivoting. So:
SELECT attr,
COUNT(*) FILTER (WHERE day_number = 1) as day1_count,
COUNT(*) FILTER (WHERE day_number = 1) / cnt as day1_percent,
COUNT(*) FILTER (WHERE day_number = 2) as day2_count,
COUNT(*) FILTER (WHERE day_number = 2) / cnt as day2_percent
FROM (SELECT attr,
DENSE_RANK() OVER (ORDER BY time::date DESC) as day_number,
1.0 * COUNT(*) OVER (PARTITION BY attr) as cnt
FROM test_table
) s
GROUP BY attr, cnt
ORDER BY attr;
Here is a SQL Fiddle.

SQL subquery with average of 3 top values in Postgresql

I need a little help to solve a query to count patients (ID) who have the average of the 3 last diastolic tension (TAD) < 90.
I've tried several type of nested subqueries with different errors.
This is my last version I've done:
SELECT CENTRO, COUNT ( DISTINCT ID )
FROM
(
SELECT PAC.CENTRO, PAC.ID, T.TAD
FROM IDDPAC PAC,
(
SELECT AVG(TA.TAD) TAD
FROM
(
SELECT
TEXT_TO_NUMBER ( PAG.TEXTO ) TAD
FROM IDDPAG PAG, DATE D
WHERE TRIM ( PAG.DGP )='AH'
AND PAG.ID=T.ID
AND PAG.FECHA=D.OMI
AND D.TIME_DATE::DATE BETWEEN DATE '2012-01-01'
AND DATE '2012-12-31'
ORDER BY PAG.FECHA DESC LIMIT 3
) TA
) T
WHERE PAC.CENTRO='10040110' AND T.ID = PAC.ID
GROUP BY PAC.CENTRO , PAC.ID
)
A
WHERE T.TAD < 90
GROUP BY CENTRO
And I get the following error:
ERROR: falta una entrada para la tabla «t» en la cláusula FROM
LINE 31: AND PAG.ID=T.ID
^
********** Error **********
Translation:
ERROR: missing an entry for the table «t» in the clause FROM
LINE 31: AND PAG.ID=T.ID
^
********** Error **********
To get the average of the last three values, use row_number() to enumerate the values. Then choose the last three and take the average. This gives you the patient level information:
SELECT PAC.CENTRO, PAG.ID, AVG(TA.TAD) AS TAD
FROM (SELECT PAG.ID, TEXT_TO_NUMBER ( PAG.TEXTO ) as TAD,
ROW_NUMBER() OVER (PARTITION BY PAG.ID ORDER BY D.TIME_DATE DESC) as seqnum
FROM IDDPAG PAG JOIN
DATE D
ON PAG.FECHA = D.OMI JOIN
IDDPAC PAC
ON PAC.ID = PAG.ID
WHERE TRIM ( PAG.DGP )='AH' AND
D.TIME_DATE::DATE BETWEEN DATE '2012-01-01' AND DATE '2012-12-31'
) TA
WHERE SEQNUM <= 3
GROUP BY PAC.CENTRO, PAD.ID
HAVING AVG(TA.TAD) < 90;
The count by centro would just be:
SELECT CENTRO, COUNT(*)
FROM (SELECT PAC.CENTRO, PAG.ID, AVG(TA.TAD) AS TAD
FROM (SELECT PAG.ID, TEXT_TO_NUMBER ( PAG.TEXTO ) as TAD,
ROW_NUMBER() OVER (PARTITION BY PAG.ID ORDER BY D.TIME_DATE DESC) as seqnum
FROM IDDPAG PAG JOIN
DATE D
ON PAG.FECHA = D.OMI JOIN
IDDPAC PAC
ON PAC.ID = PAG.ID
WHERE TRIM ( PAG.DGP )='AH' AND
D.TIME_DATE::DATE BETWEEN DATE '2012-01-01' AND DATE '2012-12-31'
) TA
WHERE SEQNUM <= 3
GROUP BY PAC.CENTRO, PAD.ID
HAVING AVG(TA.TAD) < 90
) TA
GROUP BY CENTRO;
The problem is, exactly as the error indicates, that 'T' is not defined in the place it is requested. Your error is in the innermost subquery:
SELECT
TEXT_TO_NUMBER ( PAG.TEXTO ) TAD
FROM IDDPAG PAG, DATE D
WHERE TRIM ( PAG.DGP )='AH'
AND PAG.ID=T.ID
AND PAG.FECHA=D.OMI
AND D.TIME_DATE::DATE BETWEEN DATE '2012-01-01'
AND DATE '2012-12-31'
ORDER BY PAG.FECHA DESC LIMIT 3
But there is no T defined here to be used in the PAG.ID=T.ID portion of your WHERE clause. Did you mean to join on a table called T? Or did you mean to use D.ID instead?

How can you use SQL to return values for a specified date or closest date < specified date?

I've written an SQL statement to return a list of prices based on a date parameter, but I am finding that on some dates, the price is missing. I am looking for a way to modify this statement to return the price on the date specified, but if that is not available return the price for the most recent price available before the date specified.
Select date, grp, id, price
From
price_table
Where
date In ('12/31/2009', '11/30/2009') And
grp In ('Group1')
For example, in the I would like to be able to re-write the statement above to return all of the records below, showing appropriate parameter dates for all records. Assume this is a subset of a table with daily prices and the values below are the last prices for the months noted.
12/31/2009 Group1 1111 100
12/31/2009 Group1 2222 99
12/29/2009 Group1 3333 98
11/30/2009 Group1 1111 100
11/28/2009 Group1 2222 99
11/30/2009 Group1 3333 98
UPDATE:
Thanks to some help from srgerg below, I have been able to create a statement that works for one date at a time, but I would still like to find a way to pass multiple dates to the query.
Select p1.date, p1.grp, p1.id, p1.price
From
price_table As p1 Inner Join
(Select Max(p2.date) As maxdt, id
From
price_table As p2
Where
p2.date <= '12/31/2009'
Group By
p2.id) As p On p.maxdt = p1.date And p1.id = p.id
Where grp in ('Group1')
You could try something like this:
SELECT date, grp, id
, (SELECT TOP 1 price
FROM price_table P2
WHERE P1.id = P2.id
AND P1.grp = P2.grp
AND P2.date <= P1.date
ORDER BY P2.date DESC)
FROM price_table P1
WHERE P1.date IN ('12/31/2009', '11/30/2009')
AND P1.grp IN ('Group1')
Edit To get the last record for each month, group and id you could try this:
SELECT date, grp, id, price
FROM price_table P1
WHERE P1.date = (SELECT MAX(date)
FROM price_table P2
WHERE P1.grp = P2.grp
AND P1.id = P2.id
AND YEAR(P1.date) = YEAR(P2.date)
AND MONTH(P1.date) = MONTH(P2.date))
AND P1.grp In ('Group1')
Here's my approach to solving to this problem:
Associate every search date with a date in the table.
Use search dates as (additional) group terms.
Rank the dates in descending order, partitioning them by group terms.
Select those with the desired group term values and rank = 1.
The script:
WITH price_grouped AS (
SELECT
date, grp, id, price,
dategrp = CASE
WHEN date <= '11/30/2009' THEN '11/30/2009'
WHEN date <= '12/31/2009' THEN '12/31/2009'
/* extend the list of dates here */
END
FROM price_table
),
price_ranked AS (
SELECT
date, grp, id, price, dategrp,
rank = RANK() OVER (PARTITION BY grp, dategrp ORDER BY date DESC)
FROM price_grouped
)
SELECT date, grp, id, price
FROM price_ranked
WHERE grp IN ('Group1')
AND rank = 1
The above solution may seem not very handy because of the necessity to repeat each search date twice. An alternative to that might be to define the search date list as a separate CTE and, accordingly, assign the dates in a different way:
WITH search_dates (Date) AS (
SELECT '11/30/2009' UNION ALL
SELECT '12/31/2009'
/* extend the list of dates here */
),
price_grouped AS (
SELECT
p.date, p.grp, p.id, p.price,
dategrp = MIN(d.Date)
FROM price_table p
INNER JOIN search_dates d ON p.date <= d.Date
GROUP BY
p.date, p.grp, p.id, p.price
),
price_ranked AS (
SELECT
date, grp, id, price, dategrp,
rank = RANK() OVER (PARTITION BY grp, dategrp ORDER BY date DESC)
FROM price_grouped
)
SELECT date, grp, id, price
FROM price_ranked
WHERE grp IN ('Group1')
AND rank = 1
But take into account that the former solution will most probably be more performant.