Write query on sql - mysql

I have one monitoring table with client ID columns ID, last login date to application Time. I wrote a query to display the table at what time the clients had access to the system in the form: Time - Number of entries at this time - Client IDs.
Request:
select Time, count (*) as Quantity, group_concat (ClientID) from monitoring group by Time;
How do I write the following query? Display the table in the same form, only now it is necessary for each time when at least 1 client had access, display the id of all clients who did not have access at that time.
UPD.
+---------------------+-------------+----------------+
| Time | Quantity | ClientID |
+---------------------+-------------+----------------+
| 2018-06-14 15:51:03 | 3 | 311,240,528 |
| 2018-06-14 15:51:20 | 3 | 314,312,519 |
| 2019-01-14 06:00:07 | 1 | 359 |
| 2019-08-21 14:30:04 | 1 | 269 |
+---------------------+-------------+----------------+
These are the IDs of clients who currently had access. And you need to display the IDs of all clients who did not have access at that particular time
That is, in this case:
+---------------------+-------------+-----------------------------+
| Time | Quantity | ClientID |
+---------------------+-------------+-----------------------------+
| 2018-06-14 15:51:03 | 5 | 269,359,314,312,519 |
| 2018-06-14 15:51:20 | 5 | 311,240,528,359,269 |
| 2019-01-14 06:00:07 | 7 | 311,240,528,314,312,519,269 |
| 2019-08-21 14:30:04 | 7 | 311,240,528,314,312,519,359 |
+---------------------+-------------+-----------------------------+
It is advisable not to take into account the day and time, but only the year and month. But as soon as it comes out. Thanks.

You can generate all possible combinations of clients and time with a cross join of two select distinct subqueries, and then filter out those that exist in the table with not exists. The final step is aggregation:
select t.time, count(*) as quantity, group_concat(c.clientid) as clientids
from (select distinct time from monitoring) t
cross join (select distinct clientid from monitoring) c
where not exists (
select 1
from monitoring m
where m.time = t.time and m.clientid = c.clientid
)
group by t.time
It is unclear to me what you mean by the last sentence in the question. The above query would generate the results that you showed for your sample data.

Related

SQL Query with all data from lest column and fill blank with previous row value

After searching a lot on this forum and the web, i have an issue that i cannot solve without your help.
The requirement look simple but not the code :-(
Basically i need to make a report on cumulative sales by product by week.
I have a table with the calendar (including all the weeks) and a view which gives me all the cumulative values by product and sorted by week. What i need the query to do is to give me all the weeks for each products and then add in a column the cumulative values from the view. if this value does not exist, then it should give me the last know record.
Can you help?
Thanks,
The principal is establish all the weeks that a product could have had sales , sum grouping by week, add the missing weeks and use the sum over window function to get a cumulative sum
DROP TABLE IF EXISTS T;
CREATE TABLE T
(PROD INT, DT DATE, AMOUNT INT);
INSERT INTO T VALUES
(1,'2022-01-01', 10),(1,'2022-01-01', 10),(1,'2022-01-20', 10),
(2,'2022-01-10', 10);
WITH CTE AS
(SELECT MIN(YEARWEEK(DT)) MINYW, MAX(YEARWEEK(DT)) MAXYW FROM T),
CTE1 AS
(SELECT DISTINCT YEARWEEK(DTE) YW ,PROD
FROM DATES
JOIN CTE ON YEARWEEK(DTE) BETWEEN MINYW AND MAXYW
CROSS JOIN (SELECT DISTINCT PROD FROM T) C
)
SELECT CTE1.YW,CTE1.PROD
,SUMAMT,
SUM(SUMAMT) OVER(PARTITION BY CTE1.PROD ORDER BY CTE1.YW) CUMSUM
FROM CTE1
LEFT JOIN
(SELECT YEARWEEK(DT) YW,PROD ,SUM(AMOUNT) SUMAMT
FROM T
GROUP BY YEARWEEK(DT),PROD
) S ON S.PROD = CTE1.PROD AND S.YW = CTE1.YW
ORDER BY CTE1.PROD,CTE1.YW
;
+--------+------+--------+--------+
| YW | PROD | SUMAMT | CUMSUM |
+--------+------+--------+--------+
| 202152 | 1 | 20 | 20 |
| 202201 | 1 | NULL | 20 |
| 202202 | 1 | NULL | 20 |
| 202203 | 1 | 10 | 30 |
| 202152 | 2 | NULL | NULL |
| 202201 | 2 | NULL | NULL |
| 202202 | 2 | 10 | 10 |
| 202203 | 2 | NULL | 10 |
+--------+------+--------+--------+
8 rows in set (0.021 sec)
Your calendar date may be slightly different to mine but you should get the general idea.

WHERE-clause for last non-null value

I'm making a community database for a school project and have ran into an issue. I am attempting to integrate a log system but retrieve the latest non-null value from column in a table called logs and present that information on a different page. My current code (without any attempt at filtering by their rank) looks as follows:
SELECT m.MemberID, m.MemberName, o.OfficeID, o.OfficeDesignation, p.PositionAbbreviation, l.LogRank
FROM logs l
INNER JOIN (SELECT l.LogMember, MAX(l.LogDate) AS maxLogDate FROM logs l GROUP BY l.LogMember) l2
ON (l.LogMember = l2.LogMember AND l.LogDate = l2.maxLogDate)
INNER JOIN members m
ON (l.LogMember = m.MemberID)
INNER JOIN offices o
ON (l.LogOffice = o.OfficeID)
INNER JOIN positions p
ON (l.LogPosition = p.PositionID)
GROUP BY m.MemberID;
The above query returns the latest entry for each member in the logs table, but I can't figure out how to, when l.LogRank returns NULL, take the latest non-null value only for that column.
I have struggled with a variety of approaches to this problem over the past week to no avail. Any help/pointers would be appreciated.
EDIT: Sample data seen below:
+-------+-----------+---------+-----------+-------------+-----------+
| LogID | LogMember | LogRank | LogOffice | LogPosition | LogDate |
+-------+-----------+---------+-----------+-------------+-----------+
| 1 | 1 | 1 | 7 | 5 | TIMESTAMP |
+-------+-----------+---------+-----------+-------------+-----------+
| 2 | 1 | 1 | | 1 | TIMESTAMP |
+-------+-----------+---------+-----------+-------------+-----------+
| 3 | 1 | | 1 | | TIMESTAMP |
+-------+-----------+---------+-----------+-------------+-----------+
The various INT values reference IDs in the relevant other tables.
Desired Output:
+-------+-----------+---------+-------------------+-----------------+-----------+
| LogID | LogMember | LogRank | OfficeDesignation | PositionAbbrev. | LogDate |
+-------+-----------+---------+-------------------+-----------------+-----------+
| 1 | 1 | 1 | C-6 | CO | TIMESTAMP |
+-------+-----------+---------+-------------------+-----------------+-----------+
So basically I want to retrieve the MemberID, MemberName, OfficeID, OfficeDesignation, PositionAbbreviation, and LogRank from a variety of tables and get the latest non-null record for each column.
INNER JOIN (
SELECT l.LogMember, l.LogRank FROM logs l
WHERE l.LogDate=(
SELECT LogDate FROM logs
WHERE LogMember=l.LogMember
AND LogRank IS NOT NULL
ORDER BY LogDate DESC
LIMIT 1
)
) l3
ON (l.LogMember = l3.LogMember)

How to get the average time between multiple dates

What I'm trying to do is bucket my customers based on their transaction frequency. I have the date recorded for every time they transact but I can't work out to get the average delta between each date. What I effectively want is a table showing me:
| User | Average Frequency
| 1 | 15
| 2 | 15
| 3 | 35
...
The data I currently have is formatted like this:
| User | Transaction Date
| 1 | 2018-01-01
| 1 | 2018-01-15
| 1 | 2018-02-01
| 2 | 2018-06-01
| 2 | 2018-06-18
| 2 | 2018-07-01
| 3 | 2019-01-01
| 3 | 2019-02-05
...
So basically, each customer will have multiple transactions and I want to understand how to get the delta between each date and then average of the deltas.
I know the datediff function and how it works but I can't work out how to split them transactions up. I also know that the offset function is available in tools like Looker but I don't know the syntax behind it.
Thanks
In MySQL 8+ you can use LAG to get a delayed Transaction Date and then use DATEDIFF to get the difference between two consecutive dates. You can then take the average of those values:
SELECT User, AVG(delta) AS `Average Frequency`
FROM (SELECT User,
DATEDIFF(`Transaction Date`, LAG(`Transaction Date`) OVER (PARTITION BY User ORDER BY `Transaction Date`)) AS delta
FROM transactions) t
GROUP BY User
Output:
User Average Frequency
1 15.5
2 15
3 35
Demo on dbfiddle.com
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(user INT NOT NULL
,transaction_date DATE
,PRIMARY KEY(user,transaction_date)
);
INSERT INTO my_table VALUES
(1,'2018-01-01'),
(1,'2018-01-15'),
(1,'2018-02-01'),
(2,'2018-06-01'),
(2,'2018-06-18'),
(2,'2018-07-01'),
(3,'2019-01-01'),
(3,'2019-02-05');
SELECT user
, AVG(delta) avg_delta
FROM
( SELECT x.*
, DATEDIFF(x.transaction_date,MAX(y.transaction_date)) delta
FROM my_table x
JOIN my_table y
ON y.user = x.user
AND y.transaction_date < x.transaction_date
GROUP
BY x.user
, x.transaction_date
) a
GROUP
BY user;
+------+-----------+
| user | avg_delta |
+------+-----------+
| 1 | 15.5000 |
| 2 | 15.0000 |
| 3 | 35.0000 |
+------+-----------+
I don't know what to say other than use a GROUP BY.
SELECT User, AVG(DATEDIFF(...))
FROM ...
GROUP BY User

How to count users with where condition previous row

please help me i have no idea for this...
I have table like this (create_at YYYY-MM-DD). ID is auto increment
-----------------------------------------------------------------
| ID | id_user | activity | create_at |
-----------------------------------------------------------------
| 1 | 10 | A | 2017-10-11 |
| 2 | 52 | A | 2017-10-11 |
| 3 | 41 | A | 2017-10-12 |
| 4 | 52 | A | 2017-10-12 |
| 5 | 41 | B | 2017-10-12 |
| 6 | 52 | B | 2017-10-13 |
| 7 | 10 | B | 2017-10-14 |
-----------------------------------------------------------------
How to get count (mysql) user who doing activity "B" after activity "A" in sameday create_at.. In this case, the result is 1 (IDUser 41).. How can i do this in mysql? thankyou
We could use a semi-join or a correlated subquery.
we start like this, users that are doing activity B
SELECT t.id_user
FROM table_like_this t
WHERE t.activity = 'B'
we can match those rows to users that are doing activity A on the "same day" with JOIN operation back to the same table...
SELECT t.id_user
FROM table_like_this t
JOIN table_like_this r
ON r.id_user = t.id_user
AND r.create_at = t.create_at
AND r.activity = 'A'
WHERE t.activity = 'B'
As far as whether activity B is occurring "after" activity A, I don't see any information in the table that can tell us that (we can't tell what time each activity A and B occurred, and can't determine which one was "after" the other.)
For testing, we can include other columns in the SELECT list, to verify which rows from t and r are being returned, if the matching is being done properly.
Once we are satisfied, we can replace the SELECT list, to get a count of distinct id_user
SELECT COUNT(DISTINCT t.id_user)
FROM ...
Note that this will collapse occurrences of id_user that performed activity A and B on several different days so that the id_user will be counted only once.
If we want to count the number of days for each id_user, and include each of those days in the count, the query would need to be changed.

MySQL - Selecting a weeks worth of data from week beginning date

Trying to do a select but can't seem to master the art of this particular one.
This is what I have tried:
select user_id,date,monday_am_task from users,week,timesheet_submission where user_id='1' and date='2015-04-06';
However it says it is too ambiguous. This is basically what I want to do. If the user_id=1 and the date is in between 2015-04-06 then show the data. By between and the date I mean this, I have setup a week table, this includes the week_number, week_id and date. Date is referred to as the week commencing date. So with my select statement I am trying to select the date that will pull all the data for that week, if that makes sense?
Week Table:
mysql> select * from week;
+---------+------+------------+
| week_id | week | date |
+---------+------+------------+
| 1 | 1 | 2014-12-29 |
| 2 | 2 | 2015-01-05 |
| 3 | 3 | 2015-01-12 |
| 4 | 4 | 2015-01-19 |
| 5 | 5 | 2015-01-26 |
| 6 | 6 | 2015-02-02 |
| 7 | 7 | 2015-02-09 | etc...
Users:
mysql> select user_id, username, level from users;
+---------+----------+-------+
| user_id | username | level |
+---------+----------+-------+
| 1 | tom | 1 |
| 2 | owain | 2 |
+---------+----------+-------+
2 rows in set (0.00 sec)
mysql> select user_id, date, timesheet_id, monday_am_task from timesheet_submission;
+---------+---------------------+--------------+----------------+
| user_id | date | timesheet_id | monday_am_task |
+---------+---------------------+--------------+----------------+
| 1 | 2015-04-10 12:44:54 | 34 | 5 |
+---------+---------------------+--------------+----------------+
1 row in set (0.00 sec)
This error happens when a column name exists in one or more of the selected tables. In this case, it appears to be user_id because it is in both users and timesheet_submission.
In addition, while it is all personal preference, many people prefer to use the JOIN syntax instead of listing multiple tables in the FROM clause. The second option creates a Cartesian product, which is what you are experiencing, and unless you set the proper conditions to relate a table, you can get very odd results like the ones you have now.
In short, the join syntax may look like this:
SELECT [columns]
FROM table1 t1
JOIN table2 t2 ON t2.relatedColumn = t1.relatedColumn;
For your tables, you can join users to timesheet_submission, and timesheet_submission to week, although that one is tricky because there is no direct link. To break my answer down a bit, I would start by getting all timesheet submissions for user_id 1 with a join like this:
SELECT u.user_id, ts.date, ts.monday_am_task
FROM users u
JOIN timesheet_submission ts ON ts.user_id = u.user_id AND u.user_id = 1;
Don't forget to use the table alias in select, or you'll get the ambiguity error again. As far as the date, if I were writing this query I would just include the condition that the timesheet date is between your given date, and six days later [since between is inclusive, if you go seven days later you will get the next date as well]. Try this:
SELECT u.user_id, ts.date, ts.monday_am_task
FROM users u
JOIN timesheet_submission ts ON ts.user_id = u.user_id AND u.user_id = 1
WHERE ts.date BETWEEN '2015-04-06' AND DATE_ADD('2015-04-06', INTERVAL 6 DAY);
Here is more on JOINS and on the DATE_ADD function.