SQL SUM Group by - based on 'group' from another table - sql

I hope I am explaining this correctly.
I have 2 tables, with
first table (table1)
+------------+------+-------+-------+
| Date | Item | Block | Total |
+------------+------+-------+-------+
| 2017-01 | a1 | B1 | 10.0 |
| 2017-01 | a2 | B1 | 20.0 |
| 2017-01 | a3 | B2 | 30.0 |
| 2017-02 | a1 | B1 | 40.0 |
| 2017-02 | a2 | B1 | 50.0 |
| 2017-02 | a3 | B2 | 60.0 |
+------------+------+-------+-------+
second table (table2)
+------------+------+
| Item Group | Item |
+------------+------+
| IG1 | a1 |
| IG1 | a2 |
| IG2 | a2 |
| IG2 | a3 |
+------------+------+
*Note that, one item group has multiple items.
The items may appear several time in different item groups.
Now, I need to sum the total (table1), based on Item Group (table2), Date and Block, in the end, final table:
+---------+------------+-------+-------+
| Date | Item Group | Block | Total |
+---------+------------+-------+-------+
| 2017-01 | IG1 | B1 | 30.0 |
| 2017-01 | IG2 | B1 | 20.0 |
| 2017-01 | IG1 | B2 | 0.0 |
| 2017-01 | IG2 | B2 | 30.0 |
+---------+------------+-------+-------+
How to achieve this with SQL query?
EDIT:
OK. It seems that this is an easy one. Shame on me. I didn't know the join and Group By can be applied that way. SQL is really awesome. That saves tons of coding.

A join and a simple group by should work for you in this case:
select t1.Date, t2.ItemGroup, t1.Block, sum(t1.Total) Total
from table1 t1 join table2 t2 on t1.Item = t2.Item
group by t1.Date, t2.ItemGroup, t1.Block

Simple group by after a join
SELECT f.date, s.[Item Group], f.[Block], Total = sum(f.Total) from firsttable f
INNER JOIN secondtable s
ON f.item = s.item
GROUP BY f.date, s.[Item Group], f.[Block]

We can use Window Clause Over() and Partition By to achieve this. Since your query is straight forward and used all the columns in select list and Group by clause, we can use a simple join and Group by clause.
Anyways here is the query using Over Clause:
select DISTINCT t1.dateyear, t2.ItemGroup, t1.Block,
sum(Total) over(PARTITION BY t2.ItemGroup, t1.dateyear, t1.Block) as GroupTotal
FROM tab_1 t1
JOIN tab_2 t2
ON t1.Item = t2.Item

I have a similar question. I want to select data from a table based on another table.
This is what I have:
Table1
Articlename, group, status
Table2
Articlename, date
This is what I want:
If an article in Table1 has a group and status=1 Count posts in Table2 between date1 and date2, group result by the group in Table1
The result should look something like this
Group Quantity
-----------------
GR1 250
GR2 50
GR3 110

Related

Can someone help me figure out if I'm making a mistake in my query?

I'm trying to create a query that returns the names of all people in my database that have less than half of the money of the person with the most money.
These is my query:
select P1.name
from Persons P1 left join
AccountOf A1 on A1.person_id = P1.id left join
BankAccounts B1 on B1.id = A1.account_id
group by name
having SUM(B1.balance) < MAX((select SUM(B1.balance) as b
from AccountOf A1 left join
BankAccounts B1 on B1.id = A1.account_id
group by A1.person_id
order by b desc
LIMIT 1)) * 0.5
This is the result:
+-------+
| name |
+-------+
| Evert |
+-------+
I have the following tables in the database:
+---------+--------+--+
| Persons | | |
+---------+--------+--+
| id | name | |
| 11 | Evert | |
| 12 | Xavi | |
| 13 | Ludwig | |
| 14 | Ziggy | |
+---------+--------+--+
+--------------+---------+
| BankAccounts | |
+--------------+---------+
| id | balance |
| 11 | 525000 |
| 12 | 750000 |
| 13 | 1900000 |
| 14 | 1600000 |
+--------------+---------+
+-----------+-----------+------------+
| AccountOf | | |
+-----------+-----------+------------+
| id | person_id | account_id |
| 301 | 11 | 12 |
| 302 | 13 | 12 |
| 303 | 13 | 14 |
| 304 | 14 | 11 |
| 305 | 14 | 13 |
+-----------+-----------+------------+
What am I missing here? I should get two entries in the result (Evert, Xavi)
I wouldn't approach the logic this way (I would use window functions). But your final having has two levels of aggregation. That shouldn't work. You want:
having SUM(B1.balance) < (select 0.5 * SUM(B1.balance) as b
from AccountOf A1 join
BankAccounts B1 on B1.id = A1.account_id
group by A1.person_id
order by b desc
limit 1
)
I also moved the 0.5 into the subquery and changed the left join to a join -- the tables need to match to get balances.
I would recommend window functions, if your - undisclosed! - database supports them.
You can join and aggregate just once, and then use a window max() to get the top balance. All that is then left to is to filter in an outer query:
select *
fom (
select p.id, p.name, coalesce(sum(balance), 0) balance,
max(sum(balance)) over() max_balance
from persons p
left join accountof ao on ao.person_id = p.id
left join bankaccounts ba on ba.id = ao.account_id
group by p.id, p.name
) t
where balance > max_balance * 0.5

Using SWITCH() to split data from a column into distinct columns, with associated data in reach row

I'm not quite sure how to properly phrase the question, but I am basically trying to develop an SQL query that SELECTs information from this table:
-------------------
| id | Val | Date |
|----|-----|------|
| 1 | A | 10/9 |
| 1 | B | 3/14 |
| 2 | A | 1/6 |
| 3 | A | 4/4 |
| 4 | B | 7/12 |
| 5 | A | 8/6 |
-------------------
And produces a table that looks like this:
------------------------------------------------
| id | Val_1 | Val_1_Date | Val_2 | Val_2_Date |
|----|-------|------------|-------|-------------
| 1 | A | 10/9 | B | 3/14 |
| 2 | A | 1/6 | | |
| 3 | A | 4/4 | | |
| 4 | | | B | 7/12 |
| 5 | A | 8/6 | | |
------------------------------------------------
I have already begun and developed the query to pull out the values in the Val fields into distinct columns:
SELECT * FROM
(
SELECT id, MAX(SWITCH( val='A', 'A')) as Val_1,
MAX(SWITCH( val='B', 'B')) as Val_2
FROM table1 GROUP BY id
)a
WHERE Val_1 IS NULL OR Val_2 IS NULL;
How would I expand on this to pull out their associated dates?
(I am using SWITCH() instead of CASE WHEN because I am using a driver similar to that of MS Access.)
Thanks!
I think following should work:
select id, SWITCH( val='A', 'A') as Val_1, SWITCH( val='A', Date) as Val_1_Date, SWITCH( val='B', 'B') as Val_2, SWITCH( val='B', Date) as Val_2_Date FROM table1 GROUP BY id
I do not prefer switches, so here is a query that does what you want without switches. This also answers your previous question.
Select distinct table1.ID, tableA.Val as Val_1, tableA.Date as Val_1_Date,
tableB.Val as Val_2, tableB.Date as Val_2_Date
FROM table1 left outer join
table1 as tableA on table1.id = tableA.id and tableA.Val = 'A' left outer join
table1 as tableB on table1.id = tableB.id and tableB.Val = 'B'
You can use ISNULL if that is preferred. This works because the first tables selects a distinct column of ID's, and the two joins get the A and B values. When creating selects using this method, make sure that you use tableA.Val = 'A' in the join conditions, and not in the where clause. Having tableA.Val = 'A' in the where clause will filter out all NULL's.

How to join tables and get the result from the two tables when they appears in the first table and not in the second or vice versa

I have 2 tables that i want want to join in SQL server 2012. The join must be at the column tblA.customer_id to tblB.customer, after joining the 2 table tblA and tblB, i want if the tblA.date_of_revevenue equals tblB.revenue_date, then add the tblA.revenue + tblB.revenue_amount and display them in 1 row, but and if the tblA.date_of_revevenue differs from tblB.revenue_date then display them in 2 different rows in the result table Tbl_Total_Revenue.
I've passed more than 10 hours trying to join using full outer join but it doesn't work.Please i need your help, guys.
This is the sample tables of what i have and what i am looking for is the result table that looks like the one below.
Thank you
tbl_A
*Customer_id | Date_of_revenue | Revenue
C1 | 201201 | 100
C2 | 201203 | 120.20
C4 | 201304 | 150
C5 | 201401 | 70
tbl_B
*Customer | Revenue_date | Revenue_amount
C1 | 201201 | 50
C2 | 201203 | 200
C3 | 201403 | 250
C5 | 201310 | 50
C4 | 201401 | 20
Result table I want
Tbl_Total_Revenue
*Customer_id | Revenue_date | Total_revenue
C1 | 201201 | 150
C2 | 201203 | 320.20
C3 | 201403 | 250
C4 | 201304 | 150
C4 | 201401 | 20
C5 | 201401 | 70
There is an error in sample data (or expected result), there should be two rows for C5:
select isnull(a.Customer_id, b.Customer), isnull(a.Date_of_revenue, b.Revenue_date),
sum(isnull(a.Revenue,0)+isnull(b.Revenue_amount,0)) as Total_revenue
from tbl_A a
full join tbl_B b on a.Customer_id = b.Customer
and a.Date_of_revenue = b.Revenue_date
group by isnull(a.Customer_id, b.Customer), isnull(a.Date_of_revenue, b.Revenue_date)
order by isnull(a.Customer_id, b.Customer), isnull(a.Date_of_revenue, b.Revenue_date)
Try this:
SELECT Customer_id, Date_of_revenue, SUM(Revenue)
FROM
(
(SELECT Customer_id, Date_of_revenue, Revenue FROM tbl_A)
UNION
(SELECT Customer Customer_id, Revenue_date Date_of_revenue, Revenue_amount Revenue FROM tbl_B)
)
GROUP BY Customer_id, Date_of_revenue

Oracle SQL - Making a one to many join one to one based on logic

Sorry for the broad title, I had a hard time coming up with a brief way of describing what I am looking to do. I have two tables (examples below) that I want to join but under a certain condition.
The main table has a field called "DateVal", the second table has a field called "Day". After joining on field "JoinField" I only want to keep rows where the day value in "DateVal" is less than the value of "Day". However, if this criteria is met for multiple values of "Day" I only want it to keep the first instance.
In the second table below, for JoinField "A" there are three rows, for the first I only want it to return times when the day of the month is between 1-10, the second only with the day of the month is between 11-20, and the last 20-31.
A left or inner join will bring back all values, the only way I can think of to get around this is to do a complete join and only return for min("Day"). Can anyone think of a more efficient way?
Thanks in advance.
Table 1
-------------------------------
| ID | JoinField | DateVal |
-------------------------------
| 1 | A | 01/01/2014 |
| 2 | A | 01/16/2014 |
| 3 | B | 05/20/2013 |
-------------------------------
Table 2
--------------------------------
| JoinField | Day | FieldToAdd |
--------------------------------
| A | 10 | A |
| A | 20 | AA |
| A | 31 | AAA |
| B | 15 | B |
| B | 31 | BB |
--------------------------------
Desired Results
--------------------------------------------
| ID | JoinField | DateVal | FieldToAdd |
--------------------------------------------
| 1 | A | 01/01/2014 | A |
| 2 | A | 01/16/2014 | AA |
| 3 | B | 05/20/2014 | BB |
--------------------------------------------
You can do this in a variety of ways. I think a correlated subquery is the easiest way to express it, but unfortunately, the following doesn't work in Oracle:
select t1.*,
(select *
from (select t2.*
from table2 t2
where t2.day < extract(day from t1.dateval)
order by t2.day desc
) t
where rownum = 1
)
from table1 t1;
You can instead do this with join fancy window functions:
select *
from (select t1.*,
row_number() over (partition by t1.id order by t2.day desc) as seqnum
from table1 t1 left outer join
table2 t2
on t2.day < extract(day from t1.dateval)
) t
where seqnum = 1;

How to do a SQL self join with nulls

I am using MS SQL 2008. My table looks like this:
| Name | Code | Amt |
| ----- | ---- | ---- |
| April | A | 1.23 |
| Barry | A | 2.34 |
| Barry | B | 3.45 |
| Cliff | A | 4.56 |
| Cliff | B | 5.67 |
| Cliff | C | 6.78 |
I need the output to be this:
| Name | Code_A | Code_B | Code_C |
| ----- | ------ | ------ | ------ |
| April | 1.23 | NULL | NULL |
| Barry | 2.34 | 3.45 | NULL |
| Cliff | 4.56 | 5.67 | 6.78 |
The NULLs can be zero.
With a self join I am able to get Cliff, but unable to get Barry and April because i'm using something like this which only outputs if all three conditions are available.
SELECT a.Name, a.Amt Code_A, b.Amt Code_B, c.Amt Code_C
FROM Table_1 as c INNER JOIN
Table_1 AS b ON c.Name = b.Name INNER JOIN
Table_1 AS a ON b.Name = a.Name
WHERE (a.Code = 'A') AND (b.Code = 'B') AND (c.Code = 'C')
Instead of JOINs, I think a PIVOT is more appropriate here:
SELECT
Name,
[A] AS Code_A,
[B] AS Code_B,
[C] AS Code_C
FROM (
SELECT Name, Code, Amount
FROM Table_1
) t
PIVOT (
SUM(Amount)
FOR Code IN ([A], [B], [C])
) AS pvt
A completely sql engine agnostic way is:
select names.Name,
(select sum(a2.Amt) from amounts a2
where a2.Name = names.Name
and a2.Code = 'A') as AmtA,
(select sum(a3.Amt) from amounts a3
where a3.Name = names.Name
and a3.Code = 'B') as AmtB,
(select sum(a4.Amt) from amounts a4
where a4.Name = names.Name
and Code = 'C') as AmtC
from (select distinct Name from amounts) as names
Where you select the unique set of names, and then sum up amounts for each particular code in place. This is more intended to be instructional as to how SQL works.
In practice, I wouldn't actually use this in your case -- PIVOT is going to be much more efficient for any engine that supports it. As shown here: http://sqlfiddle.com/#!3/7cb0a/5