Sum multiple rows in a joined query - sql

I'm not even sure how to ask this. So here goes. I have two tables I am joining, and am needing to sum one column of data, easy enough, but the data that needs to be summed is dependent on a certain character associated with the same job number from a different table.
Table1
JobNumber CostType Amount
1 A 10
1 B 20
1 C 50
1 C 50
3 C 75
Table 2
JobNumber Status Value
1 A 100
2 I 50
3 A 75
Okay, so some of the jobs will have multiple lines for CostType 'C'. I'm trying to display all JobNumbers with the total of any amounts for CostType C, BUT only for jobs that have the Status of 'A'. Here's my query so far:
SELECT Table1.JobNumber
,Table1.Amount
,Table2.Value
FROM DB.Table1, DB.Table2
WHERE Table1.JobNumber = Table2.JobNumber and Table1.CostType = 'C' and Table2.Status = 'A'
GROUP BY Table1.JobNumber, Table1.Amount, Table2.Value
ORDER BY Table1.JobNumber ASC
It's giving me the list of job numbers, their amounts, and the contract value, and only for CostType 'C' and with the Status of 'A'. But each separate CostType 'C' amount has its own row. Is there a way to combine them and display the total Amount along with the Value for each JobNumber, like this?
JobNumber CostTypeCTotal Value
1 100 100
3 75 75

Hmmm . . . Try aggregating table1 before joining the tables:
SELECT t2.JobNumber, t1.c_Amount, t2.Value
FROM DB.Table2 t2
(SELECT JobNumber,
SUM(CASE WHEN CostType = 'C' THEN amount END) as c_amount
FROM DB.Table1 t1
GROUP BY JobNumber
HAVING SUM(CASE WHEN CostType = 'A' THEN 1 ELSE 0 END) > 0
) t1 JOIN
ON t1.JobNumber = t2.JobNumber;
Note: Learn to use proper, explicit, standard, readable JOIN syntax. Never use commas in the FROM clause. Such use of , is archaic syntax that has been out of date since the 1990s.

Related

How to use multiple counts in where clause to compare data of a table in sql?

I want to compare data of a table with its other records. The count of rows with a specific condition has to match the count of rows without the where clause but on the same grouping.
Below is the table
-------------
id name time status
1 John 10 C
2 Alex 10 R
3 Dan 10 C
4 Tim 11 C
5 Tom 11 C
Output should be time = 11 as the count for grouping on time column is different when a where clause is added on status = 'C'
SELECT q1.time
FROM (SELECT time,
Count(id)
FROM table
GROUP BY time) AS q1
INNER JOIN (SELECT time,
Count(id)
FROM table
WHERE status = 'C'
GROUP BY time) AS q2
ON q1.time = q2.time
WHERE q1.count = q2.count
This is giving the desired output but is there a better and efficient way to get the desired result?
Are you looking for this :
select t.*
from table t
where not exists (select 1 from table t1 where t1.time = t.time and t1.status <> 'C');
However you can do :
select time
from table t
group by time
having sum (case when status <> 'c' then 1 else 0 end ) = 0;
If you want the times where the rows all satisfy the where clause, then in Postgres, you can express this as:
select time
from t
group by time
having count(*) = count(*) filter (where status = 'C');

Need sum of a column from a filter condition for each row

Need to get total sum of defect between main_date column and past 365 day (a year) from it, if any, for a single ID.
And The value need to be populated for each row.
Have tried below queries and tried to use CSUM also but it's not working:
1) select sum(Defect) as "sum",Id,MAIN_DT
from check_diff
where MAIN_DT between ADD_MONTHS(MAIN_DT,-12) and MAIN_DT group by 2,3;
2)select Defect,
Type1,
Type2,
Id,
MAIN_DT,
ADD_MONTHS(TIM_MAIN_DT,-12) year_old,
CSUM(Defect,MAIN_DT)
from check_diff
where
MAIN_DT between ADD_MONTHS(MAIN_DT,-12) and MAIN_DT group by id;
The expected output is as below:
Defect Type1 Type2 Id main_dt sum
1 a a 1 3/10/2017 1
99 a a 1 4/10/2018 99
0 a b 1 7/26/2018 99
1 a b 1 11/21/2018 100
1 a c 2 12/20/2018 1
Teradata doesn't support RANGE for Cumulative Sums, but you can rewrite it using a Correlated Scalar SUbquery:
select Defect, Id, MAIN_DT,
( select sum(Defect) as "sum"
from check_diff as t2
where t2.Id = t1.Id
and t2.MAIN_DT > ADD_MONTHS(t1.MAIN_DT,-12)
and t2.MAIN_DT <= t1.MAIN_DT group by 2,3;
) as dt
from check_diff as t1
Performance might be bad depending on the overall number of rows and the number of rows per ID.

SQL: Using one table as a criteria for itself

I am trying to use a parameter of a table as a criteria for itself, and can't quite get my sql statement right. It seems to be a relatively simple query; I'm using a sub query for my criteria, but it is not filtering out other rows on my table.
Background:
Manufacturing production floor: I have a bunch of machinists on their machines right now running an operation (OprSeq) of a job (JobNum). From the LaborDtl table, which keeps a record of all labor activity, I can see what labor is currently active (ActiveTrans = 1). With this criteria of active labor, I want to sum up all the past labor transactions on each active labor entry. So I need a LaborDtl table of inactive labor activity with the criteria of active labor from the same table.
The code:
Heres my 'criteria' subquery:
SELECT
LaborDtl.JobNum,
LaborDtl.OprSeq
FROM Erp.LaborDtl
WHERE LaborDtl.ActiveTrans = 1
Which returns active transactions, here's the first couple (sorted by job):
Job Operation
000193 90
000457 70
000457 70
020008-1 140
020008-2 130
020010 60
020035 130
020175 40
020175-2 50
020186 80
020199 10
020203 50
020212 40
020258 60
020272 10
020283 30
020298 10
020299 30
Then here's the full SQL Statement, with the query above embedded:
SELECT
LaborDtl.JobNum,
LaborDtl.OprSeq as "Op",
SUM(LaborDtl.LaborQty) as "Total Labor"
FROM Erp.LaborDtl
WHERE EXISTS
(
SELECT
LaborDtl.Company,
LaborDtl.JobNum,
LaborDtl.OprSeq
FROM Erp.LaborDtl
WHERE LaborDtl.ActiveTrans = 1 --Labor table of just current activity
)
GROUP BY LaborDtl.JobNum, LaborDtl.OprSeq
I expect to see only the Job and Operation numbers that exist in my sub query, but I'm getting both jobs and operations that don't exist in my sub query. Here are the first 10 (note, the first JobNum should be 000193 per my criteria)
JobNum Op Total Labor
0 0.00000000
000004 1 32.00000000
000019 1 106.00000000
000029 1 175.00000000
000143 1 85.00000000
000164 1 58.00000000
000181 1 500.00000000
000227 1 116.00000000
000421 1 154.00000000
000458 1 67.00000000
You're missing some condition to tie the outer and inner queries together. Right now, without that criteria, the inner query just returns "true", as there are jobs with active activities and thus all the rows in the outer query are returned. Note that you'll have to add aliases to the tables, as the inner and outer query use the same table:
SELECT a.JobNum, a.OprSeq as "Op", SUM(a.LaborQty) as "Total Labor"
FROM Erp.LaborDtl a
WHERE EXISTS (SELECT * -- The select list doesn't really matter here
FROM Erp.LaborDtl b
WHERE a.JobNum = b.JobNum AND -- Here!
a.OprSeq = b.OprSeq AND -- And here!
b.ActiveTrans = 1 -- Labor table of just current activity
)
GROUP BY a.JobNum, a.OprSeq
Note, however, that there's an easier (IMHO) way. Since you're grouping by JobNum and OprSeq anyway, you could just count the number of active transactions and using a having clause to query only those that have at least one active transaction:
SELECT JobNum, OprSeq as "Op", SUM(LaborQty) as "Total Labor"
FROM Erp.LaborDtl
GROUP BY JobNum, OprSeq
HAVING COUNT(CASE ActiveTrans WHEN 1 THEN 1 END) > 0
Without knowing RDBMS vendor and version this is the best I can do:
SELECT
t1.JobNum,
t1.OprSeq as "Op",
SUM(t1.LaborQty) as "Total Labor"
FROM Erp.LaborDtl t1
WHERE EXISTS
(
SELECT 1
FROM Erp.LaborDtl t2
WHERE t2.ActiveTrans = 1 --Labor table of just current activity
and t2.Company = t1.Company
and t2.JobNum = t1.JobNum
and t2.OprSeq = t1.OprSeq
)
GROUP BY t1.JobNum, t1.OprSeq

SQL query Splitting a column into Multiple rows divide by percentage

How to get percentage of a column and then inserting it as rows
Col1 item TotalAmount**
1 ABC 5558767.82
2 ABC 4747605.5
3 ABC 667377.69
4 ABC 3844204
6 CTB 100
7 CTB 500.52
I need to create a new column percentage for each item which is I have done as :-
Select item, (totalAmount/select sum(totalAmount) from table1) as Percentage
From table1
Group by item
Col1 item TotalAmount percentage
1 ABC 5558767.82 38
2 ABC 4747605.5 32
3 ABC 667377.69 5
4 ABC 3844204 26
6 CTB 100 17
7 CTB 500.52 83
Now, the complex part I have to calculate another amount by multiplying this percentage to an amount from another table say table2
ii) update the Total amount column by spilt the total amount column of table 1 into 2 rows – 1st row of the new Calculate PledgeAmount and 2nd row – (totalAmount – PledgeAmount)
*Select t1.percentage * t2.new amount as [PledgeAmount]
From table 1 join table2 where t1.item=t2.item*
. e.g. for col1 Amount of 5558767.82 will split into two rows.
Final Result sample for :-
Col1 item TotalAmount Type
1 ABC 363700.00 Pledge
1 ABC 5195067.82 Unpledge
....
I am using Temporary table to do calculations.
One of the way I think is to calculate the Pledged and Unpledged amount as new column and Pivot it but its huge table with hundreds of columns it will not perform fast.
Any other efficient way?
You can use a windowing function to solve this problem -- first in a sub-query calculate the total and then in the main query the percent:
Select *, (totalAmount/total_for_item)*100 as percent_of_total
from (
SELECT t.*,
SUM(totalAmount) OVER (PARTITION BY item) as total_for_item
FROM table t
) sub
First, let's get the total amount per item:
SELECT item, SUM( totalAmount ) as sumTotal
INTO #totalperitem
FROM table1
GROUP BY item
Now it's easy to get to the percentages:
SELECT t1.Col1,
t1.item,
t1.totalAmount,
t1.totalAmount/tpi.sumTotal*100 AS percentage
FROM table1 t1
INNER JOIN #totalperitem tpi on ...
Tricky part: Separate rows with/without match in table2. Can be done with a WHERE NOT EXISTS, or, my preference, with a single outer join:
SELECT t1.item,
CASE WHEN tpledged.item IS NULL
THEN "Unpledged"
ELSE "Pledged"
END,
SUM( t1.totalAmount ) AS amount
FROM table1 t1
LEFT OUTER JOIN table2 tpledged ON t1. ... = tpledged. ...
GROUP BY t1.item,
CASE WHEN tpledged.item IS NULL
THEN "Unpledged"
ELSE "Pledged"
END
The basic trick is to create an artificial column from the presence/absence of records in table2 and to also group by that artificial column.

DB2 SQL filter query result by evaluating an ID which has two types of entries

After many attempts I have failed at this and hoping someone can help. The query returns every entry a user makes when items are made in the factory against and order number. For example
Order Number Entry type Quantity
3000 1 1000
3000 1 500
3000 2 300
3000 2 100
4000 2 1000
5000 1 1000
What I want to the query do is to return filter the results like this
If the order number has an entry type 1 and 2 return the row which is type 1 only
otherwise just return row whatever the type is for that order number.
So the above would end up:
Order Number Entry type Quantity
3000 1 1000
3000 1 500
4000 2 1000
5000 1 1000
Currently my query (DB2, in very basic terms looks like this ) and was correct until a change request came through!
Select * from bookings where type=1 or type=2
thanks!
select * from bookings
left outer join (
select order_number,
max(case when type=1 then 1 else 0 end) +
max(case when type=2 then 1 else 0 end) as type_1_and_2
from bookings
group by order_number
) has_1_and_2 on
type_1_and_2 = 2
has_1_and_2.order_number = bookings.order_number
where
bookings.type = 1 or
has_1_and_2.order_number is null
Find all the orders that have both type 1 and type 2, and then join it.
If the row matched the join, only return it if it is type 1
If the row did not match the join (has_type_2.order_number is null) return it no matter what the type is.
A "common table expression" [CTE] can often simplify your logic. You can think of it as a way to break a complex problem into conceptual steps. In the example below, you can think of g as the name of the result set of the CTE, which will then be joined to
WITH g as
( SELECT order_number, min(type) as low_type
FROM bookings
GROUP BY order_number
)
SELECT b.*
FROM g
JOIN bookings b ON g.order_number = b.order_number
AND g.low_type = b.type
The JOIN ON conditions will work so that if both types are present then low_type will be 1, and only that type of record will be chosen. If there is only one type it will be identical to low_type.
This should work fine as long as 1 and 2 are the only types allowed in the bookings table. If not then you can simply add a WHERE clause in the CTE and in the outer SELECT.