Query with individual dates, counting values by criteria - ms-access

I want a query that only shows individual dates (so no single date appears twice/multiple), counts the number of records with given date and counts further the number of records with another field fulfilling other criteria:
Date | total amount | Field2 = "Aaa" | Field2 = "Bbb" | Filed2 = ....
10.7.18 | 32 | 2 | 3 | ...
11.7.18 | 20 | 5 | 2 | ...
My approach is a query with its property set to "unique values" to "yes", than I added the date-field two times, first with its function set to "group by", the second with its function set to "count"; this gives me my individual dates only and the number of records with the set date. SO far so good.
Now I add "Field2", set its function to "count", and add the criteria to "=Value("Aaa")". This brings up an empty list of records.

Try this:
Select
[Date],
Sum(Amount) As TotalAmount,
Sum(Abs(Field2 = "Aaa")) As CountA,
Sum(Abs(Field2 = "Bbb")) As CountB,
Sum(Abs(Field2 = "Ccc")) As CountC
From
YourTable
Group By
[Date]
If all you need is a total count, use:
Select
[Date],
Count(*) As TotalCount,
Sum(Abs(Field2 = "Aaa")) As CountA,
Sum(Abs(Field2 = "Bbb")) As CountB,
Sum(Abs(Field2 = "Ccc")) As CountC
From
YourTable
Group By
[Date]

Related

SQL - Select only latest insertion of each ID

I have the following SQL table:
autID (auto increment, number) | externalID (NOT unique, string) | title | price | scanDate (datetime)
I also have the following query to calculate the average price:
SELECT AVG(price) AS avgPrice from rs
My goal is to calculate the average price of the latest inserted of each autID only (or based on scanDate). For example:
1 | "baba" | 100 | date
2 | "baba | 50| newerDate
3 | "oo | 100| date
will return:
avgPrice: (50 + 100) / 2 = 75
the first 100 is neglected since it's older of the same ID of baba
Any idea?
One method is to use a correlated subquery to get the most recent value and then use average:
select avg(rs.price)
from rs
where rs.scandate = (select max(r2.scandate)
from rs rs2
where rs2.externalid = rs.externalid
) ;
Two steps, first find the newest record for each ID, second, do avg()
WITH CTE123 AS (
Select ID, Title, MAX(Scandate) as Scandate
From avgPrice
GROUP BY ID, Title
)
SELECT avg(rs.price)
FROM avgPrice rs
JOIN CTE123 rp ON rs.id=rp.id and rs.Scandate=rp.Scandate
The first step is to find the ID with its newest scandate, and the second step is to join the first CTE and calculate the average for the results. In this way, the calculation is done based on only the newest records.

How to fetch rows from which sum of a single integer/float column sums upto a certain value

I have a table. It has the following structure
goods_receiving_items
id
item_id
quantity
created_at
I am trying to fetch rows against which have the following conditions
Has one item_id
When the sum of the quantity column equals a certain value
So for example I have the following data
+----+---------+----------+------------+
| id | item_id | quantity | created_at |
+----+---------+----------+------------+
| 1 | 2 | 11 | 2019-10-10 |
| 2 | 3 | 110 | 2019-10-11 |
| 3 | 2 | 20 | 2019-11-09 |
| 4 | 2 | 5 | 2019-11-10 |
| 5 | 2 | 1 | 2019-11-11 |
+----+---------+----------+------------+
I have tried the following query:
SET #sum:= 0;
SELECT item_id, created_at, (#sum:= #sum + quantity) AS SUM, quantity
FROM goods_receiving_items
WHERE item_id = 2 AND #sum<= 6
ORDER BY created_at DESC
If I don't use ORDER BY, then the query will give me ID '1'. But if I use ORDER BY it will return all the rows with item_id = 2.
What should be returned are IDs '5' and '4' exclusively in this order
I can't seem to resolve this and ORDER BY is essential to my task.
Any help would be appreciated
You should use the order by on the resulting set
you could do this using a subquery
SET #sum:= 0;
select t.*
from t (
SELECT item_id
, created_at
, (#sum:= #sum + quantity) as sum
, quantity
FROM goods_receiving_items
WHERE item_id = 2 AND #sum<= 6
) t
ORDER BY created_at DESC
You should try an INNER JOIN with SELECT min(created_at) or SELECT max(created_at)
From MYSQL docs:
...the selection of values from each group cannot be influenced by
adding an ORDER BY clause. Sorting of the result set occurs after
values have been chosen, and ORDER BY does not affect which values the
server chooses.
The answers on the following might help in more detail: MYSQL GROUP BY and ORDER BY not working together as expected
After searching around, I have made up the following query
SELECT
t.id, t.quantity, t.created_at, t.sum
FROM
( SELECT
*,
#bal := #bal + quantity AS sum,
IF(#bal >= $search_number, #doneHere := #doneHere + 1 , #doneHere) AS whereToStop
FROM goods_receiving_items
CROSS JOIN (SELECT #bal := 0.0 , #doneHere := 0) var
WHERE item_id = $item_id
ORDER BY created_at DESC) AS t
WHERE t.whereToStop <= 1
ORDER BY t.created_at ASC
In the above query, $search_number is a variable that holds the value that has to be reached. $item_id is the item we are searching against.
This will return all rows for which the sum of the column quantity makes up the required sum. The sum will be made with rows in descending order by created_at and then will be rearranged in ascending order.
I was using this query to calculate the cost when a certain amount of items are being used in an inventory management system; so this might help someone else do the same. I took most of the query from another question here on StackOverflow

How to find multiple records(attendance) of same date with alias of same table in MySql

I have a table named rjs_attendance with following four column
_________________________________________________
|attenedance_id | admin_id | note | created_date|
-------------------------------------------------
A user can make attendance several times in a day. Odd entry is assumed as sign in and even entry is assumed as sign out.
The output I need looks something like this.
_______________________________________________________________________________
|admin_id | time_in | time_in_note | time_out | time_out_note | date |
-------------------------------------------------------------------------------
|1 |10:00 | none | 11:00 | none | 2015-12-24|
-------------------------------------------------------------------------------
|1 |11:30 |none |12:15 |none | 2015-12-24|
-------------------------------------------------------------------------------
I'm not able to fetch all record of the same date, but I'm able to fecth one record of the same date.
The query I have run is as follows:
SELECT
`atd_in`.`admin_id` AS `admin_id`,
CAST(MIN(`atd_in`.`created_date`) AS TIME) AS `time_in`,
`atd_in`.`note` AS `time_in_note`,
CAST(MAX(`atd_out`.`created_date`) AS TIME) AS `time_out`,
`atd_out`.`note` AS `time_out_note`,
CAST(`atd_in`.`created_date` AS DATE) AS `date_on`
FROM
`zf2`.`rjs_attendance` `atd_in`
LEFT JOIN `zf2`.`rjs_attendance` `atd_out`
ON
`atd_in`.`admin_id` = `atd_out`.`admin_id`
AND CAST(`atd_in`.`created_date` AS DATE) = CAST(`atd_out`.`created_date` AS DATE)
AND `atd_in`.`attendance_id` <> `atd_out`.`attendance_id`
GROUP BY
CAST(`atd_in`.`created_date` AS DATE), `atd_in`.`admin_id`
Any help is greatly appreciated.
Try this:
SELECT A.admin_id,
MAX(IF(A.ID % 2 = 1, CAST(A.created_date AS TIME), NULL)) AS time_in,
MAX(IF(A.ID % 2 = 1, note, NULL)) AS time_in_note,
MAX(IF(A.ID % 2 = 0, CAST(A.created_date AS TIME), NULL)) AS time_out,
MAX(IF(A.ID % 2 = 0, note, NULL)) AS time_out_note,
CAST(A.created_date AS DATE) AS date_on
FROM (SELECT IF(#adminId=#adminId:=A.admin_id, #id:=#id+1, #id:=1) AS ID,
A.admin_id, A.note, A.created_date
FROM zf2.rjs_attendance A, (SELECT #id:=1, #adminId:=0) AS B
ORDER BY A.admin_id, A.attendance_id
) AS A
GROUP BY A.admin_id, CAST(A.created_date AS DATE), CEILING(A.ID / 2);
sqlfiddle
before seeing any code - if you assume odd and even as entry and exit => join on this condition -
on A.id +1 = B.id
or
`atd_in`.`attendance_id` +1 = `atd_out`.`attendance_id`
you can even leave it left joined for non exit entries
(ids should be numerical of course)
if your records are not sorted this way, just reissue the ids column after sorting with order by and make new id column

Sum columns depending on another column value

I'm having trouble with summing the fields values based on another fields value.
I need to SUM(activities.points) based on activities.activity_type if it's used_points or added_points and put it in AS used_points/added_points.
Table activities:
id | subscription_id | activity_type | points
--------------------------------------------------
1 | 1 | used_points | 10
2 | 1 | used_points | 50
3 | 1 | added_points | 20
4 | 1 | added_points | 30
5 | 2 | used_points | 20
6 | 2 | used_points | 45
7 | 2 | added_points | 45
8 | 2 | added_points | 45
Table subscriptions:
id | name | current_points
-------------------------------------
1 | card_1 | 700
2 | card_2 | 900
What I need:
name | current_points | used_points | added_points
-----------------------------------------------------------
card_1 | 700 | 60 | 50
card_2 | 900 | 65 | 90
What I tried :
SELECT
subscriptions.name,
subscriptions.current_points,
IF(activities.activity_type="used_points", SUM(activities.points), null)
AS used_points,
IF(activities.activity_type="added_points", SUM(activities.points), null)
AS added_points
FROM activities
JOIN subscriptions
ON activities.subscription.id = subscription.id
GROUP BY subscriptions.name
Which is wrong.
Thanks
You want to use SUM(IF( )). You want to add up the values returned from the IF. You want that IF expression to be evaluated for each individual row. Then, use the SUM aggregate to add up the value returned for each row.
Remove the SUM aggregate from inside the IF expression and instead, wrap the IF inside a SUM.
Followup
Q But why SUM() inside of IF doesn't work ?
A Well, it does work. It's just not working the way you want it to work.
The MySQL SUM function is an "aggregate" function. It aggregates rows together, and returns a single value.
For an expression of this form: IF(col='foo',SUM(numcol),0)
What MySQL is doing is aggregating all the rows into the SUM, and returning a single value.
Other databases would pitch a fit, and throw an error with the reference to the non-aggregate col in that expression. MySQL is more lenient, and treats the col reference like it was an aggregate (like MIN(col), or MAX(col)... working on a group of rows, and returning a single value. In this case, MySQL is selecting a single, sample row. (It's not determinate which row will be "chosen" as the sample row.) So that reference to col is sort of like a GET_VALUE_FROM_SAMPLE_ROW(col). Once the aggregates are completed, then that IF expression gets evaluated once.
If you start with this query, this is the set of rows you want to operate on.
SELECT s.name
, s.current_points
, a.activity_type
, a.points
, IF(a.activity_type='used_points',a.points,NULL) AS used_points
, IF(a.activity_type='added_points',a.points,NULL) AS added_points
FROM subscriptions s
JOIN activities a
ON a.subscription_id = s.id
When you add a GROUP BY clause, that's going to aggregate some of those rows together. What you will get back for the non-aggregates is values from a sample row.
Try adding GROUP BY s.name to the query, and see what is returned.
Also try adding in some aggregates, such as SUM(a.points)
SELECT s.name
, s.current_points
, a.activity_type
, a.points
, IF(a.activity_type='used_points',a.points,NULL) AS used_points
, IF(a.activity_type='added_points',a.points,NULL) AS added_points
, SUM(a.points) AS total_points
FROM subscriptions s
JOIN activities a
ON a.subscription_id = s.id
GROUP BY s.name
Finally, we can add in the expressions in your query into the SELECT list:
, IF(a.activty_type='used_points',SUM(a.points),NULL) AS if_used_sum
, IF(a.activty_type='added_points',SUM(a.points),NULL) AS if_added_sum
What we discover is that the value returned from these expressions will either be SUM(a.points), which will match the total_points, or it will be NULL. And we can see the value of the activity_type column, retrieved from a single, sample row for each group, and we can see that this is expression is "working", it's just not doing what we you really want to happen: for the conditional test to run on each individual row, returning a value for points or a null, and then summing that up for the group.
Your code is only slightly out:
SELECT
subscriptions.name,
subscriptions.current_points,
SUM(IF(activities.activity_type="used_points", 0, activities.points))
AS used_points,
SUM(IF(activities.activity_type="added_points", 0, activities.points))
AS added_points
FROM activities
JOIN subscriptions
ON activities.subscription_id = subscription.id
GROUP BY subscriptions.name, subscriptions.current_points
Note the fixed typo in the second last line - you wrote subscription.id instead of subscription_id. Also you only grouped by name instead of name and current_points, not sure if that's allowed in mysql (I use T-SQL), it's good practice to have it there anyway.
Well, I did it not using the IF statement. Here's the example (http://sqlfiddle.com/#!2/076c3f/12):
SELECT
subs.name,
subs.current_points,
(SELECT SUM(points) FROM activities WHERE type = 1 AND subs_id = subs.id) AS used_points,
(SELECT SUM(points) FROM activities WHERE type = 2 AND subs_id = subs.id) AS added_points
FROM activities
JOIN subs ON activities.id = subs.id
GROUP BY subs.name
NOTE: I changed the type from VARCHAR to INT to simplify.
Try change
IF(activities.activity_type="used_points", null, SUM(activities.points))
AS used_points,
IF(activities.activity_type="added_points", null, SUM(activities.points))
AS added_points
To next
SUM(IF(activities.activity_type="used_points", activities.points, 0))
AS used_points,
SUM(IF(activities.activity_type="added_points", activities.points, 0))
AS added_points
In this way you check column and sum points or 0
To sum a column of integer values(c1) based on another column of character values(c2). And if you need to sum only not null values, the below code will help.
SELECT SUM(c1) FROM table_name WHERE c2 <> '' AND c2 IS NOT NULL

something like "group by" for columns?

I have table like this:
+----+---------+---------+--------+
| id | value_x | created | amount |
+----+---------+---------+--------+
value_x is set of six strings, lets say "one", "two", "three", etc.
I need to create report like this:
+--------------+-------------------------+-------------------+----------------------+
| day_of_month | "one" | "two" | [etc.] |
+--------------+-------------------------+-------------------+----------------------+
| 01-01-2011 | "sum(amount) where value_x = colum name" for this specific day |
+--------------+-------------------------+-------------------+----------------------+
Most obvious solution is:
SELECT SUM(amount), DATE(created) FROM `table_name` WHERE value_x=$some_variable GROUP BY DATE(created)
And loop this query six times with another value for $some_variable in every iteration, but I'm courious if is it possible to do this in single query?
What you're asking is called a "pivot table" and is typically achieved as below. The idea is for each potential value of value_x you either produce a 1 or 0 per row and sum 1's and 0's to get the sum for each value.
SELECT
DATE(created),
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'one',
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'two',
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'three',
etc...
FROM table_name
GROUP BY YEAR(created), MONTH(created), DAY(created)
This will come close:
SELECT
s.day_of_month
,GROUP_CONCAT(CONCAT(s.value_x,':',s.amount) ORDER BY s.value_x ASC) as output
FROM (
SELECT DATE(created) as day_of_month
,value_x
,SUM(amount) as amount
FROM table1
GROUP BY day_of_month, value_x
) s
GROUP BY s.day_of_month
You will need to read the output and look for the value_x prior to the : to place the items in the proper column.
The benefit of this approach over #Michael's approach is that you do not need to know the possible values of field value_x beforehand.