I've got a database of rooms and equipments. I want to query the database and return a list of rooms with e.g. tv, radio, sat and fridge (eq1, eq2, eq3, ...., eqN).
I have the following SELECT statement:
select * from rooms r where
exists (select id from equipments where eq_id='eq1' and room_id=r.id)
and
exists (select id from equipments where eq_id='eq2' and room_id=r.id)
and
exists (select id from equipments where eq_id='eq3' and room_id=r.id)
.......
and
exists (select id from equipments where eq_id='eqN' and room_id=r.id)
Is there any way to optimize or making this shorter?
To shorten you could
select *
from rooms r
where #N = (select count(distinct eq_id)
from equipments
where eq_id IN ('eq1','eq2',...,'eqN') and room_id=r.id)
EDIT
but not sure if it will actually make it faster... quite the opposite, the version with EXISTS AND EXISTS has a chance to prune execution branch on the first false, the above must actually count the distinct values (go through all records) and see what that value is.
So you should think what is faster:
going once through all records related to a room (one correlated subquery) or
running N (worst case) correlated (but highly selective subqueries) for each room
It depends on the statistics of your data (I would think that if most rooms don't have all the sought equipment in them then your initial version should be faster, if most rooms have all equipment in them then the proposed version might perform better; also if the EXISTS version is faster make an effort to first the queries that are most likely to fail i.e. first check for rarest equipment)
You can also try a version with GROUP BY
select r.*
from rooms r join
equipments e on r.id = e.room_id
group by r.id
where eg_id in ('eq1','eq2',...,'eqN')
having count(distinct e.eq_id) = #N
(above SQL not tested)
try this (I don't have any DB available to test it, also consider performance )
select * from
rooms r,
(
select count(distinct id) as cnt, id from equipments where eq_id in ('eq1','eq2') group by id
) as sub
where sub.id = r.id
and sub.cnt >= 2 'Options count
Note: 2 - it is the number of options that you need. In example they are: 'eq1','eq2'
select * from rooms r where
(select count(id) from equipments where eq_id='eq1' and room_id=r.id) > 0
and
...
Use an Stored Procedure.
here is the procedure for mysql:
DELIMITER $$
CREATE DEFINER=`root`#`%` PROCEDURE `GetRooms`(IN roomtable TEXT, IN equipmenttable TEXT, IN equipments TEXT )
BEGIN
DECLARE statement text;
DECLARE Pos int;
DECLARE cond text;
DECLARE element text;
DECLARE tmpTxt text;
set tmpTxt = equipments;
set cond = "";
set Pos = instr(tmpTxt,';');
while Pos <> 0 do
set element = substring(tmpTxt, 1, Pos-1);
if cond <> "" then
set cond = concat(cond,' and ');
end if;
set cond = concat(cond,'exists (select id from ' , equipmenttable ,' where eq_id=''' , element ,''' and room_id=r.id) ');
set tmpTxt = replace(tmpTxt, concat(element,';'), '');
set Pos = instr(tmpTxt,';');
end while;
if tmpTxt <> "" then
if cond <> "" then
set cond = concat(cond,' and ');
end if;
set cond = concat(cond,'exists (select id from ' , equipmenttable ,' where eq_id=''' , tmpTxt ,''' and room_id=r.id) ');
end if;
SET #statement = concat('Select * FROM ' , roomtable , " WHERE " , cond , ";");
PREPARE stmt FROM #statement;
EXECUTE stmt;
END
Execute it with: CALL GetRooms('RoomTableName','EquipmentTableName','EquipmentIDs')
Example:
Call GetRooms('rooms','equipemnts','eq1;eq2;eq3');
Hope this helps.
To Execute Query Faster use Exists
select *
from rooms as r
where exists (
select *
from equipments
where eq_id IN ('eq1','eq2',..,'eqN') and r.id= equipments.room_id);
Related
Looking for a way to return a variable that can be used inside a IN-operator.
My current result returns: 1,2,3,
but I guess the variable should be more like this: '1','2','3' to be able to use it.
Is this possible?
Or should I try something else like explode, save the query,...
-- init vars
SET #export_date = '2022-06-20';
SET #order_ids = '';
-- Tasks on given day
SELECT * FROM tasks WHERE task_execution_date = #export_date;
-- method 1
SET #order_ids = (SELECT GROUP_CONCAT(DISTINCT task_order SEPARATOR ',' ) FROM tasks WHERE task_execution_date = #export_date);
SELECT #order_ids; -- "1,2,3"
-- method 2
SET #order_ids = (SELECT GROUP_CONCAT(DISTINCT '' + cast( task_order AS CHAR(50)) +'' ) FROM tasks WHERE task_execution_date = #export_date);
SELECT #order_ids; -- "1,2,3"
-- method 3
SET #order_ids = (SELECT GROUP_CONCAT( DISTINCT + '\'' + CAST(task_order AS CHAR (100)) + '\'') FROM tasks WHERE task_execution_date = #export_date);
SELECT #order_ids; -- "1,2,3"
-- Orders by tasks
SELECT * from orders WHERE ordr_id IN (#order_ids); -- returns only one result
-- SELECT * from orders WHERE ordr_id IN #order_ids; -- error
SELECT * from orders WHERE ordr_id IN ('1', '2', '3'); -- works
SELECT * from orders WHERE ordr_id IN (SELECT DISTINCT task_order FROM tasks WHERE task_execution_date = #export_date); -- works
-- Also needed:
-- goods by orders
-- good_adrs by goods
If You really, really, I mean REALLY know that these variables can't be evil, then You can build and execute raw query:
SET #export_date = '2022-06-20';
SET #order_ids = (SELECT GROUP_CONCAT(DISTINCT task_order SEPARATOR ',' ) FROM tasks WHERE task_execution_date = #export_date);
SET #query = CONCAT('SELECT * from orders WHERE ordr_id IN (', #order_ids, ');');
PREPARE stmt FROM #query;
EXECUTE stmt;
i need help for the below code. My problem is that i want only that code executes once per statement (after i search i checked that expression don't exists anymore only once per row).
So i tried to add:
IF NOT EXISTS
(Select count(*) FROM replay_replays_access WHERE id_game = new.id_game GROUP BY id_game HAVING count(*) <5)
THEN
But didn't work either what can i do, its duplicating sometime triplicating the information?
TRIGGER replay
AFTER UPDATE
ON table_replays FOR EACH ROW
begin
IF EXISTS
(SELECT
replay_games.room_name
FROM replay_games
WHERE replay_games.room_name = 'Tournament Room' and replay_games.id = new.id_game)
THEN
IF NOT EXISTS
(Select
count(*)
FROM replay_replays_access
WHERE id_game = new.id_game
GROUP BY id_game
HAVING count(*) <5)
THEN
INSERT INTO replay_replays_access(id_game, id_player, replay_name, do_not_hide)
SELECT
new.id_game,
replay_users.id ,
CONCAT(
(SELECT game_types
FROM replay_games
WHERE id=new.id_game),
': ',
(SELECT
descr
FROM replay_games
WHERE id=new.id_game)) ,
0
FROM replay_users
WHERE
(replay_users.admin > 0 OR
replay_users.privlevel = 'TOURNAMENT MEMBER')
AND NOT replay_users.name = (
SELECT
replay_games.creator_name
FROM replay_games
WHERE replay_games.id = new.id_game);
END IF;
END IF;
END
I am writing a function that should return a floating value.
BEGIN
DECLARE due_amount DECIMAL(9,2);
SET due_amount = (SELECT due_amount FROM (
SELECT id, MAX(date), due_amount, user_id
FROM lunch_transaction
GROUP BY user_id
HAVING user_id = user) l);
IF due_amount IS NULL THEN
SET due_amount = 0.00;
END IF;
RETURN due_amount;
END
The function only returns value 0.00 even though the value should be something else.
Running only this query :
(SELECT due_amount FROM (
SELECT id, MAX(date), due_amount, user_id
FROM lunch_transaction
GROUP BY user_id
HAVING user_id = user) l);
is giving the correct output though.
How should I set the query's output to the variable?
It is really bad practice to use variable name that can conflict with column names. Also, the subquery seems very unnecessary. I would try something more like this:
BEGIN
DECLARE v_due_amount DECIMAL(9,2);
SELECT v_due_amount := l.due_amount
FROM lunch_transaction l
WHERE l.user_id = in_user; -- I'm guessing `user` is also a parameter
IF v_due_amount IS NULL THEN
SET v_due_amount = 0.00;
END IF;
RETURN v_due_amount;
END;
Your version has an aggregation function in the subquery. This makes no sense, because due_amount is not the argument of an aggregation function. This logic should perhaps be:
SELECT v_due_amount := SUM(l.due_amount)
FROM lunch_transaction l
WHERE l.user_id = in_user; -- I'm guessing `user` is also a variable
I have a query that regularly returns "nothing", and I would like to run a different query if this is the case, but I know not of the way of doing this. If anyone could be of help please.
Here is the current code I am using...
SELECT * FROM cfg_users JOIN cfg_ash ON cfg_users.iUserId = cfg_ash.iUserid WHERE iTeamId='0' AND sDisabled IS NULL AND iStatusId > 0 AND sDate = '2014-08-01' GROUP BY cfg_users.iUserId ORDER BY iStatusId, sName
I basically want to say
IF <my code> IS NULL THEN <do other code>, IF <my code> IS NOT NULL THEN return the result.
Thanks
There are some simple way only use sql.
Define your first query as a temp table, with union all, filter the second query with temp table's count.
with temp as (select * from t1 where 1=0)
select * from temp
union all
select * from t2 where (select count(*) from temp) =0
This query will return the second table's records.
with temp as (select * from t1 )
select * from temp
union all
select * from t2 where (select count(*) from temp) =0
And if temp query have result, only return temp query.
You can test with sql fiddle here.
A way you can do it is like this
set two variables equal to the queries you want to execute.
set another variable equal to the correct query when the first is not null.
execute that query with a stored procedure.
STORED PROCEDURE:
DELIMITER $$
CREATE PROCEDURE `dynamic_query`(in input varchar(255))
BEGIN
SET #a := input;
PREPARE stmt FROM #a;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$
DELIMITER ;
THE TWO SELECTS YOU WANT TO EXECUTE:
SET #A := "SELECT * FROM cfg_users JOIN cfg_ash ON cfg_users.iUserId = cfg_ash.iUserid WHERE iTeamId='0' AND sDisabled IS NULL AND iStatusId > 0 AND sDate = '2014-08-01' GROUP BY cfg_users.iUserId ORDER BY iStatusId, sName";
SET #B := "your other select here";
THE DEFINER TO GET THE CORRECT QUERY:
SET #C := (
SELECT
CASE
WHEN EXISTS
( SELECT *
FROM cfg_users
JOIN cfg_ash ON cfg_users.iUserId = cfg_ash.iUserid
WHERE iTeamId='0'
AND sDisabled IS NULL
AND iStatusId > 0
AND sDate = '2014-08-01'
GROUP BY cfg_users.iUserId
ORDER BY iStatusId, sName
)
THEN #A
ELSE #B
END
);
EXECUTE THE STATEMENT:
CALL dynamic_query(#C);
DEMO WHEN THE QUERY EXISTS
DEMO WHEN THE QUERY DOESN'T EXIST
You can store the results in a temporary table / table variable, and then check the count
e.g.
CREATE TABLE #Results ( --columns you need here )
INSERT INTO #Results SELECT *
FROM cfg_users
JOIN cfg_ash ON cfg_users.iUserId = cfg_ash.iUserid WHERE iTeamId='0' AND sDisabled IS NULL AND iStatusId > 0 AND sDate = '2014-08-01'
GROUP BY cfg_users.iUserId
ORDER BY iStatusId, sName
SET #Count = SELECT COUNT(*) FROM #Results
IF 0 = #Count THEN
INSERT INTO #Results -- Other Query Here
SELECT * FROM #Results
n.b. you should really specify what columns you want in both queries rather than using *
This question already has answers here:
Oracle Replace function
(3 answers)
Closed 9 years ago.
I need to replace the Table1's filed values from Table2's values while select query.
Eg:
Table1:
Org Permission
--------------------------------------
Company1 1,3,7
Company2 1,3,8
Table2:
Permission Permission
--------------------------------------
1 Read
3 Write
7 Execute
8 Delete
I need like this:
Org Permission
--------------------------------------
Company1 Read,Write,Execute
Company2 Read,Write,Delete
I have been following your post since it was tagged in Oracle :D
In oracle it was looking in much possible ways, But in mysql you have to follow it with the help of procedure:
Schema:
create table table1 (org varchar(50), permission_id varchar(50));
create table table2 (permission_id int, permission_name varchar(50));
insert into table1 values ('Company1','1,3,7'),('Company2','1,3,8');
insert into table2 values (1,'Read'),(3,'Write'),(7,'Execute'),(8,'Delete');
Procedure:
DELIMITER $$
DROP PROCEDURE IF EXISTS `Update_Table_data`$$
CREATE PROCEDURE `Update_Table_data`()
BEGIN
declare max_row int;
declare p1 int;
Set p1 = 0;
SET max_row = (SELECT max(#i:=#i+1) AS row_num FROM table2 AS t,(SELECT #i:=0) AS foo);
label1: LOOP
set p1 = p1 + 1;
IF p1 <= max_row THEN
UPDATE Table1
SET permission_id =
replace(permission_id, (select permission_id from
(SELECT #i:=#i+1 AS row_num ,t.* FROM table2 AS t,(SELECT #i:=0) AS foo) a
where row_num = p1),
(select permission_name from
(SELECT #i:=#i+1 AS row_num ,t.* FROM table2 AS t,(SELECT #i:=0) AS foo) a
where row_num = p1));
Iterate label1;
END IF;
LEAVE label1;
END LOOP label1;
-- SET #x = p1;
END$$
DELIMITER ;
and then make a call to update your values :
call Update_Table_data;
Hope it helps :)
See SQLFiddle for initial data and this is resultant data SQLFiddle
UPDATE
organization
SET
permisson = (SELECT
GROUP_CONCAT(VALUE)
FROM
( SELECT
org,
SUBSTRING_INDEX(permisson,',',1) AS `permisson`
FROM
organization
UNION
SELECT
org,
SUBSTRING_INDEX(SUBSTRING_INDEX(permisson,',',2),',',-1) AS `permisson`
FROM
organization
UNION
SELECT
org,
SUBSTRING_INDEX(permisson,',',-1) AS `permisson`
FROM
organization
) AS t
JOIN
permission p
WHERE
p.p_id = t.permisson AND
t.org = organization.org
GROUP BY org
)
Try to replace your numbers using REPLACE.It work properly only when you are using single digit values for permission.
SQL = " SELECT Org, REPLACE(REPLACE(REPLACE(REPLACE(Permission,"1","Read'"),"3","Write'"),"7","Execute'"),"8","Delete'") as Permission FROM myTable "
REPLACE()