SQL Server: group by, coalesce and select one of coalesce'd - sql

I have a table called Regions:
city district1 district2 district3 district4
---------------------------------------------------------
Michigan 2 NULL NULL 2
Michigan 2 20 NULL 20
Michigan 2 NULL 3 3
Ontario 3 NULL NULL 3
Quebec 4 1 NULL 1
Quebec 4 NULL NULL 4
Edmonton NULL 7 NULL 7
Edmonton NULL NULL 11 11
district4 is (coalesce(district3, district2, district1))
And I'd like to get a distinct grouped by City also with district1
city district1 district_final
--------------------------------------
Michigan 2 3
Ontario 3 3
Quebec 4 1
Edmonton NULL 11
district_final is not max; it's coalesce of group

select distinct r1.city, r1.district1, coalesce(r3.district3, r2.district2, r1.district1) district_final
from Regions r1
left outer join Regions r2 on r1.city = r2.city and r2.district2 is not null
left outer join Regions r3 on r1.city = r3.city and r3.district3 is not null

Following code should solve the purpose i guess:
SELECT CITY,dct1 as district1,MAX(DCT) as district_final FROM
(
SELECT CITY, district1 as dct1, district1 AS DCT FROM [TABLE]
UNION
SELECT CITY, district1 as dct1, district2 AS DCT FROM [TABLE]
UNION
SELECT CITY, district1 as dct1, district3 AS DCT FROM [TABLE]
) tempTable
group by CITY,dct1;

Related

Select max date for each register, null if does not exists

I have these tables: Employee (id, name, number), Configuration (id, years, licence_days), Periods (id, start_date, end_date, configuration_id, employee_id, period_type):
Employee table:
id name number
---- ----- -------
1 Bob 355
2 John 467
3 Maria 568
4 Josh 871
configuration table:
id years licence_days
---- ----- ------------
1 1 8
2 3 16
3 5 24
Periods table:
id start_date end_date configuration_id employee_id period_type
---- ---------- ------- ---------------- ----------- -----------
1 2021-05-23 2021-05-31 1 1 vaccation
2 2021-05-24 2021-06-01 1 2 vaccation
3 2021-03-01 2021-03-17 2 2 vaccation
4 2021-05-05 2021-05-21 2 2 vaccation
5 2021-01-01 2021-01-17 2 4 vaccation
I want this result:
Result:
employee_id years licence_days max(end_date)
1 1 8 2021-05-31
1 3 16 null
1 5 24 null
2 1 8 2021-06-01
2 3 16 2021-05-21
2 5 24 null
3 1 8 null
3 3 16 null
3 5 24 null
4 1 8 null
4 3 16 2021-01-17
4 5 24 null
i.e., I want to select all Employees with all configuration, and for each one of that, the max end_date of the "vaccation" type (or null if it does not exists).
How can I do that
Oracle supports cross joins, right? So may be something like that?
SELECT e.employee_id, c.years, c.licence_days, max(p.end_date)
FROM Employee e
CROSS JOIN configuration c
LEFT JOIN Periods p
ON e.employee_id = p.employee_id
AND c.configuration_id = p.configuration_id
GROUP BY e.employee_id, c.years, c.licence_days
ORDER BY e.employee_id, c.years
#umberto-petrov chooses wisely with the ANSI CROSS JOIN syntax for a cartesian join. However, in the very weak probability that your requires output of configurations even where there is no employees, you can go with something like :
EDIT: Filtering the Periods join with 'vaccation' as asked in the comments.
If you have to filter for some employee ids, change ON 1 = 1 by ON Employee.id IN (id1, id2, ...). It still keeps every configurations but only takes employees that match the ids.
SELECT Employee.employee_id,
Configuration.years,
Configuration.licence_days,
MAX(Configuration.end_date) max_end_date
FROM Configuration LEFT JOIN Employee ON 1 = 1
LEFT JOIN Periods ON Periods.configuration_id = Configuration.id
AND Periods.employee_id = Employee.id
AND Periods.period_type = 'vaccation'
GROUP BY Employee.employee_id,
Configuration.years,
Configuration.licence_days
ORDER BY Employee.employee_id,
Configuration.years,
Configuration.licence_days
We start from configuration to take every records from this one at least, then made a LEFT CARTESIAN JOIN with Employee and finally a full LET JOIN on Periods for both. That way , if there is no employees, this will output configuration_id and NULL for years, licence_days and max end_date.

Conditional Aggregation with multiple case and group by

The query below gives me average of case when QuoteStatusID = 6 but it I am having issues with associating the average by Street column.
QuoteTable
QuoteID
QuoteStateID
ProjectManager_userID
Shipping_AddressID
1
6
12
56
2
6
12
56
3
26
12
56
4
6
12
18
5
26
12
18
Shipping_AddressID
56: 338 Elizabeth St
18: 83 East St
select [User].UserID, [User].fname, [User].lname,[User].JobTitle, address.Street,
(select avg(case when QuoteStatusID = 6 then 1.0 else 0 end) as QuoteAccept
from Quote q
where ProjectManager_UserID = userId
) as AcceptanceRate
from [User]
join quote on [user].UserID=Quote.ProjectManager_UserID
join Address on quote.Shipping_AddressID=Address.AddressID
where userID in (select distinct ProjectManager_UserID from quote)
order by AcceptanceRate desc;
Current output 3/5 =0.60
userid
fname
Lname
Street
AcceptanceRate
12
Jon
Smith
338 Elizabeth St
0.6
12
Jon
Smith
83 East St
0.6
Desired output 2/3=0.66 AND 1/2=0.50
userid
fname
Lname
Street
AcceptanceRate
12
Jon
Smith
338 Elizabeth St
0.66
12
Jon
Smith
83 East St.
0.50
I think you don't need a sub-query. Just avg as part of the query you have and use group by to give you distinct users and addresses.
select U.UserID, U.fname, U.lname, U.JobTitle, A.Street
, avg(case when Q1.QuoteStatusID = 6 then 1.0 else 0 end) as QuoteAccept
from [User] U
inner join Quote Q on Q.ProjectManager_UserID = U.UserID
inner join [Address] A on A.AddressID = Q.Shipping_AddressID
group by U.UserID, U.fname, U.lname, U.JobTitle, A.Street
order by AcceptanceRate desc;
Note: Short aliases make a query more readable. And you don't need your where clause, since the join on Quote already ensures the same condition.
Can you simply amend your avg to be
select avg(case when QuoteStateID = 6 then 1.0 else 0 end) over(partition by Shipping_AddressId) as QuoteAccept
Edit
To still use as a subquery it will need correlating in the where clause on Shipping_AddressId also

Calculate value using previous and current month

I have below three tables
Stock Table
ID GlobalStock Date Country
1 10 2017/01/01 India
1 20 2017/01/01 India
2 5 2017/02/01 Africa
3 6 2017/08/01 Japan
4 7 2017/04/01 Japan
5 89 2017/08/01 Japan
2 10 2017/03/01 Japan
5 8 2017/03/01 Japan
1 20 2017/02/01 India
ShipFile
ID GlobalStock Date Country
2 10 2017/03/01 Africa
3 60 2017/08/01 India
11 70 2017/08/01 India
1 8 2017/02/01 India
1 9 2017/02/01 India
2 4 2017/03/01 Japan
2 5 2017/04/01 Japan
5 3 2017/03/01 Japan
3 8 2017/08/01 Japan
SalesFiles
ID GlobalStock Date Country
2 10 2017/03/01 India
2 20 2017/03/01 Africa
3 30 2017/08/01 Japan
7 5 2017/02/01 Japan
8 8 2018/01/01 Japan
1 9 2017/02/01 India
1 70 2017/02/01 Africa
13 10 2017/08/01 Japan
10 60 2017/11/01 Japan
I want to calculate -> StockTable(Month - 1) + ShipFile (Month) - Sales (Month)
For example
For ID 1 suppose we are considering Jan (GlobalStock -> 10 + 20) data then in other tables we must take Feb values and country should be same for all tables.
So calculation would be
(10 + 20) + (8 + 9) - (9) = 38
If we consider Feb ID of stocktable then we must consider March data from other tables and so on..
the joining all table i am considering ID and Country.
You can query using subquery or cte as below:
;With cte_Stock as (
Select ID, [Date], Country, sum(GlobalStock) Sum_GlobalStock from Stock
group by Id, [Date], Country
), cte_ShipFiles as (
Select ID, [Date], Country, sum(GlobalStock) Sum_GlobalStock from ShipFile
group by Id, [Date], Country
)
, cte_SalesFiles as (
Select ID, [Date], Country, sum(GlobalStock) Sum_GlobalStock from SalesFiles
group by Id, [Date], Country
)
select s.ID, s.[Date], sf.[Date], s.Country,
YourOutput = s.Sum_GlobalStock+sf.Sum_GlobalStock-sales.Sum_GlobalStock
from cte_Stock s
join cte_ShipFiles sf
on s.ID = sf.ID
and s.Country = sf.Country
and s.[Date] = dateadd(mm,-1, sf.[Date])
join cte_SalesFiles sales
on s.ID = sales.ID
and s.Country = sales.Country
and s.[Date] = dateadd(mm,-1, sales.[Date])
Output as below:
+----+------------+------------+---------+------------+
| ID | Date | Date | Country | YourOutput |
+----+------------+------------+---------+------------+
| 1 | 2017-01-01 | 2017-02-01 | India | 38 |
| 2 | 2017-02-01 | 2017-03-01 | Africa | -5 |
+----+------------+------------+---------+------------+
Here is an approach with derived tables:
DECLARE #CurrentMonth date = '20180101'
DECLARE #NextMonth date = DATEADD(MONTH,1,#CurrentMonth)
SELECT s.Country, SUM(s.GlobalStock) + ShipSum - SaleSum
FROM stock s
LEFT JOIN (SELECT ISNULL(SUM(GlobalStock),0) ShipSum, Country
FROM ShipFile
WHERE Date >= #NextMonth
AND Date <= EOMONTH(#NextMonth)
GROUP BY Country) sh on s.Country = sh.Country
LEFT JOIN (SELECT ISNULL(SUM(GlobalStock),0) SaleSum, Country
FROM SalesFile
WHERE Date >= #NextMonth
AND Date <= EOMONTH(#NextMonth)
GROUP BY Country) sa on s.Country = sa.Country
WHERE s.Date >= #CurrentMonth
AND s.Date <= EOMONTH(#CurrentMonth)
GROUP BY s.Country, ShipSum, SaleSum
Notes:
This uses Country for the joins because ID seems to change between tables.
It also uses a date range assuming that the day portion of your date column is not always the first of the month - if it is always the first that can be simplified to date = #CurrentMonth or date = #NextMonth

How to replace all values in grouped column except first row

I have table like this:
ID Region CreatedDate Value
--------------------------------
1 USA 2016-01-01 5
2 USA 2016-02-02 10
3 Canada 2016-02-02 2
4 USA 2016-02-03 7
5 Canada 2016-03-03 3
6 Canada 2016-03-04 10
7 USA 2016-03-04 1
8 Cuba 2016-01-01 4
I need to sum column Value grouped by Region and CreatedDate by year and month. The result will be
Region Year Month SumOfValue
--------------------------------
USA 2016 1 5
USA 2016 2 17
USA 2016 3 1
Canada 2016 2 2
Canada 2016 3 13
Cuba 2016 1 4
BUT I want to replace all repeated values in column Region with empty string except first met row. The finish result must be:
Region Year Month SumOfValue
--------------------------------
USA 2016 1 5
2016 2 17
2016 3 1
Canada 2016 2 2
2016 3 13
Cuba 2016 1 4
Thank you for a solution. It will be advantage if solution will replace also in column Year
You need to use SUM and GROUP BY to get the SumOfValue. For the formatting, you can use ROW_NUMBER:
WITH Cte AS(
SELECT
Region,
[Year] = YEAR(CreatedDate),
[Month] = MONTH(CreatedDate),
SumOfValue = SUM(Value),
Rn = ROW_NUMBER() OVER(PARTITION BY Region ORDER BY YEAR(CreatedDate), MONTH(CreatedDate))
FROM #tbl
GROUP BY
Region, YEAR(CreatedDate), MONTH(CreatedDate)
)
SELECT
Region = CASE WHEN Rn = 1 THEN c.Region ELSE '' END,
[Year],
[Month],
SumOfValue
FROM Cte c
ORDER BY
c.Region, Rn
ONLINE DEMO
Although this can be done in TSQL, I suggest you do the formatting on the application side.
Query that follows the same order as the OP.

Pivoting results in more rows than needed

I have query like this:
`SELECT [/BIC/IORSVPTX] as Region,
COUNTRY_ID,
[/BIC/IOWCNTRY] as Country,
[/BIC/IOC_TRLNO] as Trial,
[/BIC/IOWQUAL] as ResourceType,
case
when [/BIC/IOWQUAL] like '%Supporter%'
then 1
when [/BIC/IOWQUAL] like '%Monitor%'
then 3
when [/BIC/IOWQUAL] like '%PM%'
then 2
end as ResourceGroup,
[1], [2], [3], [4], [5]
FROM
(
SELECT [/BIC/IORSVPTX],
COUNTRY_ID,
[/BIC/IOWCNTRY],
[/BIC/IOC_TRLNO],
[/BIC/IOWQUAL], case
when [/BIC/IOWQUAL] like '%Supporter%'
then 1
when [/BIC/IOWQUAL] like '%Monitor%'
then 3
when [/BIC/IOWQUAL] like '%PM%'
then 2
end as ResourceGroup,
left(CALMONTH,4) as StartYear,
right(CALMONTH,2) as StartMonth,
((left(CALMONTH,4) - 2013) * 12) + right(CALMONTH,2) AS YearMonth,
QUANTITY as Hours
FROM dbo.Actuals
where [/BIC/IOC_TRLNO]<>'0000' and left(CALMONTH,4)>2012 and COUNTRY_ID='10'
and ([/BIC/IOWQUAL] like '%PM%' or [/BIC/IOWQUAL] like'%Monitor%' or [/BIC/IOWQUAL] like '%Supporter%')
) up
PIVOT (sum(Hours) FOR YearMonth IN ([1],[2],[3],[4],[5])) AS pvt;
IN ([1],[2],[3],[4],[5])) AS pvt;`
which gives me the output with 2 rows for each ResourceType and ResourceGroup with hours for 1(Jan) and 2nd row with hours for 2(Feb) instead of 1 row
Region COUNTRY_ID Country Trial ResourceType ResourceGroup 1 2 3 4 5
North America 10 USA 3619 Monitor 3 158.5 NULL NULL NULL NULL
North America 10 USA 3619 Monitor 3 NULL 42 NULL NULL NULL
North America 10 USA 3619 PM / LTM / RTM 2 20 NULL NULL NULL NULL
North America 10 USA 3619 PM / LTM / RTM 2 NULL 22 NULL NULL NULL
North America 10 USA 3619 Supporter 1 18.5 NULL NULL NULL NULL
North America 10 USA 3619 Supporter 1 NULL 15.75 NULL NULL NULL
desired output should be like this:
Region COUNTRY_ID Country Trial ResourceType ResourceGroup 1 2 3 4 5
North America 10 USA 3619 Monitor 3 158.5 42 NULL NULL NULL
North America 10 USA 3619 PM / LTM / RTM 2 20 22 NULL NULL NULL
North America 10 USA 3619 Supporter 1 18.5 15.75 NULL NULL NULL
I will appreciate your help!
I see a few things wrong with your current query.
First, you are using the CASE expression in the outer and the subquery. I don't fully understand why you are doing that as you created a column called ResourceGroup.
Second, you will get multiple rows if your subquery contains columns with distinct values and you apply the pivot.
For example in your subquery you are using:
left(CALMONTH,4) as StartYear,
right(CALMONTH,2) as StartMonth,
But you do not have these in the final select list, if you added them to the final select they will probably show distinct values which are causing the problem during the pivot grouping.
Based on your existing query I am guessing you want to use:
SELECT
[/BIC/IORSVPTX] as Region,
COUNTRY_ID,
[/BIC/IOWCNTRY] as Country,
[/BIC/IOC_TRLNO] as Trial,
[/BIC/IOWQUAL] as ResourceType,
ResourceGroup,
[1], [2], [3], [4], [5]
FROM
(
SELECT [/BIC/IORSVPTX],
COUNTRY_ID,
[/BIC/IOWCNTRY],
[/BIC/IOC_TRLNO],
[/BIC/IOWQUAL],
case
when [/BIC/IOWQUAL] like '%Supporter%' then 1
when [/BIC/IOWQUAL] like '%Monitor%' then 3
when [/BIC/IOWQUAL] like '%PM%' then 2
end as ResourceGroup,
((left(CALMONTH,4) - 2013) * 12) + right(CALMONTH,2) AS YearMonth,
QUANTITY as Hours
FROM dbo.Actuals
where [/BIC/IOC_TRLNO]<>'0000'
and left(CALMONTH,4)>2012
and COUNTRY_ID='10'
and ([/BIC/IOWQUAL] like '%PM%'
or [/BIC/IOWQUAL] like'%Monitor%'
or [/BIC/IOWQUAL] like '%Supporter%')
) up
PIVOT
(
sum(Hours)
FOR YearMonth IN ([1],[2],[3],[4],[5])
) AS pvt;