Subtract two SUM GROUP BY fields - sql

I have two tables itemOrders and itemUsage.
itemOrders has two fields: item_number and qty_ordered
itemUsage has two fields: item_number and qty_used
I'm trying to word my SQL query so that it sums up the quantity of each item number in both tables then subtracts the totals in itemUsage from itemOrders
I've come up with this so far:
SELECT itemOrders.item_number
,(
SELECT sum(qty_ordered)
FROM itemOrders
GROUP BY itemOrders.item_number
) - (
SELECT sum(qty_used)
FROM itemUsage
GROUP BY itemUsage.item_number
) AS item_total
FROM itemOrders
INNER JOIN itemUsage
ON itemOrders.item_number = itemUsage.item_number
GROUP BY itemOrders.item_number
What happens here is that all fields come out to 0.
Example if item number "A" was showing a total quantity of 3 ordered across all instances of "A" in the itemOrders table, and only a total quantity used of 1 across all instances of "A" in the itemUsage table. The sql should show the number one in the item_total field next to 1 instance of "A" in the item_number field.

The problem is you are creating a CARTESIAN PRODUCT and repeting the values on the SUM just calculate each value separated and then LEFT JOIN both. In case no item are used COALESCE will convert NULL to 0
SELECT io_total.item_number,
order_total - COALESCE(used_total, 0) as item_total
FROM (SELECT io.item_number, sum(io.qty_ordered) as order_total
FROM itemOrders io
GROUP BY io.item_number
) io_total
LEFT JOIN (SELECT iu.item_number, sum(iu.qty_used) as used_total
FROM itemUsage iu
GROUP BY iu.item_number
) as iutotal
ON io_total.item_number = iutotal.item_number

Well it looks like you are making three queries, two separate ones, one for each sum, and one with an inner join that isn't being used.
Try
Select itemOrders.item_number, sum(itemOrders.qty_ordered - itemUsage.qty_used) as item_total
from itemOrders INNER JOIN itemUsage
On itemOrders.item_number = itemUsage.item_number
GROUP BY itemOrders.item_number

Related

Including count combinations with null value in SQL

I have one dataset, and am trying to list all of the combinations of said dataset. However, I am unable to figure out how to include the combinations that are null. For example, Longitudinal? can be no and cohort can be 11-20, however for Region 1, there were no patients of that age in that region. How can I show a 0 for the count?
Here is the code:
SELECT "s_safe_005prod"."ig_eligi_group1"."site_name" AS "Site Name",
"s_safe_005prod"."ig_eligi_group1"."il_eligi_ellong" AS "Longitudinal?",
"s_safe_005prod"."ig_eligi_group1"."il_eligi_elcohort" AS "Cohort",
count(*) AS "count"
FROM "s_safe_005prod"."ig_eligi_group1"
GROUP BY "s_safe_005prod"."ig_eligi_group1"."site_name",
"s_safe_005prod"."ig_eligi_group1"."il_eligi_ellong",
"s_safe_005prod"."ig_eligi_group1"."il_eligi_elcohort"
ORDER BY "s_safe_005prod"."ig_eligi_group1"."site_name",
"s_safe_005prod"."ig_eligi_group1"."il_eligi_ellong" ASC,
"s_safe_005prod"."ig_eligi_group1"."il_eligi_elcohort" ASC
Create a cross join across the unique values from each of the three grouping fields to create a set of all possible combinations. Then left join that to the counts you have originally and coalesce null values to zero.
WITH groups AS
(
SELECT a.site_name, b.longitudinal, c.cohort
FROM (SELECT DISTINCT site_name FROM s_safe_005prod.ig_eligi_group1) a,
(SELECT DISTINCT il_eligi_ellong AS longitudinal FROM s_safe_005prod.ig_eligi_group1) b,
(SELECT DISTINCT il_eligi_elcohort AS cohort FROM s_safe_005prod.ig_eligi_group1) c
),
dat AS
(
SELECT site_name,
il_eligi_ellong AS longitudinal,
il_eligi_elcohort AS cohort,
count(*) AS "count"
FROM s_safe_005prod.ig_eligi_group1
GROUP BY site_name,
il_eligi_ellong,
il_eligi_elcohort
)
SELECT groups.site_name,
groups.longitudinal,
groups.cohort,
COALESCE(dat.[count],0) AS "count"
FROM groups
LEFT JOIN dat ON groups.site_name = dat.site_name
AND groups.longitudinal = dat.longitudinal
AND groups.cohort = dat.cohort;

How to return two aggregates in the same query on two separate rows?

I have a generic Sales table that I'm querying against and I want to return two aggregates of the table, but on two separate rows instead of one.
I have a query to get a value like this:
SELECT
SUM(o.SalesAmount) Value
,'Yearly Sales' Name
,'Yearly' Timeframe
FROM Orders o
Which returns a single row
Value
Name
Timeframe
1,000,000
Yearly Sales
Yearly
Is there a way for me to structure my query so I can return another aggregate on a second row, but with a different Name and Timeframe on the same query? Right now I'm duplicating the first query and a bunch of filters that I need the same in the second query, but changing the aggregate and the "Name" and then unioning them all together like this:
SELECT
SUM(o.SalesAmount) Value
,'Yearly Sales' Name
,'Yearly' Timeframe
FROM Orders o
WHERE
/* bunch of filters */
UNION ALL
SELECT
COUNT(o.Orders) Value
, 'Yearly Orders' Name
,'Yearly' Timeframe
FROM Orders o
WHERE
/* bunch of filters which are the same as the first */
I have about 10 other aggregations that I need to return in a similar manner and I don't think unioning 12 really similar queries is the way to go about it.
Ideally I'd want something like this (I understand this won't give me what I need) so I can use the same filters and not have to duplicate code.
SELECT
SUM(o.SalesAmount) Value
, COUNT(o.Orders) Value2
,'Yearly Sales' Name
, 'Yearly Orders' Name2
,'Yearly' Timeframe
FROM Orders o
WHERE
/* bunch of filters that I only have to manage in one spot */
That would then return this:
Value
Name
Timeframe
1,000,000
Yearly Sales
Yearly
100,000
Yearly Orders
Yearly
You can unpivot it from a cte
with cte as (
SELECT
SUM(o.SalesAmount) Value1,
COUNT(o.Orders) Value2,
'Yearly' Timeframe
FROM Orders o
WHERE
/* bunch of filters */
)
SELECT Value1 Value,'Yearly Sales' Name, Timeframe
FROM cte
UNION ALL
SELECT Value2, 'Yearly Orders', Timeframe
FROM cte
Use cross apply to split each Orders row into two:
select sum(split.Value) Value
,split.Name
,'Yearly' Timeframe
from Orders o
cross apply (values
('Yearly Sales' ,o.SalesAmount)
,('Yearly Orders',1)
) split(Name,Value)
group by split.Name
You can unpivot it after aggregating in a derived table:
SELECT
v.Value
v.Name
t.Timeframe
FROM (
SELECT
SUM(o.SalesAmount) Sum,
COUNT(o.Orders) Count,
'Yearly' Timeframe
FROM Orders o
) o
CROSS APPLY (VALUES
('Yearly Sales', Sum),
('Yearly Orders', Count)
) v(Name, Value);

SQL Pivot column values

I have tried following this and this(SQL Server specific solution) but were not helpful.
I have two tables, Product and Sale and I want to find how many products are sold on each day. But I want to pivot the table so that columns become the products name and each row will contain the amount of products sold for each day ordered by the day.
Simplified schema is as following
CREATE TABLE product (
id integer,
name varchar(40),
price float(2)
);
CREATE TABLE sale(
id integer,
product_id integer,
transaction_time timestamp
);
This is what I want
I only managed to aggregate the total sales per day per product but I am not able to pivot the product names.
select date(sale.transaction_date)
, product.id
, product.name
, count(product_id)
from sale inner join
product on sale.product_id = product.id
group by date(sale.transaction_date)
, product.id
, product.name
This is the situation so far
Please suggest.
You need pivoting logic, e.g.
select
s.transaction_date::date,
count(case when p.name = 'intelligent_rubber_clock' then 1 end) as intelligent_rubber_clock,
count(case when p.name = 'intelligent_iron_wallet' then 1 end) as intelligent_iron_wallet,
count(case when p.name = 'practical_marble_car' then 1 end) as practical_marble_car
from sale s
inner join product p
on s.product_id = p.id
group by
s.transaction_date::date;
Since your expected output aggregates by date alone, then only the transaction date should be in your GROUP BY clause. The trick used here is to take the count of a CASE expression which returns 1 when the record is from a given product, and 0 otherwise. This generates conditional counts for each product, all in separate columns. To add more columns, just add more conditional counts.

Get Sum of quantities from multiple tables?

I have at least 8 tables from where I need to match the customer name and fetch the quantities and get the sum of all the quantities fetched from these 8 tables. I am trying to write a code which will ignore the customer whose sum of quantities is zero.
For an example lets take two tables purchase_sugar and sales_sugar I have tried a lot of queries but only this one is returning some result which is wrong.
SELECT sum(purchase_sugar.qty + sales_sugar.qty) AS Total_Amount from purchase_sugar inner join sales_sugar on purchase_sugar.supplier = sales_sugar.customer WHERE purchase_sugar.supplier = "+str(x.id)+"
The Table structures are like:
purchase_sugar have two columns supplier and qty.
And sales_sugar have structure like customer and qty.
How can I get the SUM of QUANTITIES of these tables if I provide one name and search it through these tables and get the quantities. The other thing is that I dont want the customer to be found in all the tables. If it is found in one table we should just get the quantity from that one table and for that reason I don't think that JOIN is useful or may be i am wrong.
To take care of the situation where a supplier/customer is not in all the tables, you can use union all and group by:
select name, sum(p_qty) as sum_p, sum(s_qty) as sum_s,
sum(p_qty) + sum(s_qty)
from ((select ps.supplier as name, ps.qty as p_qty, 0 as s_qty
from purchase_sugar ps
) union all
(select ss.customer as name, 0, ss.qty
from sales_sugar ss
)
) s
group by name;
Notes:
This query gets results for all names. You can use a where clause to restrict the results to one name.
You don't have to split the quantities into two (or eight) different columns, if you just want the overall sum.
You can aggregate before the union all, but that is not necessary.
you should JOIN the sum and not sum the join
select t1.purchase_sum + sales_sum as Total_Amount
from (
select purchase_sugar.supplier, sum(purchase_sugar.qty) as purchase_sum
from purchase_sugar
group by purchase_sugar.supplier
) t1
inner join (
select sales_sugar.customer, sum(sales_sugar.qty) as sales_sum
from sales_sugar
group by sales_sugar.customer
) t2 on t1.supplier = t2.customer and t1.supplier = "+str(x.id)+"

Count the number of occurrences grouped by some rows

I have made a query to bring me the number of products that have not been in stock (I know that by looking at the orders which the manufacturer returned with some status code), by product, date and storage, that looks like this:
SELECT count(*) as out_of_stock,
prod.id as product_id,
ped.data_envio::date as date,
opl.id as storage_id
from sub_produtos_pedidos spp
left join cad_produtos prod ON spp.ean_produto = prod.cod_ean
left join sub_pedidos sp ON spp.id_pedido = sp.id
left join pedidos ped ON sp.id_pedido = ped.id
left join op_logisticos opl ON sp.id_op_logistico = opl.id
where spp.motivo = '201' -- this is the code that means 'not in inventory'
group by storage_id,product_id,date
That produces an answer like this:
out_of_stock | product_id | date | storage_id
--------------|------------|-------------|-------------
1 | 5 | 2012-10-16 | 1
5 | 4 | 2012-10-16 | 2
Now I need to get the number of occurrences, by product and storage, of products that have been out of stock for 2 or more days, 5 or more days and so on.
So I guess I need to do a new count on the first query, aggregating the resultant rows in some defined day intervals.
I tried looking at the datetime functions in Postgres (http://www.postgresql.org/docs/7.3/static/functions-datetime.html), but couldn't find what I need.
May be I didn't get correctly you question, but it looks you need leverage sub-query.
Now I need to get the number of occurrences, by product and storage, of products that have been out of stock for 2 or more days
So:
SELECT COUNT(*), date, product_id FROM ( YOUR BIG QUERY IS THERE ) a
WHERE a.date < (CURRENT_DATE - interval '2' day)
GROUP BY date, product_id
Since you seem to want every row in the result individually, you cannot aggregate. Use a window function instead to get the count per day. The well known aggregate function count() can also serve as window aggregate function:
SELECT current_date - ped.data_envio::date AS days_out_of_stock
,count(*) OVER (PARTITION BY ped.data_envio::date)
AS count_per_days_out_of_stock
,ped.data_envio::date AS date
,p.id AS product_id
,opl.id AS storage_id
FROM sub_produtos_pedidos spp
LEFT JOIN cad_produtos p ON p.cod_ean = spp.ean_produto
LEFT JOIN sub_pedidos sp ON sp.id = spp.id_pedido
LEFT JOIN op_logisticos opl ON opl.id = sp.id_op_logistico
LEFT JOIN pedidos ped ON ped.id = sp.id_pedido
WHERE spp.motivo = '201' -- code for 'not in inventory'
ORDER BY ped.data_envio::date, p.id, opl.id
Sort order: Products having been out of stock for the longest time first.
Note, you can just subtract dates to get an integer in Postgres.
If you want a running count in the sense of "n rows have been out of stock for this number of days or more", use:
count(*) OVER (ORDER BY ped.data_envio::date) -- ascending order!
AS running_count_per_days_out_of_stock
You get the same count for the same day, peers are lumped together.