Query to fetch all referenced entities recursively - sql

I have a datamodels which consists of 'Claims' which (to make things simple for stackoverflow) only has an OpenAmount field. There are two other tables, 'ClaimCoupling' and 'ClaimEntryReference'.
The ClaimCoupling table directly references back to the Claim table and the ClaimEntryReference is effectively the booking of a received amount that can be booked over multiple claims (See ClaimEntry_ID). See this diagram;
For simplicity I've removed all amounts as that's not what I am currently struggling with.
What I want is a query that will start # the Claim table, and fetches all a claim with an OpenAmount which is <> 0. However I want to be able to print out an accurate report of how this OpenAmount came to be, which means I'll need to also print out any Claims coupled to this claim. To make it even more interesting the same thing applies to the bookings, if a booking was made on claim X and claim Y and only X has an open amount I want to fetch both X and Y so I can then show the payment which was booked as a whole.
I've attempted to do this with a recursive CTE but this (rightfully) blows up on the circulair references. I figured I'd fix that with a simple where statement where I would say only recursively add records which are not yet part of CTE but this is not allowed....
WITH coupledClaims AS (
--Get all unique combinations
SELECT cc.SubstractedFromClaim_ID AS Claim_ID,
cc.AddedToClaim_ID AS Linked_Claim_ID FROM dbo.ClaimCoupling cc
UNION
SELECT cc.AddedToClaim_ID AS Claim_ID,
cc.SubstractedFromClaim_ID AS Linked_Claim_ID FROM dbo.ClaimCoupling cc
),
MyClaims as
(
SELECT * FROM Claim WHERE OpenAmount <> 0
UNION ALL
SELECT c.* FROM coupledClaims JOIN MyClaims mc ON coupledClaims.claim_id = mc.ID JOIN claim c ON c.ID = coupledClaims.linked_Claim_ID
WHERE c.ID NOT IN (SELECT ID FROM MyClaims)
)
SELECT * FROM MyClaims
After fiddling around with that for way too long I decided I'd do it with an actual loop... ##Rowcount and simply manually add them to a table variable but as I was writing this solution (which I'm sure I can get to work) I figured I'd ask here first because I don't like writing loops in TSQL as I always feel it's ugly and inefficient.
See the following sql Fiddle for the data models and some test data (I commented out the recursive part as otherwise I was not allowed to create a link);
http://sqlfiddle.com/#!6/129ad5/7/0
I'm hoping someone here will have a great way of handling this problem (likely I'm doing something wrong with the recursive CTE). For completion this is done on MS SQL 2016.

So here is what I've learned and done so far. Thanks to the comment of habo which refers to the following question; Infinite loop in CTE when parsing self-referencing table
Firstly I decided to at least 'solve' my problem and wrote some manual recursion, this solves my problem but is not as 'pretty' as the CTE solution which I was hoping/thinking would be easier to read as well as out perform the manual recursion solution.
Manual Recursion
/****************************/
/* CLAIMS AND PAYMENT LOGIC */
/****************************/
DECLARE #rows as INT = 0
DECLARE #relevantClaimIds as Table(
Debtor_ID INT,
Claim_ID int
)
SET NOCOUNT ON
--Get anchor condition
INSERT INTO #relevantClaimIds (Debtor_ID, Claim_ID)
select Debtor_ID, ID
from Claim c
WHERE OpenAmount <> 0
--Do recursion
WHILE #rows <> (SELECT COUNT(*) FROM #relevantClaimIds)
BEGIN
set #rows = (SELECT COUNT(*) FROM #relevantClaimIds)
--Subtracted
INSERT #relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM claim c
inner join claimcoupling cc on cc.SubstractedFromClaim_ID = c.ID
JOIN #relevantClaimIds rci on rci.Claim_ID = cc.AddedToClaim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join #relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
--Added
INSERT #relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM claim c
inner join claimcoupling cc on cc.AddedToClaim_ID = c.ID
JOIN #relevantClaimIds rci on rci.Claim_ID = cc.SubstractedFromClaim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join #relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
--Payments
INSERT #relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM #relevantClaimIds f
join ClaimEntryReference cer on f.Claim_ID = cer.Claim_ID
JOIN ClaimEntryReference cer_linked on cer.ClaimEntry_ID = cer_linked.ClaimEntry_ID AND cer.ID <> cer_linked.ID
JOIN Claim c on c.ID = cer_linked.Claim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join #relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
END
Then after I received and read the comment I decided to try the CTE solution which looks like this;
CTE Recursion
with Tree as
(
select Debtor_ID, ID AS Claim_ID, CAST(ID AS VARCHAR(MAX)) AS levels
from Claim c
WHERE OpenAmount <> 0
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM claim c
inner join claimcoupling cc on cc.SubstractedFromClaim_ID = c.ID
JOIN Tree t on t.Claim_ID = cc.AddedToClaim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM claim c
inner join claimcoupling cc on cc.AddedToClaim_ID = c.ID
JOIN Tree t on t.Claim_ID = cc.SubstractedFromClaim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM Tree t
join ClaimEntryReference cer on t.Claim_ID = cer.Claim_ID
JOIN ClaimEntryReference cer_linked on cer.ClaimEntry_ID = cer_linked.ClaimEntry_ID AND cer.ID <> cer_linked.ID
JOIN Claim c on c.ID = cer_linked.Claim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
)
select DISTINCT Tree.Debtor_ID, Tree.Claim_ID
from Tree
This solution is indeed a lot 'shorter' and easier on the eyes but does it actually perform better?
Performance differences
Manual; CPU 16, Reads 1793, Duration 13
CTE; CPU 47, Reads 4001, Duration 48
Conclusion
Not sure if it's due to the varchar cast that is required in the CTE solution or that it has to do one extra iteration before completing it's recursion but it actually requires more resources on all fronts than the manual recursion.
In the end it is possible with CTE however looks aren't everything (thank god ;-)) performance wise sticking with the manual recursion seems like a better route.

Related

Two table recursive lookup without hierarchy, is this possible?

This is the current design of the SQL Server database I am working with.
I have two tables:
recipes
recipe ingredients
Recipes consist of recipe ingredients but an ingredient can be another recipe. In theory there are infinite levels as each recipe can have another ingredient that is also a recipe.
In the above data example, the Fresh Salsa recipe (ID 3047) has 7 ingredients. Six are raw Materials but one is another recipe (Recipe ID 3008). This recipe ID references another recipe in the 'recipes' table.
There is no hierarchy and I don't think I can create a hierarchy.
The goal is to extract all the recipe items for a particular recipe that have a 'sub' recipes and 'sub-sub' recipes etc.
It would seem like a recursive lookup would be the answer but because there is no hierarchy, this doesn't seem to work.
Here's my attempted query (the recipeItem list variable is a list of all the recipeitems that are also recipes created in a previous query):
<cfquery name="whatever">
WITH MenuPrepOfPreps (recipe_id, depth, otherRecipe_id, recipe_name)
AS
(
SELECT r.recipe_id,
0 as depth,
ri.otherRecipe_id,
r.recipe_name
FROM menu_recipes r
JOIN menu_recipeItems ri
ON ri.otherRecipe_id = r.recipe_id
WHERE ri.otherRecipe_id in (#recipeItemList#)
UNION ALL
-- recursive members
SELECT
mop.recipe_id,
mop.depth + 1 as depth,
ri.otherRecipe_id,
r.recipe_name
FROM menu_recipes r
JOIN menu_recipeItems ri
ON ri.otherRecipe_id = r.recipe_id
INNER JOIN MenuPrepOfPreps AS MOP
ON ri.otherRecipe_id = MOP.recipe_id
)
SELECT top(6)recipe_id, recipe_name
FROM MenuPrepOfPreps
GROUP BY recipe_id, recipe_name
</cfquery>
It keeps creating an infinite loop. When I limit the results to the first few rows (top 6), it does give the desired result.
It is possible that the design of the database is not correct so this might never work.
Any help is appreciated.
[UPDATED QUERY BASED ON #NewBie20200101 PROPOSED SOLUTION WITH CHANGES TO VARIABLE/COLUMN NAMES]
<cfquery name="whatever">
WITH MenuPrepOfPreps AS
(
SELECT otherrecipe_id,
CASE
when
otherRecipe_id = 0 then null
else
otherRecipe_id
end
as sub_recipe
FROM menu_recipeItems as a -- anchor
UNION ALL
SELECT
a.otherrecipe_id,
CASE
when
b.otherRecipe_id = 0 then null
else
b.otherRecipe_id
end
as sub_recipe
FROM menu_recipeItems as b
where b.recipe_id = a.otherRecipe_id --recursion
and a.otherRecipe_id is null --stopper
), allrecipeitems as (
SELECT recipe_id, sub_recipe
FROM MenuPrepOfPreps
)
Select
c.recipe_id,
d.otherRecipe_id
From MENU_recipes c
INNER JOIN MENU_recipeItems d on c.recipe_id = d.otherRecipe_id
Where c.recipe in (#recipelist#)
</cfquery>
Does not work and gives the following error:
The multi-part identifier "a.otherRecipe_id" could not be bound.
Not sure if this is gonna work:
With preppreprep as (
Select
Recipeid,
Case when otherrecipeId = 0 then null else otherrecipeID end as otherrecipeID, ——remove 0 it might be a problem
From
Recipeitems as a ——————anchor
Left outer join recipeitems as b on a.otherrecipeID = b.recipeID
Union all
Select
C.recipeid,
Case when c.otherrecipeID = 0 the null else c.other recipeID end as otherrecipeID, also remove 0
From preprepprep as c
Left outer join recipeitems as d
Where c.recipeid = d.otherrecipeID———--recursion
), allrecipeitems as (
Select
RecipeID,
OtherrecipeID
From preprepprep
)
Select
C.RecipeID
D.OtherRecipeID
From recipe c
Inner recipeitems d on c.recipeid = d.recipeid
Where c.recipe in (##)
——extract unpacked sub recipes based on recipe
If you think there are more than 99 levels, add option max recursion 0
I figured this out myself with help from all contributors (thank you). First off as was mentioned by #Charlieface I was approaching this from the wrong direction. With this solution I started from the bottom of the hierarchy and worked up until there is no more data to get in the hierarchy.
The last query has multiple filters and grouping in order to get exactly the information I need.
If anyone is interested, here's the solution:
WITH MenuPrepOfPreps (recipe_id, otherRecipe_id, depth, recipe_name)
AS (
SELECT
mria.recipe_id, mria.otherRecipe_id, 0, mr.recipe_name
FROM
menu_recipeItems mria, menu_recipes mr
where
mria.otherrecipe_id <> 0
and mria.recipe_id = mr.recipe_id
UNION ALL
SELECT
MenuPrepOfPreps.recipe_id, mri.otherrecipe_id, MenuPrepOfPreps.depth+1, mr.recipe_name
FROM
menu_recipeItems mri, MenuPrepOfPreps, menu_recipes mr
WHERE MenuPrepOfPreps.otherrecipe_id = mri.recipe_id
AND MRI.otherrecipe_id <> 0
AND mr.recipe_id = mri.recipe_id
)
SELECT
mopsA.otherrecipe_id, mopsA.recipe_id, mopsA.depth, mra.recipe_name AS thePrepRecipeName
FROM
MenuPrepOfPreps mopsA, MENU_recipes MRA
WHERE
mopsA.recipe_id in (#recipelist#)
AND mopsA.otherRecipe_id = MRA.recipe_id
AND MRA.recipeType_id = 2
GROUP BY mra.recipe_name, mopsA.otherrecipe_id, mopsA.recipe_id, mopsA.depth
I will have to see how this behaves with a larger dataset than the sample set I have now, but so far it's pretty fast.
I would create a table valued function that returns all leaf recipeItem for a single recipe_id (that flattens the hierarchy), without a recursion but a loop that resolves one recursion level at a time (instead of following each single branch). If you have a million sub recipes down to 10 levels deep it takes 12 iterations to get the data (10 (the depth) + 1 (determines nothing has changed anymore) + 1 (to get all the leaves from the recipes).
This approach has also the advantage that no limit must be set, if there is an endless loop defined in the database it does not bother us the slightest.
Here the code for the table valued function:
CREATE FUNCTION [dbo].[GetRecipeItemsFlat](#recipe_id INT)
RETURNS #recipeItems TABLE (recipeItem_id INT NOT NULL)
AS
BEGIN
DECLARE #recipeList TABLE (recipe_id INT);
DECLARE #previousCount INT = 0;
DECLARE #currentCount INT = 1;
-- Get all recipe_ids recursively until infinity (no endless loop possible)
INSERT INTO #recipeList SELECT #recipe_id;
WHILE (#previousCount < #currentCount) BEGIN
-- Adding not yet added child recipe_ids
INSERT INTO #recipeList SELECT DISTINCT otherRecipe_id FROM recipeItem WHERE recipe_id IN (SELECT recipe_id FROM #recipeList) AND (otherRecipe_id IS NOT NULL) AND (otherRecipe_id != 0) AND (otherRecipe_id NOT IN (SELECT recipe_id FROM #recipeList));
SET #previousCount = #currentCount ;
SET #currentCount = (SELECT COUNT(*) FROM #recipeList);
END
INSERT INTO #recipeItems SELECT recipeItem_ID FROM recipeItem WHERE recipe_id IN (SELECT * FROM #recipeList) AND (rawMaterial_id IS NOT NULL) AND (rawMaterial_id != 0);
RETURN;
END
And then I would create a view that provides all leaf recipeItem for a recipe like this:
CREATE VIEW recipeItemFlat
AS
SELECT c.recipe_id, c.recipeItem_id, c.ItemQuantity, c.rawMaterial_id
FROM recipe a
CROSS APPLY dbo.GetRecipeItemsFlat(recipe_id) b
INNER JOIN recipeItem c ON b.recipeItem_id = c.recipeItem_id;
And then the query to get all items for recipe 3047 is trivial:
SELECT * FROM recipeItemFlat WHERE recipe_id = 3047
There is one possible issue with this solution which I usually use for rights and roles resolutions and such things where it does not matter but here it could:
If there is a recipe A that has two recipes B and C and both B and C have a recipe D, then D is going to be only once in the result and adding up the quantities would give a wrong result. If that is relevant, don't use DISTINCT to determine the end of the loop in the table valued function but a maximum level like you did in your example.

Rewrite query without using temp table

I have a query that is using a temp table to insert some data then another select from to extract distinct results. That query by it self was fine but now with entity-framework it is causing all kinds of unexpected errors at the wrong time.
Is there any way I can rewrite the query not to use a temp table? When this is converted into a stored procedure and in entity framework the result set is of type int which throws an error:
Could not find an implementation of the query pattern Select not found.
Here is the query
Drop Table IF EXISTS #Temp
SELECT
a.ReceiverID,
a.AntennaID,
a.AntennaName into #Temp
FROM RFIDReceiverAntenna a
full join Station b ON (a.ReceiverID = b.ReceiverID) and (a.AntennaID = b.AntennaID)
where (a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
select distinct r.ReceiverID, r.ReceiverName, r.receiverdescription
from RFIDReceiver r
inner join #Temp t on r.ReceiverID = t.ReceiverID;
No need for anything fancy, you can just replace the reference to #temp with an inner sub-query containing the query that generates #temp e.g.
select distinct r.ReceiverID, r.ReceiverName, r.receiverdescription
from RFIDReceiver r
inner join (
select
a.ReceiverID,
a.AntennaID,
a.AntennaName
from RFIDReceiverAntenna a
full join Station b ON (a.ReceiverID = b.ReceiverID) and (a.AntennaID = b.AntennaID)
where (a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
) t on r.ReceiverID = t.ReceiverID;
PS: I haven't made any effort to improve the query overall like Gordon has but do consider his suggestions.
First, a full join makes no sense in the first query. You are selecting only columns from the first table, so you need that.
Second, you can use a CTE.
Third, you should be able to get rid of the SELECT DISTINCT by using an EXISTS condition.
I would suggest:
WITH ra AS (
SELECT ra.*
FROM RFIDReceiverAntenna ra
Station s
ON s.ReceiverID = ra.ReceiverID AND
s.AntennaID = ra.AntennaID)
WHERE s.ReceiverID is NULL
)
SELECT r.ReceiverID, r.ReceiverName, r.receiverdescription
FROM RFIDReceiver r
WHERE EXISTS (SELECT 1
FROM ra
WHERE r.ReceiverID = ra.ReceiverID
);
You can use CTE instead of the temp table:
WITH
CTE
AS
(
SELECT
a.ReceiverID,
a.AntennaID,
a.AntennaName
FROM
RFIDReceiverAntenna a
full join Station b
ON (a.ReceiverID = b.ReceiverID)
and (a.AntennaID = b.AntennaID)
where
(a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
)
select distinct
r.ReceiverID, r.ReceiverName, r.receiverdescription
from
RFIDReceiver r
inner join CTE t on r.ReceiverID = t.ReceiverID
;
This query will return the same results as your original query with the temp table, but its performance may be quite different; not necessarily slower, it can be faster. Just something that you should be aware about.

SQL query: Iterate over values in table and use them in subquery

I have a simple SQL table containing some values, for example:
id | value (table 'values')
----------
0 | 4
1 | 7
2 | 9
I want to iterate over these values, and use them in a query like so:
SELECT value[0], x1
FROM (some subquery where value[0] is used)
UNION
SELECT value[1], x2
FROM (some subquery where value[1] is used)
...
etc
In order to get a result set like this:
4 | x1
7 | x2
9 | x3
It has to be in SQL as it will actually represent a database view. Of course the real query is a lot more complicated, but I tried to simplify the question while keeping the essence as much as possible.
I think I have to select from values and join the subquery, but as the value should be used in the subquery I'm lost on how to accomplish this.
Edit: I oversimplified my question; in reality I want to have 2 rows from the subquery and not only one.
Edit 2: As suggested I'm posting the real query. I simplified it a bit to make it clearer, but it's a working query and the problem is there. Note that I have hardcoded the value '2' in this query two times. I want to replace that with values from a different table, in the example table above I would want a result set of the combined results of this query with 4, 7 and 9 as values instead of the currently hardcoded 2.
SELECT x.fantasycoach_id, SUM(round_points)
FROM (
SELECT DISTINCT fc.id AS fantasycoach_id,
ffv.formation_id AS formation_id,
fpc.round_sequence AS round_sequence,
round_points,
fpc.fantasyplayer_id
FROM fantasyworld_FantasyCoach AS fc
LEFT JOIN fantasyworld_fantasyformation AS ff ON ff.id = (
SELECT MAX(fantasyworld_fantasyformationvalidity.formation_id)
FROM fantasyworld_fantasyformationvalidity
LEFT JOIN realworld_round AS _rr ON _rr.id = round_id
LEFT JOIN fantasyworld_fantasyformation AS _ff ON _ff.id = formation_id
WHERE is_valid = TRUE
AND _ff.coach_id = fc.id
AND _rr.sequence <= 2 /* HARDCODED USE OF VALUE */
)
LEFT JOIN fantasyworld_FantasyFormationPlayer AS ffp
ON ffp.formation_id = ff.id
LEFT JOIN dbcache_fantasyplayercache AS fpc
ON ffp.player_id = fpc.fantasyplayer_id
AND fpc.round_sequence = 2 /* HARDCODED USE OF VALUE */
LEFT JOIN fantasyworld_fantasyformationvalidity AS ffv
ON ffv.formation_id = ff.id
) x
GROUP BY fantasycoach_id
Edit 3: I'm using PostgreSQL.
SQL works with tables as a whole, which basically involves set operations. There is no explicit iteration, and generally no need for any. In particular, the most straightforward implementation of what you described would be this:
SELECT value, (some subquery where value is used) AS x
FROM values
Do note, however, that a correlated subquery such as that is very hard on query performance. Depending on the details of what you're trying to do, it may well be possible to structure it around a simple join, an uncorrelated subquery, or a similar, better-performing alternative.
Update:
In view of the update to the question indicating that the subquery is expected to yield multiple rows for each value in table values, contrary to the example results, it seems a better approach would be to just rewrite the subquery as the main query. If it does not already do so (and maybe even if it does) then it would join table values as another base table.
Update 2:
Given the real query now presented, this is how the values from table values could be incorporated into it:
SELECT x.fantasycoach_id, SUM(round_points) FROM
(
SELECT DISTINCT
fc.id AS fantasycoach_id,
ffv.formation_id AS formation_id,
fpc.round_sequence AS round_sequence,
round_points,
fpc.fantasyplayer_id
FROM fantasyworld_FantasyCoach AS fc
-- one row for each combination of coach and value:
CROSS JOIN values
LEFT JOIN fantasyworld_fantasyformation AS ff
ON ff.id = (
SELECT MAX(fantasyworld_fantasyformationvalidity.formation_id)
FROM fantasyworld_fantasyformationvalidity
LEFT JOIN realworld_round AS _rr
ON _rr.id = round_id
LEFT JOIN fantasyworld_fantasyformation AS _ff
ON _ff.id = formation_id
WHERE is_valid = TRUE
AND _ff.coach_id = fc.id
-- use the value obtained from values:
AND _rr.sequence <= values.value
)
LEFT JOIN fantasyworld_FantasyFormationPlayer AS ffp
ON ffp.formation_id = ff.id
LEFT JOIN dbcache_fantasyplayercache AS fpc
ON ffp.player_id = fpc.fantasyplayer_id
-- use the value obtained from values again:
AND fpc.round_sequence = values.value
LEFT JOIN fantasyworld_fantasyformationvalidity AS ffv
ON ffv.formation_id = ff.id
) x
GROUP BY fantasycoach_id
Note in particular the CROSS JOIN which forms the cross product of two tables; this is the same thing as an INNER JOIN without any join predicate, and it can be written that way if desired.
The overall query could be at least a bit simplified, but I do not do so because it is a working example rather than an actual production query, so it is unclear what other changes would translate to the actual application.
In the example I create two tables. See how outer table have an alias you use in the inner select?
SQL Fiddle Demo
SELECT T.[value], (SELECT [property] FROM Table2 P WHERE P.[value] = T.[value])
FROM Table1 T
This is a better way for performance
SELECT T.[value], P.[property]
FROM Table1 T
INNER JOIN Table2 p
on P.[value] = T.[value];
Table 2 can be a QUERY instead of a real table
Third Option
Using a cte to calculate your values and then join back to the main table. This way you have the subquery logic separated from your final query.
WITH cte AS (
SELECT
T.[value],
T.[value] * T.[value] as property
FROM Table1 T
)
SELECT T.[value], C.[property]
FROM Table1 T
INNER JOIN cte C
on T.[value] = C.[value];
It might be helpful to extract the computation to a function that is called in the SELECT clause and is executed for each row of the result set
Here's the documentation for CREATE FUNCTION for SQL Server. It's probably similar to whatever database system you're using, and if not you can easily Google for it.
Here's an example of creating a function and using it in a query:
CREATE FUNCTION DoComputation(#parameter1 int)
RETURNS int
AS
BEGIN
-- Do some calculations here and return the function result.
-- This example returns the value of #parameter1 squared.
-- You can add additional parameters to the function definition if needed
DECLARE #Result int
SET #Result = #parameter1 * #parameter1
RETURN #Result
END
Here is an example of using the example function above in a query.
SELECT v.value, DoComputation(v.value) as ComputedValue
FROM [Values] v
ORDER BY value

PostgreSQL - how to query "result IN ALL OF"?

I am new to PostgreSQL and I have a problem with the following query:
WITH relevant_einsatz AS (
SELECT einsatz.fahrzeug,einsatz.mannschaft
FROM einsatz
INNER JOIN bergefahrzeug ON einsatz.fahrzeug = bergefahrzeug.id
),
relevant_mannschaften AS (
SELECT DISTINCT relevant_einsatz.mannschaft
FROM relevant_einsatz
WHERE relevant_einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,relevant_mannschaften WHERE mannschaft.leiter = person.id AND relevant_mannschaften.mannschaft=mannschaft.id;
This query is working basically - but in "relevant_mannschaften" I am currently selecting each mannschaft, which has been to an relevant_einsatz with at least 1 bergefahrzeug.
Instead of this, I want to select into "relevant_mannschaften" each mannschaft, which has been to an relevant_einsatz WITH EACH from bergefahrzeug.
Does anybody know how to formulate this change?
The information you provide is rather rudimentary. But tuning into my mentalist skills, going out on a limb, I would guess this untangled version of the query does the job much faster:
SELECT m.id, m.rufname, p.id, p.nachname
FROM person p
JOIN mannschaft m ON m.leiter = p.id
JOIN (
SELECT e.mannschaft
FROM einsatz e
JOIN bergefahrzeug b ON b.id = e.fahrzeug -- may be redundant
GROUP BY e.mannschaft
HAVING count(DISTINCT e.fahrzeug)
= (SELECT count(*) FROM bergefahrzeug)
) e ON e.mannschaft = m.id
Explain:
In the subquery e I count how many DISTINCT mountain-vehicles (bergfahrzeug) have been used by a team (mannschaft) in all their deployments (einsatz): count(DISTINCT e.fahrzeug)
If that number matches the count in table bergfahrzeug: (SELECT count(*) FROM bergefahrzeug) - the team qualifies according to your description.
The rest of the query just fetches details from matching rows in mannschaft and person.
You don't need this line at all, if there are no other vehicles in play than bergfahrzeuge:
JOIN bergefahrzeug b ON b.id = e.fahrzeug
Basically, this is a special application of relational division. A lot more on the topic under this related question:
How to filter SQL results in a has-many-through relation
Do not know how to explain it, but here is an example how I solved this problem, just in case somebody has the some question one day.
WITH dfz AS (
SELECT DISTINCT fahrzeug,mannschaft FROM einsatz WHERE einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
), abc AS (
SELECT DISTINCT mannschaft FROM dfz
), einsatzmannschaften AS (
SELECT abc.mannschaft FROM abc WHERE (SELECT sum(dfz.fahrzeug) FROM dfz WHERE dfz.mannschaft = abc.mannschaft) = (SELECT sum(bergefahrzeug.id) FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,einsatzmannschaften WHERE mannschaft.leiter = person.id AND einsatzmannschaften.mannschaft=mannschaft.id;

SQL Condition based dataset

I have a SQL Server database that I did not design.
The employees have degrees, licensures and credentials stored in a few different tables.
I have written the query to join all of this information together so I can see an over all result of what the data looks like. I have been asked to create a view for this data that returns only the highest degree they have obtained and the two highest certifications.
The problem is, as it is pre existing data, there is no hierarchy built into the data. All of the degrees and certifications are simply stored as a string associated with their employee number.
The first logical step was to create an adjacency list(I believe this is the correct term).
For example 'MD' is the highest degree you can obtain in our list. So I have given that the "ranking" of 1. The next lower degree is "ranked" as 2. and so forth.
I can join on the text field that contains these and return their associated rank.
The problem I am having is returning only the two highest based on this ranking.
If the employee has multiple degrees or certifications they are listed on a second or third row. From a logical standpoint, I need to group the employee ID, First name and Last name. Then some how concatenate the degrees, certifications and licensures based on the "ranking" I created for them. It is not a true hierarchy in the way that I am thinking about it because I only need to know the highest two and not necessarily the relationship between the results.
Another potential caveat is that the database must remain in SQL Server 2000 compatibility mode.
Any help that can be given would be much appreciated. Thank you.
select a.EduRank as 'Licensure Rank',
b.EduRank as 'Degree Rank',
EmpComp.EecEmpNo,
EmpPers.EepNameFirst,
EmpPers.EepNameLast,
RTRIM(EmpEduc.EfeLevel),
RTRIM(EmpLicns.ElcLicenseID),
a.EduType,
b.EduType
from empcomp
join EmpPers on empcomp.eeceeid = EmpPers.eepEEID
join EmpEduc on empcomp.Eeceeid = EmpEduc.EfeEEID
join EmpLicns on empcomp.eeceeid = EmpLicns.ElcEEID
join yvDegreeRanks a on a.EduCode = EmpLicns.ElcLicenseID
join yvDegreeRanks b on b.EduCode = EmpEduc.EfeLevel
I think I can see what your problem is - however I'm not sure. Joining the tables together has given you "double rows". The "quick-and-dirty" way to solve this query, would be to use Subqueries other than Joins. Doing so, you can select only the TOP 1 Degree, and TOP 2 certifications.
EDIT : Can you try this query ?
SELECT *
FROM employSELECT tblLicensures.EduRank as 'Licensure Rank',
tblDegrees.EduRank as 'Degree Rank',
EmpComp.EecEmpNo,
EmpPers.EepNameFirst,
EmpPers.EepNameLast,
RTRIM(tblDegrees.EfeLevel),
RTRIM(tblLicensures.ElcLicenseID),
tblLicensures.EduType,
tblDegrees.EduType
FROM EmpComp
LEFT OUTER JOIN EmpPers ON empcom.eeceeid = EmpPers.eepEEID
LEFT OUTER JOIN
-- Select TOP 2 Licensure Ranks
(
SELECT TOP 2 a.EduType, a.EduRank, EmpLicns.ElcEEID
FROM yvDegreeRanks a
INNER JOIN EmpLicns on a.EduCode = EmpLicns.ElcLicenseID
WHERE EmpLincs.ElcEEID = empcomp.eeceeid
ORDER BY a.EduRank ASC
) AS tblLicensures ON tblLicensures.ElcEEID = empcomp.Eeceeid
LEFT OUTER JOIN
-- SELECT TOP 1 Degree
(
SELECT TOP 1 b.EduType, b.EduRank, EmpEduc.EfeEEID, EmpEduc.EfeLevel
FROM yvDegreeRanks b
INNER JOIN EmpEduc on b.EduCode = EmpEduc.EfeLevel
WHERE EmpEduc.EfeEEID = empcomp.Eeceeid
ORDER BY b.EduRank ASC
) AS tblDegrees ON tblDegrees.EfeEEID = empcomp.Eeceeid
This is not the most elegant solution, but hopefully it will at least help you out in some way.
create table #dataset (
licensurerank [datatype],
degreerank [datatype],
employeeid [datatype],
firstname varchar,
lastname varchar,
efeLevel [datatype],
elclicenseid [datatype],
edutype1 [datatype],
edutype2 [datatype]
)
select distinct identity(int,1,1) [ID], EecEmpNo into #employeeList from EmpComp
declare
#count int,
#rows int,
#employeeNo int
select * from #employeeList
set #rows = ##rowcount
set #count = 1
while #count <= #ROWS
begin
select #employeeNo = EecEmpNo from #employeeList where id = #count
insert into #dataset
select top 2 a.EduRank as 'Licensure Rank',
b.EduRank as 'Degree Rank',
EmpComp.EecEmpNo,
EmpPers.EepNameFirst,
EmpPers.EepNameLast,
RTRIM(EmpEduc.EfeLevel),
RTRIM(EmpLicns.ElcLicenseID),
a.EduType,
b.EduType
from empcomp
join EmpPers on empcomp.eeceeid = EmpPers.eepEEID
join EmpEduc on empcomp.Eeceeid = EmpEduc.EfeEEID
join EmpLicns on empcomp.eeceeid = EmpLicns.ElcEEID
join yvDegreeRanks a on a.EduCode = EmpLicns.ElcLicenseID
join yvDegreeRanks b on b.EduCode = EmpEduc.EfeLevel
where EmpComp.EecEmpNo = #employeeNo
set #count = #count + 1
end
Have tables for employees, types of degrees (including a rank), types of certs (including a rank), and join tables employees_degrees and employees_certs. [It might be better to put degrees and certs in one table with a flag is_degree, if all their other fields are the same.] You can extract the existing string values and replace them with FK ids into the degree and cert tables.
The query itself is harder, because PARTITION BY is not available in SQL Server 2000 (according to Google). UW's answer has at least two problems: you need LEFT JOINs because not all employees have degrees and certs, and there is no ORDER BY to show what you want to take the best of. TOP 2 subqueries are particularly difficult to use in this context. So for that, I can't yet give an answer.