I am trying to write a SQL query for calculating sum without success.
Let's say that we have:
table A with columns id and type
table B with columns id, a_id (relation to table A) and amount
I succeed to calculate number of records by type like in the following example:
SELECT DISTINCT
type,
COUNT(A.id) OVER (PARTITION BY type) AS numOfRecords
FROM A;
How to calculate sum of amounts also per type (to sum up all amounts from table B for all distinct types in A)?
Your query would normally be written as:
select type, count(*) as num_records
from A
group by type;
Then, you can incorporate b as:
select a.type, count(*) as num_records, sum(b.amount)
from A left join
(select a_id, sum(amount) as amount
from b
group by a_id
) b
on b.a_id = a.id
group by a.type;
You can also join and aggregate without a subquery, but this will throw off the count. To fix that, you can use count(distinct):
select a.type, count(distinct a.id) as num_records, sum(b.amount)
from A left join
from b
on b.a_id = a.id
group by a.type;
Related
I'm trying to select * from two tables (a and b) using a join (column a.id and b.id), given that the count of a column (b.owner) in b is lower than 3, i.e. the occurence of a person's name can be max 2.
I've tried:
SELECT a.*, COUNT(b.owner) AS b_count
FROM a LEFT JOIN b on a.id = b.id
GROUP BY b.owner HAVING COUNT(b_count) <3
As im pretty new to SQL, im pretty stuck here. How can i resolve this issue? The result should be all columns for owners who do not appear more than twice in the data.
The query you are trying to run is not working due to the columns missing in the GROUP BY clause.
As you are outputting all columns from table a (with SELECT a.*), you need to include all those columns in the GROUP BY statement, so that the database understand the group of fields to group by and perform the aggregation required (in your case COUNT(b.owner)).
Example
Considering that your table a has 3 columns below:
CREATE TABLE persons (
id INTEGER,
name VARCHAR(50),
birthday DATE,
PRIMARY KEY (id)
);
.. and your table b the following and referencing the first table as below:
CREATE TABLE sales (
id INTEGER,
person_id INTEGER,
sale_value DECIMAL,
PRIMARY KEY (id),
FOREIGN KEY (person_id) REFERENCES persons(id)
);
.. you should query it aggregating the COUNT() by those 3 columns:
SELECT a.id, a.name, a.birthday, COUNT(b.person_id) AS b_count
FROM persons a
LEFT JOIN sales b ON a.id = b.person_id
GROUP BY a.id, a.name, a.birthday
HAVING COUNT(b.person_id) < 3
Alternative
In case the total of records on the 2nd table is not important to you, you could use a different "strategy" here to avoid performing the JOIN between the tables (useful when joining two huge tables) and rewriting all the columns from a on the SELECT+GROUP BY.
By identifying the records that has less than the 3 occurrences firstly:
SELECT b.person_id
FROM sales b
GROUP BY b.person_id
HAVING COUNT(b.id) < 3;
.. and using it in the WHERE clause to retrieve all the columns from the 1st table only for the ids that resulted from the previous query:
SELECT a.*
FROM persons a
WHERE a.id IN (....other query here....);
.. the execution happens in a more chronological and, perhaps, easier way to visualize while getting more familiar with SQL:
SELECT a.*
FROM persons a
WHERE a.id IN (SELECT b.person_id
FROM sales b
GROUP BY b.person_id
HAVING COUNT(b.id) < 3);
DB Fiddle here
In Standard SQL, you can use:
SELECT a.*, COUNT(b.owner) AS b_count
FROM a LEFT JOIN
b
ON a.id = b.id
GROUP BY a.id
HAVING COUNT(b.owner) < 3;
This may not work in all databases (and it assumes that a.id is unique/primary key). An alternative would be to use a correlated subquery:
SELECT a.*
FROM (SELECT a.*,
(SELECT COUNT(*)
FROM b
WHERE a.id = b.id
) as b_count
FROM a
) a
WHERE b_count < 3;
I'm trying to multiply columns from a table and using subquery i have tried to sum the multiplied columns
select B.ID , sum(B) from (
select distinct A.Id,
A.Savings * A.Spent as B,
from A group by A.id ) B
group by B.ID
How can i convert this sql query to linq.
Your query is wrong at this point:
A.Savings * A.Spent as B,
from A group by A.id
a coma before the from clause will error the query out. Try the below:
SELECT
B.ID,
SUM(Total) AS TOTAL
FROM
(SELECT
A.Savings * A.Spent as Total
From A
Group by A.ID)
Group by B.ID
Your query is right, you may just need to check in future.
So I have :
CREATE TABLE A (id INT,type int,amount int);
INSERT INTO A (id,type,amount) VALUES (1,0,25);
INSERT INTO A (id,type,amount) VALUES (2,0,25);
INSERT INTO A (id,type,amount) VALUES (3,1,10);
CREATE TABLE B (id INT,A_ID int,txt text);
INSERT INTO B (id,A_id,txt) VALUES (1,1,'abc');
INSERT INTO B (id,A_id,txt) VALUES (2,1,'def');
INSERT INTO B (id,A_id,txt) VALUES (3,2,'xxx');
I run this query:
SELECT min(A.id), SUM(A.amount), COUNT(B.id) FROM A
LEFT JOIN B ON A.id = B.A_id
GROUP BY A.type
I get :
min(A.id) SUM(A.amount) COUNT(B.id)
1 75 3
3 10 0
But I'm instead expecting to get :
min(A.id) SUM(A.amount) COUNT(B.id)
1 50 3
3 10 0
Can someone help? What is the best way to achieve this exact result ?
I want group BY type and get SUM of grouped A.amount and get count() of all B corresponding to its foreign key.
here is the repro : https://www.db-fiddle.com/f/esu13uGLcgFDpX7aEQRMJR/0 please RUN sql code.
EDIT to add more detail : I know the result is correct if I remove group by we can see
1, 50, 2
2, 25, 1
But I expect the above result, what is the best way to achieve it ? I want make SUM of a TYPE then count all B related to this groupped A
Just a shorter version of the solution. It counts B_IDs first in the inner query, so I need to Sum the counts in the outer query.
SELECT min(A.id), SUM(A.amount), Sum(Bid) FROM A
LEFT JOIN (select count(id) as Bid, A_id from B group by A_id) as Bcount
ON A.id = Bcount.A_id
GROUP BY A.type
This can happen when you SUM from an 1-N relation.
The matching records can multiply the result.
For example, when 1 records in A are joined with 2 in B it returns 2 times the amount of A before the GROUP BY. So a SUM then doubles A.amount.
A way to get around that is using sub-queries that join one-on-one.
And a COUNT DISTINCT can be used to count unique id's.
So this just a way to get the SUM of A correct.
SELECT
q1.type,
q1.min_id,
q2.amount,
COALESCE(q1.totalB, 0) as totalB
FROM
(
SELECT
A.type,
MIN(A.id) AS min_id,
COUNT(DISTINCT B.id) AS totalB
FROM A
LEFT JOIN B ON B.A_id = A.id
GROUP BY A.type
) AS q1
JOIN
(
SELECT
type,
SUM(amount) AS amount
FROM A
GROUP BY type
) AS q2 ON q2.type = q1.type
View on DB Fiddle
The SQL is tested for MySql. But it's an ANSI standard SQL that would run on almost any RDBMS, including MS Sql Server.
one way of doing this would be to use ROW_NUMBER():
WITH CTE AS (SELECT A.id AS Aid,
A.[type],
A.amount,
B.id AS bid,
txt,
ROW_NUMBER() OVER (PARTITION BY A.id ORDER BY B.id) AS RN
FROM A
LEFT JOIN B ON A.id = B.A_ID)
SELECT MIN(Aid) AS Min_A_ID,
SUM(CASE RN WHEN 1 THEN amount END) AS Amount,
COUNT(bid) AS BCount
FROM CTE
GROUP BY [type];
I also recommend getting rid of that text datatype and using varchar(MAX).
Let's assume I have the following tables in an Oracle database:
TBL_A with the columns ID, C_1, C_2, C_3, ..., C_20 (primary key: ID)
TBL_B with the columns ID, A_ID, C_1, C_2, C_3, ..., C_20 (foreign key A_ID references TBL_A.ID)
TBL_C, TBL_D etc. with the same generic layout as TBL_B
Now, I am trying to build a report based on grouping rows from TBL_A and at the same time e.g. aggregating different data (sums, counts, min/max/avg values, etc.) from the additional tables (TBL_B, TBL_C, etc.), in which some additional criteria is met.
My problem probably boils down to how (if it's possible at all) to connect data from TBL_x in a subquery, if the primary query is based on a select from TBL_A using a GROUP BY clause, e.g. like this:
select a.c_1,
count(a.id) as cnt, -- number of matches in TBL_A for this group
(select count(*) from tbl_b b where b.a_id = a.id and b.c_1 = 2) as b_cnt,
(select sum(c_5) from tbl_c c where c.a_id = a.id and c.c_3 = 3) as c_sum
from tbl_a a
where ...
group by a.c_1;
Even if Oracle won't execute this code (ORA-00979, a.id is not a GROUP BY expression), I hope the purpose of the query should be obvious. In this case, I need a four column result set with:
All distinct values of TBL_A.C_1.
The number of rows in TBL_A within this group.
The number of rows in TBL_B where C_1 = 2 and A_ID refers to any of the rows in TBL_A contained in this group.
The sum of C_5 of the rows in TBL_B where C_3 = 3 and A_ID refers to any of the rows in TBL_A contained in this group.
I know I could rewrite the subqueries, so that the group-by columns are repeated in the where clause, e.g. like this for just one of the columns:
select a.c_1,
(select count(*) from tbl_b b, tbl_a a2
where a2.c_1 = a.c_1 and b.a_id = a2.id and b.c_1 = 2) as b_cnt_2
from tbl_a a
where ...
group by a.c_1;
But in this case, I would both have to repeat all group by columns and the outer where clause in all subqueries and since in reality the where clause is rather long and I have both quite a few columns in the group by clause, as well as many subqueries referring to different tables with different relations to TBL_A, the SQL statement will probably end up as a complete mess.
Is it really not possible in Oracle to use values from individual rows within a group in subqueries like I tried in the first example (both b.a_id = a.id as well as b.a_id in a.id fails)? I also considered doing some tricks with listagg, but Oracle seem not to accept any aggregating functions in the subquery clause (ORA-00934 group function is not allowed here). I would understand the limitation in the outer where clause, but not why this is not allowed in the subquery where clause.
I have tried to implement the query by joining the additional tables (TBL_B, TBL_C, etc) with outer joins instead of writing subqueries, but this will expand the result (creating several combinations of all involved tables) before grouping, so that the same row is considered more than once by the aggregation functions. E.g. having two rows in TBL_B referring to one row in TBL_A, count(a.id) would count the same row in TBL_A twice.
Anyone with an idea how to proceed?
Probably the simplest way is to use a subquery or CTE:
with a as (
select a.c_1, a.id,
count(a.id) as cnt, -- number of matches in TBL_A for this group
(select count(*) from tbl_b b where b.a_id = a.id and b.c_1 = 2) as b_cnt,
(select sum(c_5) from tbl_c c where c.a_id = a.id and c.c_3 = 3) as c_sum
from tbl_a a
where ...
group by a.id
)
select a.c_1,
sum(cnt) as cnt,
sum(b_cnt) as b_cnt,
sum(c_sum) as c_sum
from a
group by a.c_1;
This will work fine for most aggregation functions. If you have an avg(), then do the sum and count separately, and divide the totals for the average. If you have a count(distinct) then this will not work. Your question has neither of these.
If I understand what you're trying to do correctly you can get the aggregate for each a.id in a derived table, join those aggregates to table a and then aggregate again using sum
select
a.c_1
count(a.id),
coalesce(sum(b_count),0) b_cnt,
coalesce(sum(c_sum),0) c_sum
from tbl_a a
left join (
select a_id, count(*) b_count from tbl_b
where c_1 = 2
group by a_id
) b on b.a_id = a.id
left join (
select a_id, sum(c_5) c_sum from tbl_c
where c_3 = 3
group by a_id
) c on c.a_id = a.id
where ...
group by a.c_1
Usually we will select the field(s) in the SQL query. e.g.
SELECT A.id FROM Member A
But what if I want to align a column which elements correspond to the other selected field?
For example I want to select the member ID from a member table, and the COUNT that count how many times the member appear in the tuple of other table
So how do I make the COUNT column that align together with the select result?
If I understood you correctly, this is what you want:
SELECT A.id, count(B.MemberID)
FROM Member A
LEFT JOIN TableB B on A.id = B.MemberID
group by A.id
The LEFT JOIN will include records in A that do not have any corresponding records in B. Also, COUNT only counts non-null values, so you need to use it with B.MemberID. This way the count for records in A that do not have any corresponding records in B will be 0, since B.MemberID will be NULL.
I agree with #Adrian's solution, but if there were many columns in the original SELECT list, they all would have to be listed in GROUP BY. I mean something like this:
SELECT
A.id,
A.name,
A.whatever,
...
COUNT(B.member_id)
FROM Member A
LEFT JOIN Member_Something B ON A.id = B.member_id
GROUP BY
A.id,
A.name,
A.whatever,
...
It is not always convenient, especially when the columns are actually expressions. You could take another approach instead:
SELECT
A.id,
A.name,
A.whatever,
...
COALESCE(B.member_count, 0)
FROM Member A
LEFT JOIN (
SELECT member_id, COUNT(*) AS member_count
FROM Member_Something
GROUP BY member_id
) B ON A.id = B.member_id
select member_id, count(*)
from table
group by member_id;