I am still learning many new things about SQL such as PARTITION BY and CTEs. I am currently working on a query which I have cobbled together from a similar question I found online. However, I can not seem to get it to work as intended.
The problem is as follows -- I have been tasked to show rank promotions in an organization from the begining of 2022 to today. I am working with 2 primary tables, an EMPLOYEES table and a PERIODS table. This periods table captures a snapshot of any given employee each month - including their rank at the time. Each of these months is also assigned a PeriodID (e.g. Jan 2022 = PeriodID 131). Our EMPLOYEE table holds the employees current rank. These ranks are stored as an int (e.g. 1,2,3 with 1 being lowest rank). It is possible for an employee to rank up more than once in any given month.
I have simplified the used query as much as I can for the sake of this problem. Query follows as:
;WITH x AS
(
SELECT
e.EmployeeID, p.PeriodID, p.RankID,
rn = ROW_NUMBER() OVER (PARTITION BY e.EmployeeID ORDER BY p.PeriodID DESC)
FROM employees e
LEFT JOIN periods p on p.EmployeeID= e.EmployeeID
WHERE p.PeriodID <= 131 AND p.PeriodID >=118 --This is the time range mentioned above
),
rest AS (SELECT * FROM x WHERE rn > 1)
SELECT
main.EmployeeID,
PeriodID = MIN(
CASE
WHEN main.CurrentRankID = Rest.RankID
THEN rest.PeriodID ELSE main.PeriodID
END),
main.RankID, rest.RankID
FROM x AS main LEFT OUTER JOIN rest ON main.EmployeeID = rest.EmployeeID
AND rest.rn >1
LEFT JOIN periods p on p.EmployeeID = e.EmployeeID
WHERE main.rn = 1
AND NOT EXISTS
(
SELECT 1 FROM rest AS rest2
WHERE EmployeeID = rest.EmployeeID
AND rn < rest.rn
AND main.RankID <> rest.RankID
)
and p.PeriodID <= 131 AND p.PeriodID >=118
GROUP BY main.EmployeeID, main.PeriodID, main.RankID, rest.RankID
As mentioned before, this query was borrowed from a similar question and modified for my own use. I imagine the bones of the query is good and maybe I have messed up a variable somewhere but I can not seem to locate the problem line. The end goal is for the query to result in a table showing the EmployeeID, PeriodID, the rank they are being promoted from, and the rank they are being promoted to in the month the promotion was earned. Similar to the below.
EmployeeID
PeriodID
PerviousRankID
NewRank
123
131
1
2
123
133
2
3
Instead, my query is spitting out repeating previous/current ranks and the PeriodIDs seem to be static (such as what is shown below).
EmployeeID
PeriodID
PerviousRankID
NewRank
123
131
1
1
123
131
1
1
I am hoping someone with a greater knowledge base on these functions is able to quickly notice my mistake.
If we assume some example DML/DDL (it's really helpful to provide this with your question):
DECLARE #Employees TABLE (EmployeeID INT IDENTITY, Name VARCHAR(20), RankID INT);
DECLARE #Periods TABLE (PeriodID INT, EmployeeID INT, RankID INT);
INSERT INTO #Employees (Name, RankID) VALUES ('Jonathan', 10),('Christopher', 10),('James', 10),('Jean-Luc', 8);
INSERT INTO #Periods (PeriodID, EmployeeID, RankID) VALUES
(1,1,1),(2,1,1),(3,1,1),(4,1,8 ),(5,1,10),(6,1,10),
(1,2,1),(2,2,1),(3,2,1),(4,2,8 ),(5,2,8 ),(6,2,10),
(1,3,1),(2,3,1),(3,3,7),(4,3,10),(5,3,10),(6,3,10),
(1,4,1),(2,4,1),(3,4,1),(4,4,8 ),(5,4,9 ),(6,4,9 )
Then we can accomplish what I think you're looking for using a OUTER APPLY then aggregates the values based on the current-row values:
SELECT e.EmployeeID, e.Name, e.RankID AS CurrentRank, ap.PeriodID AS ThisPeriod, p.PeriodID AS LastRankChangePeriodID, p.RankID AS LastRankChangedFrom, ap.RankID - p.RankID AS LastRankChanged
FROM #Employees e
LEFT OUTER JOIN #Periods ap
ON e.EmployeeID = ap.EmployeeID
OUTER APPLY (
SELECT EmployeeID, MAX(PeriodID) AS PeriodID
FROM #Periods
WHERE EmployeeID = e.EmployeeID
AND RankID <> ap.RankID
AND PeriodID < ap.PeriodID
GROUP BY EmployeeID
) a
LEFT OUTER JOIN #Periods p
ON a.EmployeeID = p.EmployeeID
AND a.PeriodID = p.PeriodID
ORDER BY e.EmployeeID, ap.PeriodID DESC
Using the correlated subquery we get a view of the data which we can filter using the current-row values, and we aggregate that to return the period we're looking for (where it's before this period, and it's not the same rank). Then it's just a join back to the Periods table to get the values.
You used an LEFT JOIN, so I've preserved that using an OUTER APPLY. If you wanted to filter using it, it would be a CROSS APPLY instead.
EmployeeID
Name
CurrentRank
ThisPeriod
LastRankChangePeriodID
LastRankChangedFrom
LastRankChanged
1
Jonathan
10
6
4
8
2
1
Jonathan
10
5
4
8
2
1
Jonathan
10
4
3
1
7
1
Jonathan
10
3
1
Jonathan
10
2
1
Jonathan
10
1
2
Christopher
10
6
5
8
2
2
Christopher
10
5
3
1
7
2
Christopher
10
4
3
1
7
2
Christopher
10
3
2
Christopher
10
2
2
Christopher
10
1
3
James
10
6
3
7
3
3
James
10
5
3
7
3
3
James
10
4
3
7
3
3
James
10
3
2
1
6
3
James
10
2
3
James
10
1
4
Jean-Luc
8
6
5
9
-1
4
Jean-Luc
8
5
4
8
1
4
Jean-Luc
8
4
3
1
7
4
Jean-Luc
8
3
4
Jean-Luc
8
2
4
Jean-Luc
8
1
Now we can see what the previous change looked like for each period. Currently Jonathan is has RankID 10. Last time that was different was in PeriodID 4 when it was 8. The same was true for PeriodID 5. In PeriodID 4 he had RankID 8, and prior to that he had RankID 1. Before that his Rank hadn't changed.
Jean-Luc was actually demoted as his last change. I don't know if this is possible within your model.
Related
I want to know who has the most friends from the app I own(transactions), which means it can be either he got paid, or paid himself to many other users.
I can't make the query to show me only those who have the max friends number (it can be 1 or many, and it can be changed so I can't use limit).
;with relationships as
(
select
paid as 'auser',
Member_No as 'afriend'
from Payments$
union all
select
member_no as 'auser',
paid as 'afriend'
from Payments$
),
DistinctRelationships AS (
SELECT DISTINCT *
FROM relationships
)
select
afriend,
count(*) cnt
from DistinctRelationShips
GROUP BY
afriend
order by
count(*) desc
I just can't figure it out, I've tried count, max(count), where = max, nothing worked.
It's a two columns table - "Member_No" and "Paid" - member pays the money, and the paid is the one who got the money.
Member_No
Paid
14
18
17
1
12
20
12
11
20
8
6
3
2
4
9
20
8
10
5
20
14
16
5
2
12
1
14
10
It's from Excel, but I loaded it into sql-server.
It's just a sample, there are 1000 more rows
It seems like you are massively over-complicating this. There is no need for self-joining.
Just unpivot each row so you have both sides of the relationship, then group it up by one side and count distinct of the other side
SELECT
-- for just the first then SELECT TOP (1)
-- for all that tie for the top place use SELECT TOP (1) WITH TIES
v.Id,
Relationships = COUNT(DISTINCT v.Other),
TotalTransactions = COUNT(*)
FROM Payments$ p
CROSS APPLY (VALUES
(p.Member_No, p.Paid),
(p.Paid, p.Member_No)
) v(Id, Other)
GROUP BY
v.Id
ORDER BY
COUNT(DISTINCT v.Other) DESC;
db<>fiddle
I have a data set produced from a UNION query that aggregates data from 2 sources.
I want to select that data based on whether or not data was found in only of those sources,or both.
The data relevant parts of the set looks like this, there are a number of other columns:
row
preference
group
position
1
1
111
1
2
1
111
2
3
1
111
3
4
1
135
1
5
1
135
2
6
1
135
3
7
2
111
1
8
2
135
1
The [preference] column combined with the [group] column is what I'm trying to filter on, I want to return all the rows that have the same [preference] as the MIN([preference]) for each [group]
The desired output given the data above would be rows 1 -> 6
The [preference] column indicates the original source of the data in the UNION query so a legitimate data set could look like:
row
preference
group
position
1
1
111
1
2
1
111
2
3
1
111
3
4
2
111
1
5
2
135
1
In which case the desired output would be rows 1,2,3, & 5
What I can't work out is how to do (not real code):
SELECT * WHERE [preference] = MIN([preference]) PARTITION BY [group]
One way to do this is using RANK:
SELECT row
, preference
, [group]
, position
FROM (
SELECT row
, preference
, [group]
, position
, RANK() OVER (PARTITION BY [group] ORDER BY preference) AS seq
FROM t) t2
WHERE seq = 1
Demo here
Should by doable via simple inner join:
SELECT t1.*
FROM t AS t1
INNER JOIN (SELECT [group], MIN(preference) AS preference
FROM t
GROUP BY [group]
) t2 ON t1.[group] = t2.[group]
AND t1.preference = t2.preference
Forgive me for adding yet another JOIN question, but I've been stumped all day and haven't been able to find a good answer for this.
I'm trying to join 4 tables, such that they look like below:
QuarterID ReviewID SaleID PotentialID
1 1 1 1
1 2 2 null
1 3 null 2
1 4 null null
The relevant info from the tables is below
Sale:
QuarterID
ReviewID
IsArchived
Potential:
QuarterID
ReviewID
IsArchived
Quarter:
ID
Review:
ID
We can have multiple Sales and Potentials associated with one Quarter-Review pairing, but only one Sale and one Potential will have IsArchived = 0 for the given Quarter-Review pairing.
SELECT
quarter.id AS QID,
review.id AS RID,
Sales.id AS SID,
Potentials.id AS PID
FROM
dbo.quarter
JOIN
(SELECT *
FROM dbo.sale
WHERE isarchived = 0) AS Sales ON Sales.quarterid = quarter.id
JOIN
(SELECT *
FROM dbo.potential
WHERE isarchived = 0) AS Potentials ON Potentials.quarterid = quarter.id
JOIN
dbo.review ON dbo.review.id = Sales.reviewid
AND dbo.review.id = Potentials.reviewid
ORDER BY
quarter.id, rid
Using the above (there are some unnecessary columns, I know), I've managed to get the joins so that they get the 1st condition (where its all the Sales and Potentials that are in the same Quarter and Review combination, but I also want to see if there is a Quarter/Review combo with only a Sale and no Potential, if there is a Q/R combo with only a Potential and no Sale, and just every Quarter and Review combo, since there are only a few Q/R combos that have both a Sale and Potential, with almost all of the Q/R combos only having a Sale or Potential.
I guess overall the difficulty comes from needing to get the join from two intermediate tables. I can join Quarter, Sale, and Review easily, but having the Potential table joining on the same fields (ReviewID, QuarterID) as Sale is making me only get the AND, and I can't figure out an OR. I've been throwing around ORs for hours trying to get the right sequence without any luck. Help?
--Edit to include sample data--
Quarter
ID
1
2
Review
ID (Other fields, not relevant to join)
1
2
3
4
5
Sale
ID ReviewID QuarterID isArchived (Other fields, not relevant)
1 1 1 0
2 2 1 1
3 2 1 0
4 1 2 0
5 5 1 0
6 5 2 0
Potential
ID ReviewID QuarterID isArchived (Other fields, not relevant)
1 1 1 0
2 3 1 0
3 4 2 1
4 4 2 0
5 5 2 0
Joining the above sample data, I would expect the output to look like:
QuarterID ReviewID SaleID PotentialID
1 1 1 1
1 2 3 null
1 3 null 2
1 4 null null
1 5 5 null
2 1 4 null
2 2 null null
2 3 null null
2 4 null 4
2 5 6 5
But the problem I am having is I am only returning the rows like the first and last row, where there is both a Sale and Potential for a given Quarter/Review combo, and not the ones where one or many may be null.
Not sure if I understood your question correctly (some sample data will help) but I think you mean that you need all the combinations of Quarter and Review and then any related Sale and Potential data for each combination of Quarter and Review. If that is what you need, then try the below query:
SELECT [Quarter].ID AS QID, Review.ID AS RID, Sales.ID AS SID, Potentials.ID AS PID FROM [Quarter]
CROSS JOIN [Review]
LEFT JOIN (SELECT * FROM Sale WHERE IsArchived = 0) Sales ON [Quarter].ID = Sales.QuarterID AND [Review].ID = Sales.ReviewID
LEFT JOIN (SELECT * FROM Potential WHERE IsArchived = 0) Potentials ON [Quarter].ID = Potentials.QuarterID AND [Review].ID = Potentials.ReviewID
I am new to the recursive CTE concept and a problem at hand, I got a tiny feeling that the problem can be solved by using recursive CTE. Let me know what you guys think.
Two tables:
Table one is a self referencing Location table with ID, ParentID, Level and Description.
Table two is an asset table which records individual assets and has a foreign key to Location table ID field.
Table1:
ID Description ParentID Level
1 Site1 NULL 1
2 Site2 NULL 1
3 Building1 1 2
4 Building2 1 2
5 Floor1 3 3
6 Floor2 3 3
7 Floor3 4 3
8 Place1 5 4
9 Place2 7 4
Table2:
ID Description Quantity LocationID
1 Desk 3 8
2 Lamp 1 8
3 PC 10 9
I would like to create a stored procedure with a input parameter of #Level and returns all the Location records at that level and the number of assets within the location (including sub levels).
For example, if #Level = 3, the stored procedure should return:
ID Description AssetCount
5 Floor1 4
6 Floor2 0
7 Floor3 10
If #Level = 2, the stored procedure should return:
ID Description AssetCount
3 Building1 4
4 Building2 10
If the problem is not clear, please let me know.
Well, nothing special here, just a recursive CTE joined with the other table, and the results are what you expected:
declare #level int = 3
;with CTE as (
select id as origid, id, Description, parentid
from table1 where level = #level
union all
select CTE.origid, t1.id, CTE.Description, t1.parentid
from CTE join table1 t1 on
CTE.id = t1.parentid
)
select origid, CTE.description, isnull(sum(t2.Quantity),0) as Quantity
from CTE left outer join table2 t2 on CTE.id = t2.locationid
group by origid, CTE.description
SQL Fiddle
I am looking to join 2 tables with top n results of other table as explained below.
OrderHeader
OH_Id OrderDate
----------------------
1 2014-06-01
2 2014-06-02
3 2014-06-03
4 2014-06-04
5 2014-06-05
OrderProducts
OP_Id OH_Id Quantity
------------------------------
1 1 1
2 1 2
3 2 1
4 3 3
5 4 4
6 4 1
7 4 2
8 5 2
9 5 1
I am expecting result something like this for top 3 orders (4 rows).
OH_Id OrderDate Op_Id Quantity
------------------------------------------------
1 2014-06-01 1 1
1 2014-06-01 2 2
2 2014-06-02 3 1
3 2014-06-03 4 3
Note: I am looking specifically to join 2 tables rather writing as SP or looped queries.
select top 3 o.oh_id, o.orderdate, oo.op_id, oo.quantity
from orderheader o
join orderproducts oo on o.oh_id = oo.oh_id
If you want the first 3 order numbers from OrderHeader with all corresponding rows from OrderProducts try this.
select o.oh_id
,o.orderdate
,oo.op_id
,oo.quantity
from (SELECT TOP 3 *
FROM orderheader
ORDER BY OH_ID --or Date etc...
) o
INNER JOIN orderproducts oo
on o.oh_id = oo.oh_id
I think your description is confusing. You don't want top 3 as that will return only 3 rows. You just want ids 1-3 from what it sounds like.
SELECT *
FROM OrderHeader a
JOIN OrderHeader b on a.oh_id = b.oh_id
WHERE a.oh_id <= 3
you have to use a sub query like this
SELECT * FROM OrderHeader
INNER JOIN OrderProducts ON OrderHeader.OH_Id = OrderProducts.OH_Id
WHERE OrderHeader.OH_Id IN (SELECT TOP 3 OH_Id FROM OrderHeader)
A test sql fiddle is here
hope this helps