invalid identifier : sum of multiple column in sql - sql

I'm trying to calculate multible columns in this query
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS PROC,
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR,
(OPD + IPD + PROC) as Total
FROM REF_TB_APP_TRANSACTIONS A,
REF_VW_VISIT_TYPE B
WHERE A.REQ_VISIT_TYPE = B.ID
AND A.TO_EST_CODE = 20068;
but I got this error PROC invalid identifier

You can't add the three SUMS in the Total column in the SELECT directly, since you're using the aliases of those columns. You could just do your Total column with another SUM CASE.
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR,
SUM (CASE WHEN B.ID IN (1,2,4)THEN 1 END) AS Total
FROM REF_TB_APP_TRANSACTIONS A,
REF_VW_VISIT_TYPE B
WHERE A.REQ_VISIT_TYPE = B.ID
AND A.TO_EST_CODE = 20068;

Depending on the DBMS you are using. You cant sum columns that are aliased like that, you would have to use a sub select and do the sum from there. If you verify your DBMS we can create query.
If MS SQL the below will work. A couple things:
PROC is reserved word, so either change that or put brackets around it (I went for brackets). Also it is preferred if you use JOINS vs. the way you had the queries.
SELECT OPD, IPD, DC, [PROC], SUR, (OPD + IPD + [PROC]) as Total
FROM (
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR
FROM REF_TB_APP_TRANSACTIONS A
INNER JOIN REF_VW_VISIT_TYPE B ON A.REQ_VISIT_TYPE = B.ID
WHERE A.TO_EST_CODE = 20068
) SUB

You can't reference the aliased columns as part of the select because in the order of query execution, they don't exist yet.
You simply wrap your query so it becomes a derived table and then you can refer to them in an outer select, see:
select OPD, IPD, DC, [PROC], SUR, OPD + IPD + [PROC] as Total from (
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR
FROM REF_TB_APP_TRANSACTIONS A
join REF_VW_VISIT_TYPE B on B.ID=A.REQ_VISIT_TYPE
where A.TO_EST_CODE = 20068
)x
Guessing because you have a semi-colon this is SQLServer, in which case you will need to use [] around the reserved word PROC
I've also properly joined your tables as it's not 1989 any more :-0

Related

PostgreSQL - Removing NULLS row and column from conditional aggregation results

I have a query for a multidimensional table using conditional aggregation
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2)
The result:
A SUM_D1 SUM_D2
-------------------
a1 100 NULL
a1 200 NULL
a3 NULL NULL
a4 NULL NULL
However, I would like to hide all NULL rows and columns as follows:
A SUM_D1
-----------
a1 100
a1 200
I have looked for similar problems but they are not my expected answer.
Any help is much appreciated,
Thank you
I think this does what you want:
select A,
coalesce(sum(case when D = 3 then D end),
sum(case when D = 4 then D end)
) as sum_d
from t
group by A
having sum(case when d in (3, 4) then 1 else 0 end) > 0;
Note that this returns only one column -- as in your example. If both "3" and "4" are in the data, then the value is for the "3"s.
If you want a query that returns a variable number of columns, then you need to use dynamic SQL -- or some other method. SQL queries return a fixed number of columns.
One method would be to return the values as an array:
select a,
array_agg(d order by d) as ds,
array_agg(sumd order by d) as sumds
from (select a, d, sum(d) as sumd
from t
where d in (3, 4)
group by a, d
) d
group by a;
To filter all-NULL rows you can use HAVING
select *
from
(
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2)
...
) as dt
where SUM_D1 is not null
and SUM_D2 is not null
Of course, if you got simple conditions like the ones in your example you better filter before aggregation:
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2)
...
where D in (3,4)
Now at least one calculation will return a value, thus no need to check for all-NULL.
To filter all-NULL columns you need some Dynamic SQL:
materialize the data in a temporary tabke using Insert/Select
scan each column for all-NULL select 1 from temp having count(SUM_D1) > 0
dynamically create the Select list based on this
run the Select
But why do you think you need this? It will be confusing for a user to run the same Stored Procedure and receive a different number of columns for each run.
I may have misinterpreted your question because the solution seems so simple:
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2)
where D is not null
This is not what you want, is it? :-)
Null appear because the condition that's not handled by case statement
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2
from
Table1
group by
A
having
(case when D = 3 or D = 4 then D end) is not null
As comment said if you want to suppress the null value.. You can use having to suppress null using is not null

How to calculate total paid amount for each id in various stages and reducing the paid amount after each stage in SQL Server 2012

I have two tables TableA and TableB.
TableA has total demand (column name -> amount) split into various rows.
TableB has supply numbers (column name -> amount).
I want to split the supply numbers and assign them against each row in TableA for each id (column name -> number).
Sample data for TableA and TableB are as follows:
TableA:
row number amount
-------------------
1 x 10
2 y 5
3 z 120
4 z 80
5 z 5
TableB:
number amount
---------------
x 5
y 15
z 200
Required output is :
row number amount paid
-------------------------
1 x 10 5
2 y 5 5
3 z 120 120
4 z 80 80
5 z 5 0
As of now we are using the below mentioned code which is very ugly and does not give good performance as we are using recursive cte's to do this job as our system was SQL Server 2008 R2 and we had no option but now our software has been upgraded to SQL Server 2012 and I know same can be achieved using sum function with order by in over clause. But I do not know how?
with cte as
(
select
a.row, a.number, a.amount,
b.amount as total
from
tableA as a
left join
tableB as b on a.number = b.number
),
cte1 as
(
select
row, number, amount, total,
case
when amount - total < 0 then amount else total
end as paid,
case
when amount - total < 0 then amount else total
end as totalpaid
from
cte
where
row = 1
union all
select
b.row, b.number, b.amount, b.total,
case
when b.amount - (b.total - (case when b.number = a.number then a.totalpaid else 0 end)) < 0 then b.amount else (b.total - (case when b.number = a.number then a.totalpaid else 0 end)) end,
case when b.amount - (b.total - (case when b.number = a.number then a.totalpaid else 0 end)) < 0 then b.amount else (b.total - (case when b.number = a.number then a.totalpaid else 0 end)) end + ((case when b.number = a.number then a.totalpaid else 0 end))
from
cte1 as a
inner join
cte as b on b.row = a.row + 1
)
select
row, number, amount, paid
from
cte1
Can someone tell me how to write the above code efficiently in SQL Server 2012?
Thanks in advance.
Try this code:
WITH cte as
(
SELECT a.row,
a.number,
a.amount,
b.amount AS totalPaid,
SUM(a.amount) OVER (PARTITION BY a.number ORDER BY row ROWS UNBOUNDED PRECEDING) AS totalAmount
FROM (VALUES (1,'x',10),(2,'y',5),(3,'z',120),(4,'z',80),(5,'z',5)) AS a(row, number, amount)
LEFT JOIN (VALUES ('x',5),('y',15),('z',200)) as b(number, amount) ON a.number = b.number
)
SELECT row, number, amount, CASE WHEN totalPaid >= totalAmount THEN amount ELSE CASE WHEN amount - totalAmount + totalPaid < 0 THEN 0 ELSE amount - totalAmount + totalPaid END END AS paid
FROM cte;
And please give me feedback about correctness and performance improvement.

How to get count of a column value returned using subquery?

I want to get all hrc_acct_num which are not present in acct_key column of STAGING_CUST_ACCT table. The last outer select column is throwing an error. How can I get count of a column returned using a subquery?
SELECT source_sys_cd,
Count(CASE
WHEN is_delete = 0 THEN 1
END) [DEL IS 0],
Sum(CASE
WHEN trans_amt = 0 THEN 1
ELSE 0
END) [STG $0 TXN CNT],
Count(CASE
WHEN hrc_acct_num NOT IN(SELECT DISTINCT acct_key
FROM staging_cust_acct) THEN
hrc_acct_num
END)
FROM staging_transactions (nolock)
GROUP BY source_sys_cd
ORDER BY source_sys_cd
You can do a LEFT JOIN to the sub query and then do a SUM when the value is null. acct_key
SELECT source_sys_cd,
Count(CASE
WHEN is_delete = 0 THEN 1
END) [DEL IS 0],
Sum(CASE
WHEN trans_amt = 0 THEN 1
ELSE 0
END) [STG $0 TXN CNT],
SUM(CASE WHEN T.acct_key is NULL THEN 1 else 0 END ) CountNotIN
FROM staging_transactions (nolock) s
LEFT JOIN (SELECT DISTINCT acct_key
FROM staging_cust_acct) t
s.hrc_acct_num = t.acct_key
GROUP BY source_sys_cd
ORDER BY source_sys_cd
Here's a simplified demo
You can short circuit the subquery with NOT EXISTS. It's more efficient than LEFT JOIN (SELECT DISTINCT, since you don't care about enumerating all the times it does exist.
SELECT source_sys_cd,
Count(CASE is_delete WHEN
WHEN is_delete = 0 THEN 1
END) [DEL IS 0],
Count(CASE
WHEN trans_amt = 0 THEN 1
END) [STG $0 TXN CNT],
Count(CASE
WHEN NOT EXISTS (SELECT 1
FROM staging_cust_acct
WHERE acct_key = hrc_acct_num) THEN 1
END)
FROM staging_transactions (nolock)
GROUP BY source_sys_cd
ORDER BY source_sys_cd

How to compare two table values using PLSQL

I have to compare two tables values;
TABLE_A TABLE_B
ID TYPE ID TYPE
12345 12345 3
67891 12345 7
36524 67891 3
67891 2
67891 5
36524 3
Logic: I have to compare table_A id with Table_B id
if found 3&7
good
else found 3 only
avg
else if found 7 only
bad
These good, bad and avg should go back to table A type values.
could any one help me how to write this code in PLSQL.
Assuming that you are considering type 3 and 7 only for your calculations, you can use following merge statement, no need of PL-SQL
merge into table_a a
using (select id, case (listagg(type, ',') within group (order by type))
when '3,7' then 'Good'
when '3' then 'Avg'
when '7' then 'Bad'
else null
end new_type
from table_b
where type in (3,7)
group by id) b
on (a.id = b.id)
when matched then
update set type = new_type;
For Oracle versions prior to 11 g release 2, use following:
merge into table_a a
using (select id, case (trim(both ',' from min(decode(type, 3, 3, null))||','||min(decode(type, 7, 7, null))))
when '3,7' then 'Good'
when '3' then 'Avg'
when '7' then 'Bad'
else null
end new_type
from table_b
where type in (3,7)
group by id) b
on (a.id = b.id)
when matched then
update set type = new_type;
It has been assumed that there are unique combination of id an type in table_b.
I am interpreting what you mean as saying that you want to output 'good' when TableB contains both 3 and 7, 'avg' when it contains only 3, and so on. Here is a way to get this result:
select a.id,
(case when sum(case when b.type = 3 then 1 else 0 end) > 1 and
sum(case when b.type = 7 then 1 else 0 end) > 0
then 'good'
when sum(case when b.type = 3 then 1 else 0 end) > 1
then 'avg'
when sum(case when b.type = 7 then 1 else 0 end)
then 'bad'
end) as logic
from tableA a left outer join
tableB b
on a.id = b.id
group by a.id;

SQL Server Month Totals

SQL Server newbie
The following query returns SRA by Student and month only if there is a record for a student in Discipline table. I need a query to return all students and month totals even if there is no record for student in Discipline table. Any direction appreciated
SELECT TOP 100 PERCENT MONTH(dbo.Discipline.DisciplineDate) AS [Month], dbo.Discipline.StuId, dbo.Stu.Lastname + ',' + dbo.Stu.FirstName AS Student,
SUM(CASE WHEN Discipline.SRA = 1 THEN 1 END) AS [Acad Suspension], SUM(CASE WHEN Discipline.SRA = 2 THEN 1 END) AS Conduct,
SUM(CASE WHEN Discipline.SRA = 3 THEN 1 END) AS Disrespect, SUM(CASE WHEN Discipline.SRA = 4 THEN 1 END) AS [S.R.A],
SUM(CASE WHEN Discipline.SRA = 5 THEN 1 END) AS Suspension, SUM(CASE WHEN Discipline.SRA = 6 THEN 1 END) AS Tone
FROM dbo.Discipline INNER JOIN
dbo.Stu ON dbo.Discipline.StuId = dbo.Stu.StuId
GROUP BY dbo.Discipline.StuId, dbo.Stu.Lastname, dbo.Stu.FirstName, MONTH(dbo.Discipline.DisciplineDate)
ORDER BY Student
You need to change the INNER JOIN onto dbo.Stu to a LEFT JOIN:
SELECT MONTH(d.disciplinedate) AS [Month],
d.StuId,
s.Lastname + ',' + s.FirstName AS Student,
SUM(CASE WHEN d.SRA = 1 THEN 1 END) AS [Acad Suspension],
SUM(CASE WHEN d.SRA = 2 THEN 1 END) AS Conduct,
SUM(CASE WHEN d.SRA = 3 THEN 1 END) AS Disrespect,
SUM(CASE WHEN d.SRA = 4 THEN 1 END) AS [S.R.A],
SUM(CASE WHEN d.SRA = 5 THEN 1 END) AS Suspension,
SUM(CASE WHEN d.SRA = 6 THEN 1 END) AS Tone
FROM dbo.Discipline d
LEFT JOIN dbo.Stu s ON s.stuid = d.stuid
GROUP BY d.StuId, s.Lastname, s.FirstName, MONTH(d.DisciplineDate)
ORDER BY Student
The LEFT JOIN means that whatever table you're LEFT JOINing to might not have records to support the JOIN, but you'll still get records from the base table (dbo.Discipline).
I used table aliases - d and s. Less to type when you need to specify references.
generate a series of months, join discipline to that.