Join with count conditition - sql

Consider the following Schema:
Table Invoices:
Id|TotalSum|Status
1|12|1
1|13|0
Table ClientInvoices:
Id|ClientId|InvoiceId
Table "invoices" may contain multiple invoices with the same Id but with different status (there is unique constraint on Id and status.). Status is integer that represents enum (0 - inactive, 1 - active)
I want to get all the client invoices with price changes if any:
ClientId|InvoiceId|SumBefore|SumAfter
If there is no inactive invoices then PreviousPrice should be null. I am trying to achieve this using the following query:
SELECT Clients.Id AS ClientId,
Invoice.Id AS InvoiceId,
ActualInvoice.TotalSum AS SumBefore,
InActiveInvoice.TotalSum AS SumAfter
FROM Invoices
LEFT JOIN ClientInvoices AS ActualInvoice ON ActualInvoice.InvoiceId AND Status = 1
LEFT JOIN ClientInvoices AS Inactive ON ActualInvoice.InvoiceId AND Status = 0
LEFT JOIN Clients ON Clients.Id = ClientInvoices.ClientId
This works well if there are two records for one invoice: past(inative) and current. However if there is only one invoice - it has active status and using the query above I get SumBefore = null and SumAfter = value which should be in SumBefore column.
It would be great if I could specify to join record on condition if count of row of that record is f.e. 1. Is that possible?

I think this should work:
select c.ClientId as ClientId,
a.InvoiceId as InvoiceId,
a.TotalSum as SumBefore,
b.TotalSum as SumAfter
from Invoices a left join Invoices b
on a.InvoiceId = b.InvoiceId
and a.status = 1 and b.status = 0
join ClientsInvoices c on c.InvoiceId = a.InvoiceId

Related

Retrive total quantity of ordered Items using SQL

I am trying to get Quantity of an item that is requested and yet not received.
Process of purchasing and Item is as
Created Purchase Order > Ordered Items saved in ItemsRequested table
Created Purchase Invoice > Received Items are in ReceivedItems table
Using below query to get Items Requested quantity but the result is wrong. What is logically wrong in query ?
select Sum(ItemRequested.Quantity) as OnOrder from ItemRequested
Inner join PurchaseOrders on ItemRequested.PurchaseOrderId = PurchaseOrders.PurchaseOrderId
Inner join PurchaseInvoices on PurchaseInvoices.PurchaseOrderId = PurchaseOrders.PurchaseOrderId
inner join ItemReceived on ItemReceived.PurchaseInvoiceId = PurchaseInvoices.PurchaseInvoiceId
where PurchaseOrders.DateOrdered >'2019-04-15 11:37:26.537' and ItemRequested.ItemId=3439
The definition of the items you need is "requested but not yet received". So you have to retrieve the rows that are in ItemRequested but absent from ItemReceived.
Thus your query should be more like this
SELECT SUM(ItemRequested.Quantity) AS OnOrder
FROM ItemRequested
INNER JOIN PurchaseOrders ON ItemRequested.PurchaseOrderId = PurchaseOrders.PurchaseOrderId
WHERE PurchaseOrders.DateOrdered >'2019-04-15 11:37:26.537'
AND ItemRequested.ItemId=3439
-- Exclude the PO having received items
AND NOT EXISTS (
SELECT 1
FROM PurchaseInvoices
INNER JOIN ItemReceived ON ItemReceived.PurchaseInvoiceId = PurchaseInvoices.PurchaseInvoiceId
WHERE PurchaseInvoices.PurchaseOrderId = PurchaseOrders.PurchaseOrderId
)

Is there a way to condense rows further in a SQL query through grouping on more than one column?

Below you'll see a SQL query that successfully returns the revenue for a given merchant... unless any of that merchant's invoices happens to have more than one successful transaction. In that case, it will sum each invoice items revenue times its number of successful transactions.
If I wasn't already joining merchants and grouping by merchant.id, I would group by invoices.id
When you give GROUP BY multiple columns, it will only group together rows that are the same in both columns, resulting in a table that is less condensed rather than more condensed. Is there a way to run something along the lines of a second group by on this query such that it won't add the revenue any invoice's invoice items more than once?
SELECT merchants.id,
sum(invoice_items.unit_price * invoice_items.quantity) as total_revenue FROM merchants
INNER JOIN invoices
ON invoices.merchant_id = merchants.id
INNER JOIN invoice_items
ON invoice_items.invoice_id = invoices.id
INNER JOIN transactions
ON transactions.invoice_id = invoices.id
WHERE transactions.result = "success"
AND merchants.id = ?
GROUP BY merchants.id;
You have a N-to-M relationship that is causing invoices items to be sumed more than once. One solution would be to move the computation of the total per invoice to a subquery :
SELECT merchants.id, invoice_items_total.total_revenue
FROM merchants
INNER JOIN invoices
ON invoices.merchant_id = merchants.id
INNER JOIN
(
SELECT invoice.id, sum(unit_price * quantity) as total_revenue
FROM invoices_item
GROUP BY invoice.id
) invoice_items_total ON invoice_items_total.invoice_id = invoices.id
INNER JOIN transactions
ON transactions.invoice_id = invoices.id
WHERE transactions.result = "success"
AND merchants.id = ?
GROUP BY merchants.id;

SQL select item in table that are present in an other table with a quantity > 0

I'm trying this:
Table sale
id itemid
Table inv
id itemid qty
I need to have the number of item in table sale that are in table inv with a qty higher then 0
I try something like this without success
SELECT count(sale.id)
FROM sale
LEFT OUTER JOIN inv ON sale.itemid = inv.itemid
WHERE inv.qty > 0
The query run forever and never return me a result.
Both table have 1000s of input.
I think the problem here is your LEFT OUTER join. Change this to an INNER JOIN and you'll only get the records you're looking for.
SELECT count(sale.id)
FROM sale
INNER JOIN inv ON sale.itemid = inv.itemid
WHERE inv.qty > 0
Try this:
select count(*)
from sale
where id IN(SELECT id from inv where qty > 0)
This will give you the number of rows in "sale", that have a corresponding "id" value in "inv" with a qty greater than 0.

Rails/SQL: finding invoices by checking two sums

I have an Invoice model that has_many lines and has_many payments.
Invoice:
id
ref
Line:
invoice_id:
total (decimal)
Payment:
invoice_id:
total(decimal)
I need to find all paid invoices. So I'm doing the following:
Invoice.joins(:lines, :payments).having(' sum(lines.total) = sum(payments.total').group('invoices.id')
Which queries:
SELECT *
FROM "invoices"
INNER JOIN "lines" ON "lines"."invoice_id" = "invoices"."id"
INNER JOIN "payments" ON "payments"."invoice_id" = "invoices"."id"
GROUP BY invoices.id
HAVING sum(lines.total) = sum(payments.total)
But it always return empty array even if there are invoices fully paid.
Is something wrong with my code?
If you join to more than one table with a 1:n relationship, the joined rows can multiply each other.
This related answer has more detailed explanation for the problem:
Two SQL LEFT JOINS produce incorrect result
To avoid that, sum the totals before you join. This way you join to exactly 1 (or 0) rows, and nothing is multiplied. Not only correct, also considerably faster.
SELECT i.*, l.sum_total
FROM invoices i
JOIN (
SELECT invoice_id, sum(total) AS sum_total
FROM lines
GROUP BY 1
) l ON l.invoice_id = i.id
JOIN (
SELECT invoice_id, sum(total) AS sum_total
FROM payments
GROUP BY 1
) p ON p.invoice_id = i.id
WHERE l.sum_total = p.sum_total;
Using [INNER] JOIN, not LEFT [OUTER] JOIN on purpose. Invoices that do not have any lines or payments are not of interest to begin with. Since we want "paid" invoices. For lack of definition and by the looks of the provided query, I am assuming that means invoices with actual lines and payments, both totaling the same.
If one invoice have a line and two payments fully paid like this:
lines:
id total invoice_id
1 30 1
payments:
id total invoice_id
1 10 1
2 20 1
Then join lines and payments to invoice with invoce_id will get 2 rows like this:
payment_id payment_total line_id line_total invoice_id
1 10 1 30 1
2 20 1 30 1
So the sum of line_total will not equal to sum of payment_total.
To get all paid invoice could use exists instead of joins:
Invoice.where(
"exists
(select 1 from
(select invoice_id
from (select invoice_id,sum(total) as line_total
from lines
group by invoice_id) as l
inner join (select invoice_id,sum(total) as payment_total
from payments
group by invoice_id) as p
on l.invoice_id = p.invoice_id
where payment_total = line_total) as paid
where invoices.id = paid.id) ")
The sub_query paid will get all paid invoice_ids.

T-SQL Query Join on different column depending on case

I have a legacy system that has a sales table and a customer table, CMS and CUST respectively. I need to query for the shipped to address based on different criteria. The customer table treats each address as its own customer. So if I have a billing address, then a shipping address, those will both be different CUSTNUM's. The CMS table has columns CUSTNUM and SHIPNUM. If the sales order uses the billing address as the shipping address, SHIPNUM = 0. If those 2 address are different, SHIPNUM = a different customer number than CUSTNUM. I'm trying to write a query that joins CUST to CMS based on the case of SHIPNUM being > 0 or not. My original query just used CUSTNUM, and ignored the SHIPNUM. My new query is syntactically correct and executes, but the row count returned is 2860 vs 3590 for the old query. The old join statement is just the commented out line :ON CMS.CUSTNUM = CUST.CUSTNUM.
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
JOIN CUST
ON CUST.CUSTNUM =
CASE
WHEN CMS.SHIPNUM > 0
THEN CMS.SHIPNUM
Else CMS.CUSTNUM
END
-- ON CMS.CUSTNUM = CUST.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
Here is an example from the CMS table;
CUSTNUM SHIPNUM ORDERNO
41863 77394 828509 <--Different billing and shipping address
43242 69291 776888 <--Different billing and shipping address
2356 0 765022 <--Same billing and shipping address
Any thoughts on how to make this work?
PS Here is the original query in its entirety.
select
CUST.CUSTNUM as Customer,
CMS.CUSTNUM,
CMS.SHIPNUM,
CUST.CTYPE,
CMS.ORDERNO,
CMS.ODR_DATE,
LTRIM(RTRIM(CUST.FIRSTNAME)) as First,
LTRIM(RTRIM(CUST.LASTNAME)) as Last,
LTRIM(RTRIM(CUST.COMPANY)) as Company,
LTRIM(RTRIM(CUST.PHONE)) as Phone,
LTRIM(RTRIM(CUST.EMAIL)) as Email,
LTRIM(RTRIM(CUST.ADDR)) as ADDR1,
LTRIM(RTRIM(CUST.ADDR2)) as ADDR2,
LTRIM(RTRIM(CUST.ADDR3)) as ADDR3,
LTRIM(RTRIM(CUST.CITY)) as City,
LTRIM(RTRIM(CUST.State)) as State,
LTRIM(RTRIM(CUST.ZIPCODE)) as Zip,
LTRIM(RTRIM(C.NAME)) as Country,
LOT.ITEMNO,
LOT.LOTNO,
COUNT(LOT.ITEMNO) as Quantity
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
LEFT JOIN CUST
ON CMS.CUSTNUM = CUST.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
where
(
CUST.CTYPE IN ('P','W','Z')
)
AND
(
LOT.LOTNO IN ('1000001','20001','300001')
)
GROUP BY
CMS.ORDERNO,
CUST.CUSTNUM,
CMS.CUSTNUM,
CMS.SHIPNUM,
CUST.CTYPE,
CUST.FIRSTNAME,
CMS.ODR_DATE,
CUST.LASTNAME,
CUST.COMPANY,
CUST.PHONE,
CUST.EMAIL,
CUST.ADDR,
CUST.ADDR2,
CUST.ADDR3,
LOT.ITEMNO,
CUST.CITY,
CUST.STATE,
CUST.ZIPCODE,
C.NAME,
LOT.LOTNO
ORDER BY
Customer,
CMS.ORDERNO,
LOT.ITEMNO,
LOT.LOTNO
If you use INNER JOIN you have risk to exclude raws which have no reference in another table. This could be caused by any of 2 another joins in your expression - comment them and try again. If you still receive less records you should check consistency of your data - one table has values which not correspond to values in another table.
BTW, I don't like CASE in JOIN expression simply because it looks ugly. What do you thinK about this expression which seemed to do the job too:
LEFT JOIN CUST
ON CUST.CUSTNUM = COALESCE(NULLIF(CMS.SHIPNUM, 0), CMS.CUSTNUM)
You could use a CTE like this.
WITH cte (ORDERNO, SHIPNUM) AS
(
SELECT ORDERNUM, SHIPNUM = CASE
WHEN CMS.SHIPNUM > 0
FROM CMS
Fewer records join using your altered criteria, there are some CMS.SHIPNUM values that don't have matching CUSTNUM in the CUST table.
To find the problematic entries change from INNER to OUTER join and add WHERE criteria, something like:
LEFT JOIN CUST
ON CUST.CUSTNUM = CASE WHEN CMS.SHIPNUM > 0 THEN CMS.SHIPNUM
ELSE CMS.CUSTNUM
END
WHERE CUST.CUSTNUM IS NULL
AND CMS.SHIPNUM > 0
Edit: You'll have to remove the INNER JOIN to COUNTRY to see the unmatched from your updated JOIN since it joins on a field from the customer table, and make sure to have the SHIPNUM field in your SELECT.
your query looks correct but not sure why it is not working, try left joinCUST table twiceone on shipping and the other on billing and then write the case statement for each customer column.
select
LTRIM(RTRIM(case when CMS.SHIPNUM > 0 THEN CUST.FIRSTNAME else CUST_BILL.FIRSTNAME end )) as First,
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
left JOIN CUST CUST
ON CUST.CUSTNUM = CMS.SHIPNUM
left JOIN CUST CUST_BILL
ON CMS.CUSTNUM = CUST_BILL.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
if it still outputs less rows then something else is wrong