I would like help with a before update trigger that conditionally sums the amount column where day and id match, and only display the sum where variable is ‘total’.
id | day | variable | amount
-: | :------ | :------- | :-----
1 | Monday | Total | null
1 | Monday | null | 1
1 | Monday | null | 2
1 | Monday | null | 3
1 | Tuesday | Total | null
1 | Tuesday | null | 1
1 | Tuesday | null | 2
1 | Tuesday | null | 3
2 | Monday | Total | null
2 | Monday | null | 1
2 | Monday | null | 2
2 | Monday | null | 3
2 | Tuesday | Total | null
2 | Tuesday | null | 1
2 | Tuesday | null | 2
2 | Tuesday | null | 3
Is there a way to control the sum function so that it won’t update each total unless an associated value has been updated? I.E I wouldn’t want the Monday total to recalculate when values change inside a Tuesday row, or for another ID.
Fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=fc3939aa508002dab7f2af45611717cf
Note that MySQL has a restriction on updating other rows in the same table in a trigger according to the documentation.
A stored function or trigger cannot modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger.
If the following SQL statement will work
UPDATE tb1 t1
JOIN (
SELECT id, day, SUM(amount) as total
FROM tb1 t
WHERE id = 1 AND day = 'Monday' AND variable is NULL
GROUP BY id, day
) t2 ON t1.id = t2.id AND t1.day = t2.day AND t1.variable = 'Total'
SET t1.amount = t2.total;
Then its implementation inside the trigger will throw an error
You can't specify target table 'tb1' for update in FROM clause
But you can do it without the trigger and create a view
SELECT
id, day, variable,
CASE WHEN variable = 'Total'
THEN (
SELECT SUM(amount) FROM tb1 t1
WHERE t1.id = t.id AND t1.day = t.day AND t1.variable IS NULL
)
ELSE amount
END AS amount
FROM tb1 t
Or, it is even possible to completely delete all rows with a Total and retrieve them using the WITH ROLLUP clause.
SELECT
id,
day,
CASE WHEN amount IS NULL THEN 'Total' END AS variable,
SUM(amount) AS amount
FROM tb1
GROUP BY id, day, amount
WITH ROLLUP
HAVING id IS NOT NULL AND day IS NOT NULL
db<>fiddle
Related
Sorry if this is a duplicate but I never found an answer to this.
I have a User table which is as follows :
| id | pseudo | inscription date |
|----|-------------|------------------|
| 1 | johndoe | 01/01/1970 |
| 2 | janeyes | 02/01/1970 |
| 3 | thirdpseudo | 05/01/1970 |
And I am searching for a query to do statistics of accumulation. I would like to retrieve, day by day, the number of users registered.
I made a query that retrieves only for the registering days, but I don't find how to accumulate every days...
SELECT DATE_FORMAT(date, "%d/%m/%Y") AS 'Day', COUNT(*) AS 'Number of registered users'
FROM User
GROUP BY DATE(date)
ORDER BY date DESC;
This query outputs :
| date | number of registered users |
| ---------- | -------------------------- |
| 01/01/1970 | 1 |
| 02/01/1970 | 1 |
| 05/01/1970 | 1 |
The output I would like for this example is :
| date | number of registered users |
| ---------- | -------------------------- |
| 01/01/1970 | 1 |
| 02/01/1970 | 2 |
| 03/01/1970 | 2 |
| 04/01/1970 | 2 |
| 05/01/1970 | 3 |
| 06/01/1970 | 3 |
I would suggest to generate some dates data defined as range of dates. Then join all users available to these dates and count how many users were registered during such days.
Here is the code:
-- creating simple table
create table Users
(
id int not null,
pseudo varchar(15),
date date
);
-- adding some data
insert into Users
values
(1,'jonh','1970-01-01'),
(2,'doe','1970-01-02'),
(3,'janeyes','1970-01-02'),
(4,'third','1970-01-03'),
(5,'pseudo','1970-01-03'),
(6,'title','1970-01-04'),
(7,'somename','1970-01-04'),
(8,'anothername','1970-01-04');
-- defines the start date and the end date
set #startDate = '1970-01-01';
set #endDate = '1970-02-01';
-- recursively geneterates all dates within the range
with RECURSIVE dateRange (Date) as
(
select #startDate as Date
union ALL
select DATE_ADD(Date, INTERVAL 1 DAY)
from dateRange
where Date < #endDate
)
-- using SUM() over () would result in running total starting
-- from 1, it would count next day + all previous days
select Date, Sum(RegisteredUsersCount) over(order by RegisteredUsersCount asc
rows between unbounded preceding and current row) as RegisteredUsersCount
from
(
-- left join will join all users, if there is no users that correspond to the date of join, then it would be 0 for that date.
select dr.Date, Count(u.id) as RegisteredUsersCount
from dateRange as dr
left join Users as u
on dr.Date = u.date
group by dr.Date
) as t
order by Date asc;
And working example to test: SQLize Online
I am trying to create a query that can get First Non-null value for selected columns from the table.
I cant fire multiple queries and union it per every column as I have so many columns. I tried to create a query using answers form some SO questions. but it doesn't work for me.
Example Table
| orders| id | default_address |
|-------|------|-----------------|
| 1 | 1 | null |
| 2 | null | null |
| 3 | 2 | 3 |
Expected Result
| id | default_address |
|----|-----------------|
| 1 | 3 |
Another Example Table
| orders| id | default_address |
|-------|------|-----------------|
| 1 | 1 | null |
| 2 | null | 5 |
| 3 | 2 | 3 |
Expected Result
| id | default_address |
|----|-----------------|
| 1 | 5 |
What i tried is here
http://sqlfiddle.com/#!9/84a5c/1
http://sqlfiddle.com/#!9/574481/2
Here is one way to do this using analytic functions, assuming you are using MySQL 8+:
WITH cte AS (
SELECT *,
COUNT(id) OVER (ORDER BY orders) cnt_id,
COUNT(default_address) OVER (ORDER BY orders) cnt_addr
FROM yourTable
)
SELECT
MAX(CASE WHEN cnt_id = 1 THEN id END) AS id,
MAX(CASE WHEN cnt_addr = 1 THEN default_address END) AS default_address
FROM cte;
This assumes that there actually exist a third column orders which generates the ordering shown in your sample data.
This trick works because the COUNT() function by default only counts non NULL values. So, used a window function with the ordering given by the orders column, it would only equal 1 at the first non NULL value.
For earlier MySQL versions:
SELECT
MAX(id) AS id,
MAX(default_address) AS default_address
FROM
(
SELECT
CASE WHEN (SELECT COUNT(t2.id) FROM yourTable t2
WHERE t2.orders <= t1.orders) = 1 THEN id END AS id,
CASE WHEN (SELECT COUNT(t2.default_address) FROM yourTable t2
WHERE t2.orders <= t1.orders) = 1 THEN default_address END AS default_address
FROM yourTable t1
) t;
I'm trying to set the value of another column on the first occurrence of any value in a username column in monthly intervals, if there's another column with an specific value.
create table table1
(
username varchar(30) not null,
`date` date not null,
eventid int not null,
firstFlag int null
);
insert table1 (username,`date`, eventid) values
('john','2015-01-01', 1)
, ('kim','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-03-01', 2)
, ('john','2015-03-01', 1)
, ('kim','2015-01-01', 1)
, ('kim','2015-02-01', 1);
This should result in:
| username | date | eventid | firstFlag |
|----------|------------|---------|-----------|
| john | 2015-01-01 | 1 | 1 |
| kim | 2015-01-01 | 1 | 1 |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-03-01 | 2 | 1 |
| john | 2015-03-01 | 1 | (null) |
| kim | 2015-01-01 | 1 | (null) |
| kim | 2015-02-01 | 1 | 1 |
I've tried using joins as described here, but it updates all rows:
update table1 t1
inner join
( select username,min(`date`) as minForGroup
from table1
group by username,`date`
) inr
on inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
As a1ex07 points out, it would need another per row unique constrain to update the rows I need to:
update table1 t1
inner join
( select id, username,min(`date`) as minForGroup
from table1
where eventid = 1
group by username,month(`date`)
) inr
on inr.id=t1.id and inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
Add an Id column, and use it on the join on constrains.
To allow only those that satisfies a specific condition on another column you need the where clause inside the subquery, otherwise it would try to match different rows as the subquery would return rows with eventid=2 while the update query would return only those with eventid=1.
To use yearly intervals instead of monthly, change the group by statement to use years.
I have the following table:
---------------------------------
| id | class_id | time | status |
---------------------------------
| 1 | 1 | <> | 1 |
---------------------------------
| 2 | 2 | <> | 1 |
---------------------------------
| 3 | 1 | <> | 0 |
---------------------------------
| 4 | 2 | <> | 0 |
---------------------------------
I want a query that will see that the first row has class_id = 1 and status = 1. It will then look for the next row with class_id = 1 and status = 0, and find the time difference between the two (time is DATETIME).
At the end of all this, it will return me a sum of all time differences (i.e. (row 1 - row 3) + (row2 - row4)).
How is this possible? In generalisation, the question is about getting an aggregate total of differences between rows in a table, based off a condition.
For every status 0 record we search the latest status 1 record. This is from all previous status 1 records take the latest.
select
class_id,
sum
(
timestampdiff
(
second,
(
select s1.time
from mytable s1
where s1.status = 1
and s1.class_id = s0.class_id
and s1.id < s0.id
order by s1.id desc limit 1
),
s0.time
)
) as diffsecs
from mytable s0
where status = 0
group by class_id;
I have a table with columns start_date and end_date. What we need to do is Select everything and group them by date conflicts for each Object_ID.
A date conflict is when a row's start date and/or end date pass through another rows'. For instance, here are some examples of conflicts:
Row 1 has dates 1st through the 5th, Row 2 has dates 2nd through the 3rd.
Row 1 has dates 2nd through the 5th, Row 2 has dates 1st through the 3rd.
Row 1 has dates 2nd through the 5th, Row 2 has dates 3rd through the 6th.
Row 1 has dates 2nd through the 5th, Row 2 has dates 1st through the 7th.
So for example, if we have some sample data (assume the numbers are just days of the month for simplicity):
id | object_id | start_date | end_date
1 | 1 | 1 | 5
2 | 1 | 2 | 4
3 | 1 | 6 | 8
4 | 2 | 2 | 3
What i would expect to see is this:
object_id | start_date | end_date | numconflicts
1 | <na> | <na> | 2
1 | 6 | 8 | 0 or null
2 | 2 | 3 | 0 or null
And for a Second Test Case, Here is some sample data:
id | object_id | start_date | end_date
1 | 1 | 1 | 5
2 | 1 | 2 | 4
3 | 1 | 6 | 8
4 | 2 | 2 | 3
5 | 2 | 4 | 5
6 | 1 | 2 | 3
7 | 1 | 10 | 12
8 | 1 | 11 | 13
And for the second Test Case, what I would expect to see as output:
object_id | start_date | end_date | numconflicts
1 | <na> | <na> | 3
1 | 6 | 8 | 0 or null
2 | 2 | 3 | 0 or null
2 | 4 | 5 | 0 or null
1 | <na> | <na> | 2
Yes, I will need some way of differentiating the first and the second grouping (the first and last rows) but I haven't quite figured that out. The goal is to view this list, and then when you click on a group of conflicts you can view all of the conflicts in that group.
My first thought was to attempt some GROUP BY CASE ... clause but I just wrapped by head around itself.
The language I am using to call mysql is php. So if someone knows of a php-loop solution rather than a large mysql query i am all ears.
Thanks in advance.
Edit: Added in primary Keys to provide a little less confusion.
Edit: Added in a Test case 2 to provide some more reasoning.
This query finds the number of duplicates:
select od1.object_id, od1.start_date, od1.end_date, sum(od2.id is not null) as dups
from object_date od1
left join object_date od2
on od2.object_id = od1.object_id
and od2.end_date >= od1.start_date
and od2.start_date <= od1.end_date
and od2.id != od1.id
group by 1,2,3;
You can use this query as the basis of a query that gives you exactly what you asked for (see below for output).
select
object_id,
case dups when 0 then start_date else '<na>' end as start_date,
case dups when 0 then end_date else '<na>' end as end_date,
sum(dups) as dups
from (
select od1.object_id, od1.start_date, od1.end_date, sum(od2.id is not null) as dups
from object_date od1
left join object_date od2
on od2.object_id = od1.object_id
and od2.end_date >= od1.start_date
and od2.start_date <= od1.end_date
and od2.id != od1.id
group by 1,2,3) x
group by 1,2,3;
Note that I have used an id column to distinguish the rows. However, you could replace the test of id's not matching with comparisons on every column, ie replace od2.id != od1.id with tests that every other column is not equal, but that would require a unique index on all the other columns to make sense, and having an id column is a good idea anyway.
Here's a test using your data:
create table object_date (
id int primary key auto_increment,
object_id int,
start_date int,
end_date int
);
insert into object_date (object_id, start_date, end_date)
values (1,1,5),(1,2,4),(1,6,8),(2,2,3);
Output of first query when run against this sample data:
+-----------+------------+----------+------+
| object_id | start_date | end_date | dups |
+-----------+------------+----------+------+
| 1 | 1 | 5 | 1 |
| 1 | 2 | 4 | 1 |
| 1 | 6 | 8 | 0 |
| 2 | 2 | 3 | 0 |
+-----------+------------+----------+------+
Output of second query when run against this sample data:
+-----------+------------+----------+------+
| object_id | start_date | end_date | dups |
+-----------+------------+----------+------+
| 1 | 6 | 8 | 0 |
| 1 | <na> | <na> | 2 |
| 2 | 2 | 3 | 0 |
+-----------+------------+----------+------+
Oracle : This could be done with a subquery in a group by CASE statement.
https://forums.oracle.com/forums/thread.jspa?threadID=2131172
Mysql : You could have a view which had all the conflicts .
select distinct a1.appt, a2.appt from appointment a1, appointment a2 where a1.start < a2.end and a1.end > a2.start.
and then simply do a count(*) on that table.
Something like the following should work:
select T1.object_id, T1.start_date, T1.end_date, count(T1.object_id) as numconflicts
from T1
inner join T2 on T1.start_date between T2.start_date and T2.end_date
inner join T3 on T1.end_date between T2.start_date and T2.end_date
group by T1.object_id
I might be off a little bit, but it should help you get started.
Edit: Indented it properly