mysql multiple values () include, need update based on condition - mysql

I need to update the fields on duplicate key if one columns value is equal to soemthing. Right now I have something like this:
INSERT INTO `table` (metric,amount,something1,something2)
VALUES (metric,amount,something1,something2),
(metric,amount,something1,something2),
(metric,amount,something1,something2)
ON DUPLICATE KEY UPDATE
amount = IF (metric = '6', amount = amount + ( '"+amount+"'),'')
ELSEIF (metric = '8' , amount = amount + ( '"+impressions+"') ,'')
ELSEIF (metrtic = '11' , amount = amount + ( '"+impressions+"') ,'');
What am I doing wrong here?

If I understand correctly, you can use UPDATE , self JOIN With CASE WHEN
[DUPLICATE KEY] can fill you want to check DUPLICATE column.
UPDATE `table` r
JOIN (
SELECT Count(*) cnt,[DUPLICATE KEY]
from `table`
group by [DUPLICATE KEY]
) t1 on r.[DUPLICATE KEY] = t1.[DUPLICATE KEY] and t1.cnt > 1
SET r.amount = (CASE WHEN r.metric = '6' THEN r.amount+ ( '"+amount+"'),''
WHEN r.metric = '8' THEN r.amount + ( '"+impressions+"') ,''
WHEN r.metric = '11' THEN r.amount+ ( '"+impressions+"') ,''
ELSE r.amount
END)

What eventually worked for me is:
INSERT INTO `table` (metric,amount,something1,something2)
VALUES (metric,amount,something1,something2),
(metric,amount,something1,something2),
(metric,amount,something1,something2)
ON DUPLICATE KEY UPDATE
metric = VALUES(metric),
amount = amount + VALUES (amount),
modified = NOW(),
something1= VALUES(something1);
Hope this helps anyone in the future.

Related

MySQL UPDATE -- ignore records not in CASE statement

How do you ignore records not inside the case statement when using CASE/WHEN/THEN?
For example, this statement will update three matching records as expected but will make all student records that do not match a WHEN/THEN clause to NULL
UPDATE table SET student = (CASE WHEN student = '10' THEN '100412'
WHEN student = '17' THEN '100295'
WHEN student = '26' THEN '100981'
END)
WHERE year = '2019';
How can you skip over records not inside the CASE statement and only change records that have a matching clause?
For skipping records not in case statement, you can use something like this
UPDATE table SET student = (CASE WHEN student = '10' THEN '100412'
WHEN student = '17' THEN '100295'
WHEN student = '26' THEN '100981'
END)
WHERE year = '2019' AND student IN ('10','17','26');
There a two solutions I think :
Add a where clause
UPDATE table SET student = (CASE WHEN student = '10' THEN '100412'
WHEN student = '17' THEN '100295'
WHEN student = '26' THEN '100981'
END)
WHERE year = '2019' AND student IN ('10','17','26');
Use else statement but that will scan the whole table for nothing :
UPDATE table SET student = (CASE WHEN student = '10' THEN '100412'
WHEN student = '17' THEN '100295'
WHEN student = '26' THEN '100981'
ELSE student
END)
WHERE year = '2019';
You could use a default case for that:
UPDATE table SET student = (CASE WHEN student = '10' THEN '100412'
WHEN student = '17' THEN '100295'
WHEN student = '26' THEN '100981'
ELSE student
END)
WHERE year = '2019';
Otherwise, if you want to minimize the load, just add all known student values to the WHERE clause. This triggers an update only on those rows that are really affected by a change
You can use in or = operator
WHERE year = '2019' AND (student = '10' or student = '17' or student ='26');

MySQL Multiple Case When Exists Statement

I have two tables. Let's call it: SEATS and SEAT_ALLOCATION_RULE table.
Below are the table schema:
CREATE TABLE IF NOT EXISTS `SEATS` (
`SeatID` int(11) NOT NULL AUTO_INCREMENT,
`SeatName` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`SeatID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ;
INSERT INTO `SEATS` (`SeatID`, `SeatName`) VALUES
(1, 'Super VIP'),
(2, 'VIP'),
(3, 'Business'),
(4, 'Economy'),
(5, 'Standing');
CREATE TABLE IF NOT EXISTS `SEAT_ALLOCATION_RULE` (
`SeatID` int(11) NOT NULL DEFAULT '0',
`Origin` varchar(50) NOT NULL DEFAULT '0',
`Destination` varchar(50) NOT NULL DEFAULT '',
`Passenger_Type` varchar(25) NOT NULL DEFAULT '',
PRIMARY KEY (`SeatID`,`Origin`,`Destination`,`Passenger_Type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `SEAT_ALLOCATION_RULE` (`SeatID`, `Origin`, `Destination, `Passenger_Type`) VALUES
(1, 'Malaysia','',''),
(2, 'Malaysia','Singapore',''),
(3, 'Malaysia','Singapore','Senior_Citizen'),
(4, 'Bangkok','Japan','Student'),
(5, 'Cambodia','China','Senior_Citizen');
SEAT_ALLOCATION_RULE table determines which seat should a passenger be assigned to based on the following order in priority:
1. Origin, destination, and passenger_type match
2. Origin and destination match
3. Origin match
It means that if all the fields (origin, destination, and passenger_type) match, it should take higher priority than if it is just two fields match and so on. If a column is empty, it is considered as unspecified and hence has lower priority. So, for example:
If the Origin is Malaysia, Destination is Singapore, and Passenger_Type is Senior_Citizen, it should return seatID 3
If the Origin is Malaysia, Destination is Singapore, and Passenger_Type is Student, it should return seatID 2 (since it only match Origin and Destination)
If the Origin is Malaysia, Destination is US, and Passenger_Type is Student, it should return seatID 1 (since it only match Origin).
Now, based on the rules above, if the origin is Malaysia, destination is Singapore, and Passenger_Type is student, the query to return seatID is as follow:
SELECT s.SeatID, s.SeatName
FROM SEATS s
WHERE
CASE WHEN EXISTS(
select 1
from SEAT_ALLOCATION_RULE r
where s.SeatID = r.SeatID
AND r.Origin = 'Malaysia'
AND r.Destination = 'Singapore'
AND r.Passenger_Type='Student') Then 1
WHEN EXISTS(
select 1
from SEAT_ALLOCATION_RULE r
where s.SeatID = r.SeatID
AND r.Origin = 'Malaysia'
AND r.Destination = 'Singapore'
AND r.Passenger_Type='') Then 1
WHEN EXISTS(
select 1
from SEAT_ALLOCATION_RULE r
where s.SeatID = r.SeatID
AND r.Origin = 'Malaysia'
AND r.Destination = ''
AND r.Passenger_Type='') Then 1 END
However, the query above does not work as it will return seatID 1 and 2, but the expected output is only seatID 2 (since origin and destination matches and it takes higher precedence). Can someone help to correct my SQL query?
This should do the trick:
select seatid
from seat_allocation_rule sar
order by ((sar.origin = :origin) << 2) + ((sar.destination = :destination) << 1) + (sar.passenger_type = :passenger_type) desc,
((sar.origin <> '') << 2) + ((sar.destination <> '') << 1) + (sar.passenger_type <> '') asc
limit 1
To understand how:
create table testcase (
origin varchar(255),
destination varchar(255),
passenger_type varchar(255),
expected_seat int(11)
);
insert into testcase values ('Malaysia','Singapore','Senior_Citizen',3),
('Malaysia','Singapore','Student',2),
('Malaysia','US','Student',1);
select * from (
select tc.*,
sar.seatid,
case when sar.seatid = tc.expected_seat then 'Y' else '-' end as pass,
((sar.origin = tc.origin) << 2)
+ ((sar.destination = tc.destination) << 1)
+ ((sar.passenger_type = tc.passenger_type) << 0) as score,
((sar.origin <> '') << 2)
+ ((sar.destination <> '') << 1)
+ ((sar.passenger_type <> '') << 0) as priority
from seat_allocation_rule sar
cross join testcase tc
) x order by expected_seat desc, score desc, priority asc;
This fixes the existing SQL:
SELECT DISTINCT s.SeatID, s.SeatName
FROM SEATS s
LEFT JOIN SEAT_ALLOCATION_RULE r ON r.SeatID = s.SeatID
AND r.Origin = 'Malaysia'
AND (
(r.Destination = 'Singapore' AND r.Passenger_Type IN ('Student', ''))
OR
(r.Destination = '' AND r.Passenger_Type = '')
)
WHERE r.SeatID IS NOT NULL
But it's only a partial solution, and it's hand-coding logic you really want to apply based solely on the data.
A complete solution will use hypothetical inputs for your passenger's ticket info to produce all eligible seats. This is a great use of lateral joins/apply, which are sadly lacking in MySql (all of their major competitors have had these for at least two release cycles, along with other gems that are absent from the current MySql release like windowing functions, ctes, full joins... I could go on). Here's how I'd do it in Sql Server:
SELECT p.PassengerID, s.SeatID, s.SeatName
FROM Passenger p
CROSS APPLY (
SELECT TOP 1 r.SeatID
FROM SEAT_ALLOCATION_RULE r
WHERE COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
ORDER BY
CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END DESC
) r
INNER JOIN SEATS s ON s.SeatID = r.SeatID
WHERE p.PassengerID = /* passenger criteria here */
I know the Sql Server solution isn't much immediate help to you, but perhaps it will suggest a better MySql solution.
Without APPLY, the only way I know to do this is to first compute the MAX() match count for your passengers (how many parts of the rules match):
SELECT p.PassengerID,
MAX(CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END) AS MatchCount
FROM Passenger p
INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
GROUP BY p.PassengerID
And then use that to filter down to results that have the same number of matches:
SELECT p
FROM Passenger p
INNER JOIN ( /* matchecounts */
SELECT p.PassengerID,
MAX(CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END) AS MatchCount
FROM Passenger p
INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
GROUP BY p.PassengerID
) m ON m.PassengerID = p.PassengerID
INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
INNER JOIN SEATS s ON s.SeatID = r.SeatID
WHERE m.MatchCount =
(CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END)
AND p.PassengerID = /* Passenger criteria here */
Which repeats a lot of code as well as effort in the DB, and is not very efficient. You can repeat the passenger criteria in the nested query, but that would only help a little. This option might also return multiple records for a passenger if they match two rules equally, though you can solve this easily enough with a GROUP BY expression.
In either case, note you can improve performance and simplify code by using actual NULL values instead of empty strings for missing parts of the SEAT_ALLOCATION_RULE table.

Update two tables at once, using values from first table

So I need to Update table scores and use the updated value of column won to update the second table tbl_users. So far the code updates scores, but uses the old value of won for the second table update:
UPDATE scores a
left join tbl_users b on
a.uid = b.userID
SET a.won = CASE
WHEN a.nright = '0' THEN '0'
WHEN a.nright = '1' THEN '25'
WHEN a.nright = '2' THEN '50'
WHEN a.nright = '3' THEN '100'
WHEN a.nright = '4' THEN '200'
WHEN a.nright = '5' THEN '400'
WHEN a.nright = '6' THEN '700'
WHEN a.nright = '7' THEN '1000'
END,
b.pts=b.pts+a.won,
b.pts_total=b.pts_total+a.won
WHERE a.uid=$user AND b.userID=$user
What you want to do is explicitly documented as correct:
The second assignment in the following statement sets col2 to the
current (updated) col1 value, not the original col1 value. The result
is that col1 and col2 have the same value. This behavior differs from
standard SQL.
UPDATE t1 SET col1 = col1 + 1, col2 = col1;
I assume that the issue is the multi-table update, where the set pulls the value from the earlier table.
You may be able to fix this using variables. I am not 100% sure, but the following is worth a try:
UPDATE scores s JOIN
tbl_users u
ON s.uid = .uuserID
SET s.won = (#w := (CASE WHEN s.nright = '0' THEN '0'
WHEN s.nright = '1' THEN '25'
WHEN s.nright = '2' THEN '50'
WHEN s.nright = '3' THEN '100'
WHEN s.nright = '4' THEN '200'
WHEN s.nright = '5' THEN '400'
WHEN s.nright = '6' THEN '700'
WHEN s.nright = '7' THEN '1000'
END)
),
u.pts = u.pts + #w,
u.pts_total = u.pts_total + #w
WHERE s.uid = $user ;
The documentation strongly suggests that the set clauses are processed in order for a single table. Alas, it is not clear whether this is always true for multiple tables.
If not, you can use two updates.

MySQL - conditional select in group

I have a simple table with several rows and I would like to group them by id_room and select value only when condition is true. Problem is that condition is always false even there is a row with right date column year_month.
Here is schema:
CREATE TABLE tbl_account_room (
`id` int,
`year_month` date,
`value` int,
`id_room` int
);
INSERT INTO tbl_account_room
(`id`, `year_month`, `value`, `id_room`)
VALUES
(1, '2016-08-01', 1, 300),
(2, '2016-09-01', 2, 300),
(3, '2016-10-01', 3, 300);
and here query:
SELECT
(case when '2016-10-01' = ar.year_month then ar.value else 0 end) as total
FROM tbl_account_room AS ar
WHERE ar.year_month >= "2016-08-01"
AND ar.year_month <= "2016-11-01"
and ar.id_room = '300'
GROUP BY ar.id_room
LIMIT 10
Here is SQL Fiddle
In column total I'm getting 0 and I would like to get value 3 because year_month is 2016-10-01. Why this happens?
You certainly don't need that CASE condition and include that condition in your WHERE clause rather like
SELECT
ar.value as total,
GROUP_CONCAT(ar.year_month)
FROM tbl_account_room AS ar
WHERE ar.year_month = '2016-10-01'
GROUP BY ar.id_room;
Not sure why you want the result like that, here you can use self join to do that:
SELECT
MAX(t1.value) as total,
GROUP_CONCAT(ar.year_month)
FROM tbl_account_room AS ar
LEFT JOIN tbl_account_room AS t1
ON ar.id_room = t1.id_room
AND ar.year_month = t1.year_month
AND t1.year_month = '2016-10-01'
WHERE ar.year_month >= "2016-08-01"
AND ar.year_month <= "2016-11-01"
and ar.id_room = '300'
GROUP BY ar.id_room
LIMIT 10;
and here is SQLFiddle Demo.

list of several names, to update their rows in the table with same information

Can somebody refresh my memory on how to build a query for this.
I want to use a list of several names (first and last), to update their rows in the table with same information. for example:
if I have a table set up with the columns:
[first_name],[last_name],[dob],[married_status]
I want to find:
(bob, smith),
(robert, john),
(jane, doe);
and edit their field for [married_status] to 'm'.
how do I structure this search and replace?
Thanks!
Use the IN operator:
UPDATE myTable
SET married_status = 'm'
WHERE (first_name, last_name) IN (
('bob' , 'smith'),
('robert', 'john'),
('jane' , 'doe'))
Code:
UPDATE tablename
SET married_status = 'm'
WHERE
( first_name = 'bob' AND last_name = 'smith' )
OR
( first_name = 'robert' AND last_name = 'john' )
OR
( first_name = 'jane' AND last_name = 'doe' )
You would use UPDATE query:
UPDATE `table`
SET `married_status` = 'm'
WHERE
(`first_name` = 'bob' AND `last_name` = 'smith')
OR (`first_name` = 'robert' AND `last_name` = 'john')
OR (`first_name` = 'jane' AND `last_name` = 'doe')