Retrieve rows in SQL based on Maximum value in multiple columns - sql

I have a SQL table with the following fields:
Company ID
Company Name
Fiscal Year
Fiscal Quarter
There are multiple records for various fiscal years and fiscal quarters for each company. I want to retrieve the rows for each company based on Maximum Fiscal Year and Maximum Fiscal Quarter. For example, if the table has the following:
Company ID | Company Name | Fiscal Year | Fiscal Quarter
1 | Test1 | 2017 | 1
1 | Test1 | 2017 | 2
1 | Test1 | 2018 | 1
1 | Test1 | 2018 | 2
2 | Test2 | 2018 | 3
2 | Test2 | 2018 | 4
The query should return the following (Only the record with the maximum fiscal year and maximum fiscal quarter for that year):
Company ID | Company Name | Fiscal Year | Fiscal Quarter
1 | Test1 | 2018 | 2
2 | Test2 | 2018 | 4
I am able to use the below query to get the records with the maximum fiscal year but not sure how to further select the maximum quarter within the year:
SELECT fp.companyId, fp.companyname, fp.fiscalyear,fp.fiscalquarter
FROM dbo.ciqFinPeriod fp
LEFT OUTER JOIN dbo.ciqFinPeriod fp2
ON (fp.companyId = fp2.companyId AND fp.fiscalyear < fp2.fiscalyear)
WHERE fp2.companyId IS NULL
Thank you so much for any assistance!

If you have a list of companies, I would simply do:
select fp.*
from Companies c outer apply
(select top (1) fp.*
from dbo.ciqFinPeriod fp
where fp.companyId = c.companyId
order by fp.fiscalyear desc, fp.fiscalquarter desc
) fp;
If not, then row_number() is probably the simplest method:
select fp.*
from (select fp.*,
row_number() over (partition by fp.companyId order by order by fp.fiscalyear desc, fp.fiscalquarter desc) as seqnum
from dbo.ciqFinPeriod fp
) fp
where seqnum = 1;
Or the somewhat more abstruse (clever ?):
select top (1) with ties fp.*
from dbo.ciqFinPeriod fp
order by row_number() over (partition by fp.companyId order by order by fp.fiscalyear desc, fp.fiscalquarter desc)

I've had some success with the following, same output as you.
create table #table
(
CompanyID int,
CompanyName varchar(200),
Year int,
Quater int
)
insert into #table (CompanyID,CompanyName,Year,Quater)
VALUES
('1','Test1','2017','1'),
('1','Test1','2017','2'),
('1','Test1','2018','1'),
('1','Test1','2018','2'),
('2','Test2','2018','3'),
('2','Test2','2018','4')
SELECT CompanyID,CompanyName,Year,Quater
FROM
(
Select CompanyID,CompanyName,Year,Quater
, ROW_NUMBER() OVER(PARTITION BY CompanyID ORDER BY Year desc,Quater DESC)
as RowNum
from #table
) X WHERE RowNum = 1
drop table #table

Select Company I'd, company name,Max(year),Max(quarter) group by 1,2

Related

Find Customers With 4 Consecutive Years of Giving (Including Gaps)

I have a table similar to below:
+------------+-----------+
| CustomerID | OrderYear |
+------------+-----------+
| 1 | 2012 |
| 1 | 2013 |
| 1 | 2014 |
| 1 | 2017 |
| 1 | 2018 |
| 2 | 2012 |
| 2 | 2013 |
| 2 | 2014 |
| 2 | 2015 |
| 2 | 2017 |
+------------+-----------+
How would I identify which CustomerIDs have 4 consecutive years of giving? (In the above, only customer 2.) As you can see, some records will have gaps in order years.
I started down the row of trying to utilize some combination of ROW_NUMBER/LAG/LEAD with no luck to this point.
Very paired down/modified attempt...
WITH CTE
AS
(
SELECT T.ConstituentLookupID,
T.FISCALYEAR,
COUNT(T.FISCALYEAR) OVER (PARTITION BY T.ConstituentLookupID) AS
YearCount,
FIRST_VALUE(T.FISCALYEAR) OVER(PARTITION BY T.ConstituentLookupID ORDER
BY T.FISCALYEAR DESC) - T.FISCALYEAR + 1 as X,
ROW_NUMBER() OVER(PARTITION BY T.ConstituentLookupID ORDER BY
T.FISCALYEAR DESC) AS RN
FROM #Temp AS T)
SELECT CTE.ConstituentLookupID,
CTE.FISCALYEAR,
CTE.YearCount,
CTE.X,
CTE.RN,
FROM CTE
WHERE CTE.YearCount >= 4 --Have at least 4 years of giving
AND CTE.X - CTE.RN = 1 --Some kind of way to calculate consecutive years. Doesnt account current year and gaps...;
Assuming no duplicates, you can use lag():
select distinct customerid
from (
select t.*,
lag(orderyear, 3) over(partition by customerid order by orderyear) oderyear3
from mytable t
) t
where orderyear = orderyear3 + 3
A more conventional approach is to use some gaps-and-islands technique. This is convenient if you want the start and end of each series. Here, an island is a series of rows with "adjacent" order years, and you want islands that are at least 4 years long. We can identify the islands by comparing the order year against an incrementing sequence, then use aggregation:
select customerid, min(orderyear) firstorderyear, max(orderyear) lastorderyear
from (
select t.*,
row_number() over(partition by customerid order by orderyear) rn
from mytable t
) t
group by customerid, orderyear - rn
having count(*) >= 4
Assuming you have no more than one row per customer and year, the simplest method is lag():
select customerid, year
from (select t.*,
lag(orderyear, 3) over (partition by customerid order by orderyear) as prev3_year
from t
) t
where prev3_year = year - 3;
The idea is to look 3 years back. If that year is year - 3, then there are four years in a row. If your data can have duplicates, there are tweaks to the logic (they make the query more slightly more complicated).
This could return duplicates, so you might just want:
select distinct customerid
from (select t.*,
lag(orderyear, 3) over (partition by customerid order by orderyear) as prev3_year
from t
) t
where prev3_year = year - 3;
I have a simple solution using row number and group by
SELECT Max(z.customerid),
Count(z.grp)
FROM (SELECT customerid,
orderyear,
orderyear - Row_number()
OVER (
ORDER BY customerid) AS Grp
FROM mytable)z
GROUP BY z.grp
HAVING Count(z.grp) = 4

Output 2 columns with average from year t and year t+1

I have a table that looks like this:
| playerid | season | Stat |
|-----------|---------|---------|
| 1 | 2014 | 2.3 |
| 1 | 2015 | 1.4 |
| 1 | 2016 | 3.5 |
| 2 | 2011 | 1.5 |
| 2 | 2012 | 5.5 |
| 3 | 2010 | 6.7 |
| 3 | 2011 | 2.6 |
I want a table with 2 columns which average 'STAT' for year t in column 1 and year t+1 in column 2.
IE-Column 1 would have averages of 'Stat' for:
playerid=1 & season=2014,
playerid=1 & season=2015,
playerid=2 & season=2011,
playerid=3 & season=2010.
Column 2 would have averages of 'Stat' for:
playerid=1 & season=2015,
playerid=1 & season=2016,
playerid=2 & season=2012,
playerid=3 & season=2011.
You could look up next year with a left join:
select cur.playerid
, cur.season
, cur.stat as stat_this_season
, next.stat as stat_next_season
from YourTable cur
left join
YourTable next
on cur.playerid = next.playerid
and cur.year = next.year - 1
To filter out seasons that do not have a next season, change the left join to an inner join (which you can abbreviate as join.)
You could use the analytic function ROW_NUMBER() to order the rows by year, for each player. Then you can get the requested averages by excluding the first record (for Column 1) and the last record (for Column 2). In case there is only one season, Stat will be included into Column 1 only.
SELECT t.playerid,
AVG(CASE WHEN t.ord1 != 1 OR
(t.ord1 = 1 AND t.ord2 = 1) THEN Stat END) AS Column_1,
AVG(CASE WHEN t.ord2 != 1 THEN Stat END) AS Column_2
FROM (SELECT s.*,
ROW_NUMBER() OVER (PARTITION BY playerid ORDER BY season DESC) AS ord1,
ROW_NUMBER() OVER (PARTITION BY playerid ORDER BY season ASC) AS ord2
FROM table_1 s) t
GROUP BY playerid
ORDER BY playerid
If you remove the GROUP BY from previous query, you will get only one row with both averages over all players:
SELECT AVG(CASE WHEN t.ord1 != 1 OR
(t.ord1 = 1 AND t.ord2 = 1) THEN Stat END) AS Column_1,
AVG(CASE WHEN t.ord2 != 1 THEN Stat END) AS Column_2
FROM (SELECT s.*,
ROW_NUMBER() OVER (PARTITION BY playerid ORDER BY season DESC) AS ord1,
ROW_NUMBER() OVER (PARTITION BY playerid ORDER BY season ASC) AS ord2
FROM table_1 s) t

sql group by per month and year with one row id

I have a table with month, year and an id like this.
+-------+-----------+-----
| month | year | id
+-------+-----------+-----
| 1 | 2016 |1
+-------+-----------+-----
| 2 | 2016 |2
+-------+-----------+-----
| 2 | 2016 |3
+-------+-----------+-----
and i want a sql query that give me only one row per month/year with the id if is single or the id null if there are multiple row for that month year.
in the case above
+-------+-----------+-----
| month | year | id
+-------+-----------+-----
| 1 | 2016 |1
+-------+-----------+-----
| 2 | 2016 |null
+-------+-----------+-----
how can i do this query qith sql server 2012?
You can do this with aggregation and case:
select month, year,
(case when min(id) = max(id) then min(id) end) as id
from t
group by month, year;
Note: month and year are bad names for columns, because they are reserved words. If these are really the names of your columns, you will need to escape them.
**Question:**only one row per month/year with the id if is single or the id null if there are multiple row for that month year
with cte
as
(
select row_number() over (partition by month,year order by (Select 1)) as rn,
month,year,id
from
table
)
select
case when rn>1 then null else id end as 'id',
month,
year
from
cte
Do a GROUP BY that counts each month and year's number of rows. Have a case expression that if one row, returns it using min(id), otherwise (more than 1 row) returns null.
select month, year, case when count(*) = 1 then min(id) else null end
from tablename
group by month, year;
I missed the part about the NULL in the OP, here's an adjusted query
DECLARE #tbl TABLE
(
Month INT NOT NULL,
Year INT NOT NULL,
Id INT NOT NULL
)
INSERT INTO #tbl VALUES
(2016, 1, 1)
,(2016, 2, 2)
,(2016, 2, 3)
SELECT
Month
,Year
,CASE WHEN COUNT(Id) > 1 THEN NULL ELSE COUNT(Id) END AS [Id]
FROM #tbl
GROUP BY Month, Year
http://rextester.com/FWVHI57098

Rank function for date in Oracle SQL

I have the following code for example:
SELECT id, order_day, purchase_id FROM d
customer_id and purchase_id are unique. Each customer_id could have multiple purchase_id. Assume every one has made at least 5 orders.
Now, I just want to pull the first 5 purchase IDs of each customers ID (this depends on the earliest dates of purchases). I want the result to look like this:
id | purchase_id | rank
-------------------------
A | WERFEW43 | 1
A | ERTGDSFV | 3
A | FDGRT45 | 2
A | BRTE4TEW | 4
A | DFGDV | 5
B | DSFSF | 1
B | CF345 | 2
B | SDFSDFSDFS | 4
I thought of Ranking order_day, but my knowledge is not good enough to pull this off.
select id,purchase_id, rank() over (order by order_day)
from d
you also can try dense_rank() over (order by order_day) and row_number() over (order by order_day) and choose which one will be more suitable for you
select *
from
( SELECT
id
,order_day
,purchase_id
,row_number() -- ranking
over (partition by id -- each customer
order by order_day) as rn -- based on oldest dates
FROM d
) as dt
where rn <= 5

PostgreSQL list companies and rank by sales

So I have:
companies (id, name, tenant_id)
invoices (id, company_id, tenant_id, total)
What I want to do is return a result set like:
company | Feb Sales | Feb Rank | Lifetime Sales | Lifetime Rank
-----------------------------------------------------------------------
ABC Comp | 1,000 | 1 | 2,000 | 2
XYZ Corp | 500 | 2 | 5,000 | 1
I can do the sales totals using subselects, but when I do the rank always returns 1. I'm assuming because it only returns 1 row per subselect so will always be the top row?
Here is a piece of the sql:
SELECT
"public".companies."name",
(
SELECT
rank() OVER (PARTITION BY i.tenant_id ORDER BY sum(grand_total) DESC) AS POSITION
FROM
invoices i
where
company_id = companies.id
group by
i.tenant_id, i.company_id
)
from companies
Below is untested version that can have typos. Please treat it just as description of the approach. For simplicity I assumed that invoices have a month column.
SELECT
"public".companies."name",
rank() OVER (PARTITION BY sales.companies ORDER BY sales.lifetime) As "Lifetime Rank",
rank() OVER (PARTITION BY sales.companies ORDER BY sales.month As "One Month"
FROM companies LEFT JOIN
(
SELECT
SUM(grand_total) As Lifetime,
SUM(CASE WHEN i.month = <the month of report>, grand_total, 0) As Month
FROM
invoices i
GROUP BY company_id
) sales
ON companies.company_id = sales.company_id
If you run into problems, add the actual code that you used and sample data to your post and I will attempt to create a live demo for you.