Complex query possible? - salesforce

I have 3 tables, a parent table and 2 child tables. Lets say Mother_c is the parent. Then Child_c and Pet_c are the 2 child tables that have master-detail relationship pointer to Mother_c.
I have the Id of one row from Child_c, I want to retrieve all the rows from Pet_c that correspond to the Mother_c of that single Child_c row.
I'm wondering if this is possible in one SOQL query?

Yes, this is totally possible with semi-join SOQL. I tested this out with the standard CRM objects like this:
SELECT Id,
(SELECT Id FROM Cases)
FROM Account
WHERE Id IN (SELECT AccountId
FROM Contact
WHERE Id = '0036000000qCwp9'
)
To walk you through this, with a given Contact id, you first find the parent Account, and then traverse back down to the child Cases. In your example with custom objects, it would be very similar, but would use the __r custom relationships names instead:
SELECT Id,
(SELECT Id FROM Pet__r)
FROM Mother__c
WHERE Id IN (SELECT Mother__c
FROM Child__c
WHERE Id = '003a000000qCwp9'
)

Related

SOQL - Count of records where the parent hasn't had child records in 1 year

I am trying to create a SOQL query (and later a report) in Salesforce that generates the following data:
Count of Child records grouped by Parent records where the Parent does not have child records created in the past year.
I tried this first; however, salesforce returned an error stating 'Nesting of semi join sub-selects is not supported'.
SELECT Id, Name, Training_Course__c
FROM Training_Record__c
WHERE Training_Course__c IN (
SELECT Id
FROM Training_Course__c
WHERE Id NOT IN (
SELECT Training_Course__c
FROM Training_Record__c
WHERE CreatedDate != Last_n_Days:365
)
)
The requirements are to use a single query to obtain the data requested without forcing them to run two reports and use Excel to get the data. I'm not sure if that's possible given Salesforce's constraints.
Is this possible in SOQL? If so, what can I do differently?
This is not perfect but a decent start, you'd need to manually call count/size/length on the child records (exact method depends on your client application's language). In a SF report you can group by parent Id and call it a day. Read about "cross filters" in SF reports.
SELECT Id, Name,
(SELECT Id FROM Opportunities)
FROM Account
WHERE Id NOT IN (SELECT AccountId FROM Opportunity WHERE CloseDate = LAST_N_DAYS:365)
LIMIT 100
This will not compile:
SELECT AccountId, COUNT(Id)
FROM Opportunity
WHERE AccountId NOT IN (SELECT AccountId FROM Opportunity WHERE CloseDate = LAST_N_DAYS:365)
Error: The inner and outer selects should not be on the same object type
but if you really need something like that you could experiment, do it in 2 steps. Create helper field on account, tick the checkbox for eligible accs (with some nightly batch job maybe), then query based on that.
(normally you could do it with rollup summary field from opps to account but rollups have to be based on solid data, "deterministic". "LAST 365 DAYS", "TODAY" etc can't be used as rollup criteria)

Is it possible to replace cursor in SQL when the query is hierarchical?

I have three tables in MsSQL to store data.
First one is the Children contains the following rows
ID,
Name,
Age,
FatherID,
SchoolID
The second one contains the data of their fathers, like this
FatherID,
Name,
Age
The third one is the School with SchoolID and SchoolName, Address, etc.
(The relation is: multiple children can have the same father, obviously :) )
What I want to list is all the family members related to a given SchoolID.
I know a cursor based solution:
*Cursor is working with two temporary tables, at first step selecting the children
with a given schoolid into one temporary table(#tempfamilymembers).
Second step is to select the distinct fatherids from this table and save it to the second temporary table(#fatherids).
Then, I would loop over these fatherids to insert fathers with matching fatherids(from #fatherids) into the first temporary table(#tempfamilymembers).
Then, I can select all from #tempfamilymembers to complete the query*
The cursor based solution is too difficult, and relatively slow plus
I heard while loops are showing better performance and a set-based approach would be even better.
My question is : Can I achieve this somehow, without creating a cursor?
(I want to list the fathers present in this list only one time, even if he has more than one children.)
WITH family_members (id, name, age, role) AS
(
SELECT FatherID, Name, Age, 'father'
FROM Fathers
WHERE FatherID IN(SELECT DISTINCT FatherID
FROM Children
WHERE SchoolID = 1) -- 1) Put School to search here
UNION ALL
SELECT ID, Name, Age, 'child '
FROM Children
WHERE SchoolID = 1 -- 2) Put School to search here
)
SELECT * FROM family_members
If you were to put some data in rextester you could test this out.
Let me know what you think.
If columns are of differing types (Children vs Father) you may have to CAST columns.
As I mentioned originally in comments CTE's/UNION ALL are way to go. ;-)

SQL Server - Update All Records, Per Group, With Result of SubQuery

If anyone could even just help me phrase this question better I'd appreciate it.
I have a SQL Server table, let's call it cars, which contains entries representing items and information about their owners including car_id, owner_accountNumber, owner_numCars.
We're using a system that sorts 'importantness of owner' based on number of cars owned, and relies on the owner_numCars column to do so. I'd rather not adjust this, if reasonably possible.
Is there a way I can update owner_numCars per owner_accountNumber using a stored procedure? Maybe some other more efficient way I can accomplish every owner_numCars containing the count of entries per owner_accountNumber?
Right now the only way I can think to do this is to (from the c# application):
SELECT owner_accountNumber, COUNT(*)
FROM mytable
GROUP BY owner_accountNumber;
and then foreach row returned by that query
UPDATE mytable
SET owner_numCars = <count result>
WHERE owner_accountNumber = <accountNumber result>
But this seems wildly inefficient compared to having the server handle the logic and updates.
Edit - Thanks for all the help. I know this isn't really a well set up database, but it's what I have to work with. I appreciate everyone's input and advice.
This solution takes into account that you want to keep the owner_numCars column in the CARs table and that the column should always be accurate in real time.
I'm defining table CARS as a table with attributes about cars including it's current owner. The number of cars owned by the current owner is de-normalized into this table. Say I, LAS, own three cars, then there are three entries in table CARS, as such:
car_id owner_accountNumber owner_numCars
1 LAS1 3
2 LAS1 3
3 LAS1 3
For owner_numCars to be used as an importance factor in a live interface, you'd need to update owner_numCars for every car every time LAS1 sells or buys a car or is removed from or added to a row.
Note you need to update CARS for both the old and new owners. If Sam buys car1, both Sam's and LAS' totals need to be updated.
You can use this procedure to update the rows. This SP is very context sensitive. It needs to be called after rows have been deleted or inserted for the deleted or inserted owner. When an owner is updated, it needs to be called for both the old and new owners.
To update real time as accounts change owners:
create procedure update_car_count
#p_acct nvarchar(50) -- use your actual datatype here
AS
update CARS
set owner_numCars = (select count(*) from CARS where owner_accountNumber = #p_acct)
where owner_accountNumber = #p_acct;
GO
To update all account_owners:
create procedure update_car_count_all
AS
update C
set owner_numCars = (select count(*) from CARS where owner_acctNumber = C.owner_acctNumber)
from CARS C
GO
I think what you need is a View. If you don't know, a View is a virtual table that displays/calculates data from a real table that is continously updated as the table data updates. So if you want to see your table with owner_numCars added you could do:
SELECT a.*, b.owner_numCars
from mytable as a
inner join
(SELECT owner_accountNumber, COUNT(*) as owner_numCars
FROM mytable
GROUP BY owner_accountNumber) as b
on a.owner_accountNumber = b.owner_accountNumber
You'd want to remove the owner_numCars column from the real table since you don't need to actually store that data on each row. If you can't remove it you can replace a.* with an explicit list of all the fields except owner_numCars.
You don't want to run SQL to update this value. What if it doesn't run for a long time? What if someone loads a lot of data and then runs the score and finds a guy that has 100 cars counts as a zero b/c the update didn't run. Data should only live in 1 place, updating has it living in 2. You want a view that pulls this value from the tables as it is needed.
CREATE VIEW vOwnersInfo
AS
SELECT o.*,
ISNULL(c.Cnt,0) AS Cnt
FROM OWNERS o
LEFT JOIN
(SELECT OwnerId,
COUNT(1) AS Cnt
FROM Cars
GROUP BY OwnerId) AS c
ON o.OwnerId = c.OwnerId
There are a lot of ways of doing this. Here is one way using COUNT() OVER window function and an updatable Common Table Expression [CTE]. That you won't have to worry about relating data back, ids etc.
;WITH cteCarCounts AS (
SELECT
owner_accountNumber
,owner_numCars
,NewNumberOfCars = COUNT(*) OVER (PARTITION BY owner_accountNumber)
FROM
MyTable
)
UPDATE cteCarCounts
SET owner_numCars = NewNumberOfCars
However, from a design perspective I would raise the question of whether this value (owner_numCars) should be on this table or on what I assume would be the owner table.
Rominus did make a good point of using a view if you want the data to always reflect the current value. You could also use also do it with a table valued function which could be more performant than a view. But if you are simply showing it then you could simply do something like this:
SELECT
owner_accountNumber
,owner_numCars = COUNT(*) OVER (PARTITION BY owner_accountNumber)
FROM
MyTable
By adding a where clause to either the CTE or the SELECT statement you will effectively limit your dataset and the solution should remain fast. E.g.
WHERE owner_accountNumber = #owner_accountNumber

Analysis service create recursive hierarchy

I have following table:
CatId CatName parent CatId
1 Category 1 NULL
2 Category 2 NULL
3 SubCat 1 1
4 SubSubCat 1 3
5 SSSubCat 1 4
In Analysis Service I want to create Hierarchy in dimension such that it allows me to drill down till N Level.. Currently I am able to do it only 2 levels.. Category and Sub Category.. but I would like to go till N level if N level is not possible atleast till 4-5 levels.
The type of Hierarchy you appear to be attempting is called a Parent-Child Dimension. SSAS will use recursive joins to "explode" your data into a tree shape.
But your table as you describe it is a little confusing. So I am offering a solution that requires you to rethink your table a little. A classic Parent-Child will have for each node (record) in the hierarchy:
A key (ID) for the node
The literal text (Name) for the node
A foreign key called the parent
In your example, the column labelled "parent" appears to be superfluous. The last column in your example (called "CatID") is what the Parent of the dimension usually looks like. If you consider that each record in the table is a "child", the parent of the child acts as a pointer back to some record that owns or contains that record. At the highest level in the hierarchy, records will have no Parent so the Parent column is set to NULL.
Rename the second "CatID" to "parent" and remove or rename the original column called "Parent" (you don't need it). If you tweak your table as I suggest, you should check that the highest level is correct by running the following query:
SELECT CatID, CatName, parent FROM mytable WHERE (parent IS NULL)
Then to get the next level down run the following query:
SELECT HighestLevel.CatID, HighestLevel.CatName, HighestLevel.parent, Level2.CatID AS Level2ID, Level2.CatName AS Level2Name
FROM mytable AS HighestLevel
INNER JOIN mytable AS Level2 ON HighestLevel.CatID = Level2.parent
WHERE (HighestLevel.parent IS NULL)
Note the recursive INNER JOIN. Run at least one more query to view another level down to verify that the keys are "expanding" the way you expect:
SELECT HighestLevel.CatID, HighestLevel.CatName, HighestLevel.parent, Level2.CatID AS Level2ID, Level2.CatName AS Level2Name, Level3.CatID AS Level3ID, Level3.CatName AS Level3Name
FROM mytable AS HighestLevel
INNER JOIN mytable AS Level2 ON HighestLevel.CatID = Level2.parent
INNER JOIN mytable AS Level3 ON Level2.CatID = Level3.parent
WHERE (HighestLevel.parent IS NULL)
You could keep adding levels as necessary to convince yourself that the data is correct. This is essentially what SSAS is doing when it builds a Parent-Child hierarchy.
Finally, you'll add this table to the DSV and create a Parent-Child Dimension. That's a bit more complicated and this looks like a great starter article. SSAS will keep adding levels as necessary until it runs out of data.
In AdventureWorks, the Employee dimension has an example of this. Assuming your category is on your fact table:
Set your ParentCatID to be a FK of CatID in the DSV
Reference your Parent attribute as the Parent Attribute type in the Dimension Hierarcy manager
Add the attribute into your hierarchy
The nested levels should be able to be browsed in your Category Hierarchy.

Storing multiple employee IDs in one column of data

Web app is being written in classic ASP with a MSSQL backend. On this particular page, the admin can select 1 or any/all of the employees to assign the project to. I'm trying to figure out a simple way to store the employee IDs of the people assigned to it in one column.
The list of employees is generated from another table and can be dynamic (firing or hiring) so I want the program to be flexible enough to change based on these table changes.
Basically need to know how to assign multiple people to a project that can later be called up on a differen page or from a different query.
Sorry for the n00bish question, but thanks!
Don't store multiple ID's in one column! Create another table with the primary key of your existing table and a single ID that you want to store. You can then insert multiple rows into this new table, creating a 1:m (one to many) relationship. For example, let's look at an order table:
order:
order_id
order_date
and I have a product table...
product:
product_id
product_name
Now, you could go down the road of adding a column to order that let you list the products in the order, but that would be bad form. What you want instead is something like..
order_item:
order_item_id
order_id
product_id
quantity
unit_price
You can then perform a join to get all of the products for a particular order...
select
product.*
from orders
inner join order_item on order_item.order_id = order.order_id
inner join product on product.product_id = order_item.product_id
where orders.order_id = 5
Here's an example order_id of 5, and this will get all of the products in that order.
You need to create another table that stores these values such as. So this new table would store one row for each ID, and then link back to the original record with the original records ID.

Resources