Multiple Filter inside SELECT SQL Statement - mysql

I'm writing a SQL Statement to get some values in a Recordset, which I'll use to transfer the result into TextBoxes on a Form in Excel. The tables involved are:
Customers -> CustomerId, FirstName, LastName, TelNumber
Invoice -> InvoiceId, CustomerId, CarModel, CarColor, CarPlate
Repairs -> RepairId, InvoiceId, TypeOfRepair, PartOfCar, Price
Services -> ServiceId, InvoiceId, Date, Status
When a Customer comes to the Garage, an Invoice is created, which is associated with this customer. An invoice can have many Repairs. The customer goes away without repairing the car, but the invoice is there. If the customer decides to repair the car, then a Service is created, which starts with the Status "working on it...". When the service is done, the status change to "Waiting for Check Out..."
I want to use a SQL Statement to retrieve the following Values (columns) for a specific InvoiceId:
CarModel, Color, Plate, CustomerName (FirstName LastName), PaintingTotalValue (where 'Painting' is one type in the column 'Type'), OtherTotalValue (total price of all the other types of repairs in this invoice), total price (total price, that is, painting + other ones).
I wrote the following, to get the values, but I don't know how to get the PaintingTotalValue and OtherTotalVAlue.
SELECT i.CarModel, i.Color, i.Plate, CONCAT(c.FirstName,' ',c.LastName) AS Name, FORMAT(SUM(r.Price),2) AS TotalPrice
FROM Services AS s INNER JOIN Invoices AS i ON s.invoiceId=i.invoiceId
INNER JOIN Repairs AS r ON s.invoiceId=r.invoiceId
INNER JOIN Customers AS c ON i.customerId=c.customerId
WHERE s.invoiceId = 15

Use CASE WHEN in your SELECT clause, to select the value that's conditional to the type:
SELECT
...
CASE WHEN r.Type = 'Painting' THEN r.Price ELSE 0 END PaintWorkPrice,
CASE WHEN r.Type <> 'Painting' THEN r.Price ELSE 0 END OtherWorkPrice,
FROM ...
That's one thing.
The other thing is that you're not selecting anything from the Services table, and making your query much more complicated than it needs to be.
If you can modify the schema, remove the ServiceId primary key field, and use Services.InvoiceId as a primary key instead: that will enforce the 1:1 relationship naturally.
FROM Repairs r
INNER JOIN Invoices i ON r.InvoiceId = i.InvoiceId
INNER JOIN Customers c ON i.CustomerId = c.CustomerId
The data you want to aggregate is granular to Repairs, so you select FROM that, and then move your way through the foreign keys up to Customers.
SELECT
i.CarModel
,i.Color
,i.Plate
,CONCAT(c.FirstName,' ',c.LastName) Name
,CASE WHEN r.Type = 'Painting' THEN r.Price ELSE 0 END PaintWorkPrice
,CASE WHEN r.Type <> 'Painting' THEN r.Price ELSE 0 END OtherWorkPrice
,r.Price
FROM Repairs r
INNER JOIN Invoices i ON r.InvoiceId = i.InvoiceId
INNER JOIN Customers c ON i.CustomerId = c.CustomerId
That's not aggregated yet: there's a record for each repair, for every invoice, under every customer that has an invoice. That part is the sub-query. If you have a parameter, that's where you use it.
WHERE i.InvoiceId = pInvoiceId
If you're just hard-coding an ID, that's where you do it too.
Now type SELECT q.* FROM ( on the line above, and ) q under the WHERE clause, then replace the q.* with the fields you're not aggregating - and aggregate the others. The result should be something like this:
SELECT
q.CarModel
,q.Color
,q.Plate
,q.Name
,SUM(q.PaintWorkPrice) PaintAmount
,SUM(q.OtherWorkPrice) OtherAmount
,SUM(q.Price) TotalAmount
FROM (
SELECT
i.CarModel
,i.Color
,i.Plate
,CONCAT(c.FirstName,' ',c.LastName) Name
,CASE WHEN r.Type = 'Painting' THEN r.Price ELSE 0 END PaintWorkPrice
,CASE WHEN r.Type <> 'Painting' THEN r.Price ELSE 0 END OtherWorkPrice
,r.Price
FROM Repairs r
INNER JOIN Invoices i ON r.InvoiceId = i.InvoiceId
INNER JOIN Customers c ON i.CustomerId = c.CustomerId
WHERE i.InvoiceId = 15
) q
GROUP BY
q.CarModel
,q.Color
,q.Plate
,q.Name

Related

Is it possible that each condition in "case when" has a different table?

I have a query like below
select
COALESCE(Clinic,'total') as Clinic,
sum(Non_Billable) as Non_Billable,
sum(initial_Non_Billable) as initial_Non_Billable,
sum(Non_Billable)/NULLIF(sum(initial_Non_Billable),0) as Non_Billable_initial_revenue
FROM (
select
businesses.label as Clinic,
sum(CASE
WHEN appointment_types.category IN (
'Others',
'Non-Billable')
and appointment_types.name like '%initial%'
then invoices.net_amount ELSE 0 END)
as Non_Billable,
count(CASE
WHEN appointment_types.category IN (
'Others',
'Non-Billable')
and appointment_types.name like '%initial%'
then appointment_types.name ELSE null END)
as initial_Non_Billable
FROM
individual_appointments
INNER join appointment_types on
appointment_types.id = individual_appointments.appointment_type_id
inner join invoices on
invoices.appointment_id = individual_appointments.id
inner join businesses on
businesses.id = individual_appointments.business_id
group by
businesses.label,
appointment_types.name,
appointment_types.category,
invoices.net_amount
)x
group by
ROLLUP(Clinic);
is it possible for each condition in "case when then" to have a different table?
example: for "Non_Billable" only want from individual_appointment, appointment_types, invoices and businesses table only.
while "initial_Non_Billable" only wants from individual_appointment, appointment_types, and businesses table.
can it be like that?
If possible, how? can anyone give an example?
From what I see, I suppose the problem here is that there can be many invoices per appointment, so when you join invoices you get appointments multifold which also multiplies your counts.
You don't really want to join invoices hence, but invoice sums. I.e. aggregate before joining.
Then, you are only interested in non-billable appointments, so you can put that criteria into your ON clauses and outer join in order to get clinics without such appointments.
select
coalesce(b.label, 'total') as clinic,
coalesce(sum(i.total_net_amount), 0) as initial_non_billable_sum,
count(at.id) as initial_non_billable_count,
avg(i.total_net_amount) as initial_non_billable_average
from businesses b
left join individual_appointments ia on ia.business_id = b.id
left join appointment_types at on at.id = ia.appointment_type_id
and at.category in ('others', 'non-billable')
and at.name like '%initial%'
left join
(
select appointment_id, sum(net_amount) as total_net_amount
from invoices
group by appointment_id
) i on i.appointment_id = ia.id
group by rollup(b.label)
order by b.label nulls last;

SQL Query help (possibly use JOIN?)

Table A [PATIENT] has columns [PATID], [FIRSTVISITDATE]
Table B [APPT] has columns [APPTID], [PATID], [CREATEDATE]
Table C [NOTE] has columns [NOTEID], [NOTETEXT]
Table D [PROCS] has column [PROCID], [PATID]
Table E [CHARGE] has columns [CHARGEID], [AMOUNT]
I need to sum CHARGE(AMOUNT) by PATID for all PATIENTS where the NOTE.NOTETEXT contains 'text' and one of the APPT for a PATIENT containing the 'text' has an APPT.CREATEDATE = to the PATIENT.FIRSTVISITDATE
Simply put I need to SUM the charges for PATIENTS if they have an appointment with 'text' in their notes and the appointment with that 'text' was their first visit to the office
Other key points:
CHARGE.CHARGEID = PROC.PROCID
NOTE.NOTEID = APPT.APPTID
With my limited knowledge of SQL I was able to sum for all patients regardless if the 'text' was included in their first appointments notes and for that I used:
select (SUM(AMOUNT)) as 'Cash Payments' from CHARGE where CHARGEID in
(select PROCID from PROC where PATID in
(select PATID from APPT where APPTID in
(select NOTEID from NOTE where NOTETEXT like '%text%')))
You can use the GROUP BY clause to group the AMOUNT by patient. You can filter your patients to just the ones with the text in the notes and FIRSTVISITDATE = CREATEDATE using an inner query that joins the tables on those conditions.
I have not tested the following query, but it should do what you're asking.
SELECT pa.PATIENT, SUM(c.AMOUNT) AS 'Cash Payments'
FROM PATIENT pa
INNER JOIN PROCS pr
ON pa.PATID = pr.PATID
INNER JOIN CHARGE c
ON pr.PROCID = c.CHARGEID
WHERE pa.PATIENT IN (
SELECT pa.PATIENT
FROM PATIENT pa
INNER JOIN APPT a
ON pa.PATID = a.PATID
AND pa.FIRSTVISITDATE = a.CREATEDATE
INNER JOIN NOTE n
ON a.APPTID = n.NOTEID
WHERE n.NOTETEXT LIKE '%text%'
)
GROUP BY pa.PATIENT;

Splitting transaction order lines in two categories

I'm building a bookkeeping application for a friend that runs a shop-in-shop. Basically, every product is owned by a seller.
They want an overview where they can see the value of how much money in a transaction was paid for their products (owned by seller 1) and how much money went to other products (seller != 1).
Here's the ERD:
I tried the following query:
SELECT transaction.id, SUM(orderline.count), SUM(orderline.price), SUM(o1.price), SUM(o2.price), transaction.date
FROM transaction
JOIN orderline
ON orderline.transaction_id = transaction.id
JOIN product
ON product.barcode = orderline.product_barcode
JOIN orderline o1
ON o1.transaction_id = transaction.id AND o1.product_barcode IN (SELECT barcode FROM product WHERE seller_id = 1)
JOIN orderline o2
ON o2.transaction_id = transaction.id AND o2.product_barcode IN (SELECT barcode FROM product WHERE seller_id != 1)
GROUP BY transaction.id
Unfortunately, this only returns one transaction instead of all, and it doesn't return the correct sums of the prices either:
In for transaction 77, the value of SUM(o1.price) should be 4.5, and that of SUM(o2.price) should be 0.95. The value of SUM(orderline.price) is correct.
I would like to instead get all transactions, and then the total value, value of products owned by seller 1 and value of products owned by seller != 1 (products should be part of the transaction, of course).
SELECT TRANSACTION.id,
Sum(orderline.count),
Sum(orderline.price),
Sum( case when product.seller_id = 1 then orderline.price else 0 end ),
Sum( case when product.seller_id <> 1 then orderline.price else 0 end),
TRANSACTION.date
FROM TRANSACTION
JOIN orderline
ON orderline.transaction_id = TRANSACTION.id
JOIN product
ON product.barcode = orderline.product_barcode
GROUP BY TRANSACTION.id , TRANSACTION.date

MySQL SELECT all people matching X and then all in each person's household

I have two tables I need to query to print a list - PURCHASE HISTORY and PEOPLE
PURCHASE HISTORY
----------------
purchase_txnid...purchase_date...purchase_userid...purchase_productid
PEOPLE
------
people_householdid...people_userid...people_street...people_city...people_state...(etc)
I need to get everyone in householdid where someone has purchased productid = "X" (basically a list of everyone at any house where SOMEONE has purchased the product) and then display each of their purchase histories.
PURCHASE HISTORY has seven distinct products and 320,000 records.
Right now, I'm querying all people, RIGHT JOINing the purchase history to select people who purchased "X", and then iterating through the results and, with each result, querying to SELECT everyone in that household and each of their purchase histories. It's extremely slow as there are 45,000 people and 320,000 purchases.
Any ideas as to how I can merge this into a single query or optimize it?
UPDATE
Here are the queries:
$buyers = $db->get_results( " SELECT people.*, TIMESTAMPDIFF( YEAR, birth_date, CURDATE() ) AS age FROM people
RIGHT JOIN purchase_history ON purchase_history.purchase_userid = people.userid
WHERE people.region=$region AND purchase_history.purchase_productid = 'D'
GROUP BY people.userid
ORDER BY street_name ASC, street_suffix ASC, street_num ASC, street_unit ASC, household_id DESC, birth_date ASC
" );
foreach( $buyers as $buyer ){
$in_household = $db->get_results( "SELECT *, TIMESTAMPDIFF( YEAR, birth_date, CURDATE() ) AS person_age FROM people WHERE household_id = '$buyer->household_id' ORDER BY birth_date ASC" );
foreach( $in_household as $person ){
$purchases = $db->get_results( "SELECT * FROM purchase_history WHERE purchase_userid='$person->userid'" );
}
}
SELECT DISTINCT peopleB.userid, peopleB.* FROM purchase
JOIN people AS peopleA ON peopleA.people_userid = purchase_userid
JOIN people AS peopleB ON peopleB.people_householdid = peopleA.householdid
WHERE purchase.purchase_productid = "X"
You can speed up this query by adding these indices to your tables:
CREATE INDEX productid ON purchase (purchase_productid)
CREATE INDEX householdid ON people (people_householdid)
I assume people_userid is already your primary key in people, otherwise you should make it unique:
CREATE UNIQUE INDEX userid ON people (people_userid)
EDIT - You asked for the complete purchase history:
SELECT peopleB.*, phB.* FROM purchase_history AS phA
JOIN people AS peopleA ON peopleA.people_userid = phA.purchase_userid
JOIN people AS peopleB ON peopleB.people_householdid = peopleA.householdid
LEFT JOIN purchase_history AS phB ON phB.purchase_userid = peopleB.userID
GROUP BY peopleB.id, phB.purchase_txnid
WHERE purchase.purchase_productid = "X"
Note that this will give you people's data once for every purchase they made.
Also, I don't know if that query really works the way I want it to or how fast/slow it will be. You could also try reversing the colums in the GROUP BY line and see if that is faster.
You can do this with a subquery and a join:
select p.*
from people p join
(select distinct p.people_householdid
from PurchaseHistory ph join
People p
on ph.purchase_userid = p.people_userid
where ph.purchase_productid = 'X'
) ph
on ph.people_userid = p.people_userid;
EDIT:
If you want to flag the member(s) that made the purchase:
select p.*,
max(p.people_userid = ph.people_userid) as IsPurchaser
from people p join
(select p.people_householdid, p.people_userid
from PurchaseHistory ph join
People p
on ph.purchase_userid = p.people_userid
where ph.purchase_productid = 'X'
) ph
on ph.people_userid = p.people_userid
group by p.people_userid

Using two columns with the same foreign key

I have a database, which has 5 tables, but I have problem. In the shipment table, there are two columns, source and destination, which are both foreign key references to the route table, but when I select a record from the shipment table it will only show the same routename for both.
Here is the code:
SELECT fare, commission, driver,shipment._date, routename, vehiclenumber, productname, source, destination, routename,ownername
FROM route, shipment, product, vehicle,owner
WHERE vehicle.vehicleid = shipment.vehicle
AND shipment.source
and vehicle.owner=owner.ownerid
AND shipment.destination = route.routeid
AND shipment.product = product.productid
AND vehicle.vehiclenumber = 'nk-234'
ORDER BY _date
LIMIT 0 , 30
To connect a record in one table (one shipment) to more than one record in another table (two routes) you would ideally use explicit JOINs against the route table twice (or more, for however many links you happen to need).
Here's a quick modification to your query that demonstrates usage. Pay particular attention to the two routename columns in the SELECT and the two JOINs to the route table in the FROM:
SELECT fare,
commission,
driver,
shipment._date,
RS.routename, <-- field from first join
vehiclenumber,
productname,
source,
destination,
RD.routename, <-- field from second join
ownername
FROM shipment
JOIN route RS ON RS.routeID = shipment.source <-- join 1; source
JOIN route RD ON RD.routeID = shipment.destination, <-- join 2; destination
product,
vehicle,
owner
WHERE vehicle.vehicleid = shipment.vehicle
AND vehicle.owner = owner.ownerid
AND shipment.product = product.productid
AND vehicle.vehiclenumber = 'nk-234'
ORDER BY _date
LIMIT 0 , 30
Obviously, this might not match your needs exactly, because I don't know what tables the other selected fields are from to be able to make sure they are all accounted for...
This is because you select the fields from one row in shipment, in orer to actually get any effect from your foreign keys you need to use a few joins
http://dev.mysql.com/doc/refman/5.5/en/join.html
Using the old-style (non-preferred) notation, you can write:
SELECT s.fare, s.commission, s.driver, s._date, v.vehiclenumber, p.productname,
s.source, s.destination, rs.routename, rd.routename, o.ownername
FROM route AS rs, route AS rd, shipment AS s, product AS p, vehicle AS v, owner AS o
WHERE s.vehicleid = s.vehicle
AND s.source = rs.routeid
AND v.owner = o.ownerid
AND s.destination = rd.routeid
AND s.product = p.productid
AND v.vehiclenumber = 'nk-234'
ORDER BY _date
LIMIT 0, 30
I had to guess which table(s) contain the fare, commission and driver information; I assumed they are all in the shipment table.
Starting a column name with an underscore is not strictly allowed in standard SQL, though most DBMS do allow it. It looks ugly, though; why not 'ship_date' as the column name?
It is better to use the explicit join notation, not least because there is less chance of forgetting a join condition:
SELECT s.fare, s.commission, s.driver, s._date, v.vehiclenumber, p.productname,
s.source, s.destination, rs.routename, rd.routename, o.ownername
FROM shipment AS s
JOIN route AS rs ON s.source = rs.routeid
JOIN route AS rd ON s.destination = rs.routeid
JOIN vehicle AS v ON s.vehicleid = s.vehicle
JOIN owner AS o ON v.owner = o.ownerid
JOIN product AS p ON s.product = p.productid
WHERE v.vehiclenumber = 'nk-234'
ORDER BY s._date
LIMIT 0, 30