Update of nested join not working with an order - mysql

Long time reader, first time poster. (I did some deep dive searches, and was unable to find anything similar -- Thank you in advance)
I am coding a project using ruby and active record, and I ran into a situation where I'm not able to google search the answer to why two things are happening. From what I can tell, the first thing is happening because of a known bug with rails. The second, I do not know.
Here's the mock code:
class Object1 < ActiveRecord::Base
has_many :object2s, foreign_key: :object1_id, :dependent => :delete_all
end
class Object2 < ActiveRecord::Base
belongs_to :object1, class_name: "Object1"
end
Object2 has a unique index for object1_id and date.
I have an update that is failing due to a index violation:
ActiveRecord::RecordNotUnique in Object1Controller#datechange
Update code:
Object2.joins( :object1 ).where( object1: { :id => id } )**.order( obj2_date: ascdsc )**.update_all(
"obj2_date = " + direction + "(obj2_date, INTERVAL " + difference.to_s + " month)")
The index is being tripped on the update without the order (** added above), the dates are being updated in a way that causes the violation. Elsewhere in the code, I have specified the update order, and it will update them in a way that will not violate the index. With this Object, adding in the join causes the first issue:
Here's the (mock) SQL generated:
UPDATE object2 SET obj2_date = date_sub(obj2_date, INTERVAL 1 month)
WHERE object2.id IN (
SELECT id FROM (
SELECT object2.id FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria> **ORDER BY object2.obj2_date ASC**
) __active_record_temp
)
If I modify the SQL, I can run it in an SQL client where it will work as expected. [Note: I moved the location of the order]
UPDATE object2 SET obj2_date = date_sub(obj2_date, INTERVAL 1 month)
WHERE object2.id IN (
SELECT id FROM (
SELECT object2.id FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria>
) __active_record_temp
) **ORDER BY object2.obj2_date ASC**
Question 1:
The order is being added to the wrong place. How do I get it right, or work around it?
I believe this to be related to this bug:
https://github.com/rails/rails/issues/6769
Question 2:
Why is this happening? ...select id from (select id from table) __temp_table...
WHERE object2.id IN (
SELECT id FROM (
SELECT object2.id FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria> **ORDER BY object2.obj2_date ASC**
) __active_record_temp
)
Wouldn't it be better for it to be this: ...select id from table...
WHERE object2.id IN (
SELECT object2.id FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria>
)
Removing the need for a temp table just to get the id when it's already getting just the id?
Thanks.

I really don't get why the Order should matter so much, but simply add
LIMIT 18446744073709551615
behind your ORDER mBY
UPDATE object2 SET obj2_date = date_sub(obj2_date, INTERVAL 1 month)
WHERE object2.id IN (
SELECT DISTINCT id FROM (
SELECT object2.id,object2.obj2_date FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria>
) __active_record_temp
ORDER BY obj2_date ASC LIMIT 18446744073709551615
)
The cause, why order by are ignored without a LIMIT is simple, rows are per definition unordered so teh ORDER BY is removed without a Limit,
Mysql lets the ORDER BY untouched under special circumstances
Object2.joins( :object1 ).where( object1: { :id => id } ).order( obj2_date: ascdsc ).limit(18446744073709551615).update_all(
"obj2_date = " + direction + "(obj2_date, INTERVAL " + difference.to_s + " month)")

I didn't know you could have an entire active record code block inside an active record code block.
This addresses both issues. Code:
Object2.where( id: __Object1.select( :id ).where( :parent_id => id )__ ).order( obj2_date: ascdsc ).update_all(
"obj2_date = " + direction + "(obj2_date, INTERVAL " + difference.to_s + " month)")
Auto generates this SQL:
UPDATE object2 SET obj2_date = date_add(obj2_date, INTERVAL 1 month)
WHERE object2.obj1_id IN (
SELECT object1.id FROM object1 WHERE object1.parent_id = 15
) ORDER BY object2.obj2_date ASC

Related

Increment Row Index And Union With Other Tables On Dynamic Dates

Question:
I looked at various other examples to increment over rows, but all resulted in the same wrong output. The problem which I encountered was that my code did not successfully increment over rows to build a correct index per new row in the result-set per episode (highlighted in red below).
My first try was:
SET #ep_1 = "Peaky Blinders";
SET #curRow_1 = 0;
SELECT
DATE_FORMAT(created_at, "%Y%m%d") AS year_month_day,
#curRow_1 := #curRow_1 + 1 AS row_number,
#ep_1 AS episode_title,
COUNT(id) AS episode_plays
FROM netflix.episode_plays
WHERE
episode_id = "xyz"
AND created_at >= "2019-07-01" AND created_at <= "2019-07-07"
GROUP BY 1
Other than the rows not incrementing correctly; I also got the following error when I tried setting some variables in the beginning of my code:
Error running query: Illegal mix of collations (utf8_unicode_ci,IMPLICIT) and (utf8_general_ci,IMPLICIT) for operation '='
(Note: I have no affiliation with Netflix, I just used Netflix dummy data to answer my question)
I broke down my question in various sections and got to the final answer below.
The most important part was to add the initial result-sets into a subqueries, and thereafter select the data from tables x1,x2, etc.
The second part of the question was, how to combine multiple datasets together (in my case: how do one not only do it for one specific netflix episode, but multiple episodes)? I settled on the UNION ALL - clause.
In the first iteration I tried hard-coding the dates, and thereafter found the INTERVAL-function very helpful.
Finally, the unicode-error I fixed by adding COLLATE utf8_unicode_ci after setting my variables.
If you find mistakes in my code or have any other suggestions, please feel free to suggest them.
-- SET DATA
-- variables for table x1
SET #ep_1 = "Peaky Blinders" COLLATE utf8_unicode_ci;
SET #id_1 = (SELECT id FROM netflix.episodes WHERE episode_title = #ep_1);
SET #date_1 = (SELECT created_at FROM netflix.episodes WHERE episode_title = #ep_1);
SET #curRow_1 = 0;
-- variables for table x2
SET #ep_2 = "Brooklyn Nine-Nine" COLLATE utf8_unicode_ci;
SET #id_2 = (SELECT id FROM netflix.episodes WHERE episode_title = #ep_2);
SET #date_2 = (SELECT created_at FROM netflix.episodes WHERE episode_title = #ep_2);
SET #curRow_2 = 0;
-- QUERY DATA
SELECT
x1.year_month_day,
#curRow_1 := #curRow_1 + 1 AS row_number,
x1.episode_title,
x1.episode_plays
FROM (
SELECT
DATE_FORMAT(created_at, "%Y%m%d") AS year_month_day,
#ep_1 AS episode_title,
COUNT(id) AS episode_plays
FROM netflix.episode_plays
WHERE
episode_id = #id_1
AND created_at >= #date_1 AND created_at <= DATE_ADD(#date_1 , INTERVAL 7 DAY)
GROUP BY 1) x1
UNION ALL
SELECT
x2.year_month_day,
#curRow_2 := #curRow_2 + 1 AS row_number,
x2.episode_title,
x2.episode_plays
FROM (
SELECT
DATE_FORMAT(created_at, "%Y%m%d") AS year_month_day,
#ep_2 AS episode_title,
COUNT(id) AS episode_plays
FROM netflix.episode_plays
WHERE
episode_id = #id_2
AND created_at >= #date_2 AND created_at <= DATE_ADD(#date_2 , INTERVAL 7 DAY)
GROUP BY 1) x2

mysql usage of 'not in' without column value

i have a table Transactions that looks similar to this:
id Type Field ObjectId NewValue
1 AddLink HasMember 4567 someDomain/someDirectory/1231
2 AddLink HasMember 4567 someDomain/someDirectory/1232
3 AddLink HasMember 4567 someDomain/someDirectory/1233
4 DeleteLink HasMember 4567 someDomain/someDirectory/1231
The numeric end of "NewValue" is what i am interested in.
In Detail, i need those records where i have a record where type is "AddLink" and where no newer record of type "DeleteLink" exists, i.e. the records with id = 2 or 3 (since 4 deletes 1)
The "ObjectId" as well as the numeric bit of "NewValue" both are IDs of entries of the "tickets" table, and i need the relevant tickets.
i tried this:
SELECT `Tickets`.* FROM `Transactions` AS `addedLinks`
LEFT JOIN `Tickets` ON RIGHT (`addedLinks`.`NewValue`, 4) = `Tickets`.`id`
WHERE `addedLinks`.`Type` = 'AddLink'
AND `addedLinks`.`Field` = 'Hasmember'
AND `addedLinks`.`ObjectId` = '4567'
AND NOT RIGHT (`addedLinks`.`NewValue`, 4) in (
SELECT `Tickets`.* FROM `Transactions` AS `deletedLinks`
LEFT JOIN `Tickets` ON RIGHT (`deletedLinks`.`NewValue`, 4) = `Tickets`.`id`
WHERE `deletedLinks`.`Type` = 'DeleteLink'
AND `addedLinks`.`id` < `deletedLinks`.`id`
AND `deletedLinks`.`Field` = 'Hasmember'
AND `deletedLinks`.`ObjectId` = '4567' )
This gives me:
SQL Error (1241): Operand should contain 1 column(s)
Unless i got something wrong, the problem is
RIGHT (`addedLinks`.`NewValue`, 4)
in the "AND NOT ... in()" statement.
Could anyone point me in the right direction here?
[EDIT]
Thanks to David K-J, the following works:
SELECT `Tickets`.* FROM `Transactions` AS `addedLinks`
LEFT JOIN `Tickets` ON RIGHT (`addedLinks`.`NewValue`, 4) = `Tickets`.`id`
WHERE `addedLinks`.`Type` = 'AddLink'
AND `addedLinks`.`Field` = 'Hasmember'
AND `addedLinks`.`ObjectId` = '5376'
AND NOT (RIGHT (`addedLinks`.`NewValue`, 4)) in (
SELECT `id` FROM `Transactions` AS `deletedLinks`
WHERE `deletedLinks`.`Type` = 'DeleteLink'
AND `addedLinks`.`id` < `deletedLinks`.`id`
AND `deletedLinks`.`Field` = 'Hasmember'
AND `deletedLinks`.`ObjectId` = '5376' )
but i don't understand why?
The problem here is your sub-select, as you are using it to provide the value of an IN clause, your sub-select should only select the id field, i.e. Transactions.* -> Transactions.id
So you end up with:
...
AND NOT (RIGHT (`addedLinks`.`NewValue`, 4)) IN
SELECT id FROM Transactions AS deletedLinks WHERE
...
The reason for this is that IN requires a list to compare with, so foo IN ( 1,2,3,4,5 ). If your subquery is selecting multiple fields, the resulting list is conceptually a list of lists (AoAs) like, [1, 'a'], [2, 'b'], [3, 'c'] and it's going to complain at you =)
Ah that's so complicated and with subquery... make it simpler, will be much faster
CREATE TEMPORARY TABLE `__del_max`
SELECT `NewValue`, MAX(`id`) as id FROM tickets
WHERE type = 'DeleteLink'
GROUP BY NewValue;
CREATE INDEX _nv ON __del_max(`NewValue`)
SELECT * FROM `tickets`
LEFT OUTER JOIN `__del_max` ON tickets.NewValue = __del_max.NewValue AND __del_max.id > tickets.id
WHERE __del_max.id IS NULL
You can have it in single, big join, but it'd be beneficial to have it in TMP table so you can add an index ;)

MYSQL inventory slots (add item to next slot free)

$slt = mysql_query("select Slot, ItemId, UserId, max(Slot) Slot
from useritems
group by UserId");
while ($sloot = mysql_fetch_assoc($slt))
{
Echo "<br>Items with biggest slots are: " . $sloot['ItemId'] . " from user " . $sloot['UserId']. "- in slot-". $sloot['Slot'];
}
This is the table
Idi Quantity ItemId UserId Slot ExpirationDate
Outputs the smallest Slots...Why?
1.I want to show me the biggest inventory slot from each user, so when i add a new item to his inventory i can add on next slot..From example user Paul has 5 items that ocupies slots 1,2,3,4,5 and the next item will be on slot 6.
2.When a user moves his items on slots 1,2,4,5,6 the next item added will be on slot 3
I did a lot of search but i can't find out myself:) PS:The game wich im making its just for fun..but maybe someday will be a great game:) (dreams,dreams :)) )
EDIT:
SQLFIDDLE is very good thank you:) it's exactly what i need to learn some SQL
Table useritems
useritems Table IMAGE
items Table
My Echo shows me that:
Id = 1 and it should be 3; user=4(good); slot=4(good)
Id = 1 and it should be 2; user=5(good); slot=2(good)
Maybe something like:
select userid, itemid, max(slot) from useritems where itemid is not null and quantity>0
group by userid, itemid
It'd be easier to help you if you share your table script with some data.
You can use: http://sqlfiddle.com/
EDIT:
What about this? Itemid=0 means the slot is free? So min(slot) will be the first free slot by user.
select userid, min(slot) from useritems where itemid=0 group by userid
I found it!:) i just forgot to select data where UserId=my_id but it shows me the corect output only if the respective user has more than 3 items...
$slt = mysql_query("SELECT * FROM useritems WHERE Slot=(select max(Slot) from useritems) and UserId='$id'");
and $id = $_SESSION['id'];
$slt = mysql_query("
SELECT * FROM `useritems`
WHERE UserId='$id' AND Slot=(select max(Slot)
from useritems where UserId='$id')
");`
EDIT2:I found the best way with all i was searching but i don`t knwo how to use WHERE clause userId='$id'
$slt2 = mysql_query("select l.Slot + 1 as start
from useritems as l
left outer join useritems as r on l.Slot + 1 = r.Slot
where r.Slot is null and l.UserId=4;
") or die(mysql_error()); `
With this query the item should be placed on Slot 2(wich is missing) but it`s dysplays Slot 6 (wich is the highest for the user with UserId=4)
Finally this is the last edit
$slt2 = mysql_query("SELECT Slot + 1 as start
FROM useritems mo
WHERE NOT EXISTS
(
SELECT NULL
FROM useritems mi
WHERE mi.Slot = mo.Slot + 1 AND mi.UserId = '$id'
)ORDER BY Slot LIMIT 1
") or die(mysql_error()); `
This is what i was searching.:X

Simplify sql query to obtain one line per id

I have a multi-table SQL query.
My need is: The query should I generate a single line by 'etablissement_id' ... and all information that I want to be back in the same query.
The problem is that this query is currently on a table where "establishment" may have "multiple photos" and suddenly, my query I currently generates several lines for the same id...
I want the following statement - LEFT JOINetablissementContenuMultimediaON etablissement.etablissement_id = etablissementContenuMultimedia.etablissementContenuMultimedia_etablissementId - only a single multimedia content is displayed. Is it possible to do this in the query below?
Here is the generated query.
SELECT DISTINCT `etablissement`. * , `etablissementContenuMultimedia`. * , `misEnAvant`. * , `quartier`. *
FROM `etablissement`
LEFT JOIN `etablissementContenuMultimedia` ON etablissement.etablissement_id = etablissementContenuMultimedia.etablissementContenuMultimedia_etablissementId
LEFT JOIN `misEnAvant` ON misEnAvant.misEnAvant_etablissementId = etablissement.etablissement_id
LEFT JOIN `quartier` ON quartier_id = etablissement_quartierId
WHERE (
misEnAvant_typeMisEnAvantId =1
AND (
misEnAvant_dateDebut <= CURRENT_DATE
AND CURRENT_DATE <= misEnAvant_dateFin
)
)
AND (
etablissement_isActive =1
)
ORDER BY `etablissement`.`etablissement_id` ASC
LIMIT 0 , 30
Here is the code used ZF
public function find (){
$db = Zend_Db_Table::getDefaultAdapter();
$oSelect = $db->select();
$oSelect->distinct()
->from('etablissement')
->joinLeft('etablissementContenuMultimedia', 'etablissement.etablissement_id = etablissementContenuMultimedia.etablissementContenuMultimedia_etablissementId')
->joinLeft('misEnAvant', 'misEnAvant.misEnAvant_etablissementId = etablissement.etablissement_id')
->joinLeft('quartier', 'quartier_id = etablissement_quartierId ')
->where ('misEnAvant_typeMisEnAvantId = 1 AND (misEnAvant_dateDebut <= CURRENT_DATE AND CURRENT_DATE <= misEnAvant_dateFin) ')
->where ('etablissement_isActive = 1')
->order(new Zend_Db_Expr('RAND()'));
$zSql = $oSelect->__toString();
if(isset($_GET['debug']) AND $_GET['debug'] == 1)
echo $zSql ;
//die();
$oResultEtablissement = $db->fetchAll($oSelect);
return $oResultEtablissement ;
}
Can you help me?
Sincerely,
If you are looking to have only one of the media displayed out of many regardless of which it may be then you can just add a limit to the query? After that you can tweak the query for ASCending or DESCending perhaps?
Is this query supposed to have images (or image as it were) for one establishment, or one image each for each active establishment? I see you have a limit 0,30 which means you're likely paginating....
If the result you want is a search for only one establishment, and the first image it comes to would work fine .. just use "limit 1" and you'll only get one result.
I took the time to redo the whole model of the database ... and now it works. There was no solution for a system as flawed

grouping by non-database field

How can I group a query result by a field that is not saved in the database.
For example I want to group the result by duration which is came from subtraction of start time and end time.
here is how i find out the duration
date1= $row_TicketRS['CloseDate'];
$date2 = $row_TicketRS['OpenDate'];
$diff = abs(strtotime($date2) - strtotime($date1));
$days = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24)/ (60*60*24));
if ( $days > 0)
{
$time1 = $row_TicketRS['OpenTime'];
$time2= $row_TicketRS['CloseTime'];
$t1=($time1);
$t2=($time2);
$end=('14:30');
$start=('07:30');
$n = $end- $t1;
$n2 = $t2- $start;
$Hours2 = floor(($n+$n2)+(($days-1)*7));
echo $Hours2.' Hours';
but know i do not know how to add it to the query
here is my query
$strQuery = "SELECT count(`ticket`.TicketID) as TotOutput, department.`DeptName` FROM `ticket`, `user`, department where ticket.OwnerID = user.EmpNo and user.`DepartmentID` = department.`DepartmentID` and OpenDate between'".$DateFrom."' And '".$DateTo."'"
It'd be better to have details, but a derived table/inline view would allow you to group by a computed value:
SELECT x.duration,
COUNT(*)
FROM (SELECT t.col,
t.end_time - t.start_time AS duration
FROM YOUR_TABLE t) x
GROUP BY x.duration
How about adding that computed value to the query with an alias like this:
SELECT some_fields, end - start AS duration FROM table ORDER BY duration
dont put alias for hidden column , use directly
exmaple:
SELECT id, FLOOR(value/100)
FROM tbl_name
GROUP BY id, FLOOR(value/100);
Reference
MySQL permits expressions in GROUP BY
clauses, so the alias is unnecessary: