SQL Syntax Issue with getting sum - sql

Ok I have two tables.
Table IDAssoc has the columnsbill_id, year, area_id.
Table Bill has the columns bill_id, year, main_id, and amount_due.
I'm trying to get the sum of the amount_due column from the bill table for each of the associated area_ids in the IDAssoc table.
I'm doing a select statement to select the sum and joining on the bill_ids. How can I set this up so it will have a single row for each of the associated bills in each area_id from the assoc table. There may be three or four bill_ids associated with each area_id and I need those summed for each and returned so I can use this select in another statement. I have a group by set up for the area_id but it still is returning each row and not summing them up for each area_id. I have the year and main_id specified already in the where clause to return the data that I want, but I can't get the sum to work properly. Sorry I'm still learning and I'm not sure how to do this. Thanks!
Edit- Basically the query I'm trying so far is basically just like the one posted below:
select a.area_id, sum(b.amount_due)
from IDAssoc a
inner join Bill b
on a.bill_id = b.bill_id
where Bill.year = 2006 and bill.bill_id = 11111
These are just arbitrary numbers.
The data this is returning is like this:
amount_due - area_id
.05 1003
.15 1003
.11 1003
65 1004
55 1004
I need one row returned for each area_id with the amount_due summed. The area_id is only in the assoc table and not in the bill table.

select a.area_id, sum(b.amount_due)
from IDAssoc a
inner join Bill b
on a.bill_id = b.bill_id
where b.year = 2006 and b.bill_id = 11111
group by a.area_id
You might want to change inner join to left join if one IDAssoc can have many or no Bill:
select a.area_id, coalesce(sum(b.amount_due),0)
from IDAssoc a
left join Bill b
on a.bill_id = b.bill_id
where b.year = 2006 and b.bill_id = 11111
group by a.area_id

You are missing the GROUP BY clause:
SELECT a.area_id, SUM(b.amount_due) TotalAmount
FROM IDAssoc a
LEFT JOIN Bill b
ON a.bill_id = b.bill_id
GROUP BY a.area_id

Related

SQL Selecting & Counting In the same query

thanks in advance for any help on this, I am a bit of a newbie to MS SQL and I want to do something that I think is achievable but don't have the know how.
I have a simple table called "suppliers" where I can do (SELECT id, name FROM suppliers ORDER BY id ASC)
id
name
1
ACME
2
First Stop Business Supplies
3
All in One Supply Warehouse
4
Farm First Supplies
I have another table called "products"
id
name
supplier_id
1
Item 1
2
2
Item 2
1
3
Item 3
1
4
Item 4
3
5
Item 5
2
I want to list all the suppliers and get the total amount of products for each supplier if that makes sense on the same row? I am just not sure how to pass the suppliers.id through the query to get the count.
I am hoping to get to this:
id
name
total_products
1
ACME
2
2
First Stop Business Supplies
2
3
All in One Supply Warehouse
1
4
Farm First Supplies
0
I really appreciate any help on this.
Three concepts to grasp here. Left Join, group by, and Count().
select s.id, s.name, Count(*) as total_products
from suppliers s
left join products p on s.id=p.supplier_id --the left join gets your no matches
group by s.id, s.name
left join is a join where all of the values from the first table are kept even if there are no matches in the second.
Group by is an aggregation tool where the columns to be aggregated are entered.
Count() is simply a count of transactions for the grouped columns.
Try this :-
SELECT id, name, C.total_products
FROM Suppliers S
OUTER APPLY (
SELECT Count(id) AS total_products
FROM Products P
WHERE P.supplier_id = S.id
) C

SQL to fetch Data from 2 tables only using Join where we need empty records from second table as well

We have a situation in which one part of our stored procedure need to be filled with a join query, which had multiple filters in it. We need a solution only with join (it is easy to implement in the subquery, but our situation demands it to be a join [since the procedure has a where clause followed by it] )
We have two tables Customer and Order. We need to exclude the rows of Customer table, if Customer_id is present Order table & order_code = 10 & Customer.Grade = 3. It is not mandatory for all Customer_id to be present in Order table, but we still need it in the final result.
Customer Table OrderTable
Customer_id Grade Customer_id order_code
1 3 1 10
2 3 1 40
3 2 2 50
4 3 3 30
*Multiple Customer_id can be present in the OrderTable
Expected result :
Customer_id Grade
2 3
3 2
4 3
I think this may be what you need, not sure I understand the question properly.
select c.id, c.grade
from customer c left join customer_order o on (c.id = o.customer_id and o.order_code <> 10)
where c.grade = 3
This should give you all customers with a Grade of 3 that also have orders, provided the order_code is not 10. If you want to show customers that do not have any orders also, make it a left join.
You can express the logic like this:
select c.*
from customers c
where not (grade = 3 and
exists (select 1
from orders o
where o.customer_id = c.customer_id and
o.order_code = 10
)
);

Selecting from table with categories of people

I created a database in ms sql , in the database I have three category of persons namely staff, customers, suppliers whom I stored in different tables create serial unique id for each.
Now these persons id are stored under person_id and a column names person type which stores whether its a staff, custimer or supplier in the transaction table, The problem lies in selecting the records from the transaction table like this pseudo code
Select t.*,s.na as staff,sp.name as supplier, c.name as customer
From Trans t
left join Staff s on s.id = t.pid
left join Suppliers sp on sp.id = t.pid
left join Customers c on c.id = t.pid
This returns one row, instead of at least 3 or more, How do I solve this problem
My trans table
person_id Person_type Trans_id
1 staff 1
1 customer 2
2 customer 3
3 suppler 4
1 staff 5
Expected output
person_name Trans_id
james 1
mark 2
dan 3
jude 4
james 5
Staff, Customers, and suppliers are stored in their different tables
That's what the Join does, combine data from multiple tables into one result row. If you want to "keep the rows", not combine them, you can use UNION
(
Select t.* From Trans t
left join Staff s on s.id = t.pid
)
UNION
(
Select t.* From Trans t
left join Suppliers sp on sp.id = t.pid
)
UNION
(
Select t.* From Trans t
left join Customers c on c.id = t.pid
)
This will get you the multiple rows you want BUT still not sure you have defined it right. I see you are only taking columns from Trans, so you're not getting any data from the other tables. And you're doing left outer joins so the other tables won't affect the selection. So I think it's just that same as selecting from just Trans.
If what you want is data from Trans where there is corresponding entry in the other tables, then do the UNION, but also change the outer joins to inner.

Complex SQL Query - Joining 5 tables with complex conditions

I have the following tables: Reservations, Order-Lines, Order-Header, Product, Customer. Just a little explanation on each of these tables:
Reservations Contains "reservations" for a billing customer/product combination.
Order-Lines Contains line item detail for orders, including the product they ordered and the qty.
Order-Header Contains header info for orders including the date, customer and billing customer
Product Contains product detail information
Customer Contains Customer detail information.
Below are the tables with their associated fields and sample data:
Reservation
bill-cust-key prod-key qty-reserved reserve-date
10000 20000 10 05/30/2014
10003 20000 5 06/20/2014
10003 20001 15 06/20/2014
10003 20001 5 06/25/2014
10002 20001 5 06/21/2014
10002 20002 20 06/21/2014
Order-Item
order-num cust-key prod-key qty-ordered
30000 10000 20000 10
30000 10000 20001 5
30001 10001 20001 10
30002 10001 20001 5
30003 10002 20003 20
Order-Header
order-num cust-key bill-cust-key order-date
30000 10000 10000 07/01/2014
30001 10001 10003 07/03/2014
30002 10001 10003 07/15/2014
30003 10002 10002 07/20/2014
Customer
cust-key cust-name
10000 Customer A
10001 Customer B
10002 Customer C
10003 Customer D
Product
prod-key prod-name
20000 Prod A
20001 Prod B
20002 Prod C
20003 Prod D
I am attempting to write a query that will show me customer/product combinations that exist in both the reservation and order-item tables. A little snafu is that we have a customer and a billing customer. The reservation and order-header tables contain both the customers, but the order-item table only contains the customer. The results should display the billing customer. Additionally, there can be several reservations and order-items for the same customer/product combination, so I would like to show a total sum of the qty-reserved and the qty-ordered.
Below is an example of my desired output:
bill-cust-key cust-name prod-key prod-name qty-ordered qty-reserved
10000 Customer A 20000 Prod A 10 10
10003 Customer D 20001 Prod B 15 20
This is the query that I have tried and doesn't seem to be working for me.
SELECT customer.cust-key, customer.cust-name, product.prod-key, prod.prod-name,
SUM(order-item.qty-ordered), SUM(reservation.qty-reserved)
FROM ((reservation INNER JOIN order-item on reservation.prod-key = order-item.product-key)
INNER JOIN order-header on reservation.bill-cust-key = order-header.bill-cust-key and
order-item.order-num = order-header.order-num), customer, product
WHERE customer.cust-key = reservation.bill-cust-key
AND product.prod-key = reservation.prod-key
GROUP BY customer.cust-key, customer.cust-name, product.prod-key, product.prod-name
I'm sorry for such a long post! I just wanted to make sure that I had my bases covered!
You want to join your tables like this:
from reservation res join order-header oh on res.bill-cust-key = oh.bill-cust-key
join order-item oi on oi.order-num = oh.order-num
and oi.prod-key = res.prod-key
/* join customer c on c.cust-key = oi.cust-key old one */
join customer c on c.cust-key = oh.bill-cust-key
join product p on p.prod-key = oi.prod-key
I find that it can be very helpful to separate out your output rows from your aggregate rows by using CROSS APPLY (or OUTER APPLY) or simply an aliased inner query if you don't have access to those.
For example,
SELECT
customer.cust-key,
customer.cust-name,
tDetails.prod-key,
tDetails.prod-name,
tDetails.qty-ordered,
tDetails.qty-reserved
FROM customer
--note that this could be an inner-select table in which you join if not cross-join
CROSS APPLY (
SELECT
product.prod-key,
prod.prod-name,
SUM(order-item.qty-ordered) as qty-ordered,
SUM(reservation.qty-reserved) as qty-reserved
FROM reservation
INNER JOIN order-item ON reservation.prod-key = order-item.product-key
INNER JOIN product ON reservation.prod-key = product.prod-key
WHERE
reservation.bill-cust-key = customer.cut-key
GROUP BY product.prod-key, prod.prod-name
) tDetails
There are many ways to slice this, but you started out the right way saying "what recordset do I want returned". I like the above because it helps me visualize what each 'query' is doing. The inner query marked by the CROSS apply is simply grouping by prod orders and reservations but is filtering by the current customer in the outer top-most query.
Also, I would keep joins out of the 'WHERE' clause. Use the 'WHERE' clause for non-primary key filtering (e.g. cust-name = 'Bob'). I find it helps to say that one is a table join, the 'WHERE' clause is a property filter.
TAKE 2 - using inline queries
This approach still tries to get a list of customers with distinct products, and then uses that data to form the outer query from which you can get aggregates.
SELECT
customer.cust-key,
customer.cust-name,
products.prod-key,
products.prod-name,
--aggregate for orders
( SELECT SUM(order-item.qty-ordered)
FROM order-item
WHERE
order-item.cust-key = customer.cust-key AND
order-item.prod-key = products.prod-key) AS qty-ordered,
--aggregate for reservations
( SELECT SUM(reservation.qty-reserved)
FROM reservations
--join up billingcustomers if they are different from customers here
WHERE
reservations.bill-cust-key = customer.cust-key AND
reservations.prod-key = products.prod-key) AS qty-reserved
FROM customer
--get a table of distinct products across orders and reservations
--join products table for name
CROSS JOIN (
SELECT DISTINCT order-item.prod-key FROM order-item
UNION
SELECT DISTINCT reservation.prod-key FROM reservations
) tDistinctProducts
INNER JOIN products ON products.prod-key = tDistinctProducts.prod-key
TAKE 3 - Derived Tables
According to some quick googling, Progress DB does support derived tables. This approach has largely been replaced with CROSS APPLY (or OUTER APPLY) because you don't need to do the grouping. However, if your db only supports this way then so be it.
SELECT
customer.cust-key,
customer.cust-name,
products.prod-key,
products.prod-name,
tOrderItems.SumQtyOrdered,
tReservations.SumQtyReserved
FROM customer
--get a table of distinct products across orders and reservations
--join products table for name
CROSS JOIN (
SELECT DISTINCT order-item.prod-key FROM order-item
UNION
SELECT DISTINCT reservation.prod-key FROM reservations
) tDistinctProducts
INNER JOIN products ON products.prod-key = tDistinctProducts.prod-key
--derived table for order-items
LEFT OUTER JOIN ( SELECT
order-item.cust-key,
order-item.prod-key,
SUM(order-item.qty-ordered) AS SumQtyOrdered
FROM order-item
GROUP BY
order-item.cust-key,
order-item.prod-key) tOrderItems ON
tOrderItems.cust-key = customer.cust-key AND
tOrderItems.prod-key = products.prod-key
--derived table for reservations
LEFT OUTER JOIN ( SELECT
reservations.bill-cust-key,
reservations.prod-key,
SUM(reservations.qty-reserved) AS SumQtyReserved
FROM reservations
--join up billingcustomers if they are different from customers here
WHERE
reservations.bill-cust-key = customer.cust-key AND
reservations.prod-key = products.prod-key) tReservations ON
tReservations.bill-cust-key = customer.cust-key AND
tReservations.prod-key = products.prod-key
Based on your original code and request, here's the starting point of a Progress solution -
DEFINE VARIABLE iQtyOrd AS INTEGER NO-UNDO.
DEFINE VARIABLE iQtyReserved AS INTEGER NO-UNDO.
FOR EACH order-item
NO-LOCK,
EACH order-header
WHERE order-header.order-num = order-item.order-num
NO-LOCK,
EACH reservation
WHERE reservation.prod-key = order-item.prod-key AND
reservation.bill-cust-key = order-header.bill-cust-key
NO-LOCK,
EACH product
WHERE product.prod-key = reservation.prod-key
NO-LOCK,
EACH customer
WHERE customer.cust-key = reservation.bill-cust-key
NO-LOCK
BREAK BY customer.cust-key
BY product.prod-key
BY product.prod-name
:
IF FIRST-OF(customer.cust-key) OR FIRST-OF(product.prod-key) THEN
ASSIGN
iQtyOrd = 0
iQtyReserved = 0
.
ASSIGN
iQtyOrd = iQtyOrd + reservation.qty-ordered
iQtyReserved = iQtyReserved + reservation.qty-reserved
.
IF LAST-OF(customer.cust-key) OR LAST-OF(product.prod-key) THEN
DISPLAY
customer.cust-key
customer.cust-name
product.prod-key
prod.prod-name
iQtyOrd
iQtyReserved
WITH FRAME f-qty
DOWN
.
END.

Query Returning SUM of Quantity or 0 if no Quantity for a Product at a given date

I have the following 4 tables:
----------
tblDates:
DateID
30/04/2012
01/05/2012
02/05/2012
03/05/2012
----------
tblGroups:
GroupID
Home
Table
----------
tblProducts:
ProductID GroupID
Chair Home
Fork Table
Knife Table
Sofa Home
----------
tblInventory:
DateID ProductID Quantity
01/05/2012 Chair 2
01/05/2012 Sofa 1
01/05/2012 Fork 10
01/05/2012 Knife 10
02/05/2012 Sofa 1
02/05/2012 Chair 3
03/05/2012 Sofa 2
03/05/2012 Chair 3
I am trying to write a query that returns all Dates in tblDates, all GroupIDs in tblGroups and the total Quantity of items in each GroupID.
I manage to do this but only get Sum(Quantity) for GroupID and DateID that are not null. I would like to get 0 instead. For exemple for the data above, I would like to get a line "02/05/2012 Table 0" as there is no data for any product in "Table" group on 01/05/12.
The SQL query I have so far is:
SELECT tblDates.DateID,
tblProducts.GroupID,
Sum(tblInventory.Quantity) AS SumOfQuantity
FROM (tblGroup
INNER JOIN tblProducts ON tblGroup.GroupID = tblProducts.GroupID)
INNER JOIN (tblDates INNER JOIN tblInventory ON tblDates.DateID = tblInventory.DateID) ON tblProducts.ProductID = tblInventory.ProductID
GROUP BY tblDates.DateID, tblProducts.GroupID;
I reckon I should basically have the same Query on a table different from tblInventory that would list all Products instead of listed products with 0 instead of no line but I am hesitant to do so as given the number of Dates and Products in my database, the query might be too slow.
There is probably a better way to achieve this.
Thanks
To get all possible Groups and Dates combinations, you'll have to CROSS JOIN those tables. Then you can LEFT JOIN to the other 2 tables:
SELECT
g.GroupID
, d.DateID
, COALESCE(SUM(i.Quantity), 0) AS Quantity
FROM
tblDates AS d
CROSS JOIN
tblGroups AS g
LEFT JOIN
tblProducts AS p
JOIN
tblInventory AS i
ON i.ProductID = p.ProductID
ON p.GroupID = g.GroupID
AND i.DateID = d.DateID
GROUP BY
g.GroupID
, d.DateID
use the isnull() function. change your query to:
SELECT tblDates.DateID, tblProducts.GroupID,
**ISNULL( Sum(tblInventory.Quantity),0)** AS SumOfQuantity
FROM (tblGroup INNER JOIN tblProducts ON tblGroup.GroupID = tblProducts.GroupID)
INNER JOIN (tblDates INNER JOIN tblInventory ON tblDates.DateID = tblInventory.DateID)
ON tblProducts.ProductID = tblInventory.ProductID
GROUP BY tblDates.DateID, tblProducts.GroupID;
You don't say which database you're using but what I'm familiar with is Oracle.
Obviously, if you inner join all tbales, you will not get the rows containing nulls. If you use an outer join to the inventory table you also have a problem because the groupid column is not listed in tblinventory and you can only outer join to one table.
I'd say you have two choices. Use a function or have a duplicate of the groupid column in the inventory table.
So, a nicer but slower solution:
create or replace
function totalquantity( d date, g varchar2 ) return number as
result number;
begin
select nvl(sum(quantity), 0 )
into result
from tblinventory i, tblproducts p
where i.productid = p.productid
and i.dateid = d
and p.groupid = g;
return result;
end;
select dateid, groupid, totalquantity( dateid, groupid )
from tbldates, tblgroups
Or, if you include the groupid column in the inventory table. (You can still quarantee integrity using constraints)
select d.dateid, i.groupid, nvl(i.quantity, 0)
from tbldates d, tblinventory i
where i.dateid(+) = d.dateid
group by d.dateid, g.groupid
Hope this helps,
GĂ­sli