MySQL - Selecting the sum amount based on specific condition - php

I've searched for a few hours now, but couldn't find relative solution to a specific algorithm I am working on. To simplify the obstacle, I would like to present the information in just one table.
_____________________________________
| User | Item | price | qty |
-------------------------------------
| Annie | Dress | 80 | 1 |
| Bob | Jeans | 65 | 3 |
| Cathy | Shoes | 60 | 4 |
| David | Shirts | 40 | 6 |
| Annie | Shoes | 60 | 2 |
| Bob | Shirts | 55 | 2 |
| Cathy | Jeans | 65 | 1 |
| David | Ties | 20 | 5 |
-------------------------------------
Problem # 1: Show users whose total price for shopping at the store is 300 or more and quantity of their purchase is less than or equal to 3. These shoppers will be mailed a coupon for $40.
Problem # 2: Show users whose total qty is greater than or equal to 7 and the total for price is 275 or more. These shoppers will be mailed a coupon for $20.
The rows within the table are not transaction specific. The table can represent separate transactions within a month. We're just trying to find certain returning customers who we would like to reward for shopping with us.
I'm not sure if this can be done only via MySQL, or if I need to have separate queries and store rows into arrays and compare them one by one.
What I have tried so far are the followings:
SELECT * FROM table where SUM(price) as Total >= 300 AND SUM(qty) <=3;
I've also tried the following after the research:
SELECT SUM(price) as Total FROM table WHERE SUM(qty) <=3;
I keep getting syntax errors in MySQL shell. You don't have to solve the problems for me, but if you can guide me through the logic on how to solve the problems, I'd appreciate it very much.
Lastly I'd like to ask once, can I solve this with only MySQL or do I need to store the rows into PHP arrays and compare each indexes?

You can't use an aggregate function in the WHERE clause, you have to use HAVING. WHERE operates on individual rows during the selection, HAVING operates on the final results after aggregating.
SELECT *, SUM(price*qty) as Total
FROM table
GROUP BY user
HAVING Total >= 300 AND SUM(qty) <= 3

SUM is an aggregate function, meaning it applies to a group of clubbed rows. S say i am grouping the table data based on NAME then sum function would sum all the price of one NAME.
Having said this, if you think logically it would not make any sense to put the sum(price) in a WHERE clause because where clause would not know which SUM(PRICE) for which NAME to operate on(where clause operates only after a temporary view has been generated).
So we have the HAVING clause in SQL. This is used to compare the results of aggregrate function at each step of aggregation.
Consider it like this:
In where clause, when the ANNIE row from your DB is returned, it does not know what SUM(PRICE) means.
While in HAVING clause the SUM(PRICE)>300 condition is executed only when SQL has finished grouping all the ANNIE data into one group and calculated the SUM(PRICE) for her.
For question 1:
SELECT USER, SUM(PRICE)
FROM table
GROUP BY user
HAVING SUM(PRICE) >= 300 AND SUM(QTY) <= 3
For Question 2:
SELECT USER, SUM(PRICE)
FROM table
GROUP BY user
HAVING SUM(PRICE) >= 275AND SUM(QTY) >=7

Related

How Can I Order by two Values using Substring

I'm Using PHP and MySQL to show some data on the screen, but I need to order rows of a table using 2 columns.
On one column Restricted
I have the following json (["PT","GB"])
On the other column rate I have decimal values from 0 to 10
What I'm looking is for a way to order first Restriced contains GB Then rate DESC (If I can use only mysql would be the best)
Current SQL at the moment is
SELECT * FROM Partner ORDER BY FIELD (restricted, '%GB%'), FIELD rate DESC
I've searched the web for a solution, I've been at this for almost a month, no solution to be found
** EDIT **
How it should look
Partner Name | Countries | rate |
AAAAA | ["GB","FR","PT"] | 9.0 |
BBBBB | ["GB","FR","PT"] | 8.8 |
CCCCC | ["GB","FR","PT"] | 7.2 |
DDDDD | ["US","FR","PT"] | 9.0 |
EEEEE | ["US","FR","PT"] | 8.8 |
FFFFF | ["US","FR","PT"] | 7.2 |
You need to use below mentioned steps:
Break your records in 2 Parts (Having 'GB' and not Having 'GB').
Sort both record-sets separately as per you logic
join both records using UNION ALL
so you query should be as below:
(SELECT * FROM partner where restricted like '%GB%' ORDER BY restricted,
rate DESC)
union all
(SELECT * FROM partner where restricted not like '%GB%' ORDER BY restricted,
rate DESC)

Time and Material Reporting - Process to combine multiple columns and rows of different data types

I am quite new to MySql (3 months of YouTube tutorials) and most of my experience comes from PHP/HTML(4-5 years building websites) and I am trying to build a report for some data that I've started collecting.
Any help you can provide is greatly appreciated!
I have a MySql database that collects multiple columns of materials/products that correspond to a quantity of material that was used on a construction site.
The data currently look like this:
Material1 | Quantity1 | Material2 | Quantity2 | Material3 | Quantity3|
Concrete Bags| 35 | Hydroseed | 1300 | Diesel Fuel | 40 |
Straw Wattles| 32 | Wooden Stakes| 200 | Diesel Fuel | 30 |
Hydroseed | 1000 | Wooden Stakes| 100 | Diesel Fuel | 20 |
What query or process can I use to add the quantities of same name materials, and combine the material names?
Hydroseed has two entries, 1300 + 1000 = 2300; Diesel Fuel has three entries, 40 + 30 + 20 = 90; etc. Material1, Material2, Material3 = MaterialName
I want to display it in the following manner:
MaterialName | Quantity |
Concrete Bags | 35 |
Hydroseed | 2300 |
Straw Wattles | 32 |
Wooden Stakes | 300 |
Diesel Fuel | 90 |
I am not exactly sure how to approach this.
My current query looks like this:
("SELECT Material1 AS MaterialName FROM table UNION SELECT Material2 AS MaterialName FROM table UNION SELECT Material3 AS MaterialName FROM database GROUP BY MaterialName");
I do not know what to do with the Quantities - I do not know how to combine the two queries and have the totals match up with the correct material name - perhaps something of this sort:
("SELECT *, SUM(Quantity1 + Quantity2 + Quantity3) AS Quantity FROM table GROUP BY MaterialName");
You may do an aggregation over a union of the three types of column data:
SELECT material AS MaterialName, SUM(quantity) AS Quantity
FROM
(
SELECT Material1 AS material, Quantity1 AS quantity FROM yourTable UNION ALL
SELECT Material2, Quantity2 FROM yourTable UNION ALL
SELECT Material3, Quantity3 FROM yourTable
) t
GROUP BY material;
The basic strategy here is to bring the three pairs of material data into just two columns. Then, aggregate by material and find the sum of quantity, for each material.
Demo
By the way, your current data model is not good, and in general you should not be storing the same logical thing across multiple columns. Consider just having two columns, one for the material and the other for the quantity.

How to save, handle the order total amount in an orders, ordersDetails schema?

When I started designing my application database schema few months ago I have been told not to store the same data/calculated data in more than one place in the database(normalization). If I do, I will make a scope of bugs when I update the data in one place and left the other without updating. So I did an orders table and ordersDetails table. Something like this..
-- orders table
+-----+---------+----------+
| ID | clintID | date |
+-----+---------+----------+
| 1 | 1 |2018-02-22|
| 2 | 1 |2018-02-23|
| 3 | 2 |2018-02-24|
+-----+---------+----------+
-- orderDetail table
+-----+---------+------------+----------+----------+
| ID | orderID | itemNumber | quantity | unitPrice|
+-----+---------+------------+----------+----------+
| 1 | 1 | 12345 | 3 | 100.75 |
| 2 | 1 | 12346 | 3 | 100.75 |
| 3 | 2 | 12347 | 3 | 100.75 |
| 4 | 2 | 12345 | 3 | 100.75 |
| 5 | 3 | 12347 | 3 | 100.75 |
| 6 | 3 | 12345 | 3 | 100.75 |
+-----+---------+------------+----------+----------+
And to make the the queries easier for me I made a view "allOrdersSummary" like
-- allOrdersSummary
SELECT
orders.*, SUM(orderDetail.quantity * orderDetail.unitPrice) totalAmount
FROM orders INNER JOIN orderDetail ON orders.ID = orderDetail.orderID
GROUP BY orders.ID;
and I used this view later for my queries, but now I started to get the MAX_JOIN_SIZE error.
So I thought of saving the calculated total order amount along with the orders table ID, clintID, date, totalAmount and whenever I change something in the orderDeatils table I update the calculated totalAmount column in the orders table, I don't know if this is good or bad!
This problem -I don't know if this is considered a problem or not- is encountered many times, for example to know the unread messages of the client making the request I have to do sum(messages) unread from messages where to = ? and isRead = 0
A) should I make another column for calculated totalAmount in the orders table or it is a normal thing in databases to calculate the totalAmount from the orderDetails table every time I need it ?
B) If you recommend making another column in the orders table, what is the best way to update it every time a change happens in the orderDetails table ? should I update it at the PHP layer whenever I update the orderDetails table, or this is something that needs a stored procedure ?
Yes, it is normal to store pre-calculated values, based on other data in the database, in a database. But not necessarily for the reason you mention. I never had a problem with MAX_JOIN_SIZE.
The main, and probably only, reason for storing calculated values is speed. So you do it for values that don't change that often and that may be used in queries that use a lot of data and may therefore be too slow if you didn't use them.
For instance: If you want to know the average value of all the orders in your database the query would be a lot faster if you already have the order totals.
Why, and how, you update the values is completely up to you. However you have got to be consistent about it. If you use the MVC pattern it would make sense to integrate it in the controller. Or in simple terms: Whenever a form is submitted that could change one of the values, out of which the pre-calculated value is computed, you need to recompute it.
This is a clear demonstration where 'normalization' is not entirely maintained. It's not really pretty, but sometimes worth it. You could, of course, argue, that the calculated value represents 'new' information, and therefore does not offend against 'normalization'.
You have an "inflate-deflate" problem.
JOIN the two tables to make a much larger temporary table.
GROUP BY to shrink back to one row per row of the original (orders) table.
This avoids the problem:
SELECT *,
( SELECT SUM(quantity * unitPrice
FROM orderDetail WHERE orderID = orders.ID
) AS totalAmount
FROM orders;
Please let me know how your experience is with this one. It is one of the simplest examples of the inflate-deflate problem.

LIMIT result on join MySQL

take the case you have 2 table, for example tbCostumers and tbOrders.
I would like to display a summary list with all costumers, related orders and display them with a paginator.
Doing a join I can extract costumers list and all orders for each costumer, the result is something like:
idCostumer | name | ... | idProduct | productName | price | ...
Where the first n columns are all equal if the costumer has more than 1 order. So I can have:
1 | will | ... | 12 | product1 | 123 | ...
2 | bill | ... | 23 | product2 | 321 | ...
2 | bill | ... | 24 | product3 | 231 | ...
And so on
I'm trying to use LIMIT to extract only n records and using them with a paginator.
First question: if a costumer has more than 1 order, with this query I'll see n records, equal in the first column (id, name, ... and other costumer info) but different at the end, where there are products info. Is this 'correct'? Is there another way to extract this informations?
Second question: if I do that and I use a LIMIT, I could "cut" the result table between 2 (or more) records that represent the same customer; so, for example in the small table above, if I limit with 2 the third row will be lost, even if it's part of the row above, because is just another order of the same costumer.
I would like to limit the number of different idCostumer, in order to take exactly n costumers, even if they appear more than 1 times in the result table. Something like n different idCostumer, no matter if they are repeated.
Is this possible?
I hope it's clear, it was not easy to explain what I would like to achieve :)
Thank you!
You might want to have something like this:
SELECT * FROM (
(SELECT * FROM tbCustomers LIMIT 3) AS c
INNER JOIN tbOrders AS o ON o.customer = c.idcustomer
);
You can substitute the first asterisk with named columns and only receive your desired columns in the order you prefer (ie: SELECT c.name, o.price FROM...) .
Hope this works for you!
EDIT: changing the value of the LIMIT clause changes the number of the picked customers, of course.
EDIT 2: As Alvaro Pointed out, you'll probably need an order clause in the tbCustomers query.

SQL Rank of users score, from arbitrary position

Looking around the site, most questions regarding to Ranks in a highscore table assumes that you will be looking at the entire or the top of the table.
In a lot of examples on this site, the rank is found by ordering the items by score and then counting the rows from the top of the set, or counting the items as they are retrived. Like this
score name rank
1000 test345 1
999 test980 2
950 test234 3
833 test291 4
760 test573 5
731 test981 6
In my situation, I need to look at only a portion of the scores, which may not be at the top of the table, for instance, maybe halfway though the leaderboard:
scores name rank
500 test451 43
433 test768 44
425 test120 45
where the user is only shown the scores around his. The part of the leader board the user is looking at above, isn't at the top of the leader board, so I can't count the rows in the returned scores to determine their rank.
How can I determine the rank of user in a leader board at and arbitrary position efficiently, amusing there a lot of entries.
Also this is my first foray into sql and php. I might not be using the correct terminology.
I'm not really sure what you are trying to do. You can limit you result using a LIMIT clause like this:
SELECT * FROM <table> LIMIT 0, 3
Which will only return the first 3 records.
To order the result based on the rank field you would use an ORDER BY clause:
SELECT * FROM <table> ORDER BY rank DESC LIMIT 0, 3
The above query will return 3 records order by rank in descending order.
If you like to calculate the rank based on the scores column this would work:
SELECT scores,
name,
FIND_IN_SET(scores, (SELECT GROUP_CONCAT(scores ORDER BY scores DESC)
FROM <table>)) as rank
FROM <table> ORDER BY rank DESC LIMIT 0, 3;
Running the above query against a table with only two columns scores and name:
+--------+---------+
| scores | name |
+--------+---------+
| 500 | test451 |
| 433 | test768 |
| 425 | test120 |
| 300 | test001 |
| 250 | test002 |
| 200 | test003 |
+--------+---------+
Would yield the following result:
+--------+---------+------+
| scores | name | rank |
+--------+---------+------+
| 500 | test451 | 1 |
| 433 | test768 | 2 |
| 425 | test120 | 3 |
+--------+---------+------+
The GROUP_CONCAT() maximum length is depending on the group_concat_max_len system variable, so for a large table this needs to be changed and I'm not sure this would be the best approach.
Notice that you could/should add indexes to your table to speed things up:
ALTER <table> ADD INDEX `idx_scores` (`scores`);

Categories