postgresql total column sum - sql

SELECT
SELECT pp.id, TO_CHAR(pp.created_dt::date, 'dd.mm.yyyy') AS "Date", CAST(pp.created_dt AS time(0)) AS "Time",
au.username AS "User", ss.name AS "Service", pp.amount, REPLACE(pp.status, 'SUCCESS', ' ') AS "Status",
pp.account AS "Props", pp.external_id AS "External", COALESCE(pp.external_status, null, 'indefined') AS "External status"
FROM payment AS pp
INNER JOIN auth_user AS au ON au.id = pp.creator_id
INNER JOIN services_service AS ss ON ss.id = pp.service_id
WHERE pp.created_dt::date = (CURRENT_DATE - INTERVAL '1' day)::date
AND ss.name = 'Some Name' AND pp.status = 'SUCCESS'
id | Date | Time | Service |amount | Status |
------+-----------+-----------+------------+-------+--------+---
9 | 2021.11.1 | 12:20:01 | some serv | 100 | stat |
10 | 2021.12.1 | 12:20:01 | some serv | 89 | stat |
------+-----------+-----------+------------+-------+--------+-----
Total | | | | 189 | |
I have a SELECT like this. I need to get something like the one shown above. That is, I need to get the total of one column. I've tried a lot of things already, but nothing works out for me.

If I understand correctly you want a result where extra row with aggregated value is appended after result of original query. You can achieve it multiple ways:
1. (recommended) the simplest way is probably to union your original query with helper query:
with t(id,other_column1,other_column2,amount) as (values
(9,'some serv','stat',100),
(10,'some serv','stat',89)
)
select t.id::text, t.other_column1, t.other_column2, t.amount from t
union all
select 'Total', null, null, sum(amount) from t
2. you can also use group by rollup clause whose purpose is exactly this. Your case makes it harder since your query contains many columns uninvolved in aggregation. Hence it is better to compute aggregation aside and join unimportant data later:
with t(id,other_column1,other_column2,amount) as (values
(9,'some serv','stat',100),
(10,'some serv','stat',89)
)
select case when t.id is null then 'Total' else t.id::text end as id
, t.other_column1
, t.other_column2
, case when t.id is null then ext.sum else t.amount end as amount
from (
select t.id, sum(amount) as sum
from t
group by rollup(t.id)
) ext
left join t on ext.id = t.id
order by ext.id
3. For completeness I just show you what should be done to avoid join. In that case group by clause would have to use all columns except amount (to preserve original rows) plus the aggregation (to get the sum row) hence the grouping sets clause with 2 sets is handy. (The rollup clause is special case of grouping sets after all.) The obvious drawback is repeating case grouping... expression for each column uninvolved in aggregation.
with t(id,other_column1,other_column2,amount) as (values
(9,'some serv','stat',100),
(10,'some serv2','stat',89)
)
select case grouping(t.id) when 0 then t.id::text else 'Total' end as id
, case grouping(t.id) when 0 then t.other_column1 end as other_column1
, case grouping(t.id) when 0 then t.other_column2 end as other_column2
, sum(t.amount) as amount
from t
group by grouping sets((t.id, t.other_column1, t.other_column2), ())
order by t.id
See example (db fiddle):
(To be frank, I can hardly imagine any purpose other than plain reporting where a column mixes id of number type with label Total of text type.)

Related

Return count of total group membership when providers are part of a group

TABLE A: Pre-joined table - Holds a list of providers who belong to a group and the group the provider belongs to. Columns are something like this:
ProviderID (PK, FK) | ProviderName | GroupID | GroupName
1234 | LocalDoctor | 987 | LocalDoctorsUnited
5678 | Physican82 | 987 | LocalDoctorsUnited
9012 | Dentist13 | 153 | DentistryToday
0506 | EyeSpecial | 759 | OphtaSpecialist
TABLE B: Another pre-joined table, holds a list of providers and their demographic information. Columns as such:
ProviderID (PK,FK) | ProviderName | G_or_I | OtherColumnsThatArentInUse
1234 | LocalDoctor | G | Etc.
5678 | Physican82 | G | Etc.
9012 | Dentist13 | I | Etc.
0506 | EyeSpecial | I | Etc.
The expected result is something like this:
ProviderID | ProviderName | ProviderStatus | GroupCount
1234 | LocalDoctor | Group | 2
5678 | Physican82 | Group | 2
9012 | Dentist13 | Individual | N/A
0506 | EyeSpecial | Individual | N/A
The goal is to determine whether or not a provider belongs to a group or operates individually, by the G_or_I column. If the provider belongs to a group, I need to include an additional column that provides the count of total providers in that group.
The Group/Individual portion is relatively easy - I've done something like this:
SELECT DISTINCT
A.ProviderID,
A.ProviderName,
CASE
WHEN B.G_or_I = 'G'
THEN 'Group'
WHEN B.G_or_I = 'I'
THEN 'Individual' END AS ProviderStatus
FROM
TableA A
LEFT OUTER JOIN TableB B
ON A.ProviderID = B.ProviderID;
So far so good, this returns the expected results based on the G_or_I flag.
However, I can't seem to wrap my head around how to complete the COUNT portion. I feel like I may be overthinking it, and stuck in a loop of errors. Some things I've tried:
Add a second CASE STATEMENT:
CASE
WHEN B.G_or_I = 'G'
THEN (
SELECT CountedGroups
FROM (
SELECT ProviderID, count(GroupID) AS CountedGroups
FROM TableA
WHERE A.ProviderID = B.ProviderID
GROUP BY ProviderID --originally had this as ORDER BY, but that was a mis-type on my part
)
)
ELSE 'N/A' END
This returns an error stating that a single row sub-query is returning more than one row. If I limit the number of rows returned to 1, the CountedGroups column returns 1 for every row. This makes me think that its not performing the count function as I expect it to.
I've also tried including a direct count of TableA as a factored sub-query:
WITH CountedGroups AS
( SELECT Provider ID, count(GroupID) As GroupSum
FROM TableA
GROUP BY ProviderID --originally had this as ORDER BY, but that was a mis-type on my part
) --This as a standalone query works just fine
SELECT DISTINCT
A.ProviderID,
A.ProviderName,
CASE
WHEN B.G_or_I = 'G'
THEN 'Group'
WHEN B.G_or_I = 'I'
THEN 'Individual' END AS ProviderStatus,
CASE
WHEN B.G_or_I = 'G'
THEN GroupSum
ELSE 'N/A' END
FROM
CountedGroups CG
JOIN TableA A
ON CG.ProviderID = A.ProviderID
LEFT OUTER JOIN TableB
ON A.ProviderID = B.ProviderID
This returns either null or completely incorrect column values
Other attempts have been a number of variations of this, with a mix of bad results or Oracle errors. As I mentioned above, I'm probably way overthinking it and the solution could be rather simple. Apologies if the information is confusing or I've not provided enough detail. The real tables have a lot of private medical information, and I tried to translate the essence of the issue as best I could.
Thank you.
You can use the CASE..WHEN and analytical function COUNT as follows:
SELECT
A.PROVIDERID,
A.PROVIDERNAME,
CASE
WHEN B.G_OR_I = 'G' THEN 'Group'
ELSE 'Individual'
END AS PROVIDERSTATUS,
CASE
WHEN B.G_OR_I = 'G' THEN TO_CHAR(COUNT(1) OVER(
PARTITION BY A.GROUPID
))
ELSE 'N/A'
END AS GROUPCOUNT
FROM
TABLE_A A
JOIN TABLE_B B ON A.PROVIDERID = B.PROVIDERID;
TO_CHAR is needed on COUNT as output expression must be of the same data type in CASE..WHEN
Your problem seems to be that you are missing a column. You need to add group name, otherwise you won't be able to differentiate rows for the same practitioner who works under multiple business entities (groups). This is probably why you have a DISTINCT on your query. Things looked like duplicates which weren't. Once you've done that, just use an analytic function to figure out the rest:
SELECT ta.providerid,
ta.providername,
DECODE(tb.g_or_i, 'G', 'Group', 'I', 'Individual') AS ProviderStatus,
ta.group_name,
CASE
WHEN tb.g_or_i = 'G' THEN COUNT(DISTINCT ta.provider_id) OVER (PARTITION BY ta.group_id)
ELSE 'N/A'
END AS GROUP_COUNT
FROM table_a ta
INNER JOIN table_b tb ON ta.providerid = tb.providerid
Is it possible that your LEFT JOIN was going the wrong direction? It makes more sense that your base demographic table would have all practitioners in it and then the Group table might be missing some records. For instance if the solo prac was operating under their own SSN and Type I NPI without applying for a separate Type II NPI or TIN.

Teradata/SQL sum together columns

I have a query that returns two amounts. I would like to sum them together, while leaving one as it is returned by the query.
For example:
DESC | sum(AMOUNT)
A | -61149025.940000
B | -9696.910000
B needs to be the sum of B and A and replace the current value. A stays as is on the result of the query.
My SQL is similar to this:
Select SQ.DESC, SUM(SQ.AMOUNT)
FROM (subquery) SQ
GROUP by SQ.DESC
My return results would be
DESC | sum(AMOUNT)
A | -61149025.940000
B | -61158722.850000
I have not been able to logically make sense of this
If you want "B" to be the total of all the values, you can use window functions:
Select SQ.DESC,
(CASE WHEN SQ.DESC = 'A' THEN SUM(SQ.AMOUNT)
WHEN SQ.DESC = 'B' THEN SUM(SUM(SQ.AMOUNT)) OVER ()
END) as SUM_AMOUNT
FROM (subquery) SQ
GROUP by SQ.DESC
Note: Even if Teradata allows DESC as a column name, it is a poor choice because DESC is a SQL keyword (think ORDER BY).

Split date column on the basis of years in the data

I am working with the AdventureWorks2014 database and am using the following query.
select
SUM(Purchasing.PurchaseOrderDetail.OrderQty) as 'Total Quantity',
SUM(Purchasing.PurchaseOrderDetail.LineTotal) as 'Total Amount',
Purchasing.PurchaseOrderHeader.VendorID
from Purchasing.PurchaseOrderDetail
inner join Purchasing.PurchaseOrderHeader
on Purchasing.PurchaseOrderDetail.PurchaseOrderID = Purchasing.PurchaseOrderHeader.PurchaseOrderID
group by Purchasing.PurchaseOrderHeader.VendorID, DATEPART(year,Purchasing.PurchaseOrderHeader.OrderDate)
order by Purchasing.PurchaseOrderHeader.VendorID
This gives me the following output.
|------------------------------------|
|Total Quantity|Total Amount|VendorID|
|15 |694.1655 | 1492|
|288 |12370.239 | 1492|
|45 |1931.7375 | 1492|
|180 |7682.6295 | 1492|
|9350 |150404.1 | 1494|
|1650 |26541.9 | 1494|
|550 |8847.3 | 1494|
|16500 |265419 | 1494|
|------------------------------------|
From what i understand, this is each year's data, i.e,the values 2011,2012,2013 and 2014, for each vendor. Which is why each vendor is repeated 4 times.
I need to have each of these years as a separate column in the output as follows.
|--------------------------------------------------------------------------------|
|Total Quantity|Total Amount|VendorID|2011Amount|2012Amount|2013Amount|2014Amount|
|--------------------------------------------------------------------------------|
Any thoughts?
Pivot Method, make sure you first prepare the query how you want prior to pivoting.
;WITH cte AS (
SELECT
DATEPART(year,poh.OrderDate) as [Year]
,SUM(pod.OrderQty) OVER (PARTITION BY DATEPART(year,poh.OrderDate)) as TotalQuantity
,SUM(pod.LineTotal) OVER (PARTITION BY DATEPART(year,poh.OrderDate)) as TotalAmount
,pod.LineTotal as Amount
FROM
Purchasing.PurchaseOrderDetail pod
INNER JOIN Purchasing.PurchaseOrderHeader poh
ON pod.PurchaseOrderId = poh.PurchaseOrderId
)
SELECT *
FROm
cte
PIVOT (
SUM(Amount)
FOR [Year] IN ([2011],[2012],[2013],[2014])
) p
Conditional Aggregation Method
SELECT
SUM(pod.OrderQty) as TotalQuantity
,SUM(pod.LineTotal) as TotalAmount
,SUM(CASE WHEN DATEPART(year,poh.OrderDate) = 2011 THEN pod.LineTotal ELSE 0 END) as [2011Amount]
,SUM(CASE WHEN DATEPART(year,poh.OrderDate) = 2012 THEN pod.LineTotal ELSE 0 END) as [2012Amount]
,SUM(CASE WHEN DATEPART(year,poh.OrderDate) = 2013 THEN pod.LineTotal ELSE 0 END) as [2013Amount]
,SUM(CASE WHEN DATEPART(year,poh.OrderDate) = 2014 THEN pod.LineTotal ELSE 0 END) as [2014Amount]
FROM
Purchasing.PurchaseOrderDetail pod
INNER JOIN Purchasing.PurchaseOrderHeader poh
ON pod.PurchaseOrderId = poh.PurchaseOrderId
In this case I think I would go with the conditional aggregation method..... Please note I used Table Aliases to refer to the table rather than continuing to type the long names it is a good habit to get into.
This exact code is of course untested because you did not include test data and desired result but the techniques are the most standard way of doing this. Note when more than 1 column is involved in aggregation it is typically easiest to do conditional aggregation.

Do subqueries ignore an outer WHERE condition if it has it's own WHERE condition?

I want a SQL query to follow the conditions of a WHERE statement, except those that are subqueries. In the example below, I'd like to get all entries from the past week, but I want the subqueries to ignore that outer WHERE clause, but I'm not sure of how it will behave.
For example:
SELECT
ProductID,
SUM(CASE WHEN from_source = 'button' THEN 1 ELSE 0 END) 'Used Button',
(SELECT COUNT(*) FROM tracking WHERE from_source = 'button') 'Used Button to date'
FROM tracking
WHERE date_entered > (GETDATE() - 7)
GROUP BY ProductID
Expected output, where the second column is calculating based on outer WHERE, and subquery is not looking at the outer WHERE:
ProductID | Used Button | Used Button to date
123 | 5 | 102
456 | 8 | 175
You could write the query just using conditional aggregation:
select sum(case when date_entered > (GETDATE() - 7) then 1 else 0
end) as UsedButton,
count(*) as UsedButtonToDate
from tracking
where from_source = 'button';
This seems like a simpler approach.
By the way, you shouldn't use single quotes for column aliases. Either name the columns with something that doesn't have to be escaped (as above) or use square braces.

SQL query sum at bottom row

I am trying to get the sum of a column at the bottom row.
I have tried a few examples by using SUM() and COUNT(), but they have all failed with syntax errors.
Here is my current code without any sum or anything:
:XML ON
USE MYTABLE
SELECT sbc.PolicyC.PolicyName as namn,COUNT(*) as cnt
FROM sbc.AgentC, sbc.PolicyC
WHERE sbc.AgentC.PolicyGuid = sbc.PolicyC.PolicyGuid
GROUP BY sbc.AgentC.PolicyGuid, sbc.PolicyC.PolicyName ORDER BY namn ASC
FOR XML PATH ('celler'), ROOT('root')
GO
The XML output is reformatted to become a regular HTML table.
EDIT:
Here is the latest code, but it generates a "sum" (same number as the row above) on every other row:
:XML ON
USE MYTABLE
SELECT sbc.PolicyC.PolicyName as namn,COUNT(*) as cnt
FROM sbc.AgentC, sbc.PolicyC
WHERE sbc.AgentC.PolicyGuid = sbc.PolicyC.PolicyGuid
GROUP BY sbc.AgentC.PolicyGuid, sbc.PolicyC.PolicyName with rollup
FOR XML PATH ('celler'), ROOT('root')
GO
The XML output looks like this:
<root>
<celler>
<namn>example name one</namn>
<cnt>23</cnt>
</celler>
<celler>
<cnt>23</cnt>
</celler>
<celler>
<namn>example name two</namn>
<cnt>1</cnt>
</celler>
<celler>
<cnt>1</cnt>
</celler>
</root>
Try
SELECT sbc.PolicyC.PolicyName as namn,COUNT(*) as cnt
FROM sbc.AgentC, sbc.PolicyC
WHERE sbc.AgentC.PolicyGuid = sbc.PolicyC.PolicyGuid
GROUP BY sbc.AgentC.PolicyGuid, sbc.PolicyC.PolicyName
UNION
SELECT 'TOTAL' as nawn,COUNT(*) as cnt
FROM
FROM sbc.AgentC, sbc.PolicyC
WHERE sbc.AgentC.PolicyGuid = sbc.PolicyC.PolicyGuid
ORDER BY namn ASC
This will compute the total in a separate query. However, you might need to either add some non-printing, high-ASCII character to force the total to the bottom, or add some numeric ordering key... mySQL may also have an operator (similar to WITH ROLLUP in Microsoft SQL) which would be more efficient than the above code... So while this would work, there are probably more efficient options available to you...
MySQL supports a rollup extension to group by.
select * from parts;
+-----------+--------+
| part_name | amount |
+-----------+--------+
| upper | 100 |
| lower | 100 |
| left | 50 |
| right | 50 |
+-----------+--------+
select part_name
,sum(amount)
from parts
group
by part_name with rollup;
+-----------+-------------+
| part_name | sum(amount) |
+-----------+-------------+
| left | 50 |
| lower | 100 |
| right | 50 |
| upper | 100 |
| NULL | 300 |
+-----------+-------------+
Updated to answer comments:
The following items list some
behaviors specific to the MySQL
implementation of ROLLUP:
When you use ROLLUP, you cannot also
use an ORDER BY clause to sort the
results. In other words, ROLLUP and
ORDER BY are mutually exclusive.
However, you still have some control
over sort order. GROUP BY in MySQL
sorts results, and you can use
explicit ASC and DESC keywords with
columns named in the GROUP BY list to
specify sort order for individual
columns. (The higher-level summary
rows added by ROLLUP still appear
after the rows from which they are
calculated, regardless of the sort
order.)
My code became somthing like:
SELECT * FROM (...old code here... UNION ...'Total:' ... COUNT() ...)* z
ORDER BY CASE WHEN z.Namn = 'Total:' THEN '2' ELSE '1' END , z.Antal DESC
I have one column named Namn and one named Antal.
If there is the value 'Total:' in the column Namn it will order that as a '2' and if not as a '1', that makes the 'Total:' move to the botton when I have decendent ordering on the Antal column.
The magic hapens because the 'Total:' is UNION with the table, and then the CASE statement at the end puts it at the end.
My complete code that works for me that is a loot moore messy, it unions 2 tables and stuff as well:
SELECT * FROM (
SELECT acrclient.Client_Name AS 'Namn', COUNT(x.client) AS 'Antal'
FROM
(SELECT 'B' tab,t.client
FROM asutrans t
where t.voucher_type!='IP' AND t.last_update >= {ts '2019-01-01 00:00:00'}
UNION ALL SELECT
'C' tab,t.client
FROM asuhistr t
WHERE t.voucher_type!='IP' AND t.last_update >= {ts '2019-01-01 00:00:00'} ) x
LEFT JOIN acrclient ON x.client = acrclient.client
GROUP BY x.client, acrclient.Client_Name
UNION ALL
SELECT 'Total:', COUNT(client) FROM (SELECT 'B' tab,t.client
FROM asutrans t
where t.voucher_type!='IP' AND t.last_update >= {ts '2019-01-01 00:00:00'}
UNION ALL SELECT
'C' tab,t.client
FROM asuhistr t
WHERE t.voucher_type!='IP' AND t.last_update >= {ts '2019-01-01 00:00:00'} ) y
) z
ORDER BY CASE WHEN z.Namn = 'Total:' THEN '2' ELSE '1' END , z.Antal DESC