Grouping SQL results at 2 sub levels - sql

I am using an Access front end screen and SQL queries at the back end. I am trying to get a result set that is grouped at 2 levels: Region & Month and then a relevant count of total clients active in that period, clients with orders and clients without orders all within the period selected by the user.
Below are the 2 tables in use
Client Table A
ID | Region | StartDate | Name
1 | North | 1 Jan 16 | ABC
2 | North | 1 Mar 16 | DEF
3 | East | 1 Jul 16 | GHE
4 | East | 1 Aug 16 | HIJ
5 | West | 1 Feb 16 | KLM
6 | West | 1 Mar 16 | NOP
7 | South | 1 Apr 16 | QUR
8 | South | 1 Jan 16 | STU
Orders Table B
OrderID | Client ID |Order Date
1 | 1 | 15 Mar 16
2 | 3 | 15 Jul 16
3 | 5 | 15 Jun 16
4 | 8 | 15 Jul 16
5 | 6 | 15 Jul 16
6 | 4 | 15 Jan 16
7 | 2 | 15 Feb 16
8 | 1 | 15 Jul 16
9 | 3 | 05 Aug 16
10 | 3 | 16 Jul 16
11 | 2 | 15 May 16
12 | 4 | 15 May 16
13 | 6 | 15 May 16
14 | 7 | 15 Mar 16
The User picks a start date and end date for the report for Eg 1 May 2016 to 31 Jul 16
I need a query that will evaluate if client start date is within reporting period and produce the following out put:
Result Set
Region | Month |Total Clients|Clients with Orders|Clients w/o Orders
North |May-2016| 2 | 1 | 1
North |Jun-2016| 2 | 0 | 2
North |Jul-2016| 2 | 1 | 1
East |May-2016| 0 | 0 | 0
East |Jun-2016| 0 | 0 | 0
East |Jul-2016| 1 | 1 | 0
West |May-2016| 2 | 1 | 1
West |Jun-2016| 2 | 0 | 2
West |Jul-2016| 2 | 1 | 1
South |May-2016| 2 | 0 | 2
South |Jun-2016| 2 | 0 | 2
South |Jul-2016| 2 | 1 | 1
Ive been stuck on this for 2 weeks now.. Please Help!!!
if it helps this is how far I have gotten
PARAMETERS startDt DateTime, endDt DateTime, loc Text ( 255 );
SELECT DISTINCT a.[Region] AS Region,
Format(b.[Orderdate],"MMM-YY") AS MonthOrder,
(Select Count(CMet.[clientID]) as cnt from
(SELECT distinct b.[orderID], Format(b.[order date],"MMM-YY")
as Contact_Month, a.[clientID], a.[Region]
FROM Client as a
INNER JOIN orders AS b ON a.[client ID] = b.[client id]
WHERE iif(isnull(loc),a.[REgion] like '*',instr (loc,a.[region]))
and (b.[orderdate] between startDt and endDt)
and (a.[Start date] < startDt)
GROUP BY a.[Region], Format(b.[order date],"MMM-YY"),
a.[clientID], a.[Region]
HAVING count(a.[clientID]) >=1) as CMet) AS Clients_Met,
(select count([client id]) from clients where
iif(isnull(loc),[region] like '*',instr (loc,[region])) and
client.[Start date] < startDt AS Total_Client,
(Total_Client-Clients_Met) AS Not_Met,
format(Clients_Met/iif(Total_Client =0,1,Total_Client),'##.##%') AS Met_Percentage
FROM clients AS a
INNER JOIN orders AS b ON a.[client ID]=b.[client id]
WHERE iif(isnull(loc),a.[region] like '*',instr (loc,a.[region]))
and (a.[Start date] < startDt
GROUP BY a.[region], Format(b.[order date],"MMM-YY")
HAVING count(a.[client id]) >=1
ORDER BY a.[region];

I tried the solution below. The idea is to do the grouping in the inner query, then do the summing in the outer one:
SELECT Region, Month, Count(ID) AS Clients, Sum(HasOrder) AS ClientsWithOrders,
[Clients]-[ClientsWithOrders] AS ClientsWithoutOrders
FROM (SELECT Region, CDate(Month([Order Date]) & "/1/" & Year([Order Date])) AS [Month],
ID, Max(IIf([Client ID]=[ID],1,0)) AS HasOrder
FROM Orders, Client
WHERE ((([Order Date])>=#5/1/2016# And
([Order Date])<DateAdd("d",1,#7/31/2016#)))
GROUP BY Region, CDate(Month([Order Date]) & "/1/" & Year([Order Date])), ID)
AS RegionMonthClient
GROUP BY Region, Month
The difficulty is I'm getting different results from you. For example, for East clients (3,4) I see one with an order in May (client 4) and one with an order in July (client 3).
Is this approach useful to you in any way?

You can use the join statement and to check the date use the BETWEEN operation. Here is the link for sample join statement and Between Opration.

Related

SQL Grouping by year gives incorrect results

I am trying to summerize sales date, by month, sales region and type. The problem is, the results change when I try to group by year.
My simplified query is as follows:
SELECT
DAB700.DATUM,DAB000.X_REGION,DAB700.BELEG_ART, // the date, sales region, order type
// calculate the number of orders per month
COUNT (DISTINCT CASE WHEN MONTH(DAB700.DATUM) = 1 THEN DAB700.BELEG_NR END) as jan,
COUNT (DISTINCT CASE WHEN MONTH(DAB700.DATUM) = 2 THEN DAB700.BELEG_NR END) as feb,
COUNT (DISTINCT CASE WHEN MONTH(DAB700.DATUM) = 3 THEN DAB700.BELEG_NR END) as mar
FROM "DAB700.ADT" DAB700
left join "DAB050.ADT" DAB050 on DAB700.BELEG_NR = DAB050.ANUMMER // join to table 050, to pull in order info
left join "DF030000.DBF" DAB000 on DAB050.KDNR = DAB000.KDNR // join table 000 to table 050, to pull in customer info
left join "DAB055.ADT" DAB055 on DAB050.ANUMMER = left (DAB055.APNUMMER,6)// join table 055 to table 050, to pull in product info
WHERE (DAB700.BELEG_ART = 10 OR DAB700.BELEG_ART = 20) AND (DAB700.DATUM>={d '2021-01-01'}) AND (DAB700.DATUM<={d '2021-01-11'}) AND DAB055.ARTNR <> '999999' AND DAB055.ARTNR <> '999996' AND DAB055.TERMIN <> 'KW.22.22' AND DAB055.TERMIN <> 'KW.99.99' AND DAB050.AUF_ART = 0
group by DAB700.DATUM,DAB000.X_REGION,DAB700.BELEG_ART
This returns the following data, which is correct (manually checked):
| DATUM | X_REGION | BELEG_ART | jan | feb | mar |
|------------|----------|-----------|-----|-----|-----|
| 04.01.2021 | 1 | 10 | 3 | 0 | 0 |
| 04.01.2021 | 3 | 10 | 2 | 0 | 0 |
| 04.01.2021 | 4 | 10 | 1 | 0 | 0 |
| 04.01.2021 | 4 | 20 | 1 | 0 | 0 |
| 04.01.2021 | 6 | 20 | 2 | 0 | 0 |
| 05.01.2021 | 1 | 10 | 1 | 0 | 0 |
and so on....
The total number of records for Jan is 117 (correct).
Now I now want to summerize the data in one row (for example, data grouped by region and type)..
so I change my code so that I have:
SELECT
YEAR(DAB700.DATUM),
and
group by YEAR(DAB700.DATUM)
the rest of the code stays the same.
Now my results are:
| EXPR | X_REGION | BELEG_ART | jan | feb | mar |
|------|----------|-----------|-----|-----|-----|
| 2021 | 1 | 10 | 16 | 0 | 0 |
| 2021 | 1 | 20 | 16 | 0 | 0 |
| 2021 | 2 | 10 | 19 | 0 | 0 |
| 2021 | 2 | 20 | 22 | 0 | 0 |
| 2021 | 3 | 10 | 12 | 0 | 0 |
| 2021 | 3 | 20 | 6 | 0 | 0 |
Visually it is correct. But, the total count for January is now 116. A difference of 1. What am I doing wrong?
How can I keep the results from the first code - but have it presented as per the 2nd set?
You count distinct BELEG_NR. This is what makes the difference. Let's look at an example. Let's say your table contains four rows:
DATUM
X_REGION
BELEG_ART
BELEG_NR
04.01.2021
1
10
100
04.01.2021
1
10
200
05.01.2021
1
10
100
05.01.2021
1
10
300
That gives you per day, region and belegart:
DATUM
X_REGION
BELEG_ART
DISTINCT COUNT BELEG_NR
04.01.2021
1
10
2
05.01.2021
1
10
2
and per year, region and belegart
YEAR
X_REGION
BELEG_ART
DISTINCT COUNT BELEG_NR
2021
1
10
3
The BELEG_NR 100 never appears more than once per day, so every instance gets counted. But it appears twice for the year, so it gets counted once instead of twice.

Sum case from previous month

I couldn't find the answer to this on here or on google.
This is part of the main table
+---+-------+----------------+--------------+
| | Acct | Last_trans_date|Last_transpay |
+---+-------+----------------+--------------+
| 1 | ABC | July 31 | Nov 5 |
| 2 | DEF | Mar 1 | Aug 8 |
| 3 | GFH | Mar 9 | Feb 7 |
+---+------+-----------------+--------------+
I want the total account for the previous month that includes last_trans_date and Last_transpay = previous month as count.
I used this
Select
year(open)
sum(case when month(last_trans_date) = month(current date - 1) and month(last_transpay) = month(current_date - 1) then 1 else 0 end) as activity
from table
group by 1.
I don't think it's outputting the correct amount
SELECT Count(*)
FROM [table]
WHERE
CHARINDEX(#PrevMonth, Last_trans_date) = 1
AND CHARINDEX(#PrevMonth, Last_transpay) = 1

Sql Query issue and error regarding groupby cause

I am trying to calculate the total number of Projects in every year. and also how many projects are active, how many of them are canceled.
I tried to group by cause for PRojects dates so we have a total number of project in every year but I am not sure where to start and what to do
Select ts.Id as projectid ,
--a.ParentObjectId,
ts.RequestName as ProjectDates,
ts.Type,
ts.Category,
ts.SubType,
ts.status as projectstatus,
Count (ts.ReceptionDate),
cast (ts.ReceptionDate as Date) as ReceptionDate,
from [rpt].[TransmissionServicesRpt] ts
left join [dbo].[AuditHistory] a on a.ParentObjectId = ts.Id
Left join [dbo].[User] u on a.CreatedById = u.id
Group by ts.id, ts.ReceptionDate
+ -------------+--------+-----------+------------+----------+-----------------+
| New Projects | Active | Cancelled | Terminated | Inactive | Carried Forward |
+ -------------+--------+-----------+------------+----------+-----------------+
| 2013 | 32 | 45 | 4 | 11 | 30 |
| 2014 | 45 | 75 | 17 | 14 | 44 |
| 2015 | 46 | 90 | 25 | 21 | 44 |
| 2016 | 30 | 74 | 27 | 10 | 37 |
| 2017 | 82 | 119 | 11 | 26 | 82 |
| 2018 | 86 | 168 | 29 | 24 | 115 |
| 2019 | 23 | 138 | 9 | 4 | 125 |
+ -------------+--------+-----------+------------+----------+-----------------+
You want one result row per year. So group by year. You get it via YEAR or DATEPART. Then count conditionally:
select
year(receptiondate) as year,
count(*) as total,
count(case when status = 'Active' then 1 end) as active,
count(case when status = 'Cancelled' then 1 end) as cancelled,
count(case when status = 'Terminated' then 1 end) as terminated,
count(case when status = 'Inactive' then 1 end) as inactive,
count(case when status = 'Carried Forward' then 1 end) as carried_forward
from rpt.transmissionservicesrpt
group by year(receptiondate)
order by year(receptiondate);

SQL - Adding an avg column to a detail table

I'm on Teradata. I have an order table like the below.
custID | orderID | month | order_amount
-----------------------------------------
1 | 1 | jan | 10
1 | 2 | jan | 20
1 | 3 | feb | 5
1 | 4 | feb | 7
2 | 5 | mar | 20
2 | 6 | apr | 30
I'd like to add a column to the above table called "Avg order amount per month per customer". Since the table is at an order level, adding this column will cause duplicates like the below, which is ok.
custID | orderID | month | order_amount | avgOrdAmtperMonth
-------------------------------------------------------------
1 | 1 | jan | 10 | 15
1 | 2 | jan | 20 | 15
1 | 3 | feb | 5 | 6
1 | 4 | feb | 7 | 6
2 | 5 | mar | 20 | 20
2 | 6 | apr | 30 | 30
I want the output to have all the columns above, not just the custid and the new column. I'm not sure how to write this because one part of the table is an at order level and the new column needs to be grouped by customer+month. How would I do this?
This is a simple group average:
AVG(order_amount) OVER (PARTITION BY custID, month)
Why not just do the calculation when you query the table?
select t.*,
avg(order_amount) over (partition by custId, month) as avgOrderAmtPerMonth
from t;
You can add this into a view if you want to make it available to multiple downstream queries.
Actually adding the column to the table is a maintenance "nightmare". You have to add triggers to the table and update the value for updates, inserts, and deletes.

SQL - How do I query for re-admissions in TSQL?

I'm trying to figure out how to query for readmissions on Server 2008r2. Here is the basic structure of the visit table. There are other fields but none that I thought would be helpful. One issue is that some of these may be transfers instead of discharges which I have no easy way to deduce but that issue can be ignored for now. I tried my hand at this but I guess my understanding of SQL needs more work. I tried to find any info I could online but none of the queries lead me to a useful conclusion or I just didn't understand. Any suggestions would be appreciated.
EDIT: Readmission is if a patient returns within 30 days of previous discharge.
+---------+--------+-----------------+-----------------+
| VisitID | UID | AdmitDT | DischargeDT |
+---------+--------+-----------------+-----------------+
| 12 | 2 | 6/17/2013 6:51 | 6/17/2013 6:51 |
| 16 | 3 | 6/19/2013 4:48 | 6/21/2013 13:35 |
| 18 | 3 | 6/11/2013 12:08 | 6/11/2013 12:08 |
| 21 | 3 | 6/12/2013 14:40 | 6/12/2013 14:40 |
| 22 | 3 | 6/13/2013 10:00 | 6/14/2013 12:00 |
| 25 | 2 | 6/11/2013 16:13 | 6/11/2013 16:13 |
| 30 | 1 | 6/20/2013 8:35 | 6/20/2013 8:35 |
| 31 | 7 | 6/13/2013 6:12 | 6/13/2013 6:12 |
| 34 | 3 | 6/12/2013 8:40 | NULL |
| 35 | 1 | 6/12/2013 8:52 | NULL |
| 38 | 2 | 6/12/2013 10:10 | 6/12/2013 10:10 |
+---------+--------+-----------------+-----------------+
Attempt at Code:
SELECT N2.*
FROM visitTable AS N1
INNER JOIN
visitTable AS N2 ON N1.UID = N2.UID
WHERE N1.EncounterID <> N2.EncounterID AND ( N2.AdmitDT BETWEEN N1.DischargeDT and DATEADD(DD,30, N1.DischargeDT))
Here's a start:
sqlfiddle
new fiddle
It gets each visit for each UID in order of admitDT, then pairs each visit with the next visit in that result. If the current admit date is between the last discharge date and 30 days from then, select it. There are some weird points though - UID 1 is shown to have been admitted on 6/12/2012 and never discharged, but then admitted again on 6/20/2013 and discharged the same day.
edit: restructured a bit to reduce the number of joins
WITH cte AS (
SELECT visitid,uid,dischargedt,admitdt,
row_number()over(partition BY uid ORDER BY admitdt) AS r
FROM t
)
SELECT
c1.visitid AS v1, c2.visitid AS v2,
c1.uid,
c1.dischargedt as [Discharged from first visit],
c2.admitdt as [Admitted to next visit]
FROM cte c1
INNER JOIN cte c2 ON c1.uid=c2.uid
WHERE c1.visitid<>c2.visitid
AND c1.r+1=c2.r
AND c2.admitdt BETWEEN c1.dischargedt AND dateadd(d,30,c1.dischargedt )
ORDER BY c1.uid
Results:
| V1 | V2 | UID | DISCHARGED FROM FIRST VISIT | ADMITTED TO NEXT VISIT |
|----|----|-----|-----------------------------|-----------------------------|
| 25 | 38 | 2 | June, 11 2013 16:13:00+0000 | June, 12 2013 10:10:00+0000 |
| 38 | 12 | 2 | June, 12 2013 10:10:00+0000 | June, 17 2013 06:51:00+0000 |
| 18 | 34 | 3 | June, 11 2013 12:08:00+0000 | June, 12 2013 08:40:00+0000 |
| 21 | 22 | 3 | June, 12 2013 14:40:00+0000 | June, 13 2013 10:00:00+0000 |
| 22 | 16 | 3 | June, 14 2013 12:00:00+0000 | June, 19 2013 04:48:00+0000 |
try this: (Show me the visits where the admission date is after discharge for another earlier visit by the same patient)
Select * From visits v
Where Exists (Select * From Visits
Where uid = v.uid
and v.AdmitDT > DischargeDT)
You have not explained any business rules so I'll take a guess. A readmission is when multiple UID appear, and it is every record except the first one
Here is another method using windowing functions.
SELECT VT.*
FROM visitTable VT
INNER JOIN
(
SELECT VisitID, ROW_NUMBER() OVER (PARTITION BY UID ORDER BY AdmitDT) VisitCount
FROM visitTable
) RA
ON RA.VisitCount > 1 AND RA.VisitID = VT.VisitID