Summing up the value of related entries during an update - mysql

I am attempting to change a value of a field for products on Volusion shopping engine. The field I want to set is 'HideProduct' to 'Y' if it's children products are equivalent to 0 (negative stocks also count as zero). However, some products don't have children, which are ignored.
I don't have direct access to the server or database, and this script is being passed though with the errors suppressed. The error I get is simply "An error has occurred. Please try your request again, or contact customer service for assistance. Thank you." They don't offer customer support for SQL.
This is the script I am trying to run:
UPDATE
Products_Joined
SET
HideProduct = 'Y'
WHERE
-- Only apply to products where there are no in stock children
0 = SUM(
SELECT
CASE
WHEN Pb.DoNotAllowBackOrders = 'N' THEN 1
WHEN Pb.StockStatus < 0 THEN 0
ELSE Pb.StockStatus
END
FROM
Products_Joined Pb
WHERE
ProductCode = Pb.IsChildOfProductCode
)
AND
--Only apply to products with children
0 < TOTAL(
SELECT
Pc.StockStatus
FROM
Products_Joined Pc
WHERE
ProductCode = Pc.IsChildOfProductCode
)
I am attempting to pass the Product Code of the UPDATE part of the script into the SELECT parts of the code. I think that's where the issue might be.

Related

Compare results of two SQL queries?

So I'm using SAP Business One application and using B1 validation configuration to give error messages. The main goal of this is where it compares addresses from the BP master data address and the address from the sales order/delivery order address.
So here's the code for the first query which is for the open sales orders only:
SELECT dbo.ORDR.DocNum, dbo.ORDR.DocStatus, dbo.RDR12.StreetS, dbo.RDR12.BlockS, dbo.RDR12.CityS, dbo.RDR12.ZipCodeS, dbo.RDR12.StateS, dbo.RDR12.CountryS
FROM dbo.ORDR INNER JOIN
dbo.RDR12 ON dbo.ORDR.DocEntry = dbo.RDR12.DocEntry
WHERE (dbo.ORDR.DocStatus = 'o')
Heres the code for the second query from the business partner data. This contains all addresses and data
SELECT dbo.CRD1.Street, dbo.CRD1.Address, dbo.CRD1.Block, dbo.CRD1.ZipCode, dbo.CRD1.City, dbo.CRD1.Country, dbo.CRD1.State
FROM dbo.CRD1 INNER JOIN
dbo.OCRD ON dbo.CRD1.CardCode = dbo.OCRD.CardCode
So now I'm hoping to be able to create an SQL condition where it compares these two. Like for example (pseudo code):
if(street != street.s)
begin
if(zip != zip.s)
begin
if(country != country.s).....
begin
Select 'error' for browse
else
select 'passed' for browse
Overall I'm just trying to compare the 2 queries with ONLY open sales orders/delivery orders.
So I'm trying to get it to trigger the error message.
The problem being, I don't know how to pull the values from each one since there's tons of addresses to compare from and I can't just hard code it in.
For example the inputted data is 91234 for zipcode, and zipcode.s is 92134 which is obviously different and would give the error message.

Data base One To Many Relationship Query

I have 3 tables in my DB; Transactions, transaction_details, and accounts - basically as below.
transactions :
id
details
by_user
created_at
trans_details :
id
trans_id (foreign key)
account_id
account_type (Enum -[c,d])
amount
Accounts :
id
sub_name
In each transaction each account may be creditor or debtor. What I'm trying to get is an account statement (ex : bank account movements) so I need to query each movement when the account is type = c (creditor) or the account type is = d (debtor)
trans_id, amount, created_at, creditor_account, debtor_account
Update : I tried the following query but i get the debtor column values all Null!
SELECT transactions.created_at,trans_details.amount,(case WHEN trans_details.type = 'c' THEN sub_account.sub_name END) as creditor,
(case WHEN trans_details.type = 'd' THEN sub_account.sub_name END) as debtor from transactions
JOIN trans_details on transactions.id = trans_details.trans_id
JOIN sub_account on trans_details.account_id = sub_account.id
GROUP by transactions.id
After the help of #Jalos I had to convert the query to Laravel which also toke me 2 more hours to convert and get the correct result :) below is the Laravel code in case some one needs to perform such query
I also added between 2 dates functionality
public function accountStatement($from_date,$to_date)
{
$statemnt = DB::table('transactions')
->Join('trans_details as credit_d',function($join) {
$join->on('credit_d.trans_id','=','transactions.id');
$join->where('credit_d.type','c');
})
->Join('sub_account as credit_a','credit_a.id','=','credit_d.account_id')
->Join('trans_details as debt_d',function($join) {
$join->on('debt_d.trans_id','=','transactions.id');
$join->where('debt_d.type','d');
})
->Join('sub_account as debt_a','debt_a.id','=','debt_d.account_id')
->whereBetween('transactions.created_at',[$from_date,$to_date])
->select('transactions.id','credit_d.amount','transactions.created_at','credit_a.sub_name as creditor','debt_a.sub_name as debtor')
->get();
return response()->json(['status_code'=>2000,'data'=>$statemnt , 'message'=>''],200);
}
Your transactions table denotes transaction records, while your accounts table denotes account records. Your trans_details table denotes links between transactions and accounts. So, since in a transaction there is a creditor and a debtor, I assume that trans_details has exactly two records for each transaction:
select transactions.id, creditor_details.amount, transactions.created_at, creditor.sub_name, debtor.sub_name
from transactions
join trans_details creditor_details
on transactions.id = creditor_details.trans_id and creditor_details.account_type = 'c'
join accounts creditor
on creditor_details.account_id = creditor.id
join trans_details debtor_details
on transactions.id = debtor_details.trans_id and debtor_details.account_type = 'd'
join accounts debtor
on debtor_details.account_id = debtor.id;
EDIT
As promised, I am looking into the query you have written. It looks like this:
SELECT transactions.id,trans_details.amount,(case WHEN trans_details.type = 'c' THEN account.name END) as creditor,
(case WHEN trans_details.type = 'd' THEN account.name END) as debtor from transactions
JOIN trans_details on transactions.id = trans_details.trans_id
JOIN account on trans_details.account_id = account.id
GROUP by transactions.id
and it is almost correct. The problem is that due to the group-by MySQL can only show a single value for each record for creditor and debtor. However, we know that there are exactly two values for both: there is a null value for creditor when you match with debtor and a proper creditor value when you match with creditor. The case for debtor is similar. My expectation for this query would have been that MySQL would throw an error because you did not group by these computed case-when fields, yet, there are several values, but it seems MySQL can surprise me after so many years :)
From the result we see that MySQL probably found the first value and used that both for creditor and debtor. Since it met with a creditor match as a first match, it had a proper creditor value and a null debtor value. However, if you write bullet-proof code, you will never meet these strange behavior. In our case, doing some minimalistic improvements on your code transforms it into a bullet-proof version of it and provides correct results:
SELECT transactions.id,trans_details.amount,max((case WHEN trans_details.type = 'c' THEN account.name END)) as creditor,
max((case WHEN trans_details.type = 'd' THEN account.name END)) as debtor from transactions
JOIN trans_details on transactions.id = trans_details.trans_id
JOIN account on trans_details.account_id = account.id
group by transactions.id
Note, that the only change I did with your code is to wrap a max() function call around the case-when definitions, so we avoid the null values, so your approach was VERY close to a bullet-proof solution.
Fiddle: http://sqlfiddle.com/#!9/d468dc/10/0
However, even though your thought process was theoretically correct (theoretically there is no difference between theory and practice, but in practice they are usually different) and some slight changes are transforming it into a well-working code, I still prefer my query, because it avoids group by clauses, which can be useful, if necessary, but here it's unnecessary to do group by, which is probably better in terms of performance, memory usage, it's easier to read and keeps more options open for you for your future customisations. Yet, your try was very close to a solution.
As about my query, the trick I used was to do several joins with the same tables, aliasing them and from that point differentiating them as if they were different tables. This is a very useful trick that you will need a lot in the future.

How to resolve Subquery returns more than 1 row error

So currently have my code here trying to make a trigger for it, but this is just the sql query within the trigger at the moment.
Keeps giving me the #1242 - Subquery returns more than 1 row
Update Agent
Set ListingsSold=ListingsSold+1
Where (Select Availability
From Property
Where AgentID=Agent.AgentID)='N' AND (SELECT AgentID
FROM Property
Where AgentID=Agent.AgentID)=Agent.AgentID
I just want ListingsSold to increment by 1 in the Agent table when a property is sold (or its Availability changes from 'Y' to 'N' and I need it to update for the appropriate Agent with AgentID that corresponds to the Property they are selling.
Please help!
You have made your query very complex to understand, I'm not sure what exactly you are trying to achieve. But whatever you mentioned in your post, I felt, you should have used INNER JOIN instead.
UPDATE Agent AS A
INNER JOIN Property AS P ON A.AgentID = P.AgentID
SET ListingsSold = ListingsSold + 1
WHERE Availability = 'N'

How are store/website-specific (or non-global) products attributes stored/queried in the Magento mysql database?

First of all, I know I should be using the model rather than working on the database directly. That being said, does anyone know exactly how Magento handles non-global product attributes?
I have 2 websites in core_website: Admin (website_id = 0) and Main Website (website_id =1). I also have two stores in core_store: Admin (store_id = 0) and Default Store View (store_id = 0). It seems that whether or not a product (or category?) attribute is global in scope is stored in catalog_eav_attribute.is_global. A value of 0 corresponds to a scope of "Store View," a value of 1 corresponds to "Global," and 2 corresponds to "Website." So far so good.
Now, if I wanted to get the value of a store-specific attribute like "name" (eav_attribute.attribute_id = 71; eav_attribute.backend_type = 'varchar'; catalog_eav_attribute.is_global = 0) for all my products, you would think I would do something like this:
SELECT *
FROM catalog_product_entity_varchar
WHERE attribute_id = 71
AND store_id = 1
But that returns nothing. All of the names are actually in rows with store_id = 0. As far as I can tell the only attributes in the database that are stored with store_id = 1 are 'url_key' and 'url_path'. So how does Magento store these values? And how does Magento retrieve them?
Are all values initially(or also) stored with store_id = 0 as a kind of default, until a different value than that one needs to be stored? When a store-specific value that is different from the admin value needs to be stored, does magento then create a new row with store_id = 1 (or whatever store_id it is)?
If that - or something like that - is the case, then how does Magento retrieve store-specific values? Does it check catalog_eav_attribute.is_global first for the attribute in question? If it is non-global, it could then first query with store_id = 1, and if that returns nothing, then query with the default store_id = 0?
I guess my main question is how does magento actually do it. Secondarily, why does magento do it this way instead of storing values with the actual store_id? Also, if I were to write a query, should I query both store_id = 0 and store_id = 1 and choose the right value based on whether or not the attribute is global and whether or not there is a value present for store_id = 1?
You seem to have it figured out. At least you have a good idea on how things are done.
To summarize and confirm your suspicions:
All the attribute values are stored in catalog_product_entity_* where * can be anyone of theses: decimal, int, varchar, text, datetime depending on the attribute type (backend_type).
There are also other tables that keep the data related to tier pricing and images but let's leave that for now.
Attribute table definition
Each of the tables have the following columns:
value_id - just an increment id for the table
entity_type_id - the entity type id for the product (always the same)
attribute_id - reference to the attribute
store_id - reference to the store view
entity_id - reference to the product
value - actual value
There is a unique constraint on these columns entity_id,attribute_id,store_id. This means that for one product and one attribute you can have only one value for a store view.
Now the part where you are right.
store_id = 0 means that the value stored there is a default value.
If there is no value specified for a specific store view (store_id >= 1) then this value will be used.
If the attribute is set as global then the value for store_id = 0 will be used even if you have values for store_id = 1.
Examples
To get an idea of how the values are retrieved put this code in some file and run it (make sure the flat catalog is disabled - more on that later, and make sure you created an instance of the application first using Mage::app()):
Store view attribute
$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToFilter('name', 'some_name');
echo $collection->getSelect();
The code above means that I want to retrieve a list of products with the name some_name.
the sql query associated to the collection looks like this:
SELECT
`e`.*,
IF(at_name.value_id > 0, at_name.value, at_name_default.value) AS `name`
FROM
`catalog_product_entity` AS `e`
INNER JOIN
`catalog_product_entity_varchar` AS `at_name_default`
ON (`at_name_default`.`entity_id` = `e`.`entity_id`) AND
(`at_name_default`.`attribute_id` = '96') AND
`at_name_default`.`store_id` = 0
LEFT JOIN
`catalog_product_entity_varchar` AS `at_name`
ON (`at_name`.`entity_id` = `e`.`entity_id`) AND
(`at_name`.`attribute_id` = '96') AND
(`at_name`.`store_id` = 1)
WHERE
(IF(at_name.value_id > 0, at_name.value, at_name_default.value) = 'some_name')
Ugly huh?
Because the name attribute (id 96 in my case) is a store view scope attribute (is_global = 0) Magento joins twice with the table catalog_product_entity_varchar (the one that holds the name), once for the current store view and once for the detault store view (id = 0). adding a condition:
IF(at_name.value_id > 0, at_name.value, at_name_default.value)
So if there is no value for the store id 1, use the default value.
Global attribtue
Now let's see what happens if we filter by a global attribute.
$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToFilter('weight', '1');
echo $collection->getSelect();
The sql printed looks like this:
SELECT
`e`.*,
`at_weight`.`value` AS `weight`
FROM
`catalog_product_entity` AS `e`
INNER JOIN
`catalog_product_entity_decimal` AS `at_weight`
ON (`at_weight`.`entity_id` = `e`.`entity_id`) AND
(`at_weight`.`attribute_id` = '101') AND
(`at_weight`.`store_id` = 0)
WHERE
(at_weight.value = '1')
So one single join with the table catalog_product_entity_decimal for the store id = 0.
Website attribute
If the scope of the attribute is website everything happens just like it does for the store view scope, because Magento creates a line in the attribute values table for each store view in the current website when saving the product.
If you want to try it use the attribute status in the examples above.
Flat Catalog
I promised earlier some explanations about "flat catalog".
For performance reasons Magneto introduced this feature (I don't remember the version).
Basically a cron runs (or you can run it by hand) and transforms the EAV approach for the products and categories into flat tables. One for each store view you have (except store id = 0).
This means one attribute will be transformed into one column in the new table. The new table is called catalog_product_flat_{store_view_id_here}.
This avoids the numerous left/inner joins when wanting the values for some attributes.
but again, for performance reasons, not all the attributes are added as columns in the flat tables (for products only. For categories all of them are added).
Only the attributes marked in the backend with Use in product listing are transformed into columns.
You can turn on/off this feature from System->Configuration->Catalog->Frontend->Use Flat Catalog Product (or Use Flat Catalog Category).
Even if turned on, the flat tables are used only in the frontend. The backend still uses the EAV approach.
Conclusion
My conclusion is that it is almost impossible to write your own queries to retrieve data directly from the DB. You should use the models and collections that magento provides. It saves a lot of mental health.
I hope I made things a little clearer for you.

getting the updated value from an UPDATE query

I have an UPDATE Query :
UPDATE FKMS_GNST_Transaction_Details
SET Received_Quantity=Received_Quantity+(
CASE
WHEN (#int_Updated_Qty)>=(GTD.Quantity-GTD.Received_Quantity)
THEN GTD.Quantity-GTD.Received_Quantity
ELSE (#int_Updated_Qty)
END)
,#int_GNST_Reference_Id=GTD.Transaction_Detail_Id
FROM FKMS_GNST_Transaction_Details GTD
INNER JOIN #tbl_transactions tmp
ON tmp.Transaction_id=GTD.Transaction_id
AND GTD.Item_id=tmp.Item_id
I want to get the quantity which is added to the Received_Quantity field. That is, if (#int_Updated_Qty)>=(GTD.Quantity-GTD.Received_Quantity) then GTD.Quantity-GTD.Received_Quantity other wise #int_Updated_Qty.
How can we take this value (into a variable or any other way)? Please help.
Use the OUTPUT clause
UPDATE FKMS_GNST_Transaction_Details
SET Received_Quantity=Received_Quantity+(
CASE
WHEN (#int_Updated_Qty)>=(GTD.Quantity-GTD.Received_Quantity)
THEN GTD.Quantity-GTD.Received_Quantity
ELSE (#int_Updated_Qty)
END)
,#int_GNST_Reference_Id=GTD.Transaction_Detail_Id
--start gbn code
OUTPUT INSERTED.Received_Quantity
--end gbn code
FROM FKMS_GNST_Transaction_Details GTD
INNER JOIN #tbl_transactions tmp
ON tmp.Transaction_id=GTD.Transaction_id
AND GTD.Item_id=tmp.Item_id
The OUTPUT results can go
into a table (real, temp or variable)
to the client as a recordset
You can't assigned directly to a local variable