Completing a table with rows not existing - sql

I have a table like this:
buyer product quantity
tom skirt 2
anna skirt 3
tom jeans 5
The distinct(product) = skirt and jeans
I want a table that inserts another column with quantity = 0 when the <buyer, product> tuple does not exist for all possible products.
So the result would be:
buyer product quantity
tom skirt 2
anna skirt 3
tom jeans 5
anna jeans 0
It does not look very complicated but I don't know how to do it.
UPDATE
I have found one extra complication.
My product is actually defined by two fields: class and product. Product can be null and I need not to lose the information quantity when the product field is null (now it is happening with the cross join).
So if I have this:
buyer class product quantity
tom clothes skirt 2
anna clothes skirt 3
tom clothes jeans 5
jim shoes NULL 7
I would need:
buyer class product quantity
tom clothes skirt 2
anna clothes skirt 3
tom clothes jeans 5
anna clothes jeans 0
jim shoes NULL 7
jim clothes skirt 0
jim clothes jeans 0
tom shoes NULL 0
anna shoes NULL 0
Thank you all, I know I am complicating things!

You can use a cross join to generate all possible combinations of buyers and products. Then use a left join (or not exists) to filter out the ones already in the table:
insert into table(buyer, product, quantity)
select b.buyer, p.product, 0
from (select distinct buyer from table) b cross join
(select distinct product p from table) p left join
table t
on t.buyer = b.buyer and t.product = p.product
where t.buyer is null;
EDIT:
If you want a query that returns all the rows, then you would use something quite similar:
select b.buyer, p.product, coalesce(t.qty, 0) as qty
from (select distinct buyer from table) b cross join
(select distinct product p from table) p left join
table t
on t.buyer = b.buyer and t.product = p.product;
EDIT II:
If you have NULL values for buyer and/or product, then use NULL safe comparisons:
select b.buyer, p.product, coalesce(t.qty, 0) as qty
from (select distinct buyer from table) b cross join
(select distinct product p from table) p left join
table t
on t.buyer is not distinct from b.buyer and
t.product is not distinct from p.product;
(As a minor side note: I really do not like the use of distinct in this construct. Why did Postgres (ANSI ?) give it such a complicated name?)

The solution of #Gordon is almost full, I edit like this:
declare #tb table (buyer varchar(150), product varchar(150), quantity int)
insert into #tb
values('tom','skirt',2),
('anna','skirt',3),
('tom','jeans',5)
select *
from #tb a
left join( select
distinct(product)
from #tb) b on a.product = a.product
select b.buyer, p.p, isnull(t.quantity,0)
from (select distinct buyer from #tb) b cross join
(select distinct product p from #tb) p left join
#tb t
on t.buyer = b.buyer and t.product = p.p
--where t.buyer is null
Try it.

Related

How to get all products from one order to the list

I have written a query that returns data, here is my code
SELECT o.Id, p.ProductName, po.Quantity, p.Price, DeliveryMethod,
OrderDate, TotalSum, u.UserName FROM [Order] o
JOIN ProductsOrders po ON o.Id = po.OrderId
JOIN Product p ON po.ProductId = p.Id
JOIN Users u ON o.UserId = u.Id
ORDER BY o.Id
And it returns next:
OrderId ProductName Quantity Price DeliveryMethod OrderDate TotalSum UserName
98 Bed3 1 3000 Express 2018-08-04 7015 Ivan
98 Bed4 1 4000 Express 2018-08-04 7015 Ivan
100 Bed2 1 2000 2018-08-05 2000 Ivan
101 Bed2 1 2000 2018-08-05 5000 Ivan
101 Bed3 1 3000 2018-08-05 5000 Ivan
102 Bed0 1 0 Standard 2018-09-04 1005 Ivan
We can notice that for each product - one record
But I want to group all products by one order and get something like that:
- orderId
- deliveryMethod
- userName
- product1 quantity price
- ...
- productN quantity price
- totalAmount
101
Courier
John Doe
Black bag 1 1500
Fat Bike 1 5000
Thermos bottle 5 200
Total amount 7500
How can I modify the query?
The proper way to do this, IMO, would be to select 2 datasets. First dataset containing the parent Order data, and the second containing the individual ProductsOrders (product line items) for the Orders in the first dataset.
-- Select Orders
SELECT
[o].[Id] AS [OrderId]
,[o].[DeliveryMethod]
,[o].[OrderDate]
,[u].[UserName]
FROM [Order] [o]
INNER JOIN [Users] [u] ON [o].[UserId] = [u].[Id]
WHERE
[o].[Id] IN(SomeRangeOfOrderIds) -- (Or however you'd like to filter the results)
-- Select Products (product line items)
SELECT
[p].[ProductName]
,[po].[Quantity]
,[p].[Price]
FROM [ProductsOrders] [po]
INNER JOIN [Product] [p] ON [po].[ProductId] = [p].[Id]
WHERE
[po].[OrderId] IN(SameRangeOfOrderIdsAsTheQueryAbove)
...then take those results and display them however you'd like. Will this be appearing in a report? Or on a web page?
I have just implemented this issue in LINQ. Should have noticed that there is many-to-many relationship
var orders = db.Orders.Select(o => new
{
o.Id,
o.DeliveryMethod,
o.OrderDate,
o.UserId,
o.TotalSum,
Products = o.ProductsOrders.Select(p => new { p.Product.ProductName, p.Quantity, p.Product.Price}).ToList()
}).ToList();

SQL - Return group of records from different tables not including specific row

I am new at programming and SQL, so sorry if I do not include enough info.
[I have these 2 tables that are linked by an OrderID. Table1 includes OrderIDs and customer information such as FirstName, LastName, and Address. Table2 includes OrderIDs and order details such as ItemName, Price, and Quantity.
Each OrderID in Table2 might have multiple ItemName entries with the same OrderID.]1
CustInfo
OrderID FirstName LastName Address
1 Bob Pratt 123
2 Jane Doe 456
3 John Smith 789
4 Barbara Walters 147
Orders
OrderID ItemName Price Quantity
1 Milk 4.00 1
1 Eggs 5.00 2
2 Cheese 5.00 1
2 Bread 5.00 1
3 Milk 4.00 2
4 Yogurt 5.00 2
I'm trying to make a query that will send back a list of every Order, listing the OrderID and ItemName among other info, as long as the order doesn't include a specific type of item (which would be in ItemName). So if an OrderID contains 2 ItemName, one of which is the one I do not want, the entire order (OrderID) should not show up in my result.
For example, based off the img included, if I wanted to show all orders as long as they do not have Milk as an ItemName, the result should only show OrderID 2 and 4.
2 Cheese 5.00 1
2 Bread 5.00 1
4 Yogurt 5.00 2
This is what I have tried but this would return OrderIDs even though Milk is technically part of that OrderID.
SELECT OrderID, FirstName, LastName, ItemName, Price, Quantity
FROM CustInfo
JOIN Orders
ON CustInfo.OrderID = Orders.OrderID
WHERE ItemName != 'Milk'
Can you help?
select o.OrderID, o.ItemName, c.FirstName, c.LastName -- include other fields if needed
from Orders o
left join CustInfo c on o.OrderID = c.OrderID
where o.OrderID not in (
select OrderID from Orders where ItemName = 'Milk'
)
If you want the whole order to not show up, rather than just individual rows, you can use WHERE NOT EXISTS:
SELECT o.OrderID, d.ItemName, d.Price, d.Quantity
FROM Orders o
JOIN OrderDetails d ON o.OrderID=d.OrderID
WHERE NOT EXISTS
(
SELECT * FROM OrderDetails d2
WHERE o.OrderID=d2.OrderID
AND d.ItemName = 'Milk'
)
SELECT T1.OrderID, T1.FirstName, T1.LastName, T1.Address, T2.OrderID, T2.ItemName, T2.Price, T2.Quantity
FROM Table2 as T2
LEFT JOIN Table1 as T1 ON T1.OrderID = T2.OrderID
WHERE T2.OrderID <> (SELECT OrderID FROM Table2 WHERE ItemName='Milk');

How get data from 3 tables?

I have 3 tables
collecton, paymentdata, payment
Payment table is computed table and it has only one product data
So, rcvamt and restamt get from payment table
I have following data
Collection:
id(PK) clientid company Client product total note
1 2001 Company1 Client1 Product1 50000 note1
2 2002 Company2 Client2 Product2 60000 note2
3 2003 Company3 Client3 Product3 70000 note3
PaymentData:
wid(PK)wcid(FK) clientid product rcvamt restamt rcvdate nxtdate Note
1 1 2001 Product1 500 49500 10-1-2015 11-2-2015 abc1
2 1 2001 Product1 800 48700 11-2-2015 12-3-2015 xyz1
3 2 2002 Product2 1500 58500 5-3-2015 6-4-2015 qwe1
Payment
id(PK) wid(FK) clientid product rcvamt restamt
1 2 2001 Product1 1300 48700
2 3 2002 Product2 1500 58500
I want to show a report like
clientid company procudt total rcvamt restamt rcvdate nxtdate note
2001 Company1 Product1 50000 1300 48700 11-2-2015 12-3-2015 xyz1
2002 Company2 Product2 60000 1500 58500 5-3-2015 6-4-2015 qwe1
2003 Company3 Product3 70000 - - - - -
I tried to make it simple:
SELECT DISTINCT
C.clientid
, C.company
, C.product
, C.total
, P.rcvamt
, P.restamt
, ( SELECT TOP 1 rcvdate FROM PaymentData AS PD1 WHERE PD1.ClientID=PD.ClientID AND PD1.Product=PD.Product ORDER BY rcvdate DESC)
, ( SELECT TOP 1 nxtdate FROM PaymentData AS PD1 WHERE PD1.ClientID=PD.ClientID AND PD1.Product=PD.Product ORDER BY rcvdate DESC)
, ( SELECT TOP 1 Note FROM PaymentData AS PD1 WHERE PD1.ClientID=PD.ClientID AND PD1.Product=PD.Product ORDER BY rcvdate DESC)
FROM
Collection C
LEFT OUTER JOIN Payment P
ON C.clientid = P.clientid
LEFT OUTER JOIN PaymentData PD
ON P.clientid = PD.clientid
But I don't know all the relationships between the tables.
The Answer to your Question could look like this
Select Collection.clientid
,Collection.company
,Collection.product
,Collection.total
,Payment.rcvamt
,Payment.restamt
,PaymentData.rcvdate
,PaymentData.nxtdate
,PaymentData.Note
From PaymentData
Inner Join (Select wcid
,Max(PaymentData.rcvdate) as rcvdate
,Max(PaymentData.nxtdate) as nxtdate
FROM PaymentData
GROUP BY wcid) AS SubSelect ON PaymentData.wcid = SubSelect.wcid
AND PaymentData.rcvdate = SubSelect.rcvdate
AND PaymentData.nxtdate = SubSelect.nxtdate
Inner Join Payment on PaymentData.wcid = Payment.id
RIGHT OUTER JOIN Collection ON PaymentData.clientid = Collection.clientid
Here the sqlfiddle to prove my Answer.
Something like this should work. It appears you want an aggregation on the restamt, both other fields are the last payment received. I also assume this is SQL Server due to your name. If it's a different db, please provide the
UPDATE: Changed to LEFT JOIN to handle client 3 without products, fixed typo in product. SQL Fiddle: http://sqlfiddle.com/#!3/8ad566/19/0
SELECT
c.clientid,
c.company,
c.product,
c.total,
SUM(pd.rcvamt) AS rcvamt,
LastPayment.restamt,
LastPayment.rcvdate,
LastPayment.nxtDate,
LastPayment.note
FROM Collection c
LEFT OUTER JOIN PaymentData pd
ON pd.wcid = c.id
LEFT OUTER JOIN (
SELECT
wcid,
restamt,
rcvdate,
nxtdate,
Note,
ROW_NUMBER() OVER (PARTITION BY wcid ORDER BY rcvdate DESC) AS RowNum
FROM PaymentData
) LastPayment
ON LastPayment.wcid = c.id
AND LastPayment.RowNum = 1 -- Get last payment info
GROUP BY
c.clientid,
c.company,
c.product,
c.total,
LastPayment.restamt,
LastPayment.rcvdate,
LastPayment.nxtDate,
LastPayment.note
ORDER BY
c.clientid

Cross-multiplying table

I have this SQL code and I want to show the sum of each item on its charge slip and on their receipt:
select item_description, sum(receipt_qty) as Samp1, sum(chargeSlip_qty) as Samp2
from Items inner join Receipt_Detail on (Receipt_Detail.item_number =
Items.item_number)
inner join ChargeSlip_Detail on (ChargeSlip_Detail.item_number =
Items.item_number)
group by item_description
It produces this output:
Acetazolamide 2681 1730
Ascorbic Acid 1512 651
Paracetamol 1370 742
Silk 576 952
But it should be:
Acetazolamide 383 173
Ascorbic Acid 216 93
Paracetamol 274 106
Silk 96 238
What's wrong with my code?
Since you are joining tables, you might have a one-to-many relationship that is causing the problem when you then get the sum(). So you can use subqueries to get the result. This will get the sum() for the receipt and chargeslip for each item_number and then you join that back to your items table to get the final result:
select i.item_description,
r.Samp1,
c.Samp2
from Items i
inner join
(
select sum(receipt_qty) Samp1,
item_number
from Receipt_Detail
group by item_number
) r
on r.item_number = i.item_number
inner join
(
select sum(chargeSlip_qty) Samp2,
item_number
from ChargeSlip_Detail
group by item_number
) c
on c.item_number = i.item_number
Do the GROUP BYs first, per Item_Number, so you don't multiply out rows from Receipt_Detail and ChargeSlip_Detail. That is, you generate the SUM values per Item_Number before JOINing back to Items
select
I.item_description,
R.Samp1,
C.Samp2
from
Items I
inner join
(SELECT item_number, sum(receipt_qty) as Samp1
FROM Receipt_Detail
GROUP BY item_number
) R
on (R.item_number = I.item_number)
inner join
(SELECT item_number, sum(chargeSlip_qty) as Samp2
FROM ChargeSlip_Detail
GROUP BY item_number
) C
on (C.item_number = I.item_number)
A left join returns rows from the left table, and for each row in the left table, all matching rows in the right table.
So for example:
create table Customers (name varchar(50));
insert Customers values
('Tim'),
('John'),
('Spike');
create table Orders (customer_name varchar(50), product varchar(50));
insert Orders values (
('Tim', 'Guitar'),
('John', 'Drums'),
('John', 'Trumpet');
create table Addresses (customer_name varchar(50), address varchar(50));
insert Addresses values (
('Tim', 'Penny Lane 1'),
('John', 'Abbey Road 1'),
('John', 'Abbey Road 2');
Then if you run:
select c.name
, count(o.product) as Products
, count(a.address) as Addresses
from Customers c
left join Orders o on o.customer_name = c.name
left join Addresses a on a.customer_name = c.name
group by name
You get:
name Products Addresses
Tim 1 1
John 4 4
Spike 0 0
But John doesn't have 4 products!
If you run without the group by, you can see why the counts are off:
select *
from Customers c
left join Orders o on o.customer_name = c.name
left join Addresses a on a.customer_name = c.name
You get:
name customer_name product customer_name address
Tim Tim Guitar Tim Penny Lane 1
John John Drums John Abbey Road 1
John John Drums John Abbey Road 2
John John Trumpet John Abbey Road 1
John John Trumpet John Abbey Road 2
Spike NULL NULL NULL NULL
As you can see, the joins end up repeating each other. For each product, the list of addresses is repeated. That gives you the wrong counts. To solve this problem, use one of the excellent other answers:
select c.name
, o.order_count
, a.address_count
from Customers c
left join
(
select customer_name
, count(*) as order_count
from Orders
group by
customer_name
) o
on o.customer_name = c.name
left join
(
select customer_name
, count(*) as address_count
from Addresses
group by
customer_name
) a
on a.customer_name = c.name
The subqueries ensure only one row is joined per customer. The result is much better:
name order_count address_count
Tim 1 1
John 2 2
Spike NULL NULL

Full Join and sum 2 queries in access

I am very new to this type of complicated joins and summing functions.
I had 2 table queries which have same fields (i.e: ProdID, ProdName, NetQty, Unit)
The 1st one query contains OpStock and the 2nd one contains Purchase.
I want to add this 2 table query to make a single table so that I can able to see current stock.
The data looks like this for 1 st table is:
ProdID ProdName Qty
100 Rose 700
101 Lilly 550
103 Jasmine 600
105 Lavender 400
The data looks like this for 2nd table is:
ProdID ProdName Qty
100 Rose 400
101 Lilly 250
104 Lotus 1000
106 MariGold 400
The final data looks like this for 3rd table is:
ProdID ProdName Qty
100 Rose 1100
101 Lilly 800
103 Jasmine 600
104 Lotus 1000
105 Lavender 400
106 MariGold 400
How can i achieve this one using sql for access2003.
Thanks.
I am very sorry Ciaran,
This is purely access used for vb.net
Here Is my access query1
SELECT sp_OpenIandP_Prod.ProdID,
sp_OpenIandP_Prod.ProdName,
Sum(([sp_OpenIandP_Prod.SumOfNetQty]-[sp_OpenSales_Prod.SumOfNetQty])) AS NetQty,
sp_OpenIandP_Prod.UnitSName
FROM sp_OpenIandP_Prod
INNER JOIN sp_OpenSales_Prod ON sp_OpenIandP_Prod.ProdID=sp_OpenSales_Prod.ProdID
GROUP BY sp_OpenIandP_Prod.ProdID,
sp_OpenIandP_Prod.ProdName,
sp_OpenIandP_Prod.UnitSName;
The 1st query result would be like this:
ProdID ProdName NetQty UnitSName
1 Rose 0 Kgs
2 Lilly 7125 Mts
3 Lotus 12374 Nos
The second query is:
SELECT Products.ProdID, Products.ProdName,
Sum(OPDDetails.NetQty) AS SumOfNetQty, Units.UnitSName
FROM Units
INNER JOIN (Products
INNER JOIN (Brands
INNER JOIN OPDDetails ON Brands.BrID=OPDDetails.BrandID)
ON Products.ProdID=Brands.ProdID)
ON Units.UnitID=Products.UnitID
WHERE (((OPDDetails.PurID)>0)
AND ((OPDDetails.OPDDate)>=[StartDate] And (OPDDetails.OPDDate)<=[EndDate]))
GROUP BY Products.ProdID, Products.ProdName, Units.UnitSName;
and the result set would be like this:
ProdID ProdName SumOfNetQty UnitSName
1 Rose 1800 Kgs
2 Lilly 21000 Mts
I got the above result easily.
But it is not as much easy to get like this:
ProdID ProdName SumOfNetQty UnitSName
1 Rose 1800 Kgs
2 Lilly 28125 Mts
3 Lotus 12374 Nos
That's all. Thanks again.
This is not a vb.net question, however you need to use a UNION
Select ProdId, ProdName, Sum(Qty) As QtySum
From (Select ProdId, ProdName, Qty From TableA
Union All
Select ProdId, ProdName, Qty From TableB) DerrivedView
Group By ProdId, ProdName
You can create a UNION between your 2 queries then group by product:
SELECT ProdID, ProdName, Sum(NetQty) As NetQty, UnitSName
FROM
(
SELECT sp_OpenIandP_Prod.ProdID,
sp_OpenIandP_Prod.ProdName,
Sum(([sp_OpenIandP_Prod.SumOfNetQty]-[sp_OpenSales_Prod.SumOfNetQty])) AS NetQty,
sp_OpenIandP_Prod.UnitSName
FROM sp_OpenIandP_Prod
INNER JOIN sp_OpenSales_Prod ON sp_OpenIandP_Prod.ProdID=sp_OpenSales_Prod.ProdID
GROUP BY sp_OpenIandP_Prod.ProdID,
sp_OpenIandP_Prod.ProdName,
sp_OpenIandP_Prod.UnitSName
UNION ALL
SELECT Products.ProdID, Products.ProdName,
Sum(OPDDetails.NetQty) AS NetQty, Units.UnitSName
FROM Units
INNER JOIN (Products
INNER JOIN (Brands
INNER JOIN OPDDetails ON Brands.BrID=OPDDetails.BrandID)
ON Products.ProdID=Brands.ProdID)
ON Units.UnitID=Products.UnitID
WHERE (((OPDDetails.PurID)>0)
AND ((OPDDetails.OPDDate)>=[StartDate] And (OPDDetails.OPDDate)<=[EndDate]))
GROUP BY Products.ProdID, Products.ProdName, Units.UnitSName
)
GROUP BY ProdID, ProdName, UnitSName