MySQL with counters - mysql

My query:
SELECT name FROM users
It's possible add a counter for each differente name. For example:
mark 1
john 1
mark 2
louis 1
john 2
Ann 1
Thank you

If you are running MySQL 8.0, you can use row_number() for this:
SELECT name, ROW_NUMBER() OVER(PARTITION BY name ORDER BY id) rn FROM users
This assumes that you have another column, called id, which can be used to sort the records (that would typically be the primary key of your table).
If you don't have such column, you can remove the ORDER BY clause of the window function - but the results of the query are then unstable, meaning that is not guaranteed that a given row will consistently get the same row number over consecutive executions of the same query. Depending on your use case, this might, or might not be what you want.
In earlier versions of MySQL, you can emulate row_number() with a correlated subquery:
select name,
1 + (select count(*) from users u1 where u1.name = u.name and u1.id < u.id) rn
from users u

Related

MySql UPDATE Multiple Values

I want to update the sort_id for all my users. For example, since there are 10 users, I want to specify sort_id from 0-9 respectively. I can do it using foreach in PHP, but it causes a lot of performance and time problems. Is there a method to do it without running each update query again?
UPDATE users SET sort_id=LAST_INSERT_ID(sort_id)+1 WHERE id IN(100,101,102,103,104)
what I really want to do
users
#id - #sort_id
100, 0
101, 1
102, 2
103, 3
104, 4
I don't know why you want to store redundant data which can be calculated by the value of another column from the same table. Data redundancy leads to data anomalies and corruption and should be always avoided in relational database systems.
If you need sort_id only on client side, just use a simple select.
SELECT id, RANK() OVER (ORDER BY ID) - 1 as sort_id
FROM users WHERE id BETWEEN 100 and 104
If you really want to store the sort_id, then use UPDATE with a subquery:
UPDATE users AS u JOIN
(SELECT id, RANK() OVER (ORDER BY id) - 1 AS sort_id
FROM users WHERE id BETWEEN 100 AND 104) as s
ON u.id=s.id SET u.sort_id=s.sort_id
You can use the row_number() function:
UPDATE users u
SET sort_id = t.rn
FROM (
SELECT id, ROW_NUMBER() OVER(ORDER BY id) rn
FROM users
) t
WHERE t.id = u.id
The subquery assigns a sequential number to the rows, which is then used to update the sort_id column.

The last value using GROUP BY

I need to take the last value from table where can_id equal.
So I've tried this SQL query
SELECT com.text, com.can_id
FROM (SELECT * FROM comments ORDER BY id DESC) as com
GROUP BY com.can_id
But if I change ASC / DESC in the first select, the second select will just group without sorting and take the value with the first id
This select will be used like left join in the query.
Example:
I need to get com.text with value "text2" (lasts)
If you are on MySql 8, you can use row_number:
SELECT com.text, com.can_id
FROM (SELECT comments.*,
row_number() over (partition by can_id order by id desc) rn
FROM comments) as com
WHERE rn = 1;
If you are on MySql 5.6+, you can (ab)use group_concat:
SELECT SUBSTRING_INDEX(group_concat(text order by id desc), ',', 1),
can_id
FROM comments
GROUP BY can_id;
In any version of MySQL, the following will work:
SELECT c.*
FROM comments c
WHERE c.id = (SELECT MAX(c2.id)
FROM comments c2
WHERE c2.can_id = c.can_id
);
With an index on comments(can_id, id), this should also have the best performance.
This is better than a group by approach because it can make use of an index and is not limited by some internal limitation on intermediate string lengths.
This should have better performance than row_number() because it does not assign a row number to each row, only then to filter things out.
The order by clause in the inner select is redundant since it's being used as a table, and tables in a relational database are unordered by nature.
While other databases such as SQL Server will treat is as an error, I guess MySql simply ignores it.
I think you are looking for something like this:
SELECT text, can_id
FROM comments
ORDER BY id DESC
LIMIT 1
This way you get the text and can_id associated with the highest id value.

mysql/postgres window function limit result without subquery

Is it possible to limit the result of a window function, with partitioning, without a subquery? This code is in postgres/mysql. I'm looking for solution in mysql and postgres.
For example: let's say the join is irrelevant to the point of the question.
select acct.name, we.channel, count(*) as cnt,
max(count(*)) over (partition by name order by count(*) desc) as max_cnt
from web_events we join accounts acct
on we.account_id=acct.id
group by acct.name, we.channel
order by name, max_cnt desc;
The result of this query gives:
I only want to show the first line of each of the window's partition.
For example: lines with cnt: [3M,19],[Abbott Labortories,20]
I tried the following that doesn't work (added limit 1 to the window function):
select acct.name, we.channel, count(*) as cnt,
max(count(*)) over (partition by name order by count(*) desc limit 1) as max_cnt
from web_events we join accounts acct
on we.account_id=acct.id
group by acct.name, we.channel
order by name, max_cnt desc;
I only want to show the first line of each of the window's partition. For example: lines with cnt: [3M,19],[Abbott Labortories,20]
You don't actually need a window function here, since the first row's max_cnt will always equal cnt. Instead use DISTINCT ON in combination with the GROUP BY.
From the postgresql documentation
SELECT DISTINCT ON ( expression [, ...] ) keeps only the first row of each set of rows where the given expressions evaluate to equal. The DISTINCT ON expressions are interpreted using the same rules as for ORDER BY (see above). Note that the “first row” of each set is unpredictable unless ORDER BY is used to ensure that the desired row appears first
SELECT DISTINCT ON(acct.name)
acct.name
, we.channel
, COUNT(*) cnt
FROM web_events we
JOIN accounts acct
ON we.account_id=acct.id
GROUP BY 1, 2
ORDER BY name, cnt DESC;
Here's a quick demo in sqlfiddle. http://sqlfiddle.com/#!17/57694/8
1 way I always messed up when I first started using DISTINCT ON is to ensure that the order of expressions in the ORDER BY clause starts with the expressions in the DISTINCT ON. In the above example the ORDER BY starts with acct.name
If there is a tie for first position, the first row that meets the criteria will be returned. This is non-deterministic. It is possible to specify additional expressions in the ORDER BY to affect which rows are returned in this setting.
example:
ORDER BY name, cnt DESC, channel = 'direct'
will return the row containing facebook, if for a given account, both facebook and direct yield the same cnt.
However, note that with this approach, it is not possible to return all the rows that are tied for first position, i.e. both rows containing facebook & direct (without using a subquery).
DISTINCT ON may be combined in the same statement with GROUP BYs (above example) and WINDOW FUNCTIONS (example below). The DISTINCT ON clause is logically evaluated just before the LIMIT.
For instance, the following query (however pointless) shows off the combination of DISTINCT ON with WINDOW FUNCTION. It will return a distinct row per max_cnt
SELECT DISTINCT ON(mxcnt)
acct.name
, we.channel
, COUNT(*) cnt
, MAX(COUNT(*)) OVER (PARTITION BY acct.name) mxcnt
FROM web_events we
JOIN accounts acct
ON we.account_id=acct.id
GROUP BY 1, 2
ORDER BY mxcnt, cnt DESC;
Use a subquery. If you want exactly one row (even if there are ties), then use row_number():
select name, channel, cnt
from (select acct.name, we.channel, count(*) as cnt,
row_number() over (partition by acct.name order by count(*) desc) as seqnum
from web_events we join
accounts acct
on we.account_id = acct.id
group by acct.name, we.channel
) wea
order by name;
You can use rank() if you want multiple rows for an account, in the event of ties.

MySQL database | querying count() and select at the same time

i am using MySql workbench 5.7 to run this.
i am trying to get the result of this query:
SELECT COUNT(Users) FROM UserList.custumers;
and this query:
SELECT Users FROM UserList.custumers;
at the same table, meaning i want a list of users in one column and the amount of total users in the other column.
when i tries this:
SELECT Users , COUNT(Users) FROM UserList.custumers;
i get a single row with the right count but only the first user in my list....
You can either use a cross join since you know the count query will result in one row... whose value you want repeated on every row.
SELECt users, userCount
FROM userlist.custumers
CROSS JOIN (Select count(*) UserCount from userlist.custumers)
Or you can run a count in the select.... I prefer the first as the count only has to be done once.
SELECT users, (SELECT count(*) cnt FROM userlist.custumers) as userCount
FROM userlist.custumers
Or in a environment supporting window functions (not mySQL) you could count(*) over (partition by 1) as userCount
The reason you're getting one row is due to mySQL's extension of the GROUP BY which will pick a single value from non-aggregated columns to display when you use aggregation without a group by clause. If you add a group by to your select, you will not get the count of all users. Thus the need for the inline select or the cross join.
Consider: -- 1 record not all users
SELECT Users , COUNT(Users) FROM UserList.custumers;
vs --all users wrong count
SELECT Users , COUNT(Users) FROM UserList.custumers group by users;
vs -- what I believe you're after
SELECT Users, x.usercount FROM UserList.custumers
CROSS JOIN (Select count(*) UserCount from userlist.custumers) x
Use a subquery in SELECT.
Select Users,
(SELECT COUNT(Users) FROM UserList.custumers) as total
FROM UserList.custumers;

How to number Duplicate rows in sql

I'm trying to figure out the best/easiest way to number duplicate rows accordingly.I have a set of data that I am uploading to the database table. I have uploaded it, and auto incremented it, now I want to generate the order_id in the fashion shown in my question. For example
----ID-------NAME-----------ORDER_ID----------
1 Bob Smith 1
2 Steve Jones 2
3 Bob Smith 1
4 Billy Guy 3
5 Steve Jones 2
----------------------------------------------
I was thinking I could use a statement such as select NAME from table where name= duplicate_name but I can't seem to figure out how I would realistically go about that, much less then enter the appropriate ORDER_ID afterwards. Is there an easy way to do this ?
You could do something like this:
SELECT
A.ID,
A.NAME,
(SELECT TOP 1 T.ID
FROM table AS T
WHERE T.NAME = A.NAME
ORDER BY T.ID) AS ORDER_ID
FROM table AS A
If your database engine does not support the TOP keyword, but does support the LIMIT keyword, you may be able to do this:
SELECT
A.ID,
A.NAME,
(SELECT T.ID
FROM table AS T
WHERE T.NAME = A.NAME
ORDER BY T.ID
LIMIT 1) AS ORDER_ID
FROM table AS A
SELECT t.*, t1.order_id
FROM (
SELECT name, #rownum := #rownum + 1 AS order_id
FROM (
SELECT name, min(id) AS min_id
FROM tbl
GROUP BY name
) t0
, (SELECT #rownum := 0) r
ORDER BY min_id
) t1
JOIN tbl t USING (name);
In the subquery t0 aggregate all names and compute the minimum id per name. You seem to want to number names after first appearance according to id. This is subtly different from just adding dense_rank() like I commented.
Simulating the basic window function row_number(), tag a running number to each name according to this order in t1.
Join back to the base table.