concating renaming duplicate rows in mysql - php

I have a table like following
ID student_name dept email
1 Mary Wise Eng mary-wise#xxx.cc
2 John Walter Sc john-walter#xxx.cc
3 Sophia Jacob Politics sophia-jacob#xxx.cc
4 Ava William Eng ava-william#xxx.cc
5 Mary Wise Politics mary-wise#xxx.cc
6 John Walter Eng john-walter#xxx.cc
7 John Walter Politics john-walter#xxx.cc
8 Sophia Eng sophia#xxx.cc
9 Emma Eng emma#xxx.cc
10 Sherlock Eng sherlock#xxx.cc
The email ids col is generated by firstname-lastname#xxx.cc
The problem is when the name is same the email id is also same.
I want the email id to be appended with 1, 2, 3 when same name exists.
For example in table above
the mary-wise on 5th row should be mary-wise1#xxx.cc,
6th row should be, john-walter1#xxx.cc,
7th row should be, john-walter2#xxx.cc
How can I update my email column with mysql query as fast as possible.
I tried with php with mysql it takes too long when the table contains million rows.
Thanks

I believe it's better for you to make email column unique and to use ON DUPLICATE KEY UPDATE syntax (more here).
You still need to keep track of a number you want to append to the new value. For this purpose you can create a separate table with auto increment field and just get the new value from there.

The following SQL will enumerate the duplicates:
select t.*,
#rn := if(#StudentName = StudentName, 1, #rn + 1) as seqnum,
#StudentName := StudentName
from table t cross join
(select #rn := 0, #StudentName := '') const
order by StudentName;
You can put this in an update using join:
update t join
(select t.*,
#rn := if(#StudentName = StudentName, 1, #rn + 1) as seqnum,
#StudentName := StudentName
from table t cross join
(select #rn := 0, #StudentName := '') const
order by StudentName
) toupdate
on t.name = toupdate.name and toupdate.seqnum > 1
set email = concat(replace(t.StudentName, ' ', '-'), toupdate.seqnum - 1, '#xxx.cc);

It would be easy to achieve if you had CTE (maybe switch to postgres 9 if you can):
SELECT
id
, student_name
, concat(
replace(lower(student_name), ' ', '-')
, case
when cnt > 1 then numb
end
,'#xxx.cc'
) as newmail
FROM (
SELECT
count(*) over (partition BY student_name) as cnt
, count(*) over (partition BY student_name order by id) as numb
, id
, student_name
FROM tab1
order by id
) subq
sqlFiddle demo

Related

Get position of an ID based on MySQL COUNT result

I am not even sure if this has been answered because I don't even know how to coin the problem. But here is what am trying to do.
I am using COUNT() to create a tabular representation of a data from top to bottom for a 30 day period.
SELECT id FROM table WHERE col = '123' AND date >= DATE_SUB(CURRENT_DATE, INTERVAL DAYOFMONTH(CURRENT_DATE)-1 DAY) AND date <= LAST_DAY(CURRENT_DATE) GROUP BY id ORDER BY COUNT(id) DESC
And I get the result with the most at the top
id | col
==========
id3 | 123
id5 | 123
id2 | 123
id4 | 123
id8 | 123
id5 | 123
id1 | 123
id9 | 123
id7 | 123
This works fine for a tabular view and I can use ol to create a numbering system from 1 - 10. My issue is, I want to be able to tell the position of any given id. Eg. if I want to get the position of id9 in this count result i.e. 8, how do I do that?
If you are using MySQL v8.0 or higher you can use the RANK function:
SELECT COUNT(*), RANK() OVER (ORDER BY COUNT(id) DESC) AS r FROM table GROUP BY id ORDER BY COUNT(id) DESC;
For previous version of mysql, you need to create the variable your self:
SELECT COUNT(*), #rank := #rank + 1 AS r FROM table, (SELECT #rank := 0) temp ORDER BY COUNT(id) DESC;
Note SELECT #rank := 0 initiate the variable.
Updated:
To select a specific id and it's rank, you can use:
SELECT * FROM (
SELECT id, COUNT(*), RANK() OVER (ORDER BY COUNT(id) DESC) AS r FROM table GROUP BY id ORDER BY COUNT(id) DESC
) ranked WHERE id = ?;

A sort query in SQL by special criteria

I have a table products, and a table product-languages.
products:
prd_id|prd_name
product-languages:
prd_id|language_id|prd_name
1 |1 |Product_German_name
1 |2 |Product_English_name
1 |4 |Product_French_name
I want to join product-languages on prd_id and then sort it by my priority list of language_id (for example: 2,1,4) - first I want the result of lang_id=2, if this is not available I want to have lang_id=1 as the first result.
Is this possible in SQL? I think my personal order list is the problem, because I would have to check if that lang_id is even available...
Subquery maybe?
Thanks
You can do this in standard SQL via case with something like:
select
*
from
products p,
product_languages l
where
p.prd_id = l.prd_id
order by
(case language_id
when 2 then 1
when 1 then 2
when 4 then 3
end) asc
This is a bit of a pain in MySQL, but you can do it. One method uses variables:
select t.*
from (select t.*,
(#rn := if(#prd_id = prd_id, #rn + 1,
if(#prd_id := prd_id, 1, 1)
)
) as rn
from producct_languages t join
(select #prd_id := -1, #rn := 0) params
order by prd_id, field(language_id, 2, 1, 4)
) t
where rn = 1;

Select unique pairs of users from a table at random

I want to create random user pairs between our database users.
I have the following user table:
Table: tbl_users
user_id | name
--------+--------------
1 | Jay
2 | Ram
3 | John
4 | Kevin
5 | Jenny
6 | Tony
I want to generate a random result like this:
from_id | to_id
--------+---------
1 | 6
5 | 3
2 | 4
Can this be done in MySQL only?
This is indeed a duplicate of a previous question, so the answer is there.
However, even if it is indeed possible in MySQL doing this there is not really recommended. PHP is a much better tool for handling this, as what you're doing is actually manipulating data as per some business rule. It'll be a lot easier to maintain by doing it in PHP, and I suspect that it'll be less resource-intensive as well.
A possible way to do this, which I'd prefer. Is to do a random sort in SQL, and then pair up two and two rows against each other. Something like this:
$grouping = {};
// Fetching both rows to ensure that we actually have an even number paired up.
while ($row = $res->fetch_array () && $row2 = $res->fetch_array ()) {
$grouping[] = {$row['name'], $row2['name']};
}
If you want to allow for an unmatched user to be listed, simply move the second fetch to the inside of the loop. Then deal with the potentially missing result there.
You can use the following code to generate your list:
select max(from_id) as from_id,
max(to_id) as to_id
from (
select
case when rownum mod 2 = 1 then user_id else null end as from_id,
case when rownum mod 2 = 0 then user_id else null end as to_id,
(rownum - 1) div 2 as pairnum
from (
select user_id, #rownum := #rownum + 1 as rownum
from
(select #rownum := 0) as init,
(select user_id from tbl_user order by rand()) as randlist
) as randlistrownum
) as randlistpairs
group by pairnum;
Step by step, this will:
order the userlist in random order
assign a rownumber to it (otherwise the order will have no meaning)
assign two consecutive rows the same pairnum (rownum = 1 and rownum = 2 get the value pairnum = 0, the next two rows will get pairnum = 1 and so on)
the first row of these paired rows will get the values from_id = user_id and to_id = null, the other row will be to_id = user_id and from_id = null
group by these pairs together to make them into one row
if you have an odd number of users, one user will end up with to_id = null, because it has no partner
A little more compact if you prefer shorter code:
select max(case when rownum mod 2 = 1 then user_id else null end) as from_id,
max(case when rownum mod 2 = 0 then user_id else null end) as to_id
from (
select user_id, #rownum := #rownum + 1 as rownum, (#rownum - 1) div 2 as pairnum
from
(select #rownum := 0) as init,
(select user_id from tbl_user order by rand()) as randlist
) as randlistpairs
group by pairnum;

MySQL result set ordered by fixed positions

I've got following, simple table
Item (id, name, date, fixed_position)
(1, 'first entry', '2016-03-09 09:00:00', NULL)
(2, 'second entry', '2016-03-09 04:00:00', 1)
(3, 'third entry', '2016-03-09 05:00:00', NULL)
(4, 'fourth entry', '2016-03-09 19:00:00', NULL)
(5, 'fifth entry', '2016-03-09 13:00:00', 4)
(6, 'sixth entry', '2016-03-09 21:00:00', 2)
The number of items is not fixed, in fact can vary from ~100 to ~1000.
What i want to achieve is to perform a query to return set of Items ordered by date field which takes into consideration fixed_position field, which stands for something like "pinned" results to specific positions. If fixed_position for given entry is not NULL the result should be pinned to n-th position and if fixed_position is NULL the ORDER BY should take precedence.
Desired output of query for brighter explanation:
(2, 'second entry', '2016-03-09 04:00:00', 1) // pinned to 1-st position
(6, 'sixth entry', '2016-03-09 21:00:00', 2) // pinned to 2-nd position
(3, 'third entry', '2016-03-09 05:00:00', NULL) // ORDER BY `date`
(5, 'fifth entry', '2016-03-09 13:00:00', 4) // pinned to 4-th position
(1, 'first entry', '2016-03-09 09:00:00', NULL) // ORDER BY `date`
(4, 'fourth entry', '2016-03-09 19:00:00', NULL) // ORDER BY `date`
I've tried solution posted in Ordering MySql results when having fixed position for some items but even with copy-paste method this doesn't seem to work at all.
What I've tried this far is this query:
SELECT
#i := #i +1 AS iterator,
t.*,
COALESCE(t.fixed_position, #i) AS positionCalculated
FROM
Item AS t,
(
SELECT
#i := 0
) AS foo
GROUP BY
`id`
ORDER BY
positionCalculated,
`date` DESC
Which returns:
iterator | id | name | date | fixed_position | positionCalculated
1 1 first entry 2016-03-09 09:00:00 NULL 1
2 2 second entry 2016-03-09 04:00:00 1 1
6 6 sixth entry 2016-03-09 21:00:00 2 2
3 3 third entry 2016-03-09 05:00:00 NULL 3
4 4 fourth entry 2016-03-09 19:00:00 NULL 4
5 5 fifth entry 2016-03-09 13:00:00 4 4
Does MySQL can perform such task or should I take backend approach and perform PHP's array_merge() on two result sets?
A brute force method to solve this would be to first create a tally table having an amount of rows bigger than the original table:
SELECT #rn := #rn + 1 AS rn
FROM (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT #rn := 0) AS v
Then you can left join this table to a derived table containing all fixed positions of your original table:
SELECT Tally.rn
FROM (
... tally table query here
) AS Tally
LEFT JOIN (
SELECT fixed_position
FROM Item
) AS t ON Tally.rn = t.fixed_position
WHERE t.t.fixed_position IS NULL
The above returns the to-be-filled missing order positions.
Demo here
You can now use the above query as yet another derived table joined to the original table to achieve the desired ordering:
SELECT id, name, `date`, fixed_position, Gaps.rn,
derived.seq, Gaps.seq
FROM (
SELECT id, name, `date`, fixed_position,
#seq1 := IF(fixed_position IS NULL, #seq1 + 1, #seq1) AS seq
FROM Item
CROSS JOIN (SELECT #seq1 := 0) AS v
ORDER BY `date`
) AS derived
LEFT JOIN (
SELECT Tally.rn,
#seq2 := #seq2 + 1 AS seq
FROM (
SELECT #rn := #rn + 1 AS rn
FROM (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT #rn := 0) AS v
) AS Tally
LEFT JOIN (
SELECT fixed_position
FROM Item
) AS t ON Tally.rn = t.fixed_position
CROSS JOIN (SELECT #seq2 := 0) AS v
WHERE t.t.fixed_position IS NULL
ORDER BY rn
) AS Gaps ON (derived.seq = Gaps.seq) AND (derived.fixed_position IS NULL)
ORDER BY COALESCE(derived.fixed_position, Gaps.rn)
Demo here
I've had the same problem (sort by date + inject rows with fixed position values). The above solution seems to work. But you have to know how much values your table has. The line:
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
has to get extended if you have more rows because the temporary table is to short. After some google search results and tests in my DB I've figured out a solution that fits your needs and is smarter to understand I think.
SELECT *
FROM
(SELECT
create_date,
fixedposition,
#rownum:=#rownum + 1 AS colposition,
1 AS majorEntry
FROM myTable
JOIN (SELECT #rownum:=0) r
WHERE fixedposition IS NULL
ORDER BY crdate DESC) AS orderedFixed
UNION ALL
(SELECT create_date,
fixedposition,
fixedposition AS colposition,
0 AS majorEntry
FROM myTable
WHERE fixedposition IS NOT NULL
ORDER BY fixedposition ASC)
ORDER BY colposition ASC, majorEntry
So this is how it works: There are two SELECT statements.
The first SELECT searches for all columns without a fixed position and sorts them by date. Additionally it makes a JOIN to add a row counter column.
The second one searches for all rows with a fixed position and returns the sorting by "colposition".
Both select statements gets combined by an UNION ALL. The union gets sorted first by the ASCending colposition and in the second step by the majonEntry-value which indicates that the rows with fixedposition are to be placed before the other rows which have the same position.
If anyone stumbles upon this, I came up with a good solution with the help of dk1's answer. While his answer is good in principle, it has a flaw that every iteration of a pinned row has its position increased by one. For example: post pinned to the 1st position is on the 1st place (+0), post pinned to the 2nd position is on the 3rd place (+1), and post pinned to the 4th position is on the 6th place (+2).
I solved this by adding a second variable to decrease the final position of a pinned post.
Code explanation:
minor = derived table with unpinned posts
major= derived table with pinned posts
#rownum = iterator for posts in minor table
#decrease = iterator for posts in major table
SELECT
final.*
FROM
(
(
# see note #1 about this wrapper query
SELECT * FROM
(
SELECT
minor.*,
(#rownum := #rownum + 1) AS 'rowPosition',
0 AS 'rowDecrease',
0 AS 'majorEntry'
FROM table_with_your_posts minor
JOIN (#rownum := 0) x
WHERE minor.your_fixed_position IS NULL
ORDER BY minor.your_date DESC
) y
ORDER BY y.your_date DESC
)
UNION ALL
(
SELECT
major.*,
major.your_fixed_position AS 'rowPosition',
(#decrease := #decrease + 1) AS 'rowDecrease',
1 AS 'majorEntry'
FROM table_with_your_posts major
JOIN (#decrease := -1) x
WHERE major.your_fixed_position IS NOT NULL
ORDER BY major.your_fixed_position ASC
)
) final
# see note #2 about this ORDER BY clause
ORDER BY final.rowPosition - final.rowDecrease ASC, final.majorEntry DESC
note #1: Now I'm not an expert, but if you don't wrap the query of the minor table in another query and don't order it by the same thing as in the inner query, the sorting in the minor table just doesn't work. Maybe someone can shed more light on that.
note #2: ORDER BY final.rowPosition - final.rowDecrease - final.majorEntry / 2 ASC also works and looks cooler
Note that if you want to further filter your results (good example here would be to only show published posts), you will most likely need to add the filter condition to the WHERE clause of both the derived tables. Otherwise the final positions will be incorrect.
In the end I want to add that this isn't foolproof. For example if there are two pinned posts with the same position, it will shift the results by one. This could be solved by making the column with the fixed position UNIQUE. Other thing that comes to my mind is that if the table has less results than the position value of a pinned post, the post will obviously not be on its desired position (e.g. if the table contains 5 posts and you pin a post to the 8th position, it will probably end up being 5th). And remember that I didn't meaningfully test this, make sure your query is efficient for your needs.

php mysql order by priority and ranking

I have the following table :
Properties
id agency_id refno
1 1 AA101
2 3 AA201
3 2 AA501
4 1 AA762
5 3 AA555
agency
agency_id agency_name priority
1 A 30
2 B 10
3 C 20
I have defined the priority of each agency in the agency table:
Now I want to extract the rows from properties table based on the ranking and priority. I want to extract 1st row from 1st agency, 1st row from 2nd agency, 1st row from 3rd agency and so on.
Then 2nd row from 1st agency, 2nd row from 2nd agency, 2nd row from 3rd agency and so on.
Then I want to sort the whole result based on the priority of each agency. I am using the following clauses but its not giving the desired result
select properties.id,
properties.agency_id,
IF(#prev <> properties.agency_id, #cnt := 1, #cnt := #cnt + 1) AS rank, #prev := properties.agency_id,
properties.refno
where properties.agency_id = agency.agency_id
order by agency.priority, rank
i have put the join, and its working fine there is no error. but i need the results as follow:
i have defined the priority of each agency in the agency table. the query is working fine with join. i need the results as follow:
agency2 row1
agency3 row1
agency1 row1
agency2 row2
agency3 row2
agency1 row2
agency2 row3
agency3 row3
agency1 row3
according to the priority defined in the agency table and rank.
Please go through the concpet of a JOIN
The query must join the two tables.
select
properties.id,
properties.agency_id,
IF(#prev <> properties.agency_id, #cnt := 1, #cnt := #cnt + 1) AS rank,
#prev := properties.agency_id,
properties.refno
FROM properties
INNER JOIN agency ON properties.agency_id = agency.agency_id
order by agency.priority, rank

Categories