Having seen this question I'm not sure why the following code for my DocDb instance isn't working:
var userApps = _docs.CreateDocumentQuery(UriFactory.CreateDocumentCollectionUri(Constants.Databases.Applications.ID, Constants.Databases.Applications.Collections.USER_APPS),
new SqlQuerySpec(#"SELECT r.appId FROM ROOT r WHERE r.userId = #userId", (#"#userId", userId).ToSqlParameters()))
.ToList()
.Select(s => (string)s.appId);
var query = _docs.CreateDocumentQuery<Document>(UriFactory.CreateDocumentCollectionUri(Constants.Databases.Applications.ID, Constants.Databases.Applications.Collections.APP_DEFINITIONS),
new SqlQuerySpec(#"SELECT r.id, r.appName FROM ROOT r WHERE r.appId IN (#userApps)", (#"#userApps", userApps.ToArray()).ToSqlParameters()),
new FeedOptions { EnableCrossPartitionQuery = true })
.AsDocumentQuery();
When I execute this, though I know the data should be returning me back a result set, it comes back empty every time.
Troubleshooting so far
Variants of .Select()
Return strings that I string.Join in to a comma-separated list.
Eg:
var userApps = string.Join(#",", _docs.CreateDocumentQuery(UriFactory.CreateDocumentCollectionUri(Constants.Databases.Applications.ID, Constants.Databases.Applications.Collections.USER_APPS),
new SqlQuerySpec(#"SELECT r.appId FROM ROOT r WHERE r.userId = #userId", (#"#userId", userId).ToSqlParameters()))
.ToList()
.Select(s => $#"'{s.appId}'");
Don't encapsulate IN parameter
Removing the () around the parameter spec in the query thinking maybe the SqlParameter spec was doing the array specification?
Eg: #"SELECT r.id, r.appName FROM ROOT r WHERE r.appId IN #userApps")
Ends up throwing "Syntax error, incorrect syntax near '#userApps'."
Validate via Azure Portal queries
Ran the (expected) SQL that this code should be running.
I get back my expected results without issue (ie: I know there is a result set for these queries as-written).
Debugger output for Query 1
AppIds are coming back from query 1.
Unsatisfactory workaround
Change query 2 to not be parameterized. Rather, inject the comma-separated list of IDs from query 1 in to it:
var userApps = string.Join(#",", _docs.CreateDocumentQuery(UriFactory.CreateDocumentCollectionUri(Constants.Databases.Applications.ID, Constants.Databases.Applications.Collections.USER_APPS),
new SqlQuerySpec(#"SELECT r.appId FROM ROOT r WHERE r.userId = #userId", (#"#userId", userId).ToSqlParameter()))
.ToList()
.Select(s => $#"'{s.appId}'"));
var query = _docs.CreateDocumentQuery<Document>(UriFactory.CreateDocumentCollectionUri(Constants.Databases.Applications.ID, Constants.Databases.Applications.Collections.APP_DEFINITIONS),
new SqlQuerySpec($#"SELECT r.id, r.appName FROM ROOT r WHERE r.appId IN ({userApps})"),
new FeedOptions { EnableCrossPartitionQuery = true })
.AsDocumentQuery();
Works perfectly but I'm not going to accept it as an answer to this problem as it goes against a couple decades-worth of SQL best practices and, frankly, shouldn't be a solution.
Here's my ToSqlParameters() extension method in case it's the culprit (this works everywhere else I've used it, though. Maybe something special is needed for arrays?):
public static SqlParameterCollection ToSqlParameters(this (string, object) parmDef) => new SqlParameterCollection(new[] { new SqlParameter(parmDef.Item1, parmDef.Item2) });
Thanks!
If you use a parameterized IN list, then it will be considered as a single value when the parameter is expanded.
For instance, for this query:
SELECT * FROM r WHERE r.item IN (#items) And #items is defined as "['val1', 'val2', 'val3']" will be interpreted as such:
SELECT * FROM r WHERE r.item IN (['val1', 'val2', 'val3'])
which basically means that you're comparing r.item to a single value that is an array of three elements (i.e. equivalent to r.item = ['val1', 'val2', 'val3']).
To compare to multiple items, you need to use a parameter for each value. Something like this: SELECT * FROM r WHERE r.item IN (#val1, #val2, #val3])
A more convenient way to write this query is to use ARRAY_CONTAINS instead and pass the array of items as a single parameter. So the above query will be written like this:
SELECT * FROM r WHERE ARRAY_CONTAINS(#items, r.item)
Related
I'm writing a custom M Language (PowerQuery in Excel) function to query a RESTful interface. This interface has a large number of optional parameters.
Starting with a simple case- I handle an optional limit passed as a simple (primitive) value as follows-
/*
* RESTful API Get from the named endpoint
*/
(endpoint as text, optional limit) =>
let
// query limit
// If limit is supplied as a number, it will be converted to text
// If limit is not supplied it will be set to the value "1000"
limit = if limit <> null then Text.From(limit) else "1000",
As the full API has many paramaters I wanted to use a Record to pass them to the function, but then I realised I don't know how to persuade M to write the default values into the parameter record.
I tried a couple of options.
Direct access-
(endpoint as text, optional params as record) =>
let
params[limit] = if (params[limit] = null) then "1000",
the result is a syntax error-'Token equal expected'
Merging the new value of limit as a Record with "&"
(endpoint as text, optional params as record) =>
let
params = params & if params[limit] = null then [limit = "1000"] else [],
result syntax error-'Token Literal expected'
I'm clearly missing something about the syntax rules for let statements, I know I need a variable = value assignment, and it looks as if putting anything other than a plain variable name on the LHS to write elements inside a structured value is not allowed, but i'm not sure how to acieve this otherwise?
Not sure exactly what you want here, but to create a List of Records where some Records have a default parameter and others do not, you could try something like:
(newParams as record) =>
let
default = [limit=1000, param2=2, param3=3],
final = Record.Combine({default, newParams})
in
final
With regard to Record.Combine, the beauty is that the right hand record will override the left hand record if both are present; and it will just add to it if nothing is present.
So something like:
let
Source = [limit=400, param3="x", param7=246],
conv = fnParams(Source)
in
conv
=>
Depending on the required format of your output string, you can build it using List.Accumulate. eg:
let
Source = [limit=400, param3="x", param7=246],
conv = fnParams(Source),
list = List.Accumulate(List.Zip({Record.FieldNames(conv), Record.ToList(conv)}), "",
(state,current) =>state & "&" & current{0} & "=" & Text.From(current{1}) )
in
list
=> &limit=400¶m2=2¶m3=x¶m7=246
I am basically trying to run a query that gives me all the Users that have purchased a product with a particular SKU. Essentially this SQL here:
SELECT u.FirstName, u.LastName, u.Email
FROM COM_OrderItem oi INNER JOIN COM_Order o ON oi.OrderItemOrderID = o.OrderID
INNER JOIN COM_Customer c ON o.OrderCustomerID = c.CustomerID
INNER JOIN CMS_User u ON c.CustomerUserID = u.UserID
WHERE oi.OrderItemSKUID = 1013
I was trying to use the ObjectQuery API to try and achieve this but have no idea how to do this. The documentation here does not cover the specific type of scenario I am looking for. I came up with this just to try and see if it works but I don't get the three columns I am after in the result:
var test = OrderItemInfoProvider
.GetOrderItems()
.Source(orderItems => orderItems.Join<OrderInfo>("OrderItemOrderID", "OrderID"))
.Source(orders => orders.Join<CustomerInfo>("OrderCustomerID", "CustomerID"))
.Source(customers => customers.Join<UserInfo>("CustomerUserID", "UserID"))
.WhereEquals("OrderItemSKUID", 1013).Columns("FirstName", "LastName", "Email").Result;
I know this is definitely wrong and I would like to know the right way to achieve this. Perhaps using ObjectQuery is not the right approach here or maybe I can somehow just use raw SQL. I simply don't know enough about Kentico to understand the best approach here.
Actually, the ObjectQuery you created is correct. I tested it and it is providing the correct results. Are you sure that there are indeed orders in the system, which contain a product with SKUID 1013 (you can check that in the COM_OrderItem database table)?
Also, how are you accessing the results? Iterating through the results should look like this:
foreach (DataRow row in test.Tables[0].Rows)
{
string firstName = ValidationHelper.GetString(row["FirstName"], "");
string lastName = ValidationHelper.GetString(row["LastName"], "");
string email = ValidationHelper.GetString(row["Email"], "");
}
I have been using dynamic query for a project.
Here is an scenario for which I am facing problem.
For a table xyz the column version is stored as varchar (I know it's a poor design, but it's too late to change now) and has values as 9,12.
For the query :
select max(version)
from xyz
where something = 'abc';
I am getting the output as 9 instead of 12.
The dynamic query for the same is:
ClassLoader classLoader = PortletBeanLocatorUtil.getBeanLocator(ClpSerializer.getServletContextName()).getClassLoader();
DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(xyz.class, classLoader);
dynamicQuery.setProjection(ProjectionFactoryUtil.max("version"));
dynamicQuery.add(PropertyFactoryUtil.forName("something").eq("abc"));
List<Object> list = xyzLocalServiceUtil.dynamicQuery(dynamicQuery);
The query which is giving the correct value is :
select max(cast(version as signed))
from xyz
where something = 'abc';
Now, I want it to be in the dynamic query, how can I do that?
I am using liferay-6.2-ce
Try using ProjectionFactoryUtil.sqlProjection method.
That method allows using functions that are executed by SQL engine.
For example, I am using following code in order to get the max length of a string column called 'content':
Projection maxSizeProjection = ProjectionFactoryUtil.sqlProjection(
"max(length(content)) as maxSize", new String[] {"maxSize"},
new Type[] {Type.BIG_DECIMAL});
The same thing can be done with dynamic query criterions using RestrictionsFactoryUtil.sqlRestriction in case you want to use a SQL function in a condition.
In your case try following code:
import com.liferay.portal.kernel.dao.orm.ProjectionFactoryUtil;
import com.liferay.portal.kernel.dao.orm.Type;
...
Projection maxSizeProjection = ProjectionFactoryUtil.sqlProjection(
"max(cast(version as signed)) as maxVersion",
new String[] {"maxVersion"}, new Type[] {Type.BIG_DECIMAL});
dynamicQuery.setProjection(maxSizeProjection);
today I migrated to Astyanax 1.56.42 and discovered, that withValues() on prepared statements doesn't seem to work properly with SQL SELECT...WHERE...IN ().
ArrayList<ByteBuffer> uids = new ArrayList<ByteBuffer>(fileUids.size());
for (int i = 0; i < fileUids.size(); i++) {
uids.add(ByteBuffer.wrap(UUIDtoByteArray(fileUids.get(i)), 0, 16));
}
result = KEYSPACE.prepareQuery(CF_FILESYSTEM)
.withCql("SELECT * from files where file_uid in (?);")
.asPreparedStatement()
.withValues(uids)
.execute();
If my ArrayList contains more than one entry, this results in error
SEVERE: com.netflix.astyanax.connectionpool.exceptions.BadRequestException: BadRequestException: [host=hostname(hostname):9160, latency=5(5), attempts=1]InvalidRequestException(why:there were 1 markers(?) in CQL but 2 bound variables)
What am I doing wrong? Is there any other way to handle a SQL SELECT...WHERE...IN () - statement or did I find a bug?
Best regards
Chris
As you mentioned because you are supplying a collection (ArrayList) to a single ? Astyanax throws an exception. I think you need to add a ? for each element you want to have inside the IN clause.
Say you want to have 2 ints stored in an ArrayList called arrayListObj the where clause, your statement looks like this:
SELECT & FROM users WHERE somevalue IN (arrayListObj);
Because you are suppling a collection, this cant work, so you will need multiple ?'s. I.e. you want :
SELECT name, occupation FROM users WHERE userid IN (arrayListObj.get(0), arrayListObj.get(1));
I couldn't find anything on the Astyanax wiki about using the IN clause with prepared statements.
I have a list AllIDs:
List<IAddress> AllIDs = new List<IAddress>();
I want to do substring operation on a member field AddressId based on a character "_".
I am using below LINQ query but getting compilation error:
AllIDs= AllIDs.Where(s => s.AddressId.Length >= s.AddressId.IndexOf("_"))
.Select(s => s.AddressId.Substring(s.AddressId.IndexOf("_")))
.ToList();
Error:
Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<MyCompany.Common.Users.IAddress>'
AllIDs is a list of IAddress but you are selecting a string. The compiler is complaining it cannot convert a List<string> to a List<IAddress>. Did you mean the following instead?
var substrings = AllIDs.Where(...).Select(...).ToList();
If you want to put them back into Address objects (assuming you have an Address class in addition to your IAddress interface), you can do something like this (assuming the constructor for Address is in place):
AllIDs = AllIDs.Where(...).Select(new Address(s.AddressID.Substring(s.AddressID.IndexOf("_")))).ToList();
You should also look at using query syntax for LINQ instead of method syntax, it can clean up and improve the readability of a lot of queries like this. Your original (unmodified) query is roughly equivalent to this:
var substrings = from a in AllIDs
let id = a.AddressId
let idx = id.IndexOf("_")
where id.Length >= idx
select id.Substring(idx);
Though this is really just a style thing, and this compiles to the same thing as the original. One slight difference is that you only have to call String.IndexOf() one per entry, instead of twice per entry. let is your friend.
Maybe this?
var boundable =
from s id in AllIDs
where s.AddressId.Length >= s.AddressId.IndexOf("_")
select new { AddressId = s.AddressId.Substring(s.AddressId.IndexOf("_")) };
boundable = boundable.ToList();