SUM where other field is MAX value - mysql

I have a table like the one above, I want to calculate total of amount with largest log_id and group by user_id.
the results will be as below :
this is my example code but this is not work :(
CREATE TABLE IF NOT EXISTS example (
`id` INT,
`log_id` INT,
`user_id` INT,
`amount` INT
);
INSERT INTO example VALUES
(1,1,10,4),
(2,2,10,8),
(3,3,10,2),
(4,3,10,6),
(5,1,12,9),
(6,2,12,4),
(7,1,13,7),
(8,1,14,2),
(9,2,14,6),
(10,1,15,7),
(11,2,15,4),
(12,3,15,9),
(13,3,15,6);
select max(log_id) as log_id, user_id, amount from example group by user_id, amount
Any ideas?
Thanks, xmush

You can go for RANK function and get data.
Schema (MySQL v8.0)
CREATE TABLE test
(
id INT,
logid int,
userid int,
amount INT
);
insert into test(id, logid, userid, amount)
values (1, 1, 12,9),
(2,2,12,4);
Query #1
WITH testcte AS (
SELECT *, RANK() OVER (PARTITION BY userid order by logid desc) as rnk from test
)
SELECT * FROM testcte WHERE rnk = 1;
id
logid
userid
amount
rnk
2
2
12
4
1
View on DB Fiddle

Here's a 'traditional' (pre-8.0) approach...
Grab the rows holding the largest log_id for each user...
SELECT x.*
FROM example x
JOIN
( SELECT user_id
, MAX(log_id) log_id
FROM example
GROUP
BY user_id
) y
ON y.user_id = x.user_id
AND y.log_id = x.log_id
Aggregate the resulting data set...
SELECT x.log_id
, x.user_id
, SUM(amount) total
FROM example x
JOIN
( SELECT user_id, MAX(log_id) log_id FROM example GROUP BY user_id ) y
ON y.user_id = x.user_id
AND y.log_id = x.log_id
GROUP
BY x.log_id
, x.user_id
ORDER
BY user_id;

Related

How to find the pre-last(penultimate) value in SQL?

I have a table
/*CREATE TABLE Purchases (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
transaction_id INTEGE ,
user_id INTEGER,
purchase_date datetime,
product_type VARCHAR(30),
price INTEGER ,
);
*/
And I need to find the prelast purchase of unique users. have no clue how to to this. Better if would be MYSQL.
I'm trying to serch for the last. But even that seems bad
SELECT
user_id,
LAST_VALUE(transaction_id) OVER (
ORDER BY purchase_date
RANGE BETWEEN
UNBOUNDED PRECEDING AND
UNBOUNDED FOLLOWING
) last_purchase
FROM
purchases;
Please help me with the seraching prelast purcase(stransaction_id) of the unique visitor(user_id)
If "pre-last" means the second-to-last (i.e. penultimate) then use row_number():
select p.*
from (select p.*,
row_number() over (partition by user_id order by purchase_date desc) as seqnum
from purchases p
) p
where seqnum = 2;
Number the rows per user by date descending. Keep all rows numbered #2.
select *
from
(
select p.*, row_number() over (partition by user_id order by purchase_date desc) as rn
from purchases p
) numbered
where rn = 2;
Invert the order, then use limit and offset to select the second row.

Mysql set boolean to 1 when requirements are met all others with requirements to 0

I have an image table in a mysql database with the following fields
id int primary key
type_id int
product_id int
source varchar
created timestamp
expiry timestamp
active tinyint
What I need to do is set 1 picture active ( active=1 ) where the product_id, source and type_id is the same, the one that should be set active should be the one with the newest created timestamp and if I have 2 with the same timestamp I need the one with the highest id set active AND expiry should be > CURRENT_DATE, all others with the same product_id, source and type_id should have active set to 0. This should be done for all where product_id, source and type_id are the same.
Can this be done in 1 SQL?
For MySql 8.0+ you can use ROW_NUMBER() to return the rows where active must be updated to 1 and LEFT join to the table in the UPDATE statement:
update tablename t left join (
select t.*
from (
select *,
row_number() over (partition by product_id, source, type_id order by created desc, id desc) rn
from tablename
where (product_id = source) and (product_id = type_id) and (expiry > currrent_date)
) t
where t.rn = 1
) tt on tt.id = t.id
set t.active = (tt.id is not null)

How to process multiple record with same id into single record with login / logout time in SQL?

is this possible to make a "newtable" from "oldtable" like a picture down below?
Use PIVOT method :
Declare #table table (id varchar(10),[time] time)
insert into #table
SELECT '01','10:08:23'
UNION ALL
SELECT '02','10:10:50'
UNION ALL
SELECT '01','13:30:00'
SELECT *
FROM
(
SELECT id , time , CASE WHEN MIN(RNo) = 1 THEN 'CheckIn' WHEN MAX(RNo) >
1 THEN 'CheckOut' END Type
FROM
(
SELECT * , ROW_NUMBER() OVER (PARTITION BY id ORDER BY time) RNo
FROM #table
) A
GROUP BY id , time
) A
PIVOT
(
MAX(time) FOR Type IN ([CheckIn],[CheckOut])
)Pvt
This can be use for matching column (s)
INSERT INTO `NEWTABLE`(`id`, `check in`)
SELECT o.id, o.time FROM OLDTABLE o

How many different ways are there to get the second row in a SQL search?

Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1

mysql 2 set of sum using groupby rollup

I have a query
select user_id,sum(hours),date, task_id from table where used_id = 'x' and date >='' and date<= '' group by user_id, date, task_id with roll up
The query works fine. But I also need to find a second sum(hours) where the group by order is changed.
select user_id,sum(hours),date, task_id from table where used_id = 'x' group by user_id,task_id
(The actual where condition is much longer.)
Is it possible to get both the sum in a single query since the where condition almost the same?
SELECT * FROM (
SELECT 1 AS list_id
, user_id
, sum(hours) AS total_hours
, `date`
, task_id
FROM table WHERE used_id = 'x' AND `date` BETWEEN #thisdate AND #thatdate
GROUP BY user_id, `date`, task_id /*WITH ROLLUP*/
UNION ALL
SELECT 2 AS list_id
, user_id
, sum(hours) AS total_hours
, `date`
, task_id
FROM table
WHERE used_id = 'x'
GROUP BY user_id,task_id WITH ROLLUP ) q
/*ORDER BY q.list_id, q.user_id, q.`date`, q.task_id*/
Depending on your needs, you should only need one with rollup, or two.