I have this schema:
table = users
user_id - integer
user_name - string
table = transaction
transaction_id - string
user_id - integer
this sample data:
user_id - user_name
2 - Jhon
3. - barry
transaction_id - user_id
19123 - 2
20123 - 2
20124 - 2
21123 - 2
I need to get how many transactions the user jhon did in 19 and 20 only
the first 2 digits from the transaction_id is the year, but I can't seem to group them, what I have is:
select u.user_name
from transaction t join users u on u.user_id = t.user_id
group by (substr(t.transaction_id, 1, 2))
where <I have no idea in how to fill this>
what I want as a result is:
jhon 1 2
1 transction in 19
2 transactions in 20
It would be better to save dates in mysql way 2022-12-31, so you cqan use date function withput converting.
You need to GROUP By Nam,ame and the first 2 digits
And the WHERE clause belongs always before the group by
CREATE TABLE users (
`user_id` INTEGER,
`user_name` VARCHAR(5)
);
INSERT INTO users
(`user_id`, `user_name`)
VALUES
('2', 'Jhon'),
('3.', 'barry');
CREATE TABLE transaction (
`transaction_id` INTEGER,
`user_id` INTEGER
);
INSERT INTO transaction
(`transaction_id`, `user_id`)
VALUES
('19123', '2'),
('20123', '2'),
('20124', '2'),
('21123', '2');
select u.user_name, Count(*)
from transaction t join users u on u.user_id = t.user_id
where u.user_name = 'Jhon' AND (substr(t.transaction_id, 1, 2)) IN (19,20)
group by u.user_name,(substr(t.transaction_id, 1, 2))
user_name | Count(*)
:-------- | -------:
Jhon | 1
Jhon | 2
select u.user_name
, SUM((substr(t.transaction_id, 1, 2)) = 19) As `Count_19`
, SUM((substr(t.transaction_id, 1, 2)) = 20) As `Count_20`
from transaction t join users u on u.user_id = t.user_id
where u.user_name = 'Jhon' AND (substr(t.transaction_id, 1, 2)) IN (19,20)
group by u.user_name
user_name | Count_19 | Count_20
:-------- | -------: | -------:
Jhon | 1 | 2
db<>fiddle here
Related
I have two tables (user and log) in mysql.
USER TABLE
id email name status
-- ------- ------ --------
1 "x#domain.com" "Carlos" 1
2 "c#domain.com" "Marie" 1
3 "k#domain.com" "Jason" 1
LOG TABLE
id time user_id
-- ------- -------
123 "2020-09-07 08:05:03" 1
124 "2020-09-07 08:32:21" 2
125 "2020-09-09 09:01:46" 1
126 "2020-09-07 11:05:03" 3
I would like to get all the users and its last log in for each one. Then, I have this query:
SELECT
user.id,
user.name,
user.email,
MAX(log.time) as time
FROM user
LEFT JOIN log on user.id = log.user_id
WHERE user.status_id = 1
GROUP BY user.id
ORDER BY log.time DESC
The query returns all the users with its log (null if there is no log for the user) but the log is not the last of the user. May somebody help me?
Thanks in advance.
There is very little you need to change to make your query work (apart from two typos - extra comma and status vs status_id):
The order by clause does not make any sense, since the resultset does not have log.time field, it only has max(log.time), so that's you need to order on and no need to use CTEs or subqueries - as long as you do not want to include additional columns from the log table:
Schema (MySQL v5.7)
CREATE TABLE `user` (
`id` INTEGER primary key,
`email` VARCHAR(14),
`name` VARCHAR(8),
`status` INTEGER
);
INSERT INTO `user`
(`id`, `email`, `name`, `status`)
VALUES
('1', 'x#domain.com', 'Carlos', '1'),
('2', 'c#domain.com', 'Marie', '1'),
('3', 'k#domain.com', 'Jason', '1');
CREATE TABLE log (
`id` INTEGER,
`time` VARCHAR(21),
`user_id` INTEGER
);
INSERT INTO log
(`id`, `time`, `user_id`)
VALUES
('123', '2020-09-07 08:05:03', '1'),
('124', '2020-09-07 08:32:21', '2'),
('125', '2020-09-09 09:01:46', '1'),
('126', '2020-09-07 11:05:03', '3');
Query #1
SELECT
user.id,
user.name,
user.email,
MAX(log.time) as time
FROM user
LEFT JOIN log on user.id = log.user_id
WHERE user.status = 1
GROUP BY user.id
ORDER BY max(log.time) DESC;
Result:
| id | name | email | time |
| --- | ------ | ------------ | ------------------- |
| 1 | Carlos | x#domain.com | 2020-09-09 09:01:46 |
| 3 | Jason | k#domain.com | 2020-09-07 11:05:03 |
| 2 | Marie | c#domain.com | 2020-09-07 08:32:21 |
If you are using MySQL 8+, then ROW_NUMBER is one sensible approach here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY time DESC) rn
FROM log
)
SELECT
u.id,
u.name,
u.email,
t.time
FROM user u
LEFT JOIN cte t ON u.id = t.user_id
WHERE
u.status = 1 AND t.rn = 1
ORDER BY
t.time DESC;
SELECT u.*, l.*
FROM user
JOIN log ON user.id = log.user_id
JOIN ( SELECT user_id, MAX(time) time
FROM log
GROUP BY user_id ) log1 ON log.user_id = log1.user_id AND log.time = log1.time
Applicable for MySQL 5.x
I have two tables (simplified to):
+----------------+
| attendances |
+-----+----------+
| int | user_id |
+-----+----------+
| int | event_id |
+-----+----------+
+-------------------------+
| events |
+------+------------------+
| int | id |
+------+------------------+
| date | performance_date |
+------+------------------+
And a simple query:
SELECT count(DISTINCT user_id), events.performance_date
FROM attendances
INNER JOIN events
ON event_id = events.id
GROUP BY performance_date
I only wish to count each user_id once, but the above query only removes the duplicates from each performance_date (allowing them to be duplicated across multiple dates).
Is there a query that can remove duplicate user_ids from the entire result set, and only include the first occurence (date wise)? I'm suspecting it might not be possible.
Input/output examples:
If a user attended an event on 2010-10-10 and again on 2010-10-11, then the results would be:
1, 2010-10-10
Not:
1, 2010-10-10
1, 2010-10-11
Or:
2, 2010-10-10
If another user was added to the above, and they attended on 2010-10-10 and on 2010-10-12, then the results would be:
2, 2010-10-10
1, 2020-10-12
As I say, this may not be possible. The actual output isn't strictly important -- just so long as the unique number of people who attended a particular performance can be derived somehow.
The data will be used to construct a cumulative graph of the growth in the number of unique users by event.
If you want the earliest date per user, you can use aggregation:
select u.id user_id, min(e.date) first_event_date
from users u
inner join events e on u.event_id = e.id
group by u.id
Actually, you might be looking for histogram, that is the number of users per their earliest event date. You can do this by adding another level of aggregation:
select first_event_date, count(*) no_users
from (
select min(e.date) first_event_date
from users u
inner join events e on u.event_id = e.id
group by u.id
) t
group by first_event_date
If you want to count all new users per event, you could use the following query:
SELECT Count(u.user_id),
e.performance_date
FROM attendances u
INNER JOIN `events` e
ON u.event_id = e.id
WHERE NOT EXISTS(SELECT u1.user_id
FROM attendances u1
INNER JOIN `events` e1
ON u1.event_id = e1.id
WHERE u1.user_id = u.user_id
AND e1.performance_date < e.performance_date)
GROUP BY performance_date
ORDER BY performance_date
I tested it with the following set:
CREATE TABLE attendances
(
user_id INT,
event_id INT
);
CREATE TABLE `events`
(
id INT,
performance_date DATE
);
INSERT INTO attendances
(user_id,
event_id)
VALUES ( 1, 1),
( 1, 2),
( 2, 1),
( 2, 2),
( 3, 1),
( 4, 2);
INSERT INTO `events`
(id,
performance_date)
VALUES ( 1, '2020-07-24'),
( 2, '2020-07-25');
And then the result is
3 2020-07-24
1 2020-07-25
I want count column per specific user, using data from 3 tables.
TABLE 1 (users) :
CREATE TABLE `datastore`.`users` ( `uid` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(30) NOT NULL DEFAULT 'john' , `class` VARCHAR(20) NOT NULL DEFAULT 'NEW' , PRIMARY KEY (`uid`)) ENGINE = InnoDB;
INSERT INTO `users` (`uid`, `name`, `class`) VALUES (NULL, 'john', 'NEW'), (NULL, 'mark', 'OLD');
SAMPLE :
uid name class
1 john NEW
2 mark OLD
TABLE 2 (data) :
CREATE TABLE `datastore`.`data` ( `id` INT NOT NULL AUTO_INCREMENT , `source` VARCHAR(30) NULL DEFAULT NULL , `destination` VARCHAR(30) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB;
INSERT INTO `data` (`id`, `source`, `destination`) VALUES (NULL, 'NETWORK', 'SERVER_1'), (NULL, 'STATION', 'SERVER_2'), (NULL, 'DATASTORE', 'SERVER_1');
SAMPLE :
id source destination
1 NETWORK SERVER_1
2 STATION SERVER_2
3 DATASTORE SERVER_1
TABLE 3 (access):
CREATE TABLE `datastore`.`access` ( `id` INT(11) NOT NULL AUTO_INCREMENT , `uid` INT(11) NULL DEFAULT NULL , `source` VARCHAR(30) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB;
INSERT INTO `access` (`id`, `uid`, `source`) VALUES (NULL, '1', 'NETWORK'), (NULL, '2', 'STATION'), (NULL, '1', 'STATION'), (NULL, '1', 'STATION');
SAMPLE :
id uid source
1 1 NETWORK
2 2 STATION
3 1 STATION
4 1 STATION
What i tried so far :
SELECT access.uid, data.destination, COUNT(*) as count FROM data, access WHERE access.source = data.source GROUP BY destination, uid
Result :
uid destination count
1 SERVER_1 1
1 SERVER_2 2
2 SERVER_2 1
I what to link it with user name alse,
Desired Result :
uid name destination count
1 john SERVER_1 1
1 john SERVER_2 2
2 mark SERVER_2 1
Seems you need also a join for users
SELECT access.uid
, users.name
, data.destination
, COUNT(*) as count
FROM data
INNER JOIN access ON access.source = data.source
INNER JOIN users ON users.uid = access.uid
GROUP BY destination, uid, users.name
and as suggestion, you should not use the (old) implicit join syntax based on where .. but the explicit join syntax.
Use aggregation:
select
a.uid,
u.name,
d.destination,
count(*)
from
access a
inner join users u on u.uid = a.uid
inner join data on d.source = a.source
group by
a.uid,
u.name,
d.destination
All you need to get the user's name is to join your query to the table users:
SELECT u.uid, u.name, t.destination, t.count
FROM users u INNER JOIN (
SELECT a.uid, d.destination, COUNT(*) AS count
FROM data d
INNER JOIN access a ON a.source = d.source
GROUP BY d.destination, a.uid
) t ON u.uid = t.uid
ORDER BY u.uid, t.destination
See the demo.
Results:
| uid | name | destination | count |
| --- | ---- | ----------- | ----- |
| 1 | john | SERVER_1 | 1 |
| 1 | john | SERVER_2 | 2 |
| 2 | mark | SERVER_2 | 1 |
I have table users AND orders. After every UPDATE row in orders. I want update DATA in users table namely concat(OLD.DATA + ID which was updated).
Table 'users'.
ID NAME DATA
1 John 1|2
2 Michael 3|4
3 Someone 5
Table 'orders'.
ID USER CONTENT
1 1 ---
2 1 ---
3 2 ---
4 2 ---
5 3 ---
For example:
SELECT `data` from `users` where `id` = 2; // Result: 3|4
UPDATE `orders` SET '...' WHERE `id` > 0;
**NEXT LOOP**
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 1;
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 1;
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 2;
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 2;
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 3;
Result:
SELECT data from users where id = 1; // Result: 1|2|1|2
SELECT data from users where id = 2; // Result: 3|4|3|4
SELECT data from users where id = 3; // Result: 5|5
How can I do it?
I think you are making the same mistake I made not too long ago, ie storing an array/object in a column.
I would recommend using the following tables in your scenario:
users
+-----------+-----------+
| id | user_name |
+-----------+-----------+
| 1 | John |
+-----------+-----------+
| 2 | Michael |
+-----------+-----------+
orders
+-----------+-----------+------------+
| id | user_id |date_ordered|
+-----------+-----------+------------+
| 1 | 1 | 2019-03-05 |
+-----------+-----------+------------+
| 2 | 2 | 2019-03-05 |
+-----------+-----------+------------+
Where user_id is the foreign key to users
sales
+-----------+-----------+------------+------------+------------+
| id | order_id | item_sku | qty | price |
+-----------+-----------+------------+------------+------------+
| 1 | 1 | 1001 | 1 | 2.50 |
+-----------+-----------+------------+------------+------------+
| 2 | 1 | 1002 | 2 | 3.00 |
+-----------+-----------+------------+------------+------------+
| 3 | 2 | 1001 | 2 | 2.00 |
+-----------+-----------+------------+------------+------------+
where order_id is the foreign key to orders
Now for the confusing part. You will need to use a series of JOINs to access the relevant data for each user.
SELECT
t3.id AS user_id,
t3.user_name,
t1.id AS order_id,
t1.date_ordered,
SUM((t2.price * t2.qty)) AS order_total
FROM orders t1
JOIN sales t2 ON (t2.order_id = t1.id)
LEFT JOIN users t3 ON (t1.user_id = t3.id)
WHERE user_id=1
GROUP BY order_id;
This will return:
+-----------+--------------+------------+------------+--------------+
| user_id | user_name | order_id |date_ordered| order_total |
+-----------+--------------+------------+------------+--------------+
| 1 | John | 1 | 2019-03-05 | 8.50 |
+-----------+--------------+------------+------------+--------------+
These type of JOIN statements should come up in basically any project using a relational database (that is, if you are designing your DB correctly). Typically I create a view for each of these complicated queries, which can then be accessed with a simple SELECT * FROM orders_view
For example:
CREATE
ALGORITHM = UNDEFINED
DEFINER = `root`#`localhost`
SQL SECURITY DEFINER
VIEW orders_view AS (
SELECT
t3.id AS user_id,
t3.user_name,
t1.id AS order_id,
t1.date_ordered,
SUM((t2.price * t2.qty)) AS order_total
FROM orders t1
JOIN sales t2 ON (t2.order_id = t1.id)
LEFT JOIN users t3 ON (t1.user_id = t3.id)
GROUP BY order_id
)
This can then be accessed by:
SELECT * FROM orders_view WHERE user_id=1;
Which would return the same results as the query above.
Depending on your needs, you will probably need to add a few more tables (addresses, products etc.) and several more rows to each of these tables. Very often you will find that you need to JOIN 5+ tables into a view, and sometimes you might need to JOIN the same table twice.
I hope this helps despite it not exactly answering your question!
It is probably a bad idea to update the USERS table after inserting into (or updating) the ORDERS table. Avoid storing data twice. In your case: you can always get all "order ids" for a user by querying the ORDERS table. Thus, you don't need to store them in the USERS table (again). Example (tested with MySQL 8.0, see dbfiddle):
Tables and data
create table users( id integer primary key, name varchar(30) ) ;
insert into users( id, name ) values
(1, 'John'),(2, 'Michael'),(3, 'Someone') ;
create table orders(
id integer primary key
, userid integer
, content varchar(3) references users (id)
);
insert into orders ( id, userid, content ) values
(101, 1, '---'),(102, 1, '---')
,(103, 2, '---'),(104, 2, '---'),(105, 3, '---') ;
Maybe a VIEW - similar to the one below - will do the trick. (Advantage: you don't need additional columns or tables.)
-- View
-- Inner SELECT: group order ids per user (table ORDERS).
-- Outer SELECT: fetch the user name (table USERS)
create or replace view userorders (
userid, username, userdata
)
as
select
U.id, U.name, O.orders_
from (
select
userid
, group_concat( id order by id separator '|' ) as orders_
from orders
group by userid
) O join users U on O.userid = U.id ;
Once the view is in place, you can just SELECT from it, and you will always get the current "userdata" eg
select * from userorders ;
-- result
userid username userdata
1 John 101|102
2 Michael 103|104
3 Someone 105
-- add some more orders
insert into orders ( id, userid, content ) values
(1000, 1, '***'),(4000, 1, '***'),(7000, 1, '***')
,(2000, 2, ':::'),(5000, 2, ':::'),(8000, 2, ':::')
,(3000, 3, '###'),(6000, 3, '###'),(9000, 3, '###') ;
select * from userorders ;
-- result
userid username userdata
1 John 101|102|1000|4000|7000
2 Michael 103|104|2000|5000|8000
3 Someone 105|3000|6000|9000
When executing below query
SELECT `game_turns`.`in_darts`, `game_turns`.`date`, MAX(game_turns.score) AS max_score
FROM `game_turns`
JOIN `games` ON `games`.`id` = `game_turns`.`game_id` AND `games`.`training` = 1
WHERE `game_turns`.`uid` = 2
AND `game_turns`.`out` = 1
AND `game_turns`.`in_darts` = 3
ORDER BY `game_turns`.`score` DESC
LIMIT 1
I get the max score for that user id (uid) and out in 3 darts, but the rest (date) is wrong.
Fields are
Score Uid GameID Score out in_darts date
121 2 4 8 1 3 2015-07-21 13:52:12
8465 2 142 100 1 3 2015-09-05 19:46:29
It returns the score 100 from row ID 8465 but the rest is from row ID 121
I have googled it and came on some Stackoverflow results saying that I should use ORDER BY and LIMIT 1, but looks like it aint working for me.
Order by Date also didn't do the trick.
A simple order by and limit should do what you want:
SELECT gt.`in_darts`, gt.`date`, gt.score
FROM `game_turns` gt JOIN
`games` g
ON g.`id` = gt.`game_id` AND g.`training` = 1
WHERE gt.`uid` = 2 AND gt.`out` = 1 AND gt.`in_darts` = 3
ORDER BY gt.`score` DESC
LIMIT 1;
There is no need for aggregation.
If seeking a solution that would work for multiple UID's then aggregation becomes useful - via a subquery.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`Score_A` int, `Uid` int, `GameID` int, `Score_B` int, `out` int, `in_darts` int, `date` datetime)
;
INSERT INTO Table1
(`Score_A`, `Uid`, `GameID`, `Score_B`, `out`, `in_darts`, `date`)
VALUES
(121, 2, 4, 8, 1, 3, '2015-07-21 13:52:12'),
(8465, 2, 142, 100, 1, 3, '2015-09-05 19:46:29')
;
Query 1:
SELECT
t.*
FROM table1 t
INNER JOIN (
SELECT Uid, max(Score_B) as Score_B
FROM table1
GROUP BY uid
) msb ON t.Uid = msb.Uid and t.Score_B = msb.Score_B
Results:
| Score_A | Uid | GameID | Score_B | out | in_darts | date |
|---------|-----|--------|---------|-----|----------|-----------------------------|
| 8465 | 2 | 142 | 100 | 1 | 3 | September, 05 2015 19:46:29 |