MySQL advanced sub query repetition - mysql

I'm writing some reasonably complex queries for reporting for an app I am developing. I could probably achieve all of the following through using higher level PHP, but obviously I would like to get it all done with MySQL which will obviously simplify things greatly.
The report I need is a typical sales report type query, which will list a list of people, and some relevant totals relating to them. The only minor difference is that this system relates to freight/haulage, so the "sales people" are actually lorry drivers, and the "sales" are individual consignments. Also, consignments are only linked/tied to their respective driver through the creation of "routes", which record who delivers/collects what on a specific day.
Naturally, I could use an INNER JOIN to get a list of each driver, with all of the consignments they have delivered/collected, and SUM the revenue made off these. The problem comes however, when I need to show both a column for total delivery revenue and collection revenue. These figures can come from a consignments table, which lists every consignment. Each consignment can have a flag (ENUM "D","C") which donates whether it is a delivery or collection. This can almost be ascertained easily through using sub queries but still, there will be a lot of repetition.
What I have so far:
SELECT pr.driver_callsign, d.first_name, d.last_name, sum(pc.revenue) AS total_revenue
FROM pallet_routes AS pr
INNER JOIN drivers AS d ON d.driver_callsign = pr.driver_callsign
INNER JOIN pallet_consignments AS pc ON pc.route_id = pr.route_id
GROUP BY pr.driver_callsign
ORDER BY d.driver_callsign ASC
This obviously returns a list of each driver, with the total amount of revenue made from all consignments they have tied to them.
What would be the most efficient way to further split this revenue SUM field up to show a SUM(revenue) WHERE type = "C" and SUM(revenue) WHERE type="D"? Subqueries? UNION?
It may also be worth mentioning that the end query will be narrowed down to a date range. So for example there will be a WHERE date BETWEEN x AND y put against the pallet_routes table.
Any advice would be greatfully received. Please do ask if you want me to elaborate more.

I don't know where your column type is, but if it's on pallet_consignments, you can try the following:
SELECT pr.driver_callsign, d.first_name, d.last_name,
SUM(IF(pc.`type` = 'C', pc.revenue, 0)) collection_revenue,
SUM(IF(pc.`type` = 'D', pc.revenue, 0)) delivery_revenue
FROM pallet_routes AS pr
INNER JOIN drivers AS d ON d.driver_callsign = pr.driver_callsign
INNER JOIN pallet_consignments AS pc ON pc.route_id = pr.route_id
GROUP BY pr.driver_callsign
ORDER BY d.driver_callsign ASC
Otherwise, please mention where the column type is.

Related

Proper SQL Statements in a PDF?

I am in the process of creating an attendance system and have created 3 different reports to generate based on the content of 3 different MySQL tables: members, attendance, and absence.
I am having an issue though. One of the reports is working since I have the correct statement. However, I cannot get the other two to work, so I need some help on how to figure out the best SQL statement for these reports.
The first report I need has to look like this:
This report shows how many people in each precinct showed up to the event and how many excused absences are in that precinct. For this report, I will also need a "Totals" line at the very bottom to count the total number of attendees, excused absences and totals from each precinct (like this):
The second report is similar to the report that is already completed. The difference is instead of the member's email and phone address, I need to see if they were marked present and if they had an excused absence. I cannot show the report since there is real data about real people, however I can show you the SQL statement that the completed report is using:
SELECT
precinct, name, residential_address, member_email, member_phone, present, alternate
FROM
attendance INNER JOIN members ON members.id = attendance.member_id
WHERE
present = 1
ORDER BY
members.precinct
I've tried SQL COUNT statements and various JOIN queries to try and make the queries work, but nothing is working at all. What is the correct query and why?
UPDATE
Here is my table structure of the 3 tables involved in the report generation. Note that each table (other than Members) shares the Member ID column:
Members Table:
Attendance Table:
Absence Table:
This is untested against actual data, but should be close to what you're looking for.
Your first report (http://sqlfiddle.com/#!9/191d8d/1) should be:
SELECT
m.precinct as 'precinct',
COUNT(at.member_id) as 'delagates_present',
COUNT(ab.member_id) as 'delegates_absent',
COUNT(at.member_id) + COUNT(ab.member_id) as 'total'
FROM
members m
LEFT JOIN attendance at ON at.member_id = m.id
LEFT JOIN absence ab ON ab.member_id = m.id
GROUP BY
m.precinct
WITH ROLLUP;
This selects all members, groups them by precinct, counts how many were present or absent, and then adds those together for the total column. Additionally, WITH ROLLUP will give you the sums of the columns (https://dev.mysql.com/doc/refman/8.0/en/group-by-modifiers.html) as the last row.
Then your second report (http://sqlfiddle.com/#!9/191d8d/2) should be:
SELECT
precinct,
name,
residential_address,
IF(at.member_id IS NULL, 0, 1) as 'present',
IF(ab.member_id IS NULL, 0, 1) as 'absent',
alternate
FROM
members m
LEFT JOIN attendance at ON at.member_id = m.id
LEFT JOIN absence ab ON ab.member_id = m.id
ORDER BY
m.precinct,
m.id;
which selects all members and does a LEFT JOIN on the other 2 tables. Then we can use a condition in the SELECT to determine if they were present or absent. There are a number of ways that data could be represented and returned, but I've opted for a simple 1 or 0 in both of those columns.

SQL Query: Showing multiple data and total order values

As part of an SQL Queries assignment, I am required to meet the following criteria:
"Display all customers who have bought anything in the last 6 months. Show >customer name, loyalty card number, date of order, and total value of order. >Ensure this is named correctly in the query results as Total_Order_Value."
For this, I came up with a script which has been marked as wrong. I am confused by the feedback as I believe I have met the question criteria.
Find the script and feedback below:
Script
SELECT Aorder.*, Acustomer.*, AorderDetails.quantity, (AorderDetails.quantity *AmenuItem.itemCost) AS Total_Order_Value
FROM Aorder, Acustomer, AmenuItem, AorderDetails
WHERE orderDateTime < Now() AND orderDateTime > DATE_ADD(Now(), INTERVAL -6 MONTH)
AND Acustomer.customerID = Aorder.customerID
AND Aorder.orderID = AorderDetails.orderID
AND AorderDetails.itemID = AmenuItem.itemID
AND Aorder.paymentType IN ('Cash' , 'Card');
Feedback
"At the moment this will multiply cost *qty for each individual item bought in one order. You need the total value for each order. I.e. at the moment I would see a value for each item I bought in one order, I would like to see the total for the whole order. You need to add an aggregate and a group by"
I would appreciate any assistance in helping me understand what went wrong and how I may structure this correctly to meet the requirements.
Thank you in advance.
Essentially, you are reporting results at the orders items level and not customers and orders level as the original question asked. Your current resultset likely repeats customers and order details for each corresponding item which can be lengthy with its one-to-many relationships.
To resolve, simply refactor your SQL statement into an aggregate query that groups on customer and order items and sums each order item's value to retrieve the total amount of whole order. Additionally heed best practices in SQL:
EXPLICIT JOIN: As mentioned in comments do not use the old-join style of commas in FROM clause with matching conditions in WHERE. This is known as implicit joins. The current standard introduced in ANSI-92 emphasizes explicit joins using JOIN and ON clauses. While this does not change efficiency or output, it does aid in readability and maintainability.
SELECT CLAUSE: Try avoiding selecting all fields in tables with Aorder.*, Acustomer.* which is an open-ended resultset output. Your question specifically asked for certain fields: customer name, loyalty card number, date of order, and total value of order. So, select them accordingly.
TABLE ALIASES: For longer table names and tables that share the same prefixes, stems, or suffixes like your A tables, use table aliases that properly abbreviates and defines your identifiers. Again this practice should not change output but aids in readability and maintainability.
See below working SQL statement (adjust field names to actuals).
SELECT c.customer_name,
c.loyalty_card_number,
CAST(o.orderDateTime AS DATE) AS Order_Date,
SUM(d.quantity * m.itemCost) AS Total_Order_Value
FROM Aorder o
INNER JOIN Acustomer c ON c.customerID = o.customerID
INNER JOIN AorderDetails d ON o.orderID = d.orderID
INNER JOIN AmenuItem m ON d.itemID = m.itemID
WHERE o.orderDateTime < Now()
AND o.orderDateTime > DATE_ADD(Now(), INTERVAL -6 MONTH)
AND o.paymentType IN ('Cash' , 'Card')
GROUP BY c.customer_name,
c.loyalty_card_number,
CAST(o.orderDateTime AS DATE)
NOTE: Do not make the mistake as many MySQL users do in excluding non-aggregated columns in GROUP BY clause of an aggregate query which is required in ANSI-SQL. Asterisks, *, should never be used in aggregate queries. MySQL unfortunately allows this feature with its ONLY FULL GROUP BY mode turned off and can return unreliable results.

JOIN data from bridging table

I'm taking a database class and I'm having troubles with using the JOIN command to get the data I need. I hope I'm on the right track...I would appreciate any inputs if there is a better way both in my model and query.
Sales transaction that each employee has processed
, I'm trying to answer the following question:
Here is a query I tried but fail to come up with correct output. I had to use a table alias for sales because I kept getting unique errors. I only have 10 rows in my bridging table however, I got 100 results back...JOIN statements are kicking my butt right now.
SELECT sales.cashier, inventory.prod_name, inventory.unit_price
FROM inventory, sales
JOIN inv_sales ON inventory_prod_id = inventory.prod_id
JOIN sales AS sales1 ON sales_sales_id = sales_id;
edit: Additional requested data
Here is a screenshot of some sample data.
I had to put them all on one pic due to site restrictions.
Tables:
Top, Inv_sales
Middle, Sales
Bottom, Inventory
Expected output:
cashier > product name > unit price
You might try something like this:
SELECT s.cashier, i2.prod_name, i2.unit_price
FROM sales s
JOIN inv_sales i1
ON i1.sales_sales_id = s.sales_id
JOIN inventory i2
ON i1.inventory_prod_id = i2.prod_id
ORDER BY s.cashier,s.date;
It uses a unique alias for each table, and uses the modern form of joining tables. Also, since the requirement was to get the sales for each employee, I added an order by so an employees sales would be grouped together.

Multitable counting and multiplying in same query

I have got a somewhat complicated problem. This is my situation (ERD).
For a dashboard i need to create a pivot table that shows me the total amount of competences used by the vacancies. Therefore I need to:
Count the amount of vacancies per template
Count the amount of templates per competence
and last: multiply these numbers to get the total amount of comps used.
I have the first query:
SELECT vacancytemplate_id, count(id)
FROM vacancies
group by vacancytemplate_id;
And the second query isn't that difficult either, but I don't know what the right solution will be. I'm literally brainstuck. My mind can't comprehend how I can achieve the next step and put it down in a query. Please kind stranger, help me out :)
EDIT: my desired result is something like this
NameOfComp, NrOfTimesUsed
Leading, 17
Inspiring, 2
EDIT2: the meta query it should look like:
SELECT NameOfComp, (count of the competences used by templates) * (number of vacancies per template)
EDIT3: http://sqlfiddle.com/#!9/2773ca SQLFiddle
Thanks a lot!
If I am understanding your request correctly, you are wanting a count of competences per vacancy. This can be done very simply due to your table structure:
Select v.ID, count(*) from vacancy as v inner join CompTemplate_Table as CT
on v.Template_ID = CT.Template_ID group by v.ID;
The reason you can do only one join is because there will be a record in the CompTemplate_Table for every competency in each template. Additionally, the same key is used to join vacancy to templates as is used to join templates to CompTemplate_Table, so they represent the same key value (and you can skip joining the Templates table if you don't need data from there).
If you are wanting to add this data to a pivot table, I will leave that exercise to you. There are a number of tutorials available if you do a quick google search and it should not be that hard.
UPDATE: For the second query you are looking at something like:
Select cp.NameOfComp, count(*) from vacancy as v inner join CompTemplate_Table as CT
on v.Template_ID = CT.Template_ID inner join competencies as CP
on CP.ID = CT.Comp_ID
group by CP.NameOfComp
The differences here are you are adding in the comptetencies table, as you need data from that, and grouping by the CP.NameOfComp instead of the vacancy id. You can also restrict this to specific templates, competencies, or vacancies by adding in search conditions (e.g. where CP.ID = 12345)

Advanced Access Query

I have two tables. One contains Potential Customer information along with their Vehicle requirements (Vehicle Type, Vehicle Colour) etc. The other table contains a list of the Vehicles. This includes data such as NumberOfSeats, Max Speed, Price etc.
I need a query that will list Vehicles (from the Vehicles table) that satisfy the Potential Customers requirements (Vehicle Type) etc.
There's a few things I'd like to avoid in the query. I want to list these by ONLY specifying the Potential Customer's ID (Cust ID). I.E I don't want to have to do something like WHERE Cust ID = 1 AND ... AND ... AND ...
I thought about this and concluded that a JOIN or UNION is most likely needed to be used. But when I was trying to put a JOIN statement together, I found that I'd have to list loads of JOIN ON fields:
SELECT *
FROM [Potential Customer] INNER JOIN [Vehicles] AS Matches
ON Matches.`Number of Seats` >= [Potential Customer].`Min Seats` AND
ON Matches.`Color` >= [Potential Customer].`Preferred Color` = AND
...
WHERE [Potential Customer].`Cust No` = 3
Is there a better way to do this?
But you already have several ... AND ... statements. So I think that a good way to do it is:
SELECT Cars.* FROM Cars, Customer WHERE
Customer.ID = 1 AND
Cars.Whatever >= Customer.Whatever >= AND
...
I, personally, would do it that way because it's easy and understandable. Also, for about 8 years of marginal database experience, I never bothered to learn anything about joins (ashamed). And, BTW, this is not such an advanced query :P
You may be able to get what you are after by using a simple query like this:
SELECT Customer.Id, Vehicle.Id FROM Customer, Vehicle
WHERE Vehicle.criteria_1 >= Customer.Criteria_1 AND... AND Customer.Id = 3
That should give you a list of Vehicle.Id (or whatever else you select form Vehicle) for a specific customer.
BTW, how is the query going to be created? Ad-hoc in code? Stored proc?