POS Application - Simplify SQL Mutli-Queries(MySQL) - mysql

Hei guy I'm working on a POS app with MySQL. Here is my situation:
Table "purchased_item"
| id | name | check_id | real_price |
| 1 | iPhone5 | 0001 | 399 |
| 2 | iPhone4 | 0001 | 199 |
| 3 | iPhone5s | 0002 | 599 |
| 4 | iPhone5c | 0003 | 399 |
| 5 | iMac 21" | 0003 | 999 |
| 6 | iPod Touch | 0003 | 99 |
| 7 | iPhone5 | 0004 | 399 |
| 8 | iPhone3G | 0004 | 99 |
| 9 | iPhone6 | 0005 | 899 |
| 10 | iPhone3Gs | 0005 | 101 |
And I want to know how many checks's total are larger than or qual(>=) 1000, so what I'm doing now is to do several times of query. In this example, I do 5 times and sum it manually by the host program.
Later the data grow, the queries become slow because there're tons of checks everyday. So I change to record it to another table.
Table "checks"
| id | total | sales |
| 0001 | 598 | A |
| 0002 | 599 | A |
| 0003 | 1497 | B |
| 0004 | 498 | B |
| 0005 | 1000 | A |
But another problem occur in the later time: When I need to adjust the real_price in "purchased_item" table, I also need to maintain the "total" column in "checks" table. It sounds doesn't a big matter but I'd like to find a better way to solve it.
Solved:
SELECT * FROM purchased_item
GROUP BY check_id
HAVING sum(real_price) >= 1000
And the result will be:
| id | name | check_id | real_price |
| 4 | iPhone5c | 0003 | 399 |
| 9 | iPhone6 | 0005 | 899 |
Further question: If I want to count the total price for checks, how can I do it?
I found it:
SELECT check_id,sum(real_price) FROM purchased_item
GROUP BY check_id
HAVING sum(real_price) >= 1000

Try it this way
SELECT i.id, i.name, i.check_id, i.real_price
FROM
(
SELECT MIN(id) id
FROM purchased_item
GROUP BY check_id
HAVING SUM(real_price) >= 1000
) q JOIN purchased_item i
ON q.id = i.id
ORDER BY q.id DESC
Sample output:
| ID | NAME | CHECK_ID | REAL_PRICE |
|----|----------|----------|------------|
| 9 | iPhone6 | 5 | 899 |
| 4 | iPhone5c | 3 | 399 |
...I want to count how many checks's total are over 1000
For that you can just do this
SELECT COUNT(*) total
FROM
(
SELECT check_id
FROM purchased_item
GROUP BY check_id
HAVING SUM(real_price) >= 1000
) q;
Sample output:
| TOTAL |
|-------|
| 2 |
Here is SQLFiddle demo
To update total in checks after adjusting real_price in purchased_item
UPDATE checks c JOIN
(
SELECT check_id, SUM(real_price) total
FROM purchased_item
WHERE check_id IN(5) -- whatever check(s)'s total you want to recalculate
GROUP BY check_id
) p
ON c.id = p.check_id
SET c.total = p.total;
Here is SQLFiddle demo

Related

Subtract two columns of different tables with different number of rows

How can I write a single query that will give me SUM(Entrance.quantity) - SUM(Buying.quantity) group by product_id.
The problem is in rows that not exist in the first or second table. Is possible to do this?
Entrance:
+---+--------------+---------+
| id | product_id | quantity|
+---+--------------+---------+
| 1 | 234 | 15 |
| 2 | 234 | 35 |
| 3 | 237 | 12 |
| 4 | 237 | 18 |
| 5 | 101 | 10 |
| 6 | 150 | 12 |
+---+--------------+---------+
Buying:
+---+------------+-------------+
| id | product_id | quantity|
+---+------------+-------------+
| 1 | 234 | 10 |
| 2 | 234 | 20 |
| 3 | 237 | 10 |
| 4 | 237 | 10 |
| 5 | 120 | 15 |
+---+------------+------------+
Desired result:
+--------------+-----------------------+
| product_id | quantity_balance |
+--------------+-----------------------+
| 234 | 20 |
| 237 | 10 |
| 101 | 10 |
| 150 | 12 |
| 120 | -15 |
+--------------+-----------------------+
This is tricky, because products could be in one table but not the other. One method uses union all and group by:
select product_id, sum(quantity)
from ((select e.product_id, quantity
from entrance e
) union all
(select b.product_id, - b.quantity
from buying b
)
) eb
group by product_id;
SELECT product_id ,
( Tmp1.enterquantity - Tmp2.buyquantity ) AS Quantity_balance
FROM entrance e1
CROSS APPLY ( SELECT SUM(quantity) AS enterquantity
FROM Entrance e2
WHERE e1.product_id = e2.product_id
) Tmp1
CROSS APPLY ( SELECT SUM(quantity) AS buyquantity
FROM Buying b2
WHERE e1.product_id = b2.product_id
) Tmp2
GROUP BY Product_id,( Tmp1.enterquantity - Tmp2.buyquantity )

Select most recent MAX() and MIN() - WebSQL

i'm build an exercises web app and i'm working with two tables like this:
Table 1: weekly_stats
| id | code | type | date | time |
|----|--------------|--------------------|------------|----------|
| 1 | CC | 1 | 2015-02-04 | 19:15:00 |
| 2 | CC | 2 | 2015-01-28 | 19:15:00 |
| 3 | CPC | 1 | 2015-01-26 | 19:15:00 |
| 4 | CPC | 1 | 2015-01-25 | 19:15:00 |
| 5 | CP | 1 | 2015-01-24 | 19:15:00 |
| 6 | CC | 1 | 2015-01-23 | 19:15:00 |
| .. | ... | ... | ... | ... |
Table 2: global_stats
| id | exercise_number |correct | wrong |
|----|-----------------|--------|-----------|
| 1 | 138 | 1 | 0 |
| 2 | 246 | 1 | 0 |
| 3 | 988 | 1 | 10 |
| 4 | 13 | 5 | 0 |
| 5 | 5 | 4 | 7 |
| 6 | 5 | 4 | 7 |
| .. | ... | ... | ... |
What i would like is to get MAX(correct-wrong) and MIN(correct-wrong) and now i'm working with this query:
SELECT
exercise_number,
date,
time
FROM weekly_stats AS w JOIN global_stats AS g
ON w.id=g.id
WHERE correct - wrong = (SELECT MAX(correct - wrong) from global_stats)
UNION
SELECT
exercise_number,
date,
time
FROM weekly_stats AS w JOIN global_stats AS g
ON w.id=g.id
WHERE correct - wrong = (SELECT MIN(correct - wrong) from global_stats);
This query is working good, except for one thing: when "WHERE correct - wrong = (SELECT MIN(correct - wrong)[...]" selects more than one row, the row selected is the first but i would like to have returned the most recent (in other words: ordered by datetime(date, time)). Is it possible?
Thanks!
I think you can solve it like this:
SELECT * FROM (
SELECT
1 as sort_column,
exercise_number,
date,
time
FROM weekly_stats AS w JOIN global_stats AS g
ON w.id=g.id
WHERE correct - wrong = (SELECT MAX(correct - wrong) from global_stats)
ORDER BY date DESC, time DESC
LIMIT 1 ) as a
UNION
SELECT * FROM (
SELECT
2 as sort_column,
exercise_number,
date,
time
FROM weekly_stats AS w JOIN global_stats AS g
ON w.id=g.id
WHERE correct - wrong = (SELECT MIN(correct - wrong) from global_stats)
ORDER BY date DESC, time DESC
LIMIT 1) as b
ORDER BY sort_column;
Here is the documentation about how UNION works.

How can I SELECT rows from a table when I MAX(ColA) and GROUP BY ColB

I found this question which is very similar but I'm still having some troubles.
So I start with table named Scores
id | player | time | scoreA | scoreB |
~~~|~~~~~~~~|~~~~~~|~~~~~~~~|~~~~~~~~|
1 | John | 10 | 70 | 80 |
2 | Bob | 22 | 75 | 85 |
3 | John | 52 | 55 | 75 |
4 | Ted | 39 | 60 | 90 |
5 | John | 35 | 90 | 90 |
6 | Bob | 27 | 65 | 85 |
7 | John | 33 | 60 | 80 |
I would like to select the best average score for each player along with the information from that record. To clarify, best average score would be the highest value for (scoreA + scoreB)/2.
The results would look like this
id | player | time | scoreA | scoreB | avg_score |
~~~|~~~~~~~~|~~~~~~|~~~~~~~~|~~~~~~~~|~~~~~~~~~~~|
5 | John | 35 | 90 | 90 | 90 |
2 | Bob | 22 | 75 | 85 | 80 |
4 | Ted | 39 | 60 | 90 | 75 |
Based on the question I linked to above, I tried a query like this,
SELECT
s.*,
avg_score
FROM
Scores AS s
INNER JOIN (
SELECT
MAX((scoreA + scoreB)/2) AS avg_score,
player,
id
FROM
Scores
GROUP BY
player
) AS avg_s ON s.id = avg_s.id
ORDER BY
avg_score DESC,
s.time ASC
What this actually gives me is,
id | player | time | scoreA | scoreB | avg_score |
~~~|~~~~~~~~|~~~~~~|~~~~~~~~|~~~~~~~~|~~~~~~~~~~~|
1 | John | 10 | 70 | 80 | 90 |
2 | Bob | 22 | 75 | 85 | 80 |
4 | Ted | 39 | 60 | 90 | 75 |
As you can see, it has gotten the correct max avg_score, from record 5, but gets the rest of the information from another record, record 1. What am I missing? How do I ensure that the data all comes from the same record? I'm getting the correct avg_score but I want the rest of the data associated with that record, record 5 in this case.
Thanks in advance!
SELECT x.*
, (scoreA+scoreB)/2 avg_score
FROM scores x
JOIN
( SELECT player, MAX((scoreA+scoreB)/2) max_avg_score FROM scores GROUP BY player) y
ON y.player = x.player
AND y.max_avg_score = (scoreA+x.scoreB)/2;
Try
SELECT s.*,
q.avg_score
FROM scores s JOIN
(
SELECT player,
MAX((scoreA + scoreB)/2) AS avg_score
FROM scores
GROUP BY player
) q ON s.player = q.player
AND (s.scoreA + s.scoreB)/2 = q.avg_score
ORDER BY q.avg_score DESC, s.time ASC
Sample output:
| ID | PLAYER | TIME | SCOREA | SCOREB | AVG_SCORE |
----------------------------------------------------
| 5 | John | 35 | 90 | 90 | 90 |
| 2 | Bob | 22 | 75 | 85 | 80 |
| 4 | Ted | 39 | 60 | 90 | 75 |
Here is SQLFiddle demo

Order by and group by

I am trying to show delivery charges for a shop I am building, there are three tables in the database 1 for the service ie Royal Mail, Carrier..., one for the band ie. UK, Europe, Worldwide1 etc.. and one for the charges (qty = weight)
I have a database of three tables that, when joined form the following
+------------------+-----+-----------+-------+---------+---------------+----------+-------+-------------+
| name | qty | serviceID | basis | bandID | initial_charge | chargeID | price | total_price |
+------------------+-----+-----------+-------+---------+---------------+----------+-------+-------------+
| Collect in store | 0 | 3 | | 1 | 3 | 0.00 | 2 | 0.00 |
| Royal mail | 0 | 1 | 2 | 4 | 2.00 | 3 | 0.00 | 2.00 |
| Royal mail | 1 | 1 | 2 | 4 | 2.00 | 4 | 1.00 | 3.00 |
| APC | 0 | 2 | 1 | 1 | 0.00 | 6 | 5.95 | 5.95 |
+------------------+-----+-----------+-------+---------+---------------+----------+-------+-------------+
Basically what I want to do is (as you can see) Royal Mail has two entries as there are more than one entry in the joined table. What I would like to do is show the highest of the two royal mail entries (I was initially trying to group by service_id) whilst also maintaining the two other services with different service id's
Any assistance would be great as this is driving me mad. I feel like I have tried every combination going!
In the example below the qty (weight) of the items is 3kg
SELECT
`service`.`name`,
`charge`.`qty`,
`service`.`serviceID`,
`band`.`bandID`,
`band`.`initial_charge`,
`charge`.`chargeID`,
`charge`.`price`,
`band`.`initial_charge` + `charge`.`price` AS `total_price`
FROM
`delivery_band` AS `band`
LEFT JOIN
`delivery_charge` AS `charge`
ON
`charge`.`bandID` = `band`.`bandID`
AND
`charge`.`qty` < '3'
LEFT JOIN
`delivery_service` AS `service`
ON
`service`.`serviceID` = `band`.`serviceID`
WHERE
FIND_IN_SET( '225', `band`.`accepted_countries` )
AND
(
`band`.`min_qty` >= '3'
OR
`band`.`min_qty` = '0'
)
AND
(
`band`.`max_qty` <= '3'
OR
`band`.`max_qty` = '0'
)
delivery_service
+-----------+------------------+
| serviceID | name |
+-----------+------------------+
| 1 | Royal mail |
| 2 | APC |
| 3 | Collect in store |
+-----------+------------------+
delivery_band
+--------+-----------+-----------------+----------------+---------+---------+-------------------------------------------------------+
| bandID | serviceID | name | initial_charge | min_qty | max_qty | accepted_countries |
+--------+-----------+-----------------+----------------+---------+---------+-------------------------------------------------------+
| 1 | 2 | UK Mainland | 0.00 | 0 | 0 | 225 |
| 2 | 2 | UK Offshore | 14.00 | 0 | 0 | 240 |
| 3 | 3 | Bradford Store | 0.00 | 0 | 0 | 225 |
| 4 | 1 | UK | 2.00 | 0 | 0 | 225 |
| 5 | 2 | World wide | 15.00 | 0 | 0 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20... |
| 6 | 1 | World wide Mail | 5.00 | 0 | 0 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20... |
+--------+-----------+-----------------+----------------+---------+---------+-------------------------------------------------------+
delivery_charge
+----------+--------+-----+-------+
| chargeID | bandID | qty | price |
+----------+--------+-----+-------+
| 1 | 2 | 0 | 5.00 |
| 2 | 3 | 0 | 0.00 |
| 3 | 4 | 0 | 0.00 |
| 4 | 4 | 1 | 1.00 |
| 5 | 4 | 5 | 3.00 |
| 6 | 1 | 0 | 5.95 |
| 7 | 1 | 10 | 10.95 |
| 8 | 2 | 10 | 14.00 |
| 9 | 5 | 0 | 0.00 |
| 10 | 5 | 3 | 5.00 |
| 11 | 5 | 6 | 10.00 |
| 12 | 5 | 9 | 15.00 |
| 13 | 6 | 0 | 0.00 |
| 14 | 6 | 2 | 5.00 |
| 15 | 6 | 4 | 10.00 |
| 16 | 6 | 6 | 15.00 |
+----------+--------+-----+-------+
When I tried adding the charge table as a sub query and then limiting that query it gave me NULL's for all the charge table fields
If I try the following query:
SELECT
`service`.`name`,
`charge`.`qty`,
`service`.`serviceID`,
`band`.`bandID`,
`band`.`initial_charge`,
`charge`.`chargeID`,
MAX( `charge`.`price` ) AS `price`,
`band`.`initial_charge` + `charge`.`price` AS `total_price`
FROM
`delivery_band` AS `band`
LEFT JOIN
`delivery_charge` AS `charge`
ON
`charge`.`bandID` = `band`.`bandID`
AND
`charge`.`qty` < '3'
LEFT JOIN
`delivery_service` AS `service`
ON
`service`.`serviceID` = `band`.`serviceID`
WHERE
FIND_IN_SET( '225', `band`.`accepted_countries` )
AND
(
`band`.`min_qty` >= '3'
OR
`band`.`min_qty` = '0'
)
AND
(
`band`.`max_qty` <= '3'
OR
`band`.`max_qty` = '0'
)
GROUP BY
`service`.`serviceID`
I get this returned:
+------------------+-----+-----------+--------+----------------+----------+-------+-------------+
| name | qty | serviceID | bandID | initial_charge | chargeID | price | total_price |
+------------------+-----+-----------+--------+----------------+----------+-------+-------------+
| Royal mail | 0 | 1 | 4 | 2.00 | 3 | 1.00 | 2.00 |
| APC | 0 | 2 | 1 | 0.00 | 6 | 5.95 | 5.95 |
| Collect in store | 0 | 3 | 3 | 0.00 | 2 | 0.00 | 0.00 |
+------------------+-----+-----------+--------+----------------+----------+-------+-------------+
Which looks fine in principle until you realise that the chargeID = 3 has a price of 0.00 and yet the table is showing a price of 1.00 so the values seem to have become disassociated
What I would like to do is show the highest of the two royal mail entries
You can use MAX to obtain the maximum of a given column, e.g.
SELECT … MAX(charge.price) … FROM …
If you absolutely need the other columns (like charge.chargeID) to match, things will become a lot more complicated. So make sure you actually need that. For details on the general idea behind this kind of query, have a closer look at Select one value from a group based on order from other columns. Adapting this answer by #RichardTheKiwi, I came up with the following query:
SELECT s.name,
c.qty,
s.serviceID,
b.bandID,
b.initial_charge,
c.chargeID,
c.price,
b.initial_charge + c.price AS total_price
FROM delivery_band AS b,
delivery_service AS s,
(SELECT chargeID, price, qty,
#rowctr := IF(bandId = #lastBand, #rowctr+1, 1) AS rowNumber,
#lastBand := bandId AS bandId
FROM (SELECT #rowctr:=0, #lastBand:=null) init,
delivery_charge
WHERE qty < 3
ORDER BY bandId, price DESC
) AS c
WHERE FIND_IN_SET(225, b.accepted_countries)
AND (b.min_qty >= 3 OR B.min_qty = 0)
AND (b.max_qty <= 3 OR B.max_qty = 0)
AND s.serviceID = b.serviceID
AND c.bandID = b.bandID
AND c.rowNumber = 1
See this fiddle for the corresponding output. Note that I only do inner queries, not left queries, since that seems sufficient for the query in question, and keeps things a lot more readable so you can concentrate on the important parts, i.e. those involving rowNumber. The idea is that the subquery generates row numbers for the items of the same band, resetting them for the next band. When you select only rows with rowNumber being 1, you only get the highest price, with all other columns associated with that.

Select rows with alternate ordered field from another table

Given a *students_exam_rooms* table:
+------------+---------+---------+
| student_id | room_id | seat_no |
+------------+---------+---------+
| 1 | 30 | 1001 |
| 2 | 30 | 1002 |
| 3 | 31 | 2001 |
| 4 | 32 | 2002 |
| 5 | 33 | 3001 |
| 6 | 33 | 3002 |
| 7 | 34 | 4001 |
| 8 | 34 | 4002 |
+------------+---------+---------+
And *students_tbl*:
+------------+-------------+------+
| student_id | studen_name | year |
+------------+-------------+------+
| 1 | Eric | 1 |
| 2 | Mustafa | 1 |
| 3 | Michael | 2 |
| 4 | Andy | 2 |
| 5 | Rafael | 3 |
| 6 | Mark | 3 |
| 7 | Jack | 4 |
| 8 | peter | 4 |
+------------+-------------+------+
How can I select from *students_exam_rooms* ordering by *students_tbl.year* but with one after one like this:
+--------------+------+
| student_name | year |
+--------------+------+
| Eric | 1 |
| Michael | 2 |
| Rafael | 3 |
| Jack | 4 |
| Mustafa | 1 |
| Andy | 2 |
| Mark | 3 |
| Peter | 4 |
+--------------+------+
I'm assuming that you want to order by the "occurrence-count" of the year then the year, e.g. all the first-occurrences of all years first, sorted by year, then all second-occurrences of all years also sorted by year, and so on. That would be a perfect case for emulating other RDBMS' analytic / windowing functions:
select *
from (
select
s.studen_name,
s.year,
ser.*,
(
select 1 + count(*)
from students_tbl s2
where s.year = s2.year
and s.student_id > s2.student_id
) rank
from students_tbl s
JOIN students_exam_rooms ser
ON s.student_id = ser.student_id
) i_dont_really_want_to_name_this
order by rank, year
Here it is against a slightly tweaked version of JW's fiddle: http://www.sqlfiddle.com/#!2/27c91/1
Emulating Analytic (AKA Ranking) Functions with MySQL is a good article that gives more background and explanation.
try any of these below:
SELECT a.studen_name, a.year
FROM students_tbl a
INNER JOIN students_exam_rooms b
ON a.student_id = b.student_id
ORDER BY REVERSE(b.seat_no),
a.year
SQLFiddle Demo
by using Modulo
SELECT a.studen_name, a.year
FROM students_tbl a
INNER JOIN students_exam_rooms b
ON a.student_id = b.student_id
ORDER BY CASE WHEN MOD(b.seat_no, 2) <> 0 THEN 0 ELSE 1 END,
a.year
SQLFiddle Demo
Looks to me like you're trying to sort first by seat and then by year. Looking at your students_exam_rooms table, it looks like you started with a simple seat number and prepended year * 1000. So, if we omit the year, it looks like this:
> select * from fixed_students_exam_rooms;
+------------+---------+---------+
| student_id | room_id | seat_no |
+------------+---------+---------+
| 1 | 30 | 1 |
| 2 | 30 | 2 |
| 3 | 31 | 1 |
| 4 | 32 | 2 |
| 5 | 33 | 1 |
| 6 | 33 | 2 |
| 7 | 34 | 1 |
| 8 | 34 | 2 |
+------------+---------+---------+
And if you had that table, your query is simple:
select
student_name, year
from
modified_student_exame_rooms
left join students_tbl using (student_id)
order by
seat_no, year
;
Using the table as you currently have it, it's only slightly more complicated, assuming the "core seat number" doesn't excede 999.
select
student_name, year
from
modified_student_exame_rooms
left join students_tbl using (student_id)
order by
convert(substr(seat_no, 2), unsigned),
year
;