Extract subset from the main query in SQL - sql

In the below query, I get the count of customers who were active between "2017-09-01 00:00:00" and "2017-11-31 23:59:59" as cust_90 and would like to add another column to find the count of customers who were active between "2017-11-01 00:00:00" and "2017-11-31 23:59:59" (a subset of the whole period).
select custid, count(distinct concat(visit1, visit2)) as cust_90
from test1
where date_time between "2017-09-01 00:00:00" and "2017-11-31 23:59:59"
and custid = '234214124'
group by custid;
Sample output:
CustomerName cust_90 cust_30
David 38 15
Wondering whether I could have a subquery in the above query to find the customers active in a month. Any suggestions would be great.

This is called conditional aggregation which can be done using a case expression.
select custid,
count(distinct concat(visit1, visit2) end) as cust_90,
count(distinct case when to_date(date_time)>='2017-11-01' then concat(visit1, visit2) end) as cust_30
from test1
where date_time >= '2017-09-01' and date_time < '2017-12-01'
and custid = '234214124'
group by custid;

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;

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.

Multiple where in one query

I have table with the following columns
id, money, date
I want a SQL query that returns the sum(amount) calculated between two custom dates like
Date between '2014-1-10' and '2016-3-12'
Date between '2015-8-10' and '2017-6-12'
Date between '2014-2-10' and '2016-6-12'
The result will be some thing similar to
customWhere amount
1 20000000$
2 23495000$
3 12940593$
use a select case in the groupby clause
Select (select case
when MyDate between Convert(date,'Some Date')
and Convert(date,'Other Date')
then 1 else 2 end) 'Case',
SUM(Ammount) 'Sum'
FROM YourTable
GROUP BY (select case
when MyDate between Convert(date,'Some Date')
and Convert(date,'Other Date')
then 1 else 2 end)
Group By - https://msdn.microsoft.com/en-us/library/ms177673.aspx
Case - https://msdn.microsoft.com/en-us/library/ms181765.aspx
select 1 as cn, sum(amount) as sss
from table
where Date between '2014-1-10' and '2016-3-12'
union
select 2 as cn, sum(amount) as sss
from table
where Date between '2015-8-10' and '2017-6-12'
...

how to return total sold p / year for several years in columnar format?

The SQL below give me the columns for Account, Name and the Total Sale for the year of 2015.But how, if possible, can I add another column for the previous year of 2014 ?
select a.AcctNo, b.Name, Sum(a.TotSold) as [ Total Sold ]
from Orders as A
Join Accounts as b on a.AcctNo = b.AcctNo
where (a.PurchaseDate between '1/1/2015' and '12/31/2015' )
Group by a.AcctNo, b.Name
You can use conditional aggregation:
select o.AcctNo, a.Name,
Sum(case when year(PurchaseDate) = 2015 then TotSold else 0 end) as tot_2015,
Sum(case when year(PurchaseDate) = 2014 then TotSold else 0 end) as tot_2014
from Orders o Join
Accounts a
on a.AcctNo = o.AcctNo
where PurchaseDate >= '2014-01-01'
PurchaseDate < '2016-01-01'
Group by o.AcctNo, a.Name ;
Notes:
Use ISO standard formats for date constants.
When using dates, try not to use between. It doesn't work as expected when there is a time component. The above inequalities work regardless of a time component.
Use table aliases that are abbreviations of the table name. That makes the query easier to follow.

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.