Good day,
I am currently making a sales tracking report where the user pick a date range then the table should display the result.
for now I have this query that counts how many items are sold
select x_transaction_details.xitem,
SUM(x_transaction_details.qty) as totalNumberSold,
count(x_transaction_details.xitem) as occurence,
x_transaction_details.cost,
i_inventory.xitem,
x_transaction_details.date_at as transDate
from x_transaction_details
left join i_inventory on x_transaction_details.xitem = i_inventory.xid
where (x_transaction_details.date_at BETWEEN '2015-08-13 08:34:12' AND '2015-09-14 08:34:12')
GROUP BY x_transaction_details.xitem
ORDER BY occurence DESC
this query displays
|itemName| totalNumberSold | occurence | date
|item 1 | 23 pcs | 2 |
|item 2 | 18 pcs | 6 |
|item 3 | 203 pcs | 18 |
etc..
Now I want to know the breakdown of sales per day so I tried
select x_transaction_details.xitem,
SUM(x_transaction_details.qty) as sold,
count(x_transaction_details.xitem) as occurence,
x_transaction_details.cost,
i_inventory.xitem,
x_transaction_details.date_at as transDate
SUM(CASE WHEN date_at = DAYOFWEEK(1) THEN
count(x_transaction_details.xitem) END) as Sunday
from x_transaction_details
left join i_inventory on x_transaction_details.xitem = i_inventory.xid
where (x_transaction_details.date_at BETWEEN '2015-08-13 08:34:12' AND '2015-09-14 08:34:12')
GROUP BY x_transaction_details.xitem
ORDER BY occurence DESC
But its generating an error instead. I want to create a more detailed table
|itemName|Mon|Tue|Wed|Thur|Fri|Sat|Sun| totalNumberSold | occurence | date
|item 1 | 10| 0| 0 | 13 | 0 |0 |0 | 23 pcs | 2 |
|item 2 | 1 | 3| 12| 0 | 16|0 |0 | 32 pcs | 6 |
|item 3 | 0 | 6| 1 | 13 | 8 |7 |1 | 36 pcs | 12 |
etc..
Thanks for the tips, I can make this table using php (hard-way) too but I guess its doable using sql query as well. Have a good day ahead.
You are missing a comma and nesting aggregation functions. I think you want:
select i.xid, SUM(td.qty) as sold, count(td.xitem) as occurrence,
avg(td.cost) as avg_cost, i.xitem,
SUM(case when DAYOFWEEK(td.date_at) = 1 then td.qty else 0 end) as Sunday
from i_inventory i join
x_transaction_details td
on td.xitem = i.xid
where td.date_at BETWEEN '2015-08-13 08:34:12' AND '2015-09-14 08:34:12'
GROUP BY i.xid
ORDER BY occurrence DESC;
Notes:
"occurrence" has two r's.
Table aliases make the query easier to write and to read.
You can't nest aggregation functions.
You shouldn't include date_at in the select list because the value is not unique for each row.
The left join is either backwards (inventory should go first) or should be an inner join. In a properly formed database, you should not have items in the transaction table that are not in the inventory table (I don't think).
The actual code (might be helpful for anyone)
select x_transaction_details.xitem, count(x_transaction_details.xitem) as occurrence, i_inventory.xitem,
SUM(case when DAYOFWEEK(x_transaction_details.date_at) = 1 then x_transaction_details.qty else 0 end) as Sun,
SUM(case when DAYOFWEEK(x_transaction_details.date_at) = 2 then x_transaction_details.qty else 0 end) as Mom,
SUM(case when DAYOFWEEK(x_transaction_details.date_at) = 3 then x_transaction_details.qty else 0 end) as tue,
SUM(case when DAYOFWEEK(x_transaction_details.date_at) = 4 then x_transaction_details.qty else 0 end) as wed,
SUM(case when DAYOFWEEK(x_transaction_details.date_at) = 5 then x_transaction_details.qty else 0 end) as thur,
SUM(case when DAYOFWEEK(x_transaction_details.date_at) = 6 then x_transaction_details.qty else 0 end) as fri,
SUM(case when DAYOFWEEK(x_transaction_details.date_at) = 7 then x_transaction_details.qty else 0 end) as sat,
SUM(x_transaction_details.qty) as totalNumberSold
from x_transaction_details
left join i_inventory on x_transaction_details.xitem = i_inventory.xid
where (x_transaction_details.date_at BETWEEN '2015-08-13 08:34:12' AND '2015-09-14 08:34:12')
GROUP BY x_transaction_details.xitem
ORDER BY occurrence DESC
I did not used table aliases though :)
Related
I am having trouble trying to work out how to structure my query to allow me to 'reset' and only count records after there has been a reset.
Basic Structure
Log Table
ID | Date | Time | SectorID | personnumber
1 | 2020-02-10 | 13:23:00 | 23 | 66 (This is a row to be counted)
2 | 2020-02-10 | 13:28:00 | 38 | 66 (This is a row to be counted)
3 | 2020-02-10 | 13:30:00 | 5 | 66 (This is a 'reset' row) (SectorID 5 is a reset)
4 | 2020-02-10 | 13:38:00 | 12 | 66 (This is a row to be counted)
5 | 2020-02-10 | 13:42:00 | 56 | 66 (This is a row to be counted)
For the above, there are 2 records, then there was a reset (which is indicated by sector ID of 5), and then 2 more records (the other records can be any other number other than 5).
So I want the 'count' to return 2
The query below is what I have for counting all records without any reset function
SELECT
personnumber,
count(*) as occurrences
FROM log
WHERE personnumber IS NOT NULL
AND sectorid != 5
GROUP BY personnumber
HAVING count(*) > 1
ORDER BY occurrences DESC, personnumber
This would return
Personnumber | Occurrences
66 | 4
I hope this explains my problem sufficiently. Any help would be much appreciated.
Thanks
Jon
You could JOIN the same table by using sectorId and personnumeber in order to count all the records after this id:
SELECT
l.personnumber,
COUNT(*) as occurrences
FROM
`log` l
INNER JOIN
(
SELECT id, personnumber
FROM `log` ll
WHERE ll.sectorid = 5
) AS ll ON l.personnumber = ll.personnumber
WHERE
l.personnumber IS NOT NULL
AND l.id > ll.id
GROUP BY l.personnumber
HAVING COUNT(*) > 1
ORDER BY
occurrences DESC,
l.personnumber
Output:
+--------------+-------------+
| personnumber | occurrences |
+--------------+-------------+
| 66 | 2 |
+--------------+-------------+
1 row in set (0.01 sec)
If you need only the records after the last occurrence of sector 5 for particular personnumber, then you need to get the maximum id within the derived table:
SELECT
l.personnumber,
COUNT(*) as occurrences
FROM
`log` l
INNER JOIN
(
SELECT MAX(id) AS id, personnumber
FROM `log` ll
WHERE ll.sectorid = 5
GROUP BY personnumber
) AS ll ON l.personnumber = ll.personnumber
WHERE
l.personnumber IS NOT NULL
AND l.id > ll.id
GROUP BY l.personnumber
HAVING COUNT(*) > 1
ORDER BY
occurrences DESC,
l.personnumber
This is sample data in my table
id_item | qty | t_in | t_out | created_at
1 5 1 0 2018-07-05 10:41:00
1 5 1 0 2018-08-03 10:41:00
1 5 0 1 2018-08-05 10:41:00
1 5 1 0 2018-09-05 10:41:00
1 5 1 0 2018-09-20 10:41:00
1 5 0 1 2018-10-31 10:41:00
My expected result will be
id_item | qty | year | month
1 5 2018 07
1 5 2018 08
1 15 2018 09
1 10 2018 10
What i have tried it works, but not desired output when want to group by montly
$date = '2018-10-31'
$test = Model::whereDate('created_at','<=',$date)->select(DB::raw('(SUM(CASE T_IN WHEN 1 THEN qty ELSE qty * - 1 END)) as total'))->groupBy('id_item')->get();
Raw queries to get the quantity for one month
Select id_item,
(SUM(CASE T_IN WHEN 1 THEN qty ELSE qty * - 1 END)) as total
from transactions
where DATE(created_at) <= 2018-10-31
group by id_item
Worst case
$last_day_of_month = [//list of last day of each month]
//then using loop to get qty of each month refer to the raw queries above
From the query above, i only able to get one line of record. I also tried to group by month and year but incorrect result caused of the date condition. How can i include multiple <= $date condition and group it accordingly to get desired output?
Any idea or is that possible to make it? Thanks.
It is a Rolling Sum problem. In newer versions of Mariadb/MySQL, it can be solved using Window Functions with Frames. However, you don't have that available.
We can rather solve this using user-defined variables. In a Derived table, we first determine the total change in qty for a month. Then, we use this result-set to calculate "final qty" at the end of a month, by adding up the previous month (row)'s qty with current month (row)'s qty_change.
I have also extended the query to consider the cases when there are more than one id_item values.
Try the following Raw query:
SELECT
#roll_qty := CASE WHEN #id_itm = dt.id_item
THEN #roll_qty + dt.qty_change
ELSE dt.qty_change
END AS qty,
#id_itm := dt.id_item AS id_item,
dt.year,
dt.month
FROM
(
SELECT
t.id_item,
SUM(t.qty * t.t_in - t.qty * t.t_out) AS qty_change,
YEAR(t.created_at) AS `year`,
LPAD(MONTH(t.created_at), 2, '0') AS `month`
FROM your_table AS t
GROUP BY t.id_item, `year`, `month`
ORDER BY t.id_item, `year`, `month`
) AS dt
CROSS JOIN (SELECT #roll_qty := 0,
#id_itm := 0
) AS user_init_vars;
| id_item | year | month | qty |
| ------- | ---- | ----- | --- |
| 1 | 2018 | 07 | 5 |
| 1 | 2018 | 08 | 5 |
| 1 | 2018 | 09 | 15 |
| 1 | 2018 | 10 | 10 |
View on DB Fiddle
If you are going to use variables, you need to do it correctly. MySQL does not guarantee the order of evaluation of expressions in a SELECT. So, a variable should not be assigned in one expression and then used in another.
This makes for complicated expressions, but it is possible:
select yyyy, mm, total,
(#t := if(#ym = concat_ws('-', yyyy, mm), #t + total,
#ym := concat_ws('-', yyyy, mm), total
)
) as running_total
from (select year(created_at) as yyyy, month(created_at) as mm,
id_item,
sum(case T_IN when 1 then qty else - qty end) as total
from transactions
where created_at < '2018-11-01'
group by id_item
order by id_item, min(created_at)
) i cross join
(select #ym := '', #n := 0);
Table example
id | name | value | date
--------------------------------------------
1 | abc | 20 | 2018-01-26
1 | abc | 24 | 2018-01-27
1 | abc | 25 | 2018-01-28
1 | abc | 30 | 2018-01-29
I know how to fetch data from 28th Jan or today. But I need some way to show values of two dates in two columns. Is it possible in one mysql query?
Like this
name | value_today | value_pre
---------------------------------------
abc | 30 | 25
You can achieve this using the case. This query returns the today's value and previous date value:
SELECT
id, name,
Sum(Case When date = CURDATE()
Then value Else 0 End) TodaySum,
Sum(Case When (date = CURDATE()-1)
Then value Else 0 End) PreviousSum
FROM tbl1
group by id, name
Here's a hypothetical query that would do it.
SELECT t1.value AS value_today,(SELECT t2.value FROM table AS t2 WHERE t2.date=2018-01-29 ) AS value_pre
FROM table AS t1 WHERE t1.date=2018-01-28
I currently have a database table which records the bookings of jobs
and there are 8 timeslots available
+-----------+
|tbl_booking|
+-----------+
|room_id |
|date |
|timeslot |
|booking |
+-----------+
sample data
+-----------+----------+-----------+
|room_id | date | timeslot |
+-----------+----------+-----------+
|1 |2018-01-01| 1 |
|1 |2018-01-01| 2 |
|1 |2018-01-01| 4 |
|2 |2018-01-01| 1 |
+-----------+----------+-----------+
intended outcome - when statement filters for bookings on 2018-01-01
+-----------+----------+-----------+----------+-----------+
|room |timeslot1 | timeslot2 |timeslot3 | timeslot4 |
+-----------+----------+-----------+----------+-----------+
|1 | X | X | | X |
|2 | X | | | |
+-----------+----------+-----------+----------+-----------+
i started off with this statement:
SELECT * from tbl_booking WHERE date = '2018-01-01' GROUP BY room_id
and this would return results to see the results grouped by rooms.
I would like to know where i should go from here to also have the results display it's timeslots that are shown in a table displaying the booking status of eacah room's timeslot in the day?
Should there be an SQL statement that i should be using or am I on the wrong track completely?
Any help would be appreciated.
Thank you!
What you want to do with the data isn't nice to do and if you have a fixed number of time slots then you can hardcode the columns like this:
SELECT room_id,
SUM(CASE WHEN timeslot = 1 then 1 else 0 END) AS Timeslot1,
SUM(CASE WHEN timeslot = 2 then 1 else 0 END) AS Timeslot2,
SUM(CASE WHEN timeslot = 3 then 1 else 0 END) AS Timeslot3,
SUM(CASE WHEN timeslot = 4 then 1 else 0 END) AS Timeslot4
FROM tbl_booking
GROUP BY room_id
(see SQL Fiddle)
You could use MAX if you just want to see if at least 1 booking exist
SQL to include remark, you can trick it to select the remark through a group by with MAX
SELECT room_id,
SUM(CASE WHEN timeslot = 1 then 1 else 0 END) AS Timeslot1,
MAX(CASE WHEN timeslot = 1 THEN remark ELSE '' END) AS Timeslot1Remark,
SUM(CASE WHEN timeslot = 2 then 1 else 0 END) AS Timeslot2,
MAX(CASE WHEN timeslot = 2 THEN remark ELSE '' END) AS Timeslot2Remark,
SUM(CASE WHEN timeslot = 3 then 1 else 0 END) AS Timeslot3,
MAX(CASE WHEN timeslot = 3 THEN remark ELSE '' END) AS Timeslot3Remark,
SUM(CASE WHEN timeslot = 4 then 1 else 0 END) AS Timeslot4,
MAX(CASE WHEN timeslot = 4 THEN remark ELSE '' END) AS Timeslot4Remark
FROM tbl_booking
GROUP BY room_id
your extended SQL Fiddle. This won't work for multiple dates only 1 selected date
If you are looking for dynamic solutions then you must need to use pivot table.
Select * from
(select * from yourtable) as
Temptable
Pivot (
Count(room)
For timeslot
In (list of timeslot))
As tempSlot
For more information check this link MySQL pivot table
Here's my table definition:
Table ___Rooms:
|--------|-----------|--------|----------|
|ROO_Id |ROO_HotelId|ROO_Name|ROO_Number|
|--------|-----------|--------|----------|
| 1|AAA00 |Room 12 | 12|
| 2|AAA00 |Room 14 | 14|
| 3|AAA00 |Room 16 | 16|
| 4|ZZZ99 |Room 11 | 11|
| 5|ZZZ99 |Room 22 | 22|
| 6|ZZZ99 |Room 33 | 33|
|--------|-----------|--------|----------|
Table ___Bookings:
|--------|-----------|----------|
|BOO_Id |BOO_HotelId|BOO_RoomId|
|--------|-----------|----------|
| 1|AAA00 | 1|
| 2|AAA00 | 1|
| 3|AAA00 | 3|
| 4|ZZZ99 | 5|
| 5|ZZZ99 | 5|
| 6|ZZZ99 | 5|
|--------|-----------|----------|
Actually, I have:
Number of booking for AAA00 = 3
Number of rooms for AAA00 = 3
I want to list rooms for the property AAA00 only and rank them by the most popular in them of number of bookings.
So I use this query:
SELECT r.ROO_Number BOO_RoomId,
( ( ifnull(cnt_book,0)*100)/(SELECT count(*) FROM ___Bookings)) percentage,
ifnull(cnt_book,0) `count`
FROM ___Rooms r
LEFT JOIN (
SELECT BOO_RoomId, count(*) cnt_book
FROM ___Bookings
WHERE BOO_HotelId='AAA00'
GROUP BY BOO_RoomId
) cnt ON r.ROO_Id=cnt.BOO_RoomId
ORDER BY percentage DESC
The expecting result of this query was:
1 - Room 2 - 2 bookings - 66.66%
2 - Room 3 - 1 booking - 33.33%
3 - Room 2 - 0 booking - 00.00$
But it returns me all the rooms.
Could you please help me with that ?
Thanks.
Solution
Use CASE with SUM to add together all bookings per room. Then, JOIN to subquery to make the hotel total-bookings available to every row.
SELECT r.ROO_Name
, Sum(CASE WHEN BOO_id IS NULL THEN 0 ELSE 1 END) NumBookings
, Concat(
Format(
Sum(CASE WHEN BOO_id IS NULL THEN 0 ELSE 1 END)
/ TotalBookings
* 100
, 0)
, '%') AS PercentageTotal
FROM ( __Rooms r LEFT JOIN __Bookings b ON r.ROO_Id = b.BOO_RoomId
) INNER JOIN (SELECT BOO_HotelId
, Count(*) AS TotalBookings
FROM __Bookings
GROUP BY BOO_HotelId
) AS TotalHotelBookings
ON r.ROO_HotelId = TotalHotelBookings.BOO_HotelId
WHERE r.ROO_HotelId = 'AAA00'
GROUP BY r.ROO_Name
ORDER BY r.ROO_Name
;
Result Set
ROO_Name NumBookings PercentageTotal
-------- ----------- ---------------
Room 12 2 67%
Room 14 0 0%
Room 16 1 33%
Key point
Sum(CASE WHEN BOO_id IS NULL THEN 0 ELSE 1 END)
Pretty much should be something like this:
SELECT
foo.ROO_Id,
foo.ROO_Name,
foo.cnt,
(foo.cnt * 100.0) / (SELECT count(*) FROM ___Bookings WHERE BOO_HotelId = foo.ROO_HotelId) AS percentage
FROM (
SELECT
ROO_Id,
ROO_Name,
ROO_HotelId,
(SELECT count(*) FROM ___Bookings b WHERE b.BOO_RoomId = r.ROO_Id) AS cnt
FROM ___Rooms r
WHERE ROO_HotelId = 'AAA00'
) AS foo
ORDER BY cnt DESC