How to update duplicated rows with a index (Mysql) - mysql

I have a table, named city.
city_name
---------------
New York
Beijing
New York
New York
Dubai
Beijing
---------------
After update, I want it be:
city_name, index
---------------
New York, 0
Beijing, 0
New York, 1
New York, 2
Dubai, 0
Beijing, 1
---------------
The pattern is like this: the first New York will be have an index of 0, the second New York's index is 1 and the third one will be 2. There are millions of rows in this table.
Any easy way to make this update?
I am thinking to solve this problem in two step.
First step:
#cities = Select distinct city_name from city;
Second step:
foreach #cities as #city
update city set index = row_num where city_name = #city.cityname
It seems row_num is not availbe in mysql.

Try this:
update city cross join
(select #city := '', #prevcity := '', #i := 0) const
set `index` = (case when (#prevcity := #city) is null then null
when (#city := city) is null then null
else #i := if(#prevcity = city, #i + 1, 1) is null then null
end)
order by city;
If you are familiar with the use of variables for enumeration in a select statement, then this is similar. The complication is ensuring the order of evaluation for the update. This is handled by using a case statement, which sequentially evaluates each clause until one is true. The first two are guaranteed to be false (because the values should never be NULL).
EDIT:
If you have a unique id, then the solution is a bit easier. I wish you could do this:
update city c
set `index` = (select count(*) from city c2 where c2.city = c.city and c2.id <= c.id);
But instead, you can do it with more joins:
update city c join
(select id, (select count(*) from city c2 where c2.city = c1.city and c2.id <= c1.id) as newind
from city c1
) ci
on c.id = ci.id
set c.`index` = ci.newind;

A way to do this using session() for storage AND comparing purposes:
session_start();
$number=0;
$result=mysqli_query($yourconnection,"SELECT * FROM city ORDER BY city_name");
while($row=mysqli_fetch_array($result)){
if(empty($_SESSION["storage"])){
/* THIS CONDITION ONLY GOES THROUGH THE VERY FIRST ARRAY FETCH */
$_SESSION["storage"]=$row['city_name'];
mysqli_query($yourconnection, "UPDATE city SET index='$number' WHERE city_name='$cityname'"); /* STORE TO THE FIRST CITY'S INDEX 0 */
}
else if($_SESSION["storage"]==$row['city_name']){
/* IF SESSION IS THE SAME COUNTRY AS THE CURRENT ROW COUNTRY */
$_SESSION["storage"]=$row['city_name'];
$number=$number+1; /* INCREMENT NUMBER FOR THE SAME COUNTRY */
mysqli_query($yourconnection, "UPDATE city SET index='$number' WHERE city_name='".$row['city_name']."'");
}
else {
/* THIS IS FOR THE NEXT NEW COUNTRY */
$number=0; /* START AGAIN THE COUNT TO 0 IF NEW COUNTRY */
$_SESSION["storage"]=$row['city_name'];
mysqli_query($yourconnection, "UPDATE city SET index='$number' WHERE city_name='".$row['city_name']."'");
}
} /* END OF WHILE LOOP */
I've done this before, but with different output but with the same logic. AND I use another table for storage purposes and comparing purposes. But the code above that I've made, I used session instead.

Related

Group by city name and only display the same city name once in the first row

I have a table
city|locality
a | bc
a | dc
a | ef
a | gh
a | ij
I want to create a group by so that it shows
a |bc
|dc
|ef
|gh
|ij
I'm currently using
select(*) from 'tablename' group by city;
but it only gets me one locality value.
Not pretty sure if this is what you want, but if you want to get all rows from locality for a in one new cell, you can use this.
SELECT *, GROUP_CONCAT(locality) as localities FROM table GROUP BY city
This will output:
city localities
a bc, dc, ef, gh, ij
The solution is to just use:
select city, locality from yourtable order by city, locality
You can then solve the problem in your presentation layer by only printing/using city if the value changes.
Something like (in Java):
String previousCity = null;
while (rs.next()) {
String currentCity = rs.getString("city");
if (Objects.equals(previousCity, currentCity) {
// Same as previous: no need to print so set blank
String currentCity = "";
} else {
previousCity = currentCity;
}
System.out.println(currentCity + " | " + rs.getString("locality"));
}
Try this,
select col1, GROUP_CONCAT(col2 SEPARATOR ';') from tablename;
It will give you the result like:
a | bc;dc;ef;gh;ij
Now use it in your own way.
You can try by writing 2 queries,
select distinct (city) from 'tablename';
then u will get city as 'a';
select * from tablename where city='a';// 'a' means out put of first query

Set value of MySQL field when displaying data

I am trying to clean up some MySQL code another programmer put together so that, whenever the state abbreviation is either QC (for Quebec) or ON (for Ontario), the sortOrder is set to 'ZZZZ.'
The MySQL query is pulling from a table with addresses of businesses, and the state field holds the abbreviation of the state or province, and the state_full field holds the full name of the state or province.
This is how the MySQL query looks at the moment:
SELECT DISTINCT
state_full,
state as sortOrder,
'' as state,
state as searchState
FROM vendorlocator
WHERE LENGTH(zip) < 6 OR ((LENGTH(zip) > 5) AND LOCATE('-',zip) > 0)
UNION
(SELECT DISTINCT
state_full,
'ZZZZ',
state,
state as searchState
FROM vendorlocator
WHERE LENGTH(zip) > 5 AND LOCATE('-', zip) = 0)
ORDER BY sortOrder, state
The way the other programmer set this up looks rather complex and messy, in my opinion..is there an easier way to set the value of sortOrder to 'ZZZZ' for addresses located in Quebec (QC) or Ontario (ON)?
You can use case expression in the select clause and >= instead of separate > and = in the where clause:
select distinct state_full
, state as sortOrder
, case when (length(zip) > 5 and locate('-',zip) = 0) then 'ZZZZ' else '' as state
, state as searchState
from vendorlocator
where length(zip) < 6 or ((length(zip) > 5) and locate('-',zip) >= 0)

Update multiple rows, but only first row with different value

table
create table tst(locationId int,
scheduleCount tinyint(1) DEFAULT 0,
displayFlag tinyint(1) DEFAULT 0);
INSERT INTO tst(locationId,scheduleCount)
values(5,0),(2,0),(5,1),(5,2),(2,1),(2,2);
I update multiple rows and multiple columns with one query, but want to change the one of the columns only for the first row and keep the other things the same for that column.
I want to update all the rows with some location id and change displayFlag to 1 and increment scheduleCount of only the top entry with 1 , rest would remain the same
**Query **
update tst,(select #rownum:=0) r,
set tst.displayFlag =1,
scheduleCount = (CASE WHEN #rownum=0
then scheduleCount+1
ELSE scheduleCount
END),
#rownum:=1 where locationId = 5
But it gives error and does not set the user defined variable rownum, I am able to join the tables in a select and change the value of the rownum, is there any other way to update the values.
I'm not sure this is the correct way of doing such a thing, but it is possible to include the user variable logic in the CASE condition:
UPDATE tst
JOIN (SELECT #first_row := 1) r
SET tst.displayFlag = 1,
scheduleCount = CASE
WHEN #first_row = 1 AND ((#first_row := 0) OR TRUE) THEN scheduleCount+1
ELSE scheduleCount
END
WHERE locationId = 5;
I have used a #first_row flag as this is more inline with your initial attempt.
The CASE works as follows:
On the first row #first_row = 1 so the second part of the WHEN after AND is processed, setting #first_row := 0. Unfortunately for us, the assignment returns 0, hence the OR TRUE to ensure the condition as a whole is TRUE. Thus scheduleCount + 1 is used.
On the second row #first_row != 1 so the condition is FALSE, the second part of the WHEN after AND is not processed and the ELSE scheduleCount is used.
You can see it working in this SQL Fiddle. Note; I have had to set the column types to TINYINT(3) to get the correct results.
N.B. Without an ORDER BY there is no guarantee as to what the '1st' row will be; not even that it will be the 1st as returned by a SELECT * FROM tst.
UPDATE
Unfortunately one cannot add an ORDER BY if there is a join.. so you have a choice:
Initialise #first_row outside the query and remove the JOIN.
Otherwise you are probably better off rewriting the query to something similar to:
UPDATE tst
JOIN (
SELECT locationId,
scheduleCount,
displayFlag,
#row_number := #row_number + 1 AS row_number
FROM tst
JOIN (SELECT #row_number := 0) init
WHERE locationId = 5
ORDER BY scheduleCount DESC
) tst2
ON tst2.locationId = tst.locationId
AND tst2.scheduleCount = tst.scheduleCount
AND tst2.displayFlag = tst.displayFlag
SET tst.displayFlag = 1,
tst.scheduleCount = CASE
WHEN tst2.row_number = 1 THEN tst.scheduleCount+1
ELSE tst.scheduleCount
END;
Or write two queries:
UPDATE tst
SET displayFlag = 1
WHERE locationId = 5;
UPDATE tst
SET scheduleCount = scheduleCount + 1
WHERE locationId = 5
ORDER BY scheduleCount DESC
LIMIT 1;

Trying to merge multiple UPDATE statements into one within SQL stored procedure

I have multiple update statements in a stored procedure (as shown below).
Question is I am trying to combine them into one UPDATE statement as there is a performance issue (takes longer to execute stored procedure). I tried putting columns (such as PONUMBER, VENDORID etc) in a single update statement but it is throwing errors.
Please suggest.
UPDATE rptMaster SET PONUMBER = (select top 1 poMaster.PONUMBER from poMaster where poMaster.ITEMNMBR =rptMaster.ITEMNMBR and
poMaster.UnCommited > 0)
UPDATE rptMaster SET VENDORID = (select top 1 poMaster.VENDORID from poMaster where poMaster.ITEMNMBR =rptMaster.ITEMNMBR and
poMaster.UnCommited > 0)
UPDATE rptMaster SET DUEDATE = (select top 1 poMaster.REQDATE from poMaster where poMaster.ITEMNMBR =rptMaster.ITEMNMBR and
poMaster.UnCommited > 0)
UPDATE rptMaster SET POQTYORDER = (select top 1 (poMaster.QTYORDER / rptMaster.UOMQTY) from poMaster where poMaster.ITEMNMBR =rptMaster.ITEMNMBR and
poMaster.UnCommited > 0)
Mine is similar to polkduran's:
WITH PO AS (
SELECT PONUMBER
, VENDORID
, REQDATE
, QTYORDER
, ITEMNMBR
, ROW_NUMBER() OVER (PARTITION BY ITEMNMBR ORDER BY ??) as RN
FROM poMaster
WHERE UnCommited > 0
)
UPDATE rptMaster
SET PONUMBER = po.PONUMBER
, VENDORID = po.VENDORID
, DUEDATE = po.REQDATE
, POQTYORDER = po.QTYORDER / rptMaster.UOMQTY
FROM rptMaster
JOIN PO
ON PO.ITEMNMBR = rptMaster.ITEMNMBR
and PO.RN = 1
I'm using a Common Table Expression (CTE) to assign a row number to each poMaster record, with the records for each value of ITEMNMBR numbered separately. This allows us to pick to the first record for each ITEMNBR in our JOIN, later, similar to the way you were using Top 1 in your subqueries.
Please note, though: because you didn't indicate how you wanted to select the Top 1 record in your query, I had to leave the ORDER BY clause in the CTE unspecified. (I put ?? in as a placeholder.) You need to specify one or more sort fields in place of the ?? so it knows how to sort and number the records.
You can make an update using a join clause:
update rpt
set
PONUMBER = po.PONUMBER,
VENDORID = po.VENDORID,
DUEDATE = po.REQDATE,
POQTYORDER = (po.QTYORDER / rpt.UOMQTY)
from rptMaster rpt
inner join poMaster po
on po.ITEMNMBR = rpt.ITEMNMBR
where po.UnCommited > 0
I don't have a way to test it right now but that might work.

Conditional Join: don't join on null

I have relationships that might not necessarily exist (they could be optional i.e. null); for example, a image may not have an address so it may be null.
I am unsure how to not return all null values.
Is there some condition I can put in place on the join that says if the address is null don't do a join and don't return all the null columns?
SELECT im.title, im.alias_title, im.description, im.main_image, im.hits,
im.show_comment, im.created_on, im.date_taken, im.account_type_id,
c.make, c.model, ad.address_line_1, ad.address_line_2,
spc.state_province_county, tvc.town_village_city, co.country,
ge.latitude, ge.longitude, ge.zoom, ge.yaw, ge.pitch,
us.first_name, us.surname, us.user_set_online, ut.username,
ut.account_type_id, aty.`type`, ufy.realname, ufy.location,
ufy.location, ufy.account_type_id
FROM image im
INNER JOIN user us
ON im.user_id = us.id
LEFT JOIN user_type ut
ON us.id = ut.user_id
LEFT JOIN user_flickr_youtube ufy
ON ut.id = ufy.user_type_id
LEFT JOIN account_type aty
ON ut.account_type_id =aty.id
LEFT JOIN address ad
ON im.address_id = ad.id
LEFT JOIN state_province_county spc
ON ad.state_province_county_id = spc.id
LEFT JOIN town_village_city tvc
ON ad.town_village_city_id =tvc.id
LEFT JOIN country co
ON ad.country_id =co.id
LEFT JOIN geolocation ge
ON im.geolocation_id = ge.id
LEFT JOIN camera c
ON im.camera_id = c.id
WHERE im.alias_title = 'test'
AND im.approved = 'Yes'
AND im.visible = '1'
LIMIT 1;
Is there some condition i can put in place on the join that says if the address is null dont do a join and dont bring me back all the null columns
Yes; you can run a JOIN instead of a LEFT JOIN. But that won't simply exclude the address if it is NULL, it will ignore the whole row altogether.
Usually this kind of situation is either handled by supplying a default value, possibly empty, for example directly in MySQL
SELECT
...COALESCE(ad.address_line_1,'(no address)') AS address_line_1,
COALESCE(ad.address_line_2,'') AS address_line_2, ...
or it is handled by the application:
if row['address_line_1']:
result = result + ("<td class=\"address\">%s</td>" % ( row['address_line_1'] ))
...
This also because a query could potentially return not one record, but several, and of these, some might have a NULL colum and some might not.
UPDATE
There is a way, but it's likely to make milk go sour in cows fifty miles downrange.
This is a proof of concept, on a MUCH smaller query and table, and takes advantage of the possibility of dynamically building a query.
First of all we have our query WHERE condition, here represented by "id = 1". We want to have the name column if the name column is not NULL.
SELECT #address := COALESCE(MIN(',name'),'') FROM client WHERE name IS NOT NULL AND id = 1;
This will return an empty string if the selected column is NULL. Otherwise it will return a comma and the name of that column.
This is the statement that in your case will be humongous, given your query. It contains the same WHERE as before, without the request that the name be NULL. And the field list is now dynamic.
SELECT #string := CONCAT('SELECT id', #address, ' FROM client WHERE id = 1');
Except that #string is, well, a string. To execute it as a query we do
PREPARE query FROM #string;
EXECUTE query;
DEALLOCATE PREPARE query;
How this might interact with your application, I do not dare fathom. I have tried an implementation in PHP on an expendable VM :-), cycling between the values of 1 and 3 (one row has a NULL name, one hasn't).
<?php
// Connect to this VM's local DB
mysql_connect('localhost','root','') or die("Cannot connect");
mysql_select_db('test');
foreach(array(1, 3) as $id)
{
mysql_query("SELECT #address := COALESCE(MIN(',name'),'') FROM client WHERE name IS NOT NULL AND id = $id;");
mysql_query("SELECT #string := CONCAT('SELECT id', #address, ' FROM client WHERE id = ', $id);");
mysql_query("PREPARE query FROM #string;");
$exec = mysql_query("EXECUTE query;");
while($tuple = mysql_fetch_assoc($exec))
{
print implode(" | ", $tuple) . "\n";
}
mysql_query("DEALLOCATE PREPARE query;");
}
?>
The answer seems to indicate it's working:
1 | Rossi
3
(I wouldn't have been surprised if it returned something like 'Ia! Cthulhu fhtagn!').