sql file cant validate multiple stored procedures - mysql

So some background I am setting up a cloudformation stack from our CICD pipeline. This stack sets up our entire database. We have migration files that get triggered, great! So far this is working well, my db builds all my views and tables, except it does not build my procedures and I've been banging my head against my desk trying to figure out why. I've looked at a ton of documentation, I really don't understand why what I have is not working when it looks EXACTLY as it should from the documentation and several other similar sites I found.
Now I believe I know the cause of the issue, I am currently using SQLPro which generates a SQL dump of my DB, which is used in my cicd process and gets triggered. I've ran the dump file in SQLPro and it works fine, but does not work when just trying to execute from an sql file, not in SQLpro, but in our cicd pipeline.
I feel like this must be a really simple problem, I've tried every flavor I can think of trying to format it correctly and I really have no idea why it's not working. I've been using an SQL validator online, and if I put ONE stored procedure it says the syntax is fine, but when I have more than 1 it's not.
Here's 2 procedures as an example
CREATE PROCEDURE cropsByGrowerIdProc(IN userID Text)
BEGIN
SELECT DISTINCT
g.growerID AS growerID,
g.firstName AS firstName,
g.lastName AS lastName,
z.zoneID AS zoneID,
g.farmName AS farmName,
g.email AS email,
g.phoneNumber AS phoneNumber,
u.userID AS userID,
g.source AS source,
json_object('cropID',c.cropID,'plantedAcreage',c.acres) AS crops
FROM GrowerDICT g
inner join UserGrowerTransaction u on (u.growerID = g.growerID AND u.userID = userID)
left join CropTransaction c on (c.growerID = g.growerID)
inner join ZoneTransaction z on(z.growerID = g.growerID)
where (u.userID = userID AND g.isActive IS TRUE);
END;
CREATE PROCEDURE cropsByGrowerIdProc_OLD(IN userID Text)
BEGIN
SELECT DISTINCT
g.growerID AS growerID,
g.firstName AS firstName,
g.lastName AS lastName,
z.zoneID AS zoneID,
g.farmName AS farmName,
g.email AS email,
g.phoneNumber AS phoneNumber,
u.userID AS userID,
g.source AS source,
json_object('cropID',c.cropID,'plantedAcreage',c.acres) AS crops
FROM GrowerDICT g
inner join UserGrowerTransaction u on (u.growerID = g.growerID)
left join CropTransaction c on (c.growerID = g.growerID)
inner join ZoneTransaction z on(z.growerID = g.growerID)
where (u.userID = userID AND g.isActive IS TRUE AND c.isActive IS TRUE);
END;
Yes, I've tried DELIMITER's in any spot I can it just always says you have an error in your sql syntax near CREATE PROCEDURE cropsByGrowerIdProc_OLD
So again, this is just what I have now, I have tried many other things like delimiters, removing the begin/end, removing the drop procedure if exists (couldnt get that to work either, and errored out on the first create stored procedure line instead of the second without the drop procedure calls)

Related

Mysql Update select subquery vs trigger loop

Hello guys.
I've an issue with a simple query.
Here we go, that's the code.
UPDATE user_resources AS ures
LEFT JOIN user_buildings as ub
ON ub.city_id = ures.city_id
INNER JOIN building_consumption AS bcons
ON bcons.resource_id = ures.resource_id
SET ures.quantity = ures.quantity - abs(FORMULA HERE that requires
building level and consumption at lvl 1 [default])
WHERE
(SELECT COUNT(id) FROM building_consumption AS bc2
WHERE bc2.building_id=ub.building_id) =
(SELECT COUNT(bc3.id) FROM building_consumption AS bc3
LEFT JOIN tmp_user_resources AS ures
ON ures.resource_id = bc3.resource_id
WHERE ures.city_id = ub.city_id
AND bc3.building_id=ub.building_id
AND bc3.quantity>0
AND IFNULL(ures.quantity, 0) - abs(FORMULA AGAIN);
I'll try to explain a bit.
As you can imagine, this is for a game.
Users (players) can has different buildings in different cities.
tab user_buildings
|id, city_id, buildings_id, level, usage|
A building can produce different resources
tab building_production
|id, building_id, resource_id, quantity_h|
but it can consume some resources too:
tab building_consumption
|id, building_id, resource_id, quantity_h|
Obviously a building cannot produce if there are not enough resources to consume for his job.
That's why I'm trying to compare WHERE SELECT COUNT how many resources it has to consume AND how many resources it can actually consume.
Mysql does NOT ALLOW to subquery same table inside an UPDATE stmt.
Using a cursor + loop is too much slow. I prefer to use different solution.
Temp table could be a solution but my problem now is how to update the temp table without triggers? (UPDATE + SELECT fires triggers and to avoid endless loops mysql block the query, and i can't pause/resume triggers because
IF ((#TRIGGER_CHECKS = FALSE)
OR (#TRIGGER_BEFORE_INSERT_CHECKS = FALSE))
AND (USER() = 'root#localhost')
THEN
LEAVE thisTrigger;
END IF;
is inside the trigger itself).
I am open to all your suggestions!
Thanks
P.S. The code must be inside a scheduled event.

MySQL Different results from same query/data

I have two servers running MySQL. Both are on windows. One, is my local machime (Windows 7, MySQL 5.6.25, 32bit) and the other is my production webserver (Windows 2012, MySQL 5.7.11-log, 64bit (that's what show variables showed me).
The data is identical between the two. I backed the data up from the windows 7 (using MySQL Workbench) and restored it on the 2012 machine.
I am running a query on both machine but I am getting different results. I have two tables, projects and projectsnotes with a 1:m relationship between them related on projects.id to projectsnotes.idProject. Each note is marked with a date (dComment). The goal of the query is to retrieve project information and the latest comment only.
Here's the query:
select space(1) as cAction,
p.id,
p.iNum,
p.cStatus,
p.cName,
p.cDesc,
ifnull(pl.cNickName, 'UNASSIGNED') as cProjectLeader,
IFNULL(concat (
date_format(pn.dComment, '%Y-%m-%d'),
': ',
pn.cComment
), '') as cComment,
date_format(p.dRequested, '%Y-%m-%d') as dRequested,
date_format(p.dRequired, '%Y-%m-%d') as dRequired,
format(p.nPercentComplete, 2) as nPercentComplete,
p.tLastUpdated,
p.bCompleted,
p.idProjectLeader
from projects p
left outer join projectleaders pl on p.idProjectLeader = pl.id
left outer join (
select idProject,
dComment,
cComment
from projectnotes
order by dComment desc,
tLastUpdated desc
) pn on p.id = pn.idProject
where p.cInstallCode = 'ITM'
and cStatus in ('Pending', 'Active', 'On Hold', 'Completed', 'Cancelled')
and bCompleted = 0
group by iNum
order by iNum;
Now, here's the weird part. When I run this on my Windows 7 machine, I get the right value for cComment. Specifically:
2017-03-28: Text from note replace
That is the latest note. When I run it on the 2012 server:
2016-05-17: Text from this note replaced too
If I run the subquery alone on the 2012 server, I get the right values (namely, a list of all the notes in the reverse order.
Oh, and this note is neither the first nor the last in the notes for this project.
So I am honestly wondering what is going on. Any thoughts on this would be greatly appreciated.
Thanks in advance.
This is expected behavior.
select ...
from projects p
left outer join projectleaders pl on p.idProjectLeader = pl.id
left outer join (...) pn on p.id = pn.idProject
where ...
group by iNum
order by iNum;
Due to MySQL's peculiar handling of GROUP BY, it will not report an error on this query. However, you must keep in mind that, since you use no aggregates, and the GROUP BY will eliminate lots of rows, the rows that are kept in the final result set are determined by rather obscure criteria...
For example:
SELECT a,b FROM t GROUP BY a
Which b will be returned? In some MySQL versions, this will be the first value of b that is found in table t. If table t is ordered in a certain way, this can be exploited. But I would definitely not trust that behavior to stay unchanged between versions... Also, remember MySQL is free to change your join order...
OK. I think I have a solution to this. Instead of doing it with a join I wrote a function that returned the value I needed as follows:
DROP FUNCTION if exists f_lastprojectnote;
DELIMITER $$
CREATE FUNCTION f_lastprojectnote(tidProject varchar(36))
RETURNS varchar(1000) DETERMINISTIC
BEGIN
DECLARE cRetVal VARCHAR(1000);
SELECT concat(date_format(pn.dComment, '%Y-%m-%d'), ': ', pn.cComment) INTO cRetVal
FROM projectnotes pn
WHERE idProject = tidProject
ORDER BY dComment DESC, tLastUpdated DESC
LIMIT 1;
RETURN cRetVal;
END$$
DELIMITER ;
It works...

MySQL can only use 61 tables in a join; How can I bypass this error for the following query?

I get a MySQL Error saying, I cannot use more than 61 tables in a join. I need to avoid this error. How do I do it? Please Help.
select
view_pdg_institutes.user_id as User_ID,
view_pdg_institutes.institute_id as Teacher_ID,
view_pdg_institutes.institute_name as Institute_Name,
view_pdg_institutes.user_email as Email,
view_pdg_institutes.contact_person_name as Contact_Person,
view_pdg_institutes.alternative_contact_no as Alternative_Mobile_No,
view_pdg_institutes.primary_contact_no as Mobile_No,
view_pdg_institutes.correspondance_address as Address,
view_pdg_institutes.other_communication_mode as Preferred_Contact_Mode,
view_pdg_institutes.size_of_faculty as Size_of_Faculty,
view_pdg_institutes.operation_hours_from as Operation_Hours_From,
view_pdg_institutes.operation_hours_to as Operation_Hours_To,
view_pdg_institutes.teaching_xp as Teaching_Experience,
view_pdg_institutes.installment_allowed as Installment_Allowed,
view_pdg_institutes.about_fees_structure as About_Fees_Structure,
view_pdg_institutes.no_of_demo_class as No_of_Demo_Classes,
view_pdg_institutes.demo_allowed as Demo_Allowed,
view_pdg_institutes.price_per_demo_class as Price_Per_Demo_Class,
view_pdg_tuition_batch.tuition_batch_id as Batch_ID,
view_pdg_batch_subject.subject_name as Subject_Name,
view_pdg_batch_subject.subject_type as Subject_Type,
view_pdg_batch_subject.academic_board as Academic_Board,
view_pdg_batch_fees.fees_type as Fees_Type,
view_pdg_batch_fees.fees_amount as Fees_Amount,
view_pdg_tuition_batch.course_days as Course_Days,
view_pdg_tuition_batch.days_per_week as Days_Per_Week,
view_pdg_tuition_batch.class_duration as Class_Duration,
view_pdg_tuition_batch.class_type as Class_Type,
view_pdg_tuition_batch.course_length as Course_Length,
view_pdg_tuition_batch.course_length_type as Course_Length_Type,
view_pdg_tuition_batch.no_of_locations as No_of_Locations,
view_pdg_tuition_batch.class_capacity_id as Class_Capacity_ID,
view_pdg_tutor_location.locality as Locality,
view_pdg_tutor_location.address as Address,
view_pdg_batch_class_timing.class_timing as Class_Timing
from view_pdg_tuition_batch
left join view_pdg_institutes on (view_pdg_tuition_batch.tutor_institute_user_id = view_pdg_institutes.user_id)
left join view_pdg_batch_subject on (view_pdg_batch_subject.tuition_batch_id = view_pdg_tuition_batch.tuition_batch_id)
left join view_pdg_batch_fees on (view_pdg_batch_fees.tuition_batch_id = view_pdg_tuition_batch.tuition_batch_id)
left join view_pdg_batch_class_timing on (view_pdg_batch_class_timing.tuition_batch_id = view_pdg_tuition_batch.tuition_batch_id)
left join view_pdg_tutor_location on (view_pdg_tutor_location.tuition_batch_id = view_pdg_tuition_batch.tuition_batch_id)
group by view_pdg_tuition_batch.tuition_batch_id;
I need a solution that would not require changing the current approach of writing the query.
I don't think it's possible to do what you're asking without some elaborate changes in the way you store and query data. You can
denormalize your DB to store JSON data;
create materialized views, emulating them via triggers, because they're absent in MySQL;
use temporary tables;
join partial selects by hand at the call site;
compile MySQL with another join limit;
use proper SQL engine like Postgres, that doesn't suffer from such stupid things.
Insert the contents of each view into its own temporary table. Then do the same query with the temporary table names substituted for the original view names.

design a stored procedure with multiple parameters and HAVING LIKE

I' m trying to write a stored procedure that will search a fairly simple database with
a USER table (user_id,name,...)
a USER_TYPE table (user_id,type_id) - multi to multi
a TYPE table (type_id,type_name)
a USER_GAME (user_id,game_id) -multi to multi
a GAME table (game_id,game_name)
A same user can have several games. Now, I want to be able to get the user according to a particular type and also according to a/some particular game(s), so that for example I can get all the user with, say type1, and with the games, say game2 and game5. I think I can get round the problem of several game names by passing them as a string parameter and do some kind of HAVING LIKE condition (I call get_user_spec('type1' , 'game3,game5') for example).
So far I get to that point:
CREATE DEFINER=`root`#`localhost` PROCEDURE `get_user_spec`(
IN inTypeName VARCHAR(50),
IN inGameName VARCHAR(150)
)
BEGIN
PREPARE statement FROM
"SELECT u.user_id,t.type_name,GROUP_CONCAT(g.game_name) AS game
FROM user u
INNER JOIN user_type ut
ON u.user_id=ut.user_id
INNER JOIN type t
ON ut.type_id=t.type_id
LEFT JOIN user_game ug
ON u.user_id=ug.user_id
LEFT JOIN game g
ON ug.game_id=g.game_id
WHERE t.type_name=?
GROUP BY u.user_id
HAVING game LIKE CONCAT('%',?,'%')
ORDER BY u.user_id";
SET #p1=inTypeName;
SET #p2=inGameName;
EXECUTE statement USING #p1,#p2;
END
But my real problem is that if I don't pass any game name, I then want to get all users with type1 (I then call get_user_spec('type1' , NULL). But I am then not getting anything as the procedure sees
HAVING game LIKE CONCAT('%',NULL,'%').
I hope that was clear enough. If anybody has any suggestions to get around that problem, I would be very grateful.
Thank you very much.
Change this line:
EXECUTE statement USING #p1,#p2;
to
EXECUTE statement USING #p1, ifnull(#p2, '');
This will cause the LIKE expression to be just '%%', which means "match everything"

subsonic 2 join on multiple columns

I want to transfer the following statement to SubSonic 2.2
SELECT b.*
FROM tableA a
INNER JOIN tableB b
ON (a.year = b.year AND a.month = b.monath AND a.userid = b.userid);
My problem is that SubSonic's SqlQuery.LeftInnerJoin() command has no overload which takes more than one column.
Since any join can be rewritten only using where clauses, I did the following in my sql:
SELECT b.*
FROM tableA a, tableB b
WHERE a.year = b.year
AND a.month = b.month
AND a.userid = b.userid
which should deliver the same result (in fact, at least for mysql, there is logically absolutely no difference between these statements).
But I also got stuck transfering this to subsonic because the "IsEqualTo(...)" member is smart enough to figure out that my parameter is a string and puts it into quotes.
DB.Select("TableB.*")
.From<TableA>()
.From<TableB>()
.Where(TableA.YearColumn).IsEqualTo("TableB.Year")
.And(TableA.MonthColumn).IsEqualTo("TableB.Month")
.And(TableA.UseridColumn).IsEqualTo("TableB.UserId")
(I tried different ways in setting the IsEqualTo parameter)
IsEqualTo(TableB.YearColumn)
IsEqualTo(TableB.YearColumn.QualifiedName)
Either the parameter is interpreted as
TableA.Year = 'TableB.Year'
or I get a SqlQueryException.
Can somebody tell me how to do this query with subsonic (Either the first - with JOIN or the second one)? Thanks
With SubSonic 2 out of the box you can't.
This said, you have the following alternatives:
Extend SubSonic
If you're already familiar with SubSonic, you may consider to add multi-column joins to SubSonic itself.
Use views, Stored procedures, table functions
If you do not want to mess with SubSonics code, use views, stored procedures and/or table functions within sql server. SubSonic makes it easy to access data from views and stored procedures.
Use an InlineQuery
InlineQuery allows you to execute any sql - if it is an option to have bare sql in your code.
Ugly workaround with InlineQuery
If you absolutely want to create your query with SubSonic, you can try this:
SqlQuery q = DB.Select()
.From<TableA>()
.CrossJoin<TableB>()
.Where(TableA.YearColumn).IsEqualTo(0)
.And(TableA.MonthColumn).IsEqualTo(0)
.And(TableA.UseridColumn).IsEqualTo(0);
Build the SQL statement, and replace the parameter names:
string s = q.BuildSqlStatement();
s = s.Replace(q.Constraints[0].ParameterName, TableB.YearColumn.QualifiedName);
s = s.Replace(q.Constraints[1].ParameterName, TableB.MonthColumn.QualifiedName);
s = s.Replace(q.Constraints[2].ParameterName, TableB.UserIdColumn.QualifiedName);
Then use s with an InlineQuery.