Enabling Duplicates in WHERE … IN? - mysql

I want to use a simple query to decrement a value in a table like so:
UPDATE `Table`
SET `foo` = `foo` - 1
WHERE `bar` IN (1, 2, 3, 4, 5)
This works great in examples such as the above, where the IN list contains only unique values, so each matching row has its foo column decremented by 1.
The problem is when the list contains duplicates, for example:
UPDATE `Table`
SET `foo` = `foo` - 1
WHERE `bar` IN (1, 3, 3, 3, 5)
In this case I would like the row where bar is 3 to be decremented three times (or by three), and 1 and 5 to be decremented by 1.
Is there a way to change the behaviour, or an alternative query that I can use where I can get the desired behaviour?
I'm specifically using MySQL 5.7, in case there are any MySQL specific workarounds that are helpful.
Update: I'm building the query in a scripting language, so feel free to provide solutions that perform any additional processing prior to running the query (perhaps as pseudo code, to be as useful to as many as possible?). I don't mind doing it this way, I just want to keep the query as simple as possible while giving the expected result.

If you can process your original list first to get the counts, you could dynamically construct this kind of query:
UPDATE `Table`
SET `foo` = `foo` - CASE `bar` WHEN 1 THEN 1 WHEN 3 THEN 3 WHEN 5 THEN 1 ELSE 0 END
WHERE `bar` IN (1, 3, 5)
;
Note: the ELSE is just being thorough/paranoid; the WHERE should prevent it from ever getting that far.

There is an example might be beneficial for your purpose:
create table #temp (value int)
create table #mainTable (id int, mainValue int)
insert into #temp (value) values (1),(3),(3),(3),(4)
insert into #mainTable values (1,5),(2,5),(3,5),(4,5)
select value,count(*) as AddValue
into #otherTemp
from #temp t
group by value
update m
set mainValue = m.mainValue+ ot.AddValue
from #otherTemp ot
inner join #mainTable m on m.id=ot.value
select * from #mainTable

This is a little tricky, but you can do it by aggregating first:
update table t join
(select bar, count(*) as factor
from (select 1 as bar union all select 3 as bar union all select 3 as bar union all select 3 as bar union all select 5
) b
) b
on t.bar = b.bar
t.foo = t.foo - bar.factor;

Related

SQL: is there a single query to get "all rows with X, or if none, all rows with Y"?

I'm wondering how to get "all rows where col='X'; if there are none, all rows where col='Y'"
Simplfied database;
CREATE TABLE CHARACTER_NAMES(CHARACTER_ID, LANG VARCHAR(3), NAME);
INSERT INTO CHARACTER_NAMES(1, "ENG", "DONALD DUCK");
INSERT INTO CHARACTER_NAMES(1, "ENG", "GOOD OL' DONALD");
INSERT INTO CHARACTER_NAMES(1, "SWE", "KALLE ANKA");
INSERT INTO CHARACTER_NAMES(1, "SWE", "KALLEN");
INSERT INTO CHARACTER_NAMES(2, "ENG", "MICKEY MOUSE");
INSERT INTO CHARACTER_NAMES(2, "SWE", "MUSSE PIGG");
INSERT INTO CHARACTER_NAMES(2, "SWE", "MUSEN");
INSERT INTO CHARACTER_NAMES(3, "ENG", "GOOFY");
INSERT INTO CHARACTER_NAMES(3, "NOR", "FEDTMULE");
(It's a bit forced that the characters have several names in the same language, but that's how the real database looks like. Also, "CHARACTER_ID" is also a foreign key to the CHARACTER table, but that's not part of the problem, so omitted.)
The user has a language setting, and when there is a database query for a specific character, the query should return the names in the selected language, or the names in English, if the selected language has no results. In the above example, if the setting was "Swedish" and the users selected character 3 (Goofy) the name search should return "Goofy", as there is no Swedish name registered. If the user selected Mickey Mouse, the search should return 2 rows: "Musse Pigg" and "Musen".
I wonder if this is possible to express in an SQL query.
If I just wanted the FIRST in the selected language, if none, english, I could use:
SELECT NAME
FROM CHARACTER_NAMES
WHERE CHARACTER_ID=?
ORDER BY CASE
WHEN LANG='NOR' THEN 1
WHEN LANG='ENG' THEN 2
END
LIMIT 1;
But as I can't know how many names there will be in the selected language, I have to let this LIMIT vary, and I don't really know how to do that in a nice and proper way.
I'm wondering how to get "all rows where col='X'; if there are none, all rows where col='Y'"
SELECT *
FROM table
WHERE col='X'
UNION ALL
SELECT *
FROM table
WHERE col='Y'
AND NOT EXISTS ( SELECT NULL
FROM table
WHERE col='X' )
If a row(s) with col='X' exists then WHERE EXISTS will give FALSE and 2nd subquery will return nothing - i.e. only output of 1st subquery only will be returned.
And backward, if there is no row with col='X' then 1st subquery won't return rows, but WHERE EXISTS will give TRUE and 2nd subquery will return all rows with col='Y' - i.e. only output of 2nd subquery only will be returned.
Or you may use
SELECT *
FROM table
WHERE col = CASE WHEN EXISTS ( SELECT NULL
FROM table
WHERE col='X' )
THEN 'X'
ELSE 'Y'
END;
There are more variants, of course...
In MySQL 8 you can use CASE expression with RANK to get the desired result:
WITH cte AS (
SELECT *, RANK() OVER (PARTITION BY character_id ORDER BY CASE
WHEN lang = 'NOR' THEN 1
WHEN lang = 'ENG' THEN 2
ELSE 3
END) AS rnk
FROM character_names
)
SELECT *
FROM cte
WHERE rnk = 1
Identical result could be achieved with NOT EXISTS:
SELECT *
FROM character_names AS t1
WHERE lang = 'NOR'
OR lang = 'ENG' AND NOT EXISTS (
SELECT *
FROM character_names AS t2
WHERE t2.character_id = t1.character_id
AND t2.lang = 'NOR'
)
Result:
character_id
lang
name
rnk
1
ENG
DONALD DUCK
1
1
ENG
GOOD OL' DONALD
1
2
ENG
MICKEY MOUSE
1
3
NOR
FEDTMULE
1

SQL Query on selecting rows only if its available in other table for a particular section

I am trying to write a SQL Query. I want to select all the records from table 1, with one condition.
If a Type is 'Prime' in Table 1, and that Semi+Prime combination exist in Table 2, only then we will select it from table 1.
For example, in this sample attached here, 4 and 6 (having type=prime) are there in table 2, so we consider it for output. 8( having type=prime) does not exist in table 2, so we dont take it in output.
This condition will be applicable only when Type is Prime in table1.
I have created the table here:https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=13a5a2a6be51804a89ff5fd47edeeb70
Please try out below query it may help you:(Try it in your fiddle only)
First do the left join and using case condition remove non-prime:
select Group2,Type2,Group3 from t1 left join t2 on t1.type2=t2.group1 where (case when t1.group3 = 'PRIME' then t1.group3=t2.type1 else 1=1 end)
Try this query
select t1.Group1,t2.Semi,t1.Type from t1,t2 where t1.Type='PRIME' and t1.Semi=t2.Semi and t1.Type=t2.Type;
table t1:
table t2:
Output:
You can use Exists to get what you want.
create table t2
(Semi integer(10),
Type varchar(10)
);
create table t1
(Group2 varchar(10),
Semi int,
Type varchar(10)
);
insert into t2
VALUES(17,'PRIME'),
(4,'PRIME'),
(2,'PRIME'),
(3,'PRIME'),
(43,'PRIME'),
(34,'PRIME'),
(6,'PRIME'),
(23,'PRIME')
insert into t1
VALUES('A',1,'X'),
('A',2,'Y'),
('A',3,'Z'),
('A',4,'PRIME'),
('A',5,'X'),
('A',6,'PRIME'),
('A',7,'X'),
('A',8,'PRIME')
Query:
select * from t1
where type <>'PRIME'
or EXISTS
(
SELECT 1 FROM t2 WHERE t2.Semi=t1.Semi and t2.Type=t1.Type and t1.Type='PRIME'
)
Output:
Group2
Semi
Type
A
1
X
A
2
Y
A
3
Z
A
4
PRIME
A
5
X
A
6
PRIME
A
7
X
db<>fiddle here

Trying to update a table related to multiple other rows in the same table

I have a table which represents a tree using nested sets with the typical left and right columns (trLeft and trRight). For optimization, I also include the row's level in the tree, which is the number of parents it has up to the tree ROOT.
In nested sets the Parents of a row A are all other rows B where B.trLeft < A.trLeft AND B.trRight > A.trRight.
So counting those rows will return the level, resulting in the following update query for a start in order to update all rows with the correct level:
UPDATE Groups AS g1 SET g1.trLevel = (
SELECT COUNT(*) FROM Groups AS g2 WHERE g2.trLeft < g1.trLeft AND g2.trRight > g1.trRight ) ;
but this results in Error 1093 "You can't specify target table 'g1' for update in FROM clause.
Is there a way to get around this ?
You have to sort of nest the count in a select. I think this will work:
EDITED: Updated query
UPDATE Groups SET trLevel = (
SELECT * FROM
(
SELECT COUNT(*) FROM Groups g2 inner join Groups g1 on g2.unique_id = g1.unique_id WHERE g2.trLeft < g1.trLeft AND g2.trRight > g1.trRight
) AS my_count
) ;
The AS my_count is important because it tells MySQL to create a tmp table from this query. The my_count tmp table is then used as the source for the UPDATE statement.
A silly workaround is to nest a derived table inside a subquery, like so:
UPDATE Groups AS g1
SET g1.trLevel = (
SELECT COUNT(*)
FROM (
SELECT *
FROM Groups AS g2
) AS temp
WHERE
temp.trLeft < g1.trLeft
AND temp.trRight > g1.trRight
);
I tested this by first building the table like so:
CREATE TABLE Groups (trLeft int, trRight int, trLevel int);
INSERT INTO Groups (trLeft, trRight) VALUES (1, 4), (5, 6), (2, 3);
and it works.

Re-ordering numeric columns

I am using PHP/MySQL for a web application. Users can enter line items for different jobs. The order of the line items is important. For this I have an 'ordernumber' field. It's easy to insert new lines; I just add one to the highest current order number. But for deleting and inserting lines it becomes tricky. Right now I use a query to update all of the ordernumbers after each delete. However, sometimes there could be many deletes in the same request. I am thinking there is an easier way. For example, if the ordernumbers in the table are
1, 2, 5, 8, 9, 10
Is there an update query I could run to update them to
1, 2, 3, 4, 5, 6
When deleting a row:
update mytable
set ordernumber = ordernumber-1
where ordernumber > {number deleted}
It shouldn't matter if there are multiple deletes as this is a relative update.
To update the entire sequence:
(this works in SQL Server - maybe you can adapt it for MySQL!)
while exists (
select *
from mytable mt1
where not exists (
select *
from mytable mt2
where mt2.ordernumber = mt1.ordernumber - 1
)
and mt1.ordernumber > 1
)
begin
update mytable
set ordernumber = (
select isnull( max( mt3.ordernumber ), 0) + 1
from dbo.mytable mt3
where mt3.ordernumber < mytable.ordernumber
)
end

Using an IF Statement in a MySQL SELECT query

I am trying to use an IF statement in a MySQL select query.
I am getting an error after the AND statement where the first IF.
SELECT J.JOB_ID,E.COMPANY_NAME,J.JOB_DESC,JT.JOBTYPE_NAME,J.COMPENSATION,ST.STATE_NAME,MC.METRO_CITY_NAME,I.INDUSTRY_NAME,
J.JOB_CONTACT_PERSON,J.DT_INSRT,J.JOB_TITLE,J.JOB_EXP_DATE,J.SKILLS
FROM JOBS J
JOIN EMPLOYER E ON J.COMPANY_ID=E.COMPANY_ID
JOIN LOOKUP_JOBTYPE JT ON J.JOB_TYPE=JT.JOBTYPE_ID
JOIN LOOKUP_STATE ST ON J.STATE_ID=ST.STATE_ID
JOIN JOBS_LOCATION JL ON J.JOB_ID=JL.JOB_ID
JOIN LOOKUP_METRO_CITY MC ON JL.METRO_CITY_ID=MC.METRO_CITY_ID
JOIN LOOKUP_INDUSTRY I ON J.INDUSTRY_ID=I.INDUSTRY_ID
JOIN JOBS_QUALIFICATION JQ ON J.JOB_ID=JQ.JOB_ID
JOIN LOOKUP_DEGREE_QUALIFICATION LDQ ON LDQ.QUALIFICATION_ID = JQ.QUALIFICATION_ID
WHERE J.ACTIVE='Y' AND J.DT_INSRT > COALESCE(pEmailSntDt,DATE_SUB(SYSDATE(),INTERVAL 4 DAY))
AND
IF(JQ.COURSE_ID=0)
THEN
IF(JQ.DEGREE_ID=0)
THEN J.SKILLS LIKE CONCAT('%', pSkills,'%')
ELSE
JQ.DEGREE_ID=pDegreeId OR J.SKILLS LIKE CONCAT('%', pSkills,'%')
END IF
ELSE
JQ.COURSE_ID=pCourseId OR IF(JQ.DEGREE_ID=0)
THEN
J.SKILLS LIKE CONCAT('%', pSkills,'%')
ELSE
JQ.DEGREE_ID=pDegreeId OR J.SKILLS LIKE CONCAT('%', pSkills,'%')
END IF
END IF
GROUP BY J.JOB_ID ORDER BY J.DT_INSRT DESC;
Why doesn't this work and what is the proper way to do an IF statement in a MySQL query?
The IF/THEN/ELSE construct you are using is only valid in stored procedures and functions. Your query will need to be restructured because you can't use the IF() function to control the flow of the WHERE clause like this.
The IF() function that can be used in queries is primarily meant to be used in the SELECT portion of the query for selecting different data based on certain conditions, not so much to be used in the WHERE portion of the query:
SELECT IF(JQ.COURSE_ID=0, 'Some Result If True', 'Some Result If False'), OTHER_COLUMNS
FROM ...
WHERE ...
How to use an IF statement in the MySQL "select list":
select if (1>2, 2, 3); //returns 3
select if(1<2,'yes','no'); //returns yes
SELECT IF(STRCMP('test','test1'),'no','yes'); //returns no
How to use an IF statement in the MySQL where clause search condition list:
create table penguins (id int primary key auto_increment, name varchar(100))
insert into penguins (name) values ('rico')
insert into penguins (name) values ('kowalski')
insert into penguins (name) values ('skipper')
select * from penguins where 3 = id
-->3 skipper
select * from penguins where (if (true, 2, 3)) = id
-->2 kowalski
How to use an IF statement in the MySQL "having clause search conditions":
select * from penguins
where 1=1
having (if (true, 2, 3)) = id
-->1 rico
Use an IF statement with a column used in the select list to make a decision:
select (if (id = 2, -1, 1)) item
from penguins
where 1=1
--> 1
--> -1
--> 1
If statements embedded in SQL queries is a bad "code smell". Bad code has high "WTF's per minute" during code review. This is one of those things. If I see this in production with your name on it, I'm going to automatically not like you.
try this code worked for me
SELECT user_display_image AS user_image,
user_display_name AS user_name,
invitee_phone,
(CASE WHEN invitee_status = 1 THEN "attending"
WHEN invitee_status = 2 THEN "unsure"
WHEN invitee_status = 3 THEN "declined"
WHEN invitee_status = 0 THEN "notreviwed"
END) AS invitee_status
FROM your_table