Silverstripe 2.4 - 3.1 Upgrade - innerJoin 'unknown column' issue - mysql

I am trying to update a website from SS 2.4 to SS 3.1 and have been digging around the web on this issue for a while now.
The old code looks like this...
return DataObject::get('SupportItem', "SupportItemType = '$itemType' AND ProductPageID = $productID", null, 'INNER JOIN SupportItem_Products ON SupportItem_Products.SupportItemID = SupportItem.ID');
I am trying to switch out of the deprecated INNER JOIN and DataObject::get to the now current innerJoin and DataobjectName::get. This is what I have for the new code
$productID = $this->productToView->ID;
return SupportProductListingPage::get()->innerJoin('SupportItem_Products', '"SupportItem_Products"."SupportItemID" = "SupportItem"."ID"', null)->filter(array('SupportItemType'=>'$itemType', 'ProductPageID' => '$productID'));
It should be noted that the "SupportItemID" column exists in "SupportItem_Products" and the "ID" column exists in "SupportItem". However, "SupportItemID" does not exist in the "SupportItem" table.
I am receiving the below error when loading the page...
[User Error] Couldn't run query: SELECT DISTINCT count(DISTINCT "SiteTree"."ID") AS "0" FROM "SiteTree" LEFT JOIN "Page" ON "Page"."ID" = "SiteTree"."ID" INNER JOIN "SupportItem_Products" ON "SupportItem_Products"."SupportItemID" = "SupportItem"."ID" WHERE ("ProductPageID" = '$productID') AND ("SiteTree"."ClassName" IN ('SupportProductListingPage')) Unknown column 'SupportItem.ID' in 'on clause'
Can anyone help?

It seems that you are doing the join wrongly. The error message says that Unknown column 'SupportItem.ID' in 'on clause.
The original starts with:
DataObject::get('SupportItem', "SupportItemType = '
And yours starts with:
return SupportProductListingPage::get()->innerJoin('SupportItem_Products',
This says basically says join SupportItem_Products with SupportProductListingPage with a table that`s not part of your query at all.
This should be the innerjoin that you actually need (not tested of course):
SupportItem::get()->innerJoin('SupportItem_Products','"SupportItem_Products"."SupportItemID" = "SupportItem"."ID"');
With that the query should be right if the class relations are declared with the right has/belongs variables.
Also your filter bit wont probably work as expected:
filter(array('SupportItemType'=>'$itemType', 'ProductPageID' => '$productID'))
You are trying to use a variable inside single quotes. So either do
'SupportItemType'=>"$itemType" or 'SupportItemType'=>$itemType

Related

Flask - Convert request.args to dict

I've been trying to figure out how to pass the request.args to sqlalchemy filter.
I thought this should work:
model.query.filter(**request.args).all()
But it's throwing the error:
TypeError: <lambda>() got an unexpected keyword argument 'userid'
When userid or any other get arg is present.
According to this post - https://stackoverflow.com/questions/19506105/flask-sqlalchemy-query-with-keyword-as-variable - you can pass a dict to the filter function.
Any ideas what I'm doing wrong?
Many thanks :)
UPDATE: Many thanks to the poster below, however now it's throwing the following error:
ProgrammingError: (ProgrammingError) (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') ORDER BY tblclients.clientname' at line 3") 'SELECT favourites.id AS favourites_id, favourites.userid AS favourites_userid, favourites.clientid AS favourites_clientid, favourites.last_visit AS favourites_last_visit \nFROM favourites INNER JOIN tblclients ON tblclients.clientid = favourites.clientid \nWHERE favourites.userid = %s ORDER BY tblclients.clientname' ([u'41'],)
Any ideas?
First, you have to use filter_by, not filter.
Second, Flask request.args uses a MultiDict, a dict with the values inside a list, allowing more than one value for the same key, because the same field can appear more than once in a querystring. You got the error because the SQL query got the [u'41'] when it expected only '41'. You can use request.args.to_dict() to fix that:
model.query.filter_by(**request.args.to_dict()).all()
Use filter_by:
model.query.filter_by(**request.args).all()
filter is used like this: query.filter(Class.property == value) while filter_by is used like this: query.filter_by(property=value) (the first one being an expression and the latter one being a keyword argument).
filter_by(**request.args) doesn't work well if you have non-model query parameters, like page for pagination, otherwise you get errors like these:
InvalidRequestError: Entity '<class 'flask_sqlalchemy.JobSerializable'>' has no property 'page'
I use something like this which ignores query parameters not in the model:
builder = MyModel.query
for key in request.args:
if hasattr(MyModel, key):
vals = request.args.getlist(key) # one or many
builder = builder.filter(getattr(MyModel, key).in_(vals))
if not 'page' in request.args:
resources = builder.all()
else:
resources = builder.paginate(
int(request.args['page'])).items
Considering a model with a column called valid, something like this will work:
curl -XGET "http://0.0.0.0/mymodel_endpoint?page=1&valid=2&invalid=whatever&valid=1"
invalid will be ignored, and page is available for pagination and best of all, the following SQL will be generated: WHERE mymodel.valid in (1,2)
(get the above snippet for free if you use this boilerplate-saving module)
You can:
http://localhost:5000/filter-test?var=test
query_dict = request.args.to_dict()
print(query_dict)
{'var': 'test'}
print(query_dict['var'])
var

How to put a literal "?" in a Mysql query with bind variables

I have the following query:
select subclasses.id,participants_subclasses.participant_id
from subclasses
left outer join participants_subclasses on
participants_subclasses.participant_id = ?
and subclasses.id = participants_subclasses.subclass_id
where
subclasses.classification_id = ?
and subclasses.showhover
order by subclasses.seq,
IF(LEFT(subclasses.code, 1) = '<',
Extractvalue(subclasses.code, "//texts/text/content"),
subclasses.code)
The above query is processing a table where the code column sometimes has text and sometimes has XML with text inside a tag. The above query works. The side-effect is that a code value cannot start with a "<" which should be acceptable, but the order by would mistake it for XML content. The query below would be more specific and accurate:
select subclasses.id,participants_subclasses.participant_id
from subclasses
left outer join participants_subclasses on
participants_subclasses.participant_id = ?
and subclasses.id = participants_subclasses.subclass_id
where
subclasses.classification_id = ?
and subclasses.showhover
order by subclasses.seq,
IF(LEFT(subclasses.code, 5) = '<?xml',
Extractvalue(subclasses.code, "//texts/text/content"),
subclasses.code)
However this variation checking the XML header in the content fails with a "NameInput Array does not match ?" error in MySQL. It appears that the ? inside <?xml literal is being mistaken for a bind target. And I am passing 2 values to be bound - which again is correct.
So my question is - how do I get the <?xml literal to not be mistaken for a bind value target???
SOLVED
This turns out to be a bug in ADODB interface from phpLens, and NOT in MySQL itself. It exists in current version which is 5.17 for PHP5.

ActiveRecord + SQLite 3 behaves strangely

Using activerecord I made this query
AdImage.select("ad_images.id, ad_images.locale_id, ad_campaigns.click_url,
ad_campaigns.default_ad_image_id").joins("left outer join ad_campaigns on
ad_campaigns.id = ad_images.ad_campaign_id").where("ad_images.ad_campaign_id" => 1)
which generates the following sql query:
SELECT ad_images.id, ad_images.locale_id, ad_campaigns.click_url,
ad_campaigns.default_ad_image_id FROM "ad_images" left outer join ad_campaigns on
ad_campaigns.id = ad_images.ad_campaign_id WHERE "ad_images"."ad_campaign_id" = 1
and the result is the following:
=> [#<AdImage id: 22, click_url: "market://details?id=com.mobiata.flighttrack",
locale_id: 2>]
which is wrong.
So I used ActiveRecord::Base.connection.execute method to directly run the sql query:
ActiveRecord::Base.connection.execute("SELECT ad_campaigns.click_url, ad_images.id,
ad_images.locale_id, ad_campaigns.default_ad_image_id FROM ad_campaigns inner join
ad_images on ad_campaigns.id = ad_images.ad_campaign_id WHERE ad_images.ad_campaign_id = 1")
which returns the following:
[{"click_url"=>"market://details?id=com.mobiata.flighttrack", "id"=>22, "locale_id"=>2,
"default_ad_image_id"=>22, 0=>"market://details?id=com.mobiata.flighttrack", 1=>22,
2=>2, 3=>22}]
which has the strange repetition in it.
The only difference between the first and the second is "ad_images" vs ad_images in the table names.
My questions are:
1) I don't understand what makes this difference.
2) Why does the second query returns the garbage in SQLite3 while it doesn't happen in MySQL server?
I ended up with using "ActiveRecord::Base.connection.execute" instead of using Rails' ActiveRecord helpers. There doesn't seem to be other solutions to it.
It turns out that you should use the index values instead of double quoted column names when you call values. Otherwise you will bump into Type errors when used in production with MySQL.

Could not format node 'Value' for execution as SQL

I've stumbled upon a very strange LINQ to SQL behaviour / bug, that I just can't understand.
Let's take the following tables as an example: Customers -> Orders -> Details.
Each table is a subtable of the previous table, with a regular Primary-Foreign key relationship (1 to many).
If I execute the follow query:
var q = from c in context.Customers
select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();
Then I get an exception: Could not format node 'Value' for execution as SQL.
But the following queries do not throw an exception:
var q = from c in context.Customers
select (c.Orders.FirstOrDefault() ?? new Order()).OrderDateTime;
var q = from c in context.Customers
select (new Order()).Details.Count();
If I change my primary query as follows, I don't get an exception:
var q = from r in context.Customers.ToList()
select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();
Now I could understand that the last query works, because of the following logic:
Since there is no mapping of "new Order()" to SQL (I'm guessing here), I need to work on a local list instead.
But what I can't understand is why do the other two queries work?!?
I could potentially accept working with the "local" version of context.Customers.ToList(), but how to speed up the query?
For instance in the last query example, I'm pretty sure that each select will cause a new SQL query to be executed to retrieve the Orders. Now I could avoid lazy loading by using DataLoadOptions, but then I would be retrieving thousands of Order rows for no reason what so ever (I only need the first row)...
If I could execute the entire query in one SQL statement as I would like (my first query example), then the SQL engine itself would be smart enough to only retrieve one Order row for each Customer...
Is there perhaps a way to rewrite my original query in such a way that it will work as intended and be executed in one swoop by the SQL server?
EDIT:
(longer answer for Arturo)
The queries I provided are purely for example purposes. I know they are pointless in their own right, I just wanted to show a simplistic example.
The reason your example works is because you have avoided using "new Order()" all together. If I slightly modify your query to still use it, then I still get an exception:
var results = from e in (from c in db.Customers
select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
select new { e.CustomerID, Count = (e.FirstOrder != null ? e.FirstOrder : new Order()).Details().Count() }
Although this time the exception is slightly different - Could not format node 'ClientQuery' for execution as SQL.
If I use the ?? syntax instead of (x ? y : z) in that query, I get the same exception as I originaly got.
In my real-life query I don't need Count(), I need to select a couple of properties from the last table (which in my previous examples would be Details). Essentially I need to merge values of all the rows in each table. Inorder to give a more hefty example I'll first have to restate my tabels:
Models -> ModelCategoryVariations <- CategoryVariations -> CategoryVariationItems -> ModelModuleCategoryVariationItemAmounts -> ModelModuleCategoryVariationItemAmountValueChanges
The -> sign represents a 1 -> many relationship. Do notice that there is one sign that is the other way round...
My real query would go something like this:
var q = from m in context.Models
from mcv in m.ModelCategoryVariations
... // select some more tables
select new
{
ModelId = m.Id,
ModelName = m.Name,
CategoryVariationName = mcv.CategoryVariation.Name,
..., // values from other tables
Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
let mmcvia = cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId) ?? new ModelModuleCategoryVariationItemAmount()
select new
{
cvi.Id,
Amount = (mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.FirstOrDefault() ?? new ModelModuleCategoryVariationItemAmountValueChange()).Amount
... // select some more properties
}
}
This query blows up at the line let mmcvia =.
If I recall correctly, by using let mmcvia = new ModelModuleCategoryVariationItemAmount(), the query would blow up at the next ?? operand, which is at Amount =.
If I start the query with from m in context.Models.ToList() then everything works...
Why are you looking into only the individual count without selecting anything related to the customer.
You can do the following.
var results = from e in
(from c in db.Customers
select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
select new { e.CustomerID, DetailCount = e.FirstOrder != null ? e.FirstOrder.Details.Count() : 0 };
EDIT:
OK, I think you are over complicating your query.
The problem is that you are using the new WhateverObject() in your query, T-SQL doesnt know anyting about that; T-SQL knows about records in your hard drive, your are throwing something that doesn't exist. Only C# knows about that. DON'T USE new IN YOUR QUERIES OTHER THAN IN THE OUTER MOST SELECT STATEMENT because that is what C# will receive, and C# knows about creating new instances of objects.
Of course is going to work if you use ToList() method, but performance is affected because now you have your application host and sql server working together to give you the results and it might take many calls to your database instead of one.
Try this instead:
Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
let mmcvia =
cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(
mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId)
select new
{
cvi.Id,
Amount = mmcvia != null ?
(mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.Select(
x => x.Amount).FirstOrDefault() : 0
... // select some more properties
}
Using the Select() method allows you to get the first Amount or its default value. I used "0" as an example only, I dont know what is your default value for Amount.

MySQL Zend Framework - SQLSTATE[42000]: Syntax error or access violation: 1064

I've read every response I could fine on SO before posting this question. Although similar, none addressed my particular problem (or I didn't recognize them doing so).
I have a table class that extends Zend_Db_Table_Abstract. In the model, I'm trying to return a single row using a join() method and based on the table ID like this:
$getCategoryResults = $this->select();
$getCategoryResults->setIntegrityCheck(false)
->from(array('c'=> 'categories', '*'))
->join(array('e' => 'events'),'c.events_idEvent = e.idEvent', array())
->where("e.idEvent = ?", $idEvent);
when I echo the sql object, I get this:
SELECT `c`.* FROM `categories` AS `c`
INNER JOIN `events` AS `e` ON c.events_idEvent = e.idEvent
WHERE (e.idEvent = '1')
Oddly enough, if I use this format,
->where("e.idEvent = $idEvent");
my output is "WHERE (e.idEvent = 1)". The value is not enclosed in ticks, but either seems to work for MySQL. When I run the query in phpMyAdmin, I get this:
idCategory type displayOrder description localStartTime events_idEvent
1 individual 1 5k Run / Walk 2010-02-18 23:59:59 12 team 2 5k Team Category 2010-02-18 23:59:591 1
which is what I expected to see. But when I run my app in a browser, I get this ugliness:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT c.* FROM categories AS c INNER JOIN events AS e ON c.events_id' at line 1
I've checked every resource that I can think of. Hopefully, the combined awesomeness of SO uber-experts will make this my last stop. :D
Check out the second part of the error statement. Most likely it is regarding an access violation if the mysql elsewhere.
For reasons unknown to me, the app believed my $pageResult variable wasn't set. I discovered this after adding an isset() to the code like this:
try {
$getCategoryResults = $this->select();
$getCategoryResults->setIntegrityCheck(false)
->from(array('c'=> 'categories', '*'))
->join(array('e' => 'events'),'c.events_idEvent = e.idEvent', array())
->where("e.idEvent = ?", $idEvent);
if (isset($pageResult)) {
$pageResult .= $getCategoryResults;
}
else {
$pageResult = $getCategoryResults;
}
} catch (Exception $e) {
echo ( "Could not find matching categories for event id = $idEvent");
}
Problem went away which, of course, revealed the next problem lurking behind it. :D