Adding another column with a different condition in SQL - sql

I'm trying to find different information about my data all in the same query. I want to create new columns based on separate conditions. Here is what my table looks like right now
SELECT COUNT (distinct ext_project_id) as "Total Projects"
FROM dbo.v_report_project
INNER JOIN dbo.v_report_program
ON dbo.v_report_program.program_id = dbo.v_report_project.program_id
WHERE project_status = 'Active'
AND datediff (day, creation_date, getdate()) < 15 ;
The outcome is:
Total Projects
163
Here is what I want my table to look like:
Total Projects | Projects Under 15 Days | Projects Between 15 and 60 Days | Projects Over 60 Days
163 ?? ?? ??
How can I find these different counts, all at the same time?

You can use a case to limit a particular count to a certain date range:
select count(distinct project_id) as total
, count(distinct case
when datediff(day, creation_date, getdate()) < 6
then project_id end) as total_6day
, count(distinct case
when datediff(day, creation_date, getdate()) < 7
then project_id end) as total_7day
, count(distinct case
when datediff(day, creation_date, getdate()) < 42
then project_id end) as total_42day
from dbo.v_report_project proj
join dbo.v_report_program prog
on proj.program_id = prog.program_id
where project_status = 'Active'
If no when matches and no else is specified, a case returns null. Since count ignores null you get the proper subtotals.

Related

How to solve a nested aggregate function in SQL?

I'm trying to use a nested aggregate function. I know that SQL does not support it, but I really need to do something like the below query. Basically, I want to count the number of users for each day. But I want to only count the users that haven't completed an order within a 15 days window (relative to a specific day) and that have completed any order within a 30 days window (relative to a specific day). I already know that it is not possible to solve this problem using a regular subquery (it does not allow to change subquery values for each date). The "id" and the "state" attributes are related to the orders. Also, I'm using Fivetran with Snowflake.
SELECT
db.created_at::date as Date,
count(case when
(count(case when (db.state = 'finished')
and (db.created_at::date between dateadd(day,-15,Date) and dateadd(day,-1,Date)) then db.id end)
= 0) and
(count(case when (db.state = 'finished')
and (db.created_at::date between dateadd(day,-30,Date) and dateadd(day,-16,Date)) then db.id end)
> 0) then db.user end)
FROM
data_base as db
WHERE
db.created_at::date between '2020-01-01' and dateadd(day,-1,current_date)
GROUP BY Date
In other words, I want to transform the below query in a way that the "current_date" changes for each date.
WITH completed_15_days_before AS (
select
db.user as User,
count(case when db.state = 'finished' then db.id end) as Completed
from
data_base as db
where
db.created_at::date between dateadd(day,-15,current_date) and dateadd(day,-1,current_date)
group by User
),
completed_16_days_before AS (
select
db.user as User,
count(case when db.state = 'finished' then db.id end) as Completed
from
data_base as db
where
db.created_at::date between dateadd(day,-30,current_date) and dateadd(day,-16,current_date)
group by User
)
SELECT
date(db.created_at) as Date,
count(distinct case when comp_15.completadas = 0 and comp_16.completadas > 0 then comp_15.user end) as "Total Users Churn",
count(distinct case when comp_15.completadas > 0 then comp_15.user end) as "Total Users Active",
week(Date) as Week
FROM
data_base as db
left join completadas_15_days_before as comp_15 on comp_15.user = db.user
left join completadas_16_days_before as comp_16 on comp_16.user = db.user
WHERE
db.created_at::date between '2020-01-01' and dateadd(day,-1,current_date)
GROUP BY Date
Does anyone have a clue on how to solve this puzzle? Thank you very much!
The following should give you roughly what you want - difficult to test without sample data but should be a good enough starting point for you to then amend it to give you exactly what you want.
I've commented to the code to hopefully explain what each section is doing.
-- set parameter for the first date you want to generate the resultset for
set start_date = TO_DATE('2020-01-01','YYYY-MM-DD');
-- calculate the number of days between the start_date and the current date
set num_days = (Select datediff(day, $start_date , current_date()+1));
--generate a list of all the dates from the start date to the current date
-- i.e. every date that needs to appear in the resultset
WITH date_list as (
select
dateadd(
day,
'-' || row_number() over (order by null),
dateadd(day, '+1', current_date())
) as date_item
from table (generator(rowcount => ($num_days)))
)
--Create a list of all the orders that are in scope
-- i.e. 30 days before the start_date up to the current date
-- amend WHERE clause to in/exclude records as appropriate
,order_list as (
SELECT created_at, rt_id
from data_base
where created_at between dateadd(day,-30,$start_date) and current_date()
and state = 'finished'
)
SELECT dl.date_item
,COUNT (DISTINCT ol30.RT_ID) AS USER_COUNT
,COUNT (ol30.RT_ID) as ORDER_COUNT
FROM date_list dl
-- get all orders between -30 and -16 days of each date in date_list
left outer join order_list ol30 on ol30.created_at between dateadd(day,-30,dl.date_item) and dateadd(day,-16,dl.date_item)
-- exclude records that have the same RT_ID as in the ol30 dataset but have a date between 0 amd -15 of the date in date_list
WHERE NOT EXISTS (SELECT ol15.RT_ID
FROM order_list ol15
WHERE ol30.RT_ID = ol15.RT_ID
AND ol15.created_at between dateadd(day,-15,dl.date_item) and dl.date_item)
GROUP BY dl.date_item
ORDER BY dl.date_item;

Is there a way to split sql results by year?

I currently have the following SQL statement:
SELECT
[Manager].[Name],
COUNT([Project].[ProjectId]) AS TotalProjects
FROM
([Project]
INNER JOIN
[Manager] ON [Project].[ManagerId] = [Manager].[ManagerId])
WHERE
[Project].[CurrentStatusId] = 5
GROUP BY
[Manager].[Name]
It currently spits out total projects by each manager. I would like to have it split the projects out by the years they were completed. So basically count the total projects for each manager for each year (2016, 2017, and so on), as well as the total projects all time. I can use the column [Project].[CurrentStatusDt] for the date.
Just add the project year to the SELECT and GROUP BY clauses:
SELECT
[Manager].[Name],
YEAR([Project].[CurrentStatusDt]) PojectYear,
COUNT([Project].[ProjectId]) AS TotalProjects
FROM [Project]
INNER JOIN [Manager] ON [Project].[ManagerId] = [Manager].[ManagerId]
WHERE [Project].[CurrentStatusId] = 5
GROUP BY [Manager].[Name], YEAR([Project].[CurrentStatusDt])
Side note: you don't need parentheses around the joins in SQL Server (this is a MS Access limitation).
EDIT
If you want to spread the years over columns instead of rows, then one solution is to use conditional aggregation
SELECT
[Manager].[Name],
SUM(CASE
WHEN [Project].[CurrentStatusDt] < CAST('2019-01-01' AS DATE)
THEN 1
ELSE 0
END) TotalProjects2018,
SUM(CASE
WHEN [Project].[CurrentStatusDt] >= CAST('2019-01-01' AS DATE)
THEN 1
ELSE 0
END) TotalProjects2019
FROM [Project]
INNER JOIN [Manager] ON [Project].[ManagerId] = [Manager].[ManagerId]
WHERE
[Project].[CurrentStatusId] = 5
AND [Project].[CurrentStatusDt] >= CAST('2018-01-01' AS DATE)
AND [Project].[CurrentStatusDt] < CAST('2020-01-01' AS DATE)
GROUP BY [Manager].[Name]

SQL: calculate value based on two different conditions

Below is my table schema:- Appointments
--------------------------------------
| schID | appointment_date | amount | location |
--------------------------------------
I want to fire a single query where I can get the sum of amount, total appointment_date this year i.e 2016 and remaining appointment_date this year i.e 2016.
So I wrote the below query to calculate the above fields:-
SELECT sum(a.amount) as total,
count(distinct a.appointment_date) as total_appointment,
count(distinct a2.appointment_date) as remaining appointments
from Appointments a
LEFT JOIN Appointments a2 ON a.schID = a2.schID
WHERE a2.appointment_date > GETDATE() AND year(a.appointment_date) = 2016
group by a.location
The above query doesnt return value as per requirement :(
The database belongs to SQL Server.
You can use conditional aggregation for this:
SELECT sum(amount) as total,
count(appointment_date) as total_appointment,
count(DISTINCT CASE
WHEN appointment_date > GETDATE() AND YEAR(appointment_date) = 2016
THEN DATE(appointment_date)
END) as remaining appointments
from Appointments a
group by a.location
You shouldn't need a join for this type of query:
SELECT sum(a.amount) as total, count(a.appointment_date) as total_appointment,
sum(case when a.appointment_date > getdate() then 1 else 0
end) as remaining appointments
from Appointments a
where year(a.appointment_date) = year(GETDATE() );
If you need the breakdown by location, then include location in both the select and group by clauses.

Grouping multiple selects within a SQL query

I have a table Supplier with two columns, TotalStock and Date. I'm trying to write a single query that will give me stock totals by week / month / year for a list of suppliers.
So results will look like this..
SUPPLIER WEEK MONTH YEAR
SupplierA 50 100 2000
SupplierB 60 150 2500
SupplierC 15 25 200
So far I've been playing around with multiple selects but I can't get any further than this:
SELECT Supplier,
(
SELECT Sum(TotalStock)
FROM StockBreakdown
WHERE Date >= '2014-5-12'
GROUP BY Supplier
) AS StockThisWeek,
(
SELECT Sum(TotalStock)
FROM StockBreakdown
WHERE Date >= '2014-5-1'
GROUP BY Supplier
) AS StockThisMonth,
(
SELECT Sum(TotalStock)
FROM StockBreakdown
WHERE Date >= '2014-1-1'
GROUP BY Supplier
) AS StockThisYear
This query throws an error as each individual grouping returns multiple results. I feel that I'm close to the solution but can't work out where to go
You don't have to use subqueries to achieve what you want :
SELECT Supplier
, SUM(CASE WHEN Date >= CAST('2014-05-12' as DATE) THEN TotalStock END) AS StockThisWeek
, SUM(CASE WHEN Date >= CAST('2014-05-01' as DATE) THEN TotalStock END) AS StockThisMonth
, SUM(CASE WHEN Date >= CAST('2014-01-01' as DATE) THEN TotalStock END) AS StockThisYear
FROM StockBreakdown
GROUP BY Supplier
You may need to make the selects for the columns return only a single result. You could try this (not tested currently):
SELECT Supplier,
(
SELECT TOP 1 StockThisWeek FROM
(
SELECT Supplier, Sum(TotalStock) AS StockThisWeek
FROM StockBreakdown
WHERE Date >= '2014-5-12'
GROUP BY Supplier
) tmp1
WHERE tmp1.Supplier = Supplier
) AS StockThisWeek,
(
SELECT TOP 1 StockThisMonth FROM
(
SELECT Supplier, Sum(TotalStock) AS StockThisMonth
FROM StockBreakdown
WHERE Date >= '2014-5-1'
GROUP BY Supplier
) tmp2
WHERE tmp2.Supplier = Supplier
) AS StockThisMonth,
...
This selects the supplier and then tries to create two columns StockThisWeek and StockThisMonth by selecting the first entry from the select you created before. As through the GROUP BY there should only be one entry per supplier, so you don't lose and data.

Count records with a criteria like "within days"

I have a table as below on sql.
OrderID Account OrderMethod OrderDate DispatchDate DispatchMethod
2145 qaz 14 20/3/2011 23/3/2011 2
4156 aby 12 15/6/2011 25/6/2011 1
I want to count all records that have reordered 'within 30 days' of dispatch date where Dispatch Method is '2' and OrderMethod is '12' and it has come from the same Account.
I want to ask if this all can be achieved with one query or do I need to create different tables and do it in stages as I think I wll have to do now? Please can someone help with a code/query?
Many thanks
T
Try the following, replacing [tablename] with the name of your table.
SELECT Count(OriginalOrders.OrderID) AS [Total_Orders]
FROM [tablename] AS OriginalOrders
INNER JOIN [tablename] AS Reorders
ON OriginalOrders.Account = Reorders.Account
AND OriginalOrders.OrderDate < Reorders.OrderDate
AND DATEDIFF(day, OriginalOrders.DispatchDate, Reorders.OrderDate) <= 30
AND Reorders.DispatchMethod = '2'
AND Reorders.OrderMethod = '12';
By using an inner join you'll be sure to only grab orders that meet all the criteria.
By linking the two tables (which are essentially the same table with itself using aliases) you make sure only orders under the same account are counted.
The results from the join are further filtered based on the criteria you mentioned requiring only orders that have been placed within 30 days of the dispatch date of a previous order.
Totally possible with one query, though my SQL is a little stale..
select count(*) from table
where DispatchMethod = 2
AND OrderMethod = 12
AND DATEDIFF(day, OrderDate, DispatchDate) <= 30;
(Untested, but it's something similar)
One query can do it.
SELECT COUNT(*)FROM myTable reOrder
INNER JOIN myTable originalOrder
ON reOrder.Account = originalOrder.Account
AND reOrder.OrderID <> originalOrder.OrderID
-- all re-orders that are within 30 days or the
-- original orders dispatch date
AND DATEDIFF(d, originalOrder.DispatchDate, reOrder.OrderDate) <= 30
WHERE reOrder.DispatchMethod = 2
AND reOrder.OrderMethod = 12
You need a self-join.
The query below assumes that a given account will have either 1 or 2 records in the table - 2 if they've reordered, else 1.
If 3 records exist for a given account, 2 orders + 1 reorder then this won't work - but we'd then need more information on how to distinguish between an order and a reorder.
SELECT COUNT(*) FROM myTable new, myTable prev
WHERE new.DispatchMethod = 2
AND new.OrderMethod = 12
AND DATEDIFF(day, prev.DispatchDate, new.OrderDate) <=30
AND prev.Account == new.Account
AND prev.OrderDate < new.OrderDate
Can we use GROUP BY in this case, such as the following?
SELECT COUNT(Account)
FROM myTable
WHERE DispatchMethod = 2 AND OrderMethod = 12
AND DATEDIFF(d, DispatchDate, OrderDate) <=30
GROUP BY Account
Will the above work or am I missing something here?