Delete duplicate assets from Salesforce - duplicates

I came to know that there are several duplicate assets in our Salesforce sandbox org. To do a cleaner testing of my current assignment, I need to cleanup the duplicate assets. Under each account, I could see that there are multiple active assets for the same product.
I ran the below query and it is fetching the duplicate counts.
SELECT AccountId, ProductCode, COUNT(Id)
FROM Asset
WHERE ProductCode != null AND RecordType.Name = 'Internal' and IsActive__c = true
GROUP BY AccountId, ProductCode
HAVING COUNT(Id) > 1
LIMIT 100
Is there a way to retain only the most recent active asset and delete the remaining ones?

Something like this? Untested but along these lines.
You might have to run it several times or add your own filter on Accounts to somehow slice data.
List<Asset> toDelete = new List<Asset>();
for(Account acc : [SELECT Id,
(SELECT ProductCode
FROM Assets
WHERE ProductCode != null AND RecordType.Name = 'Internal' and IsActive__c = true
ORDER BY LastModifiedDate DESC)
FROM Account
WHERE Id IN (SELECT AccountId FROM Asset WHERE ProductCode != null AND RecordType.Name = 'Internal' and IsActive__c = true)
LIMIT 1000]){
Set<String> productCodes = new Set<String>();
for(Asset a : acc.Assets){
if(productCodes.contains(a.ProductCode)){
toDelete.add(a);
} else {
productCodes.add(a.ProductCode);
}
}
}
delete toDelete;
Probably you can simplify it a bit, from what I remember set.add returns boolean saying whether collection has modified as result of the addition.

Related

Querying MySQL result set for existence of a particular value

MySQL 8.x here. I have the following tables:
[users] (users in the system)
===
user_id
user_username
user_password_enc
user_first_name
user_last_name
[events] (CRUDdable things the users can participate in)
===
event_id
event_name
event_date
event_location
[event_roles] (coarse-grained, event-specific roles that users can be assigned to)
===
event_role_id
event_role_name
event_role_label -- ex: EVENT_ADMIN, EVENT_REP, EVENT_FOLLOWER
[event_permissions] (granular permissions that get assigned to roles)
===
event_permission_id
event_permission_name
event_permission_label -- ex: CAN_VIEW_LOCATION, CAN_CHANGE_DATE, CAN_CHANGE_LOCATION, CAN_CANCEL_EVENT
[event_permissions_x_roles] (crosswalk table mapping permissions to roles)
===
event_permissions_x_role_id
event_permission_id
event_role_id
[event_user_roles] (which users are assigned to which event roles)
===
event_user_role_id
event_id
user_id
event_role_id
I now want to write a query that asks the following question:
Does user_id = 234 have CAN_CHANGE_LOCATION permission to event_id=123?
My best attempt thus far:
SELECT
event_permission_label
FROM
event_permissions_x_roles
INNER JOIN
event_permissions
ON
event_permissions_x_roles.event_permission_id = event_permissions.event_permission_id
WHERE
event_role_id = (
SELECT
event_role_id
FROM
event_user_roles
WHERE
user_id = 234
AND
event_id = 123
);
However this will only give me a list of event_permission_labels that the user_id=234 has for event_id=123.
That's close to what I want, but I want my query to go further and check that list to see if CAN_CHANGE_LOCATION is part of that list. I don't know if there's a way to write this so that it returns a boolean true/false (yes user does have the desired permission for the given event; or no the user does not, etc.). Can anyone spot where I'm falling a bit short?
Schematically:
SELECT EXISTS ( SELECT NULL
FROM {joined tables set}
WHERE t1.user_id = 234
AND t2.event_permission_label = 'CAN_CHANGE_LOCATION'
AND t3.event_id = 123 )

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.

Update unique matching records in same table

I have a database tables of transactions. Because of some flukes in the code, there have been a number of cases where positive transactions, and negative transactions have been made on the same invoice, in the same amount. There is a soft delete column named void. I am trying to update this, to void matching pairs.
Basically what I want is this:
UPDATE transactions pos JOIN transactions neg on pos.invoice_id = neg.invoice_id
AND pos.amount = neg.amount AND pos.effect = 1 AND neg.effect = -1
AND pos.void IS NULL AND neg.void IS NULL
SET pos.void = true, neg.void = true
Just running this as-is though, will not uniquely identify pairs. So, if there is a +X, -X, +X, or alternatively -Y, +Y, -Y for an invoice, it would void all. I would have thought this would be a common problem, but I am not seeing any questions or answers that quite fit my problem.
Most answers to similar questions involve using a sub-query, but they are only trying to update 1 entry, not 2, and I don't see how I'd be able to update both entries when doing that.
Thanks,
-Eric
maybe if you post your table structure i can help you better, but i think this is what you want:
UPDATE transactions
SET void = true
WHERE invoice_id in (
SELECT invoice_id from transactions as pos
JOIN transactions neg
ON pos.invoice_id = neg.invoice_id
AND pos.amount = neg.amount AND pos.effect = 1 AND neg.effect = -1
AND pos.void IS NULL AND neg.void IS NULL
)

Return zero when records not found

Im making a table generator as a school project.
In MySQL I have 3 tables namely process,operation,score. Everything looked fine until i tested out my "ADD column" button in the web app.
Previous saved data should be read properly but also include the new column in the format, problem is the previous data queried does not include any values for the new table, so I intended it to return a score of 0 if no records were found, tried IFNULL & COALESCE but nothing happens(maybe im just using it wrong)
process - processID, processName
operation - operationID, operationName
score - scoreID, score, processID, operationID, scoreType (score
types are SELF,GL,FINAL)
ps = (PreparedStatement)dbconn.prepareStatement("SELECT score FROM score WHERE processID=? and operationID=? and type=?ORDER BY processid");
here's a pic of a small sample http://i50.tinypic.com/2yv3rf9.jpg
The reason that IFNULL doesn't work is that it only has an effect on values. A result set with no rows has no values, so it does nothing.
First, it's probably better to do this on the client than on the server. But if you have to do it on the server, there's a couple of approaches I can think of.
Try this:
SELECT IFNULL(SUM(score), 0) AS score
FROM score
WHERE processID=? and operationID=? and type=?
ORDER BY processid
The SUM ensures that exactly one row will be returned.
If you need to return multiple rows when the table contains multiple matching rows then you can use this (omitting the ORDER BY for simplicity):
SELECT score
FROM score
WHERE processID = ? and operationID = ? and type = ?
UNION ALL
SELECT 0
FROM (SELECT 0) T1
WHERE NOT EXISTS
(
SELECT *
FROM score
WHERE processID = ? and operationID = ? and type = ?
)

Converting a SQL query with group by into LINQ query

I'm stuggling to replicate a SQL query into LINQ.
Can any one help?
SQL:
SELECT tblInvoice.lngID AS InvoiceID,
tblInvoice.dtTimeStamp AS InvoiceDate,
tblInvoice.strReference,
tblInvoice.fltTotalValue,
max(Project.ProjectID) AS ProjectID,
max(Project.ProjectName) AS ProjectName,
max(Project.Location) AS ProjectLocation
FROM tblInvoice INNER JOIN
tblInvoiceLine ON tblInvoice.lngID = tblInvoiceLine.lngInvoiceID
WHERE (tblInvoice.intStatus != 0)
AND (tblInvoice.lngPersonID = #PersonID)
GROUP BY tblInvoice.lngID, tblInvoice.dtTimeStamp, strReference, fltTotalValue
ORDER BY tblInvoice.lngID DESC
LINQ so far:
var invoices = from inv in db.TblInvoices
join invLine in db.TblInvoiceLines on inv.LngID equals invLine.LngInvoiceID
where inv.IntStatus != 0
where inv.LngPersonID == personID
group inv by new {inv.LngID,inv.DtTimeStamp,inv.StrReference,inv.FltTotalValue} into newInv
Part of the problem is that I want to do a
select new Invoice(){
}
and build up my custom Invoice object but, I cant see any of the properties in newInv.
Can any one advise?
I don't have time for a full answer now, but:
To get at properties of the key, use newInv.Key.StrReference etc
To get at aggregates (e.g. max values) use newInv.Max(x => x.ProjectId) etc
Hopefully that'll be enough to get you going. Basically, newInv will be a group of entries, with an associated key (which is what you grouped by).