CFQuery 'WHERE' from results of another query? - coldfusion

I'll explain the 'real life' application of this so it's easier to understand.
I'm working on an eCommerce app that has a category structure. It starts at the top level and gradually moves down through subcategories. For example Home > Electronics > TVs > Plasma
I'm using a single page for this, showing the 'home' page if no category is defined, the subcategories if there are any, and finally the products if there are no subcategories.
This all works fine, however when I get to the 2nd part - displaying subcategories, the page is a little empty. Therefore, I'd like to display a selection of products that span all of the subcategories applicable.
This is where I'm struggling - in most cases, there will be a few subcategories. However, I'm not sure how to structure the 'where' query using the results of the previous query (code snippets below for reference).
I don't believe QofQ would be worth exploring, and I've made a vain attempt at doing something with substrings, without success.
Any pointers much appreciated!
<cfquery name="getcategories">
SELECT p.ID AS CategoryID, p.Cat_Name as CategoryName, p.Cat_Shortname, c.ID AS SubCategoryID, c.Cat_Name as SubCategoryName, c.Cat_Shortname AS SubCatShortname
FROM product_categories p LEFT JOIN product_categories c ON p.ID = c.SubcategoryOf
WHERE p.SubcategoryOf = 0
</cfquery>
<cfif IsDefined('url.cat')>
<!--- Look for additional subcategories --->
<cfquery name="getsubcategories">
SELECT *
FROM product_categories
WHERE Subcategoryof='#url.cat#'
</cfquery>
<cfquery name="getproducts">
SELECT *
FROM products
WHERE categoryid='#url.cat#'
ORDER BY RAND()
</cfquery>
</cfif>

Assuming your products table contains a subcategoryID of some kind you can use the following to get a list of sub category IDs from the query getsubcategories:
<cfset subCategoryIDs = valueList(getsubcategories.subCategoryID) >
This will give you a list of all subCategoryIDs. You can the feed this into the getproducts query like so:
<cfquery name="getproducts">
SELECT *
FROM products
WHERE subCategoryID in (<cfqueryparam cfsqltype="cf_sql_integer" value="#subCategoryIDs#" list="true">)
ORDER BY RAND()
</cfquery>
You should always cfqueryparam your query parameters.

If i understand your database structure, this query should return all products in all subcategories.
<cfquery name="getallproducts">
SELECT *
FROM products p LEFT JOIN product_categories pc ON p.categoryID = pc.ID
WHERE pc.Subcategoryof= <cfqueryparam cfsqltype="cf_sql_integer" value="#url.cat#">
</cfquery>
note: you really do want to use cfqueryparam here.

Related

how can I check if a record already in database before submitting form in ColdFusion applications

<cfquery datasource = "myDb" name = "compare">
select *
from users
where cnic = #form.cnic#
</cfquery>
<cfif compare.cnic eq form.cnic>
<p> *CNIC already Exists </p>
</cfif>
I think you're misstating the problem. It should be more like,
"How can I show a form to add a record I know is not in the database?"
Please clarify if that is not the case.
Based on your code, I assume there's been a form submission from another page already. You're running a query to see if there is a record in the users table where cnic = #form.cnic#. If there was no previous form submission, then form.cnic wouldn't exist.
<cfquery datasource="myDb" name="compare">
select *
from users
where
cnic = #form.cnic#
</cfquery>
So when this page loads, you've done your "check if a record is already in the database" with the query named compare. Now all you need to do is check if there are 0 records in the query.
<cfif compare.recordcount EQ 0>
<!--- Display form here. ---->
</cfif>
If the query returns any records, then there is at least one record in the database, so no need to show the form or allow it to be submitted.
You can use bellow code in Jquery ajax calling
<cfquery datasource = "myDb" name = "compare">
select *
from users
where cnic = #form.cnic#
</cfquery>
<cfif compare.recordcount GT 0>
<p> *CNIC already Exists </p>
</cfif>
Depending on your database, too, there are other options. MySQL has some features for INSERT ON DUPLICATE KEY UPDATE (https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html)
In MSSQL you can do:
IF NOT EXISTS (SELECT 1 FROM [users] u WITH (NOLOCK) WHERE cnic = <cfqueryparam cfsqltype="CF_SQL_VARCHAR" value="#form.cnic#")
INSERT INTO [users].....
(My cfsqltype syntax might not be 100% correct, but always use . Always.
Those might give you some other ways to handle your scenario.

Move a row to the beginning of a query

I have a recordset of messages, some of which are are from a group named "ADMIN". I want all the messages from the "ADMINS" placed at the top of the query object but I can't re-order them within the original query -- I have to do it after I already have the query object. I have a way to see which rows are from "ADMINS" but I'm not sure the best way to extract them from the query object and put them at the top.
<!--- this is the query object of all the messages --->
<cfset messageData = application.message.getMessages(inboxID)>
<!--- this returns a list of which users inside the recordset are ADMINS --->
<cfset getAdmins = application.message.getAdmins(valueList(messageData.useridfk)) />
Now I want to take all the UserIDs returned in #getAdmins#, remove the messages that are theirs in #messageData# and put them at the top of #messageData# so I can output the results. Is this a QoQ or is there a better way?
I can't really get my head around the QoQ, but I'm taking a stab at it. IS there a better way then doing THREE different QoQs?
<!--- this gets all the rows from Admins --->
<cfquery name="getAdminAnswers" dbtype="query">
SELECT *
FROM messageData
WHERE useridfk in ('#getAdmins.id#')
ORDER BY UpvotesNum DESC, posted DESC
</cfquery>
<!--- this gets all the rows from non-RDs --->
<cfquery name="getNonAdminAnswers" dbtype="query">
SELECT *
FROM messageData
WHERE useridfk NOT IN ('#getAdmins.id#')
ORDER BY UpvotesNum DESC, posted DESC
</cfquery>
<!--- join the queries with a UNION in a QoQ --->
<cfquery dbtype="query" name="data">
SELECT * FROM getAdminAnswers
UNION
SELECT * FROM getNonAdminAnswers
</cfquery>
Answer:
Got it: Like this:
<!--- this combines two queries into one --->
<cfquery name="data" dbtype="query">
SELECT *, 1 sortCol
FROM messageData
WHERE useridfk in ('#variables.getAdmins.id#')
UNION
SELECT *, 2 sortCol
FROM messageData
WHERE useridfk NOT IN ('#variables.getAdmins.id#')
ORDER BY sortCol, UpvotesNum DESC, posted DESC
</cfquery>
I can't see that there's a more expedient way of doing this without using QoQ, no. It's kinda what it's for: these exceptional circumstances where the app needs both the original data as from the DB, and a variation of it as well.
Can you say
SELECT *,
(CASE WHEN USERIDFK IN ('#getadmins.id#') THEN 1 ELSE 0 END) as mRank
FROM MessagesTableName
ORDER BY mRank DESC, UpvotesNum DESC, posted DESC
That would be the SQL Server Syntax anyway, I'm not sure what SQL flavor you're using.
I realize that all three queries are QoQs, but with just one query to get your admin list, this direct query to the db might work.
(Previously, I was unsure if you could use CASE in QoQ. You can't, so if this works at all, it would have to a direct query to the database.)

how to store query data in variables in ColdFusion for use later?

I am trying to retrieve and store ID's for each item retrieved from my table, so I can use these ID's later. I tried nesting the queries, but this didn't, work. Here is my first query:
<CFQUERY datasource="MyDSN" name="MAIN2"> SELECT * from order_items where orderID= #orderID#</CFQUERY>
Now, if I output this query it displays, 1 and 117 which are the two ID's I need.
My next query is:
<CFQUERY datasource="MyDSN" name="MAIN3">select c.catalogueID,
c.product_name,
c.product_price,
c.description,
p.productID
from products p
join product_catalogue c on c.catalogueid = p.catalogueid
where p.productid = "#productID#"</CFQUERY>
But it is telling me that productID is not defined, it is obviously empty. I am just getting started using ColdFusion, so I am not sure the best way to store the values I need so I use them again. I also need to loop the second query to run for each ID 1 and 117, so twice.
Any suggestions on how to accomplish this would be greatly appreciated.
Thanks
My basic rule is that if I find myself using queries to create other queries or looping over a query to execute other queries; it is time to consider combining the queries.
I'm not sure what field you are using in the MAIN2 query to feed the MAIN3 query. So, I put in "productID" in the query below. You may have to change it to fit your field name.
<CFQUERY datasource="MyDSN" name="MAIN3">select c.catalogueID,
c.product_name,
c.product_price,
c.description,
p.productID
from products p
join product_catalogue c on c.catalogueid = p.catalogueid
where p.productid IN (SELECT DISTINCT productID from order_items where orderID= <cfqueryparam value="#orderID#" cfsqltype="CF_SQL_INTEGER">)
</CFQUERY>
You could also change this query to utilize a "join" to connect [order_items] to the query.
Lastly, you should use the <cfqueryparam> tag for the where clauses; this helps protect your query from sql injection attacks.
Whenever I'm caching data for use later, I tend to ask myself how I'll be using that data, and whether it belongs in another data type rather than query.
For instance, if I'm wanting a bunch of data that I'm likely to access via ID, I can create a structure where the key is the ID, and the data is another structure of a dataset. Then I'll save this structure in application scope and only refresh it when it needs to be. This is zippy fast and so much easier to grab with
rather than querying for it every time. This is especially useful when the query that creates the original data set is kind of a resource hog with lots of joins, sub-queries, magical cross-db stored procedures, but the datasets returns are actually fairly small.
So creating your products structure would look something like this:
<CFQUERY datasource="MyDSN" name="MAIN3">
SELECT
c.catalogueID,
c.product_name,
c.product_price,
c.description,
p.productID
FROM products p
JOIN product_catalogue c
ON c.catalogueid = p.catalogueid
WHERE p.productid = <cfqueryparam value="#ProductID#" cfsqltype="cf_sql_integer">
</CFQUERY>
<cfset products = structNew() />
<cfset item = structNew() />
<cfloop query="MAIN3">
<cfif NOT structKeyExists(products, productID)>
<cfset item = structNew() />
<cfset item.catalogueID = catalogueID />
<cfset item.product_name = product_name />
<cfset item.product_price = product_price />
<cfset item.description = description />
<cfset products[productID] = structCopy(item) />
</cfif>
</cfloop>
<cfset application.products = structCopy(products) />

Using array data to drill a query

I am learning about arrays and structs, and putting together a shopping cart as a project. I am currently creating a recently viewed products module for the cart. When a user views a product i collect the product id into an array,
What is the best way to use this info to query my database, my query would be
<cfquery name="getRecent" datasource="#application.dsn#">
SELECT uid_product,txt_product_name
FROM tbl_products
</cfquery>
I thought of outputting the array as a list and using the IN operator, but not sure if that is the right way of doing it, seems a bit cumbersome? Can I use a query of queries using the array data? If so any pointers?
If you just maintain a list of productIDs then you don't need to use arrays or structs, you can just store them as a simple list and query them as follows:
<cfset recentProducts = "4,5" />
<cfquery name="getRecent" datasource="#application.dsn#">
SELECT uid_product,txt_product_name
FROM tbl_products
where productID IN (<cfqueryparam cfsqltype="cf_sql_integer" value="#recentProducts#" list="true" />)
</cfquery>
Firstly, unless you're going to put additional things into the struct, I'd say having a struct just to hold ProductID seems excessive. I'd just have the Array with the productID as each element, no struct.
So your array is structured like:
[
{ ProductID= 4 },
{ ProductID= 5 },
...
]
I'd suggest you just do:
[4,5]
This will make the next step easier. Right now I'd say you have to loop over your array, adding each productID into a list that you can then use in your query. Then modify your original query to use that list (remember and use cfqueryparam).
<cfset lstIDs = "">
<cfloop index="productStruct" array="#productsArray#">
<cfset lstIDs = listAppend(lstIDs, productStruct.ProductID)>
</cfloop>
<cfquery name="getRecent" datasource="#application.dsn#">
SELECT uid_product,txt_product_name
FROM tbl_products
WHERE productID IN <cfqueryparam value="#lstIDs#" cfsqltype="CF_SQL_INTEGER" list="yes">
</cfquery>
Or, if you simplify your array, just do ArrayToList to achieve the same thing.
<cfquery name="getRecent" datasource="#application.dsn#">
SELECT uid_product,txt_product_name
FROM tbl_products
WHERE productID IN <cfqueryparam value="#ArrayToList(productsArray)#" cfsqltype="CF_SQL_INTEGER" list="yes">
</cfquery>

can't define id ColdFusion

here is my ColdFusion code:
Example1:
<cfquery name="GET_BRAND" datasource="#dsn1#">
SELECT PRODUCT_CATID
FROM PRODUCT_CAT
WHERE PRODUCT_CATID = PRODUCT_CATID
</cfquery>
#get_brand.product_catid#
But it shows all the time number 1, i just can't understand why, and how do i make it work properly, this code should have defined the brand_id, but instead shows 1.
The system is Workcube.
Here is my example for getting from the static product's id, its dynamic price:
Example 2:
<cfset product_id = 630>
<cfquery name="price_standart" datasource="#dsn3#">
SELECT
PRICE_STANDART.PRICE PRICE
FROM
PRICE_STANDART
WHERE
PRICE_STANDART.PRODUCT_ID =
<cfqueryparam value="#product_id#" cfsqltype="cf_sql_integer">
</cfquery>
But this time i need to get from dynamic product's ID its dynamic brand id.
This script works the same way as the Example 1:
<cfquery name="GET_BRAND" datasource="#dsn1#">
SELECT BRAND_ID
FROM PRODUCT_BRANDS
WHERE BRAND_ID = BRAND_ID
</cfquery>
#get_brand.BRAND_ID#
As Andreas shows in his code, your query isn't going to work as written. The statement WHERE PRODUCT_CATID = PRODUCT_CATID doesn't actually pass a value - it would actually just be self-referential within the table values. In this case, it would return everything in the table.
You should instead have:
WHERE PRODUCT_CATID = #PRODUCT_CATID#
Where #PRODUCT_CATID# represents a variable. Better yet, use cfqueryparam as Andreas shows (this prevents SQL injection and improves query performance). However, I am not even sure that is what you intend since if you have the product ID why do you need to get it from the database? Instead, I assume you probably want to get the brands from the product in a particular category. Not knowing your table structure, it's hard to write that query for you but it might look something like:
<cfquery name="GET_BRAND" datasource="#dsn1#">
SELECT PRODUCT.BRAND_ID
FROM PRODUCT
INNER JOIN PRODUCT_CAT
ON PRODUCT.PRODUCT_CATID = PRODUCT_CAT.PRODUCT_CATID
WHERE PRODUCT_CATID = <cfqueryparam cfsqltype="cf_sql_integer" value="#product_catid#">
</cfquery>
Lastly, as both comments indicate, you would need to loop through the results to see all the records returned.
You need to wrap the statement in tags like this.
<cfquery name="GET_BRAND" datasource="#dsn1#">
SELECT PRODUCT_CATID FROM PRODUCT_CAT WHERE PRODUCT_CATID = PRODUCT_CATID
</cfquery>
<cfoutput query =GET_Brand">
#get_brand.product_catid#
</cfoutput>
It's not very clear what your question really is about, but let me guess:
<cfquery name="GET_BRAND" datasource="#dsn1#">
SELECT PRODUCT_CATID
FROM PRODUCT_CAT
WHERE PRODUCT_CATID = <cfqueryparam cfsqltype="cf_sql_integer" value="#product_catid#">
</cfquery>
where #product_catid# refers to a variable you defined earlier in your code or received via form or url scope.
<cfloop query="GET_BRAND">
#get_brand.product_catid#<br />
</cfloop>
will show a list of all the product_catid's returned by the query.
It's not too clear what you are after here, but in the queries there are at least 2 problems. First your WHERE clause
WHERE PRODUCT_CATID = PRODUCT_CATID
is like saying
WHERE 1=1
This will return the full recordset. You can see this by adding
<cfdump var="#GET_BRAND#">
under your code to see the query output. I'm guessing this will show all records in the table.
To match just one record you need your WHERE clause to be like
WHERE PRODUCT_CATID = 3
or have the #...# wrapped around the variable you are trying to match to make it dynamic.
Secondly to the query result may be more than one record, and to see any more than the first record you need to loop over the output. One way is to use
<cfoutput query="GET_BRAND">
#BRAND_ID# <br>
</cfoutput>
My guess of what you are after is
<cfset ID_TO_MATCH=3>
<cfquery name="GET_BRAND" datasource="#dsn1#">
SELECT BRAND_ID
FROM PRODUCT_CAT
WHERE PRODUCT_CATID = #ID_TO_MATCH#
</cfquery>
<cfoutput query="GET_BRAND">
#BRAND_ID# <br>
</cfoutput>