I have a table with transaction history for 3 years, I need to compare the sum ( transaction) for 12 months with sum( transaction) for 4 weeks and display the customer list with the result set.
Table Transaction_History
Customer_List Transaction Date
1 200 01/01/2014
2 200 01/01/2014
1 100 10/24/2014
1 100 11/01/2014
2 200 11/01/2014
The output should have only Customer_List with 1 because sum of 12 months transactions equals sum of 1 month transaction.
I am confused about how to find the sum for 12 months and then compare with same table sum for 4 weeks.
the query below will work, except your sample data doesnt make sense
total for customer 1 for the last 12 months in your data set = 400
total for customer 1 for the last 4 weeks in your data set = 200
unless you want to exclude the last 4 weeks, and not be a part of the last 12 months?
then you would change the "having clause" to:
having
sum(case when Dt >= '01/01/2014' and dt <='12/31/2014' then (trans) end) - sum(case when Dt >= '10/01/2014' and dt <= '11/02/2014' then (trans) end) =
sum(case when Dt >= '10/01/2014' and dt <= '11/02/2014' then (trans) end)
of course doing this would mean your results would be customer 1 and 2
create table #trans_hist
(Customer_List int, Trans int, Dt Date)
insert into #trans_hist (Customer_List, Trans , Dt ) values
(1, 200, '01/01/2014'),
(2, 200, '01/01/2014'),
(1, 100, '10/24/2014'),
(1, 100, '11/01/2014'),
(2, 200, '11/01/2014')
select
Customer_List
from #trans_hist
group by
Customer_List
having
sum(case when Dt >= '01/01/2014' and dt <='12/31/2014' then (trans) end) =
sum(case when Dt >= '10/01/2014' and dt <= '11/02/2014' then (trans) end)
drop table #trans_hist
I suggest a self join.
select yourfields
from yourtable twelvemonths join yourtable fourweeks on something
where fourweek.something is within a four week period
and twelvemonths.something is within a 12 month period
You should be able to work out the details.
If your transactions are always positive and you want customers whose 12-month totals equal the 4-week total, then you want customers who have transactions in the past four weeks but not in the preceding 12 months - 4 weeks.
You can get this more directly using aggregation and a having clause. The logic is to check for any transactions in the past year that occurred before the previous 4 weeks:
select Customer_List
from Transaction_History
where date >= dateadd(month, -12, getdate())
group by CustomerList
having min(date) >= dateadd(day, -4 * 7, getdate());
Look here for methods to aggregate by month, year, etc.
http://weblogs.sqlteam.com/jeffs/archive/2007/09/10/group-by-month-sql.aspx
Related
I have a table A that we import based on the day that it lands on a location. We dont receive files on weekend and public holidays, and the table has multiple countries data so the public holidays vary. In essence we looking to duplicate a row multiple times till it encounters the next record for that ID (unless its the max date for that ID). A typical record looks like this:
Account Datekey Balance
1 20181012 100
1 20181112 100
1 20181212 100
1 20181512 100
1 20181712 100
And needs to look like this (sat, sun & PH added to indicate the day of week):
Account Datekey Balance
1 20181012 100
1 20181112 100
1 20181212 100
1 20181312 100 Sat
1 20181412 100 Sun
1 20181512 100
1 20181612 100 PH
1 20181712 100
Also Datekey is numeric and not a date. I tried a couple solutions suggested but found that it simply duplicates the previous row multiple times without stopping when the next dates record is found. I need to run it as an update query that would execute daily on table A and add missing records when its executed (sometimes 2 or 3 days later).
Hope you can assist.
Thanks
This question has multiple parts:
Converting an obscene date format to a date
Generating "in-between" rows
Filling in the new rows with the previous value
Determining the day of the week
The following does most of this. I refuse to regenerate the datekey format. You really need to fix that.
This also assumes that your setting are for English week day names.
with t as (
select Account, Datekey, Balance, convert(date, left(dkey, 4) + right(dkey, 2) + substring(dkey, 5, 2)) as proper_date
from yourtable
),
dates as (
select account, min(proper_date) as dte, max(proper_date) as max_dte
from t
group by account
union all
select account, dateadd(day, 1, dte), max_dte
from dates
where dte < max_dte
)
select d.account, d.dte, t.balance,
(case when datename(weekday, d.dte) in ('Saturday', 'Sunday')
then left(datename(weekday, d.dte), 3)
else 'PH'
end) as indicator
from dates d cross apply
(select top (1) t.*
from t
where t.account = d.account and
t.proper_date <= d.dte
order by t.proper_date desc
) t
option (maxrecursion 0);
I have a search set up which gives total count of new patient visits and total count of patient visits, and comparing the totals for the requested year to the previous year's totals.
The SQL queries the date fields firstexam and lastexam from the table patient_info.
I have since found out that some users do not update the lastexam with every patient visit, and therefore the lastexam would not give the total number of patient visits.
Total number of patient visits can be obtained by searching the transactions table. Invoices in the transaction table are marked with the column transtype as 'Inv'. So, the total number of patient visits would be the total number of invoices in the date range (taking into account that two invoices entered for a patient in a single day count as one visit).
Below is the code for the SQL query set up based on firstexam and lastexam.
I have been struggling with this for some time now and am stuck. Any help would be greatly appreciated.
select
to_char(('2012-' || m || '-01')::date, 'Month'),
thisyear, lastyear, totalthisyear, totallastyear
from (
select
extract(month from m) as m,
sum(case
when firstexam between '2013-01-01' and '2013-12-31' then firstexam_count
else 0 end
) as thisyear,
sum(case
when firstexam between '2012-01-01' and '2012-12-31' then firstexam_count
else 0 end
) as lastyear,
sum(case
when lastexam between '2013-01-01' and '2013-12-31' then lastexam_count
else 0 end
) as totalthisyear,
sum(case
when lastexam between '2012-01-01' and '2012-12-31' then lastexam_count
else 0 end
) as totallastyear
from
generate_series (
'2012-01-01'::date, '2013-12-31', '1 month'
) g(m)
left join (
select count(*) as firstexam_count, date_trunc('month', firstexam) as firstexam
from patient_info
where firstexam between '2012-01-01' and '2013-12-31'
group by 2
) pif on firstexam = m
left join (
select count(*) as lastexam_count, date_trunc('month', lastexam) as lastexam
from patient_info
where lastexam between '2012-01-01' and '2013-12-31'
group by 2
) pil on lastexam = m
group by 1
) s
order by m
If you want to report information about exams, you ought to store information about exams.
More specifically, if you want to count exams, you ought to store information about each exam.
Don't use column names like "thisyear" and "lastyear". This year isn't 2013, although that's how you present it.
Usually, visits and exams are different things. Be careful with terminology. (Here it's not such a big deal, because we don't have information about either visits or exams. Only about invoices. Still, it's a good habit.)
If you're concerned about a particular output format, ask yourself whether you're building a query or a report. Build queries in SQL. Build reports with a report writer or application code.
For simplicity, I'm going to
ignore the "patient_info" table,
ignore the outer join you need in order to generate zeroes for months in which there were no exams, and
use common table expressions. (In production I'd rather use views than common table expressions).
Let's start with just a table of transactions.
create table transactions (
ptnumber INT,
dateofservice date,
transtype varchar(3)
);
-- Not quite the same data you started with.
insert into transactions (ptnumber, dateofservice, transtype)
values
(1, '2012-01-01', 'Inv'),
(1, '2012-02-11', 'Inv'),
(2, '2012-01-02', 'Inv'),
(3, '2013-01-01', 'Inv'),
(4, '2013-02-12', 'Inv'),
(5, '2012-12-31', 'Inv'),
(5, '2013-12-31', 'Inv'),
(5, '2013-12-31', 'Inv'),
(6, '2013-06-21', 'Inv');
You said "two invoices entered for a patient in a single day count as one [exam]". I guess that means two or more. So we can extract the set of patient exams like this. I expect two rows for patient 5--one in 2012 and one in 2013.
select distinct ptnumber, dateofservice
from transactions
where transtype = 'Inv'
and dateofservice between '2012-01-01' and '2013-12-31'
order by ptnumber;
ptnumber dateofservice
--
1 2012-01-01
1 2012-02-11
2 2012-01-02
3 2013-01-01
4 2013-02-12
5 2012-12-31
5 2013-12-31
6 2013-06-21
This is the key to your whole problem--a set of distinct patient exams over a defined range of dates. Based on this set, counting patient visits by month is straightforward. (Counting them every which way is straightforward.)
with patient_exams as (
select distinct ptnumber, dateofservice
from transactions
where transtype = 'Inv'
and dateofservice between '2012-01-01' and '2013-12-31'
)
select to_char(dateofservice, 'YYYY-MM') as month_of_service, count(*) as num_patient_exams
from patient_exams
group by 1
order by 1;
month_of_service num_patient_visits
--
2012-01 2
2012-02 1
2012-12 1
2013-01 1
2013-02 1
2013-06 1
2013-12 1
First exams
Again, start by deriving a set that will give you reliable counts. You want one row per patient, and you want the earliest invoice date. The date of a patient's first exam has nothing to do with the date range you want to report; including the date range in this query's WHERE clause will give you the wrong data.
select ptnumber, min(dateofservice) as first_exam_date
from transactions
where transtype = 'Inv'
group by ptnumber
order by ptnumber;
ptnumber first_exam_date
--
1 2012-01-01
2 2012-01-02
3 2013-01-01
4 2013-02-12
5 2012-12-31
6 2013-06-21
Now counting how many new patients you gained each month is straightforward.
with first_exams as (
select ptnumber, min(dateofservice) as first_exam_date
from transactions
where transtype = 'Inv'
group by ptnumber
)
select to_char(first_exam_date, 'YYYY-MM') exam_month, count(*) num_first_exams
from first_exams
where first_exam_date between '2012-01-01' and '2013-12-31'
group by 1
order by 1;
exam_month num_first_exams
--
2012-01 2
2012-12 1
2013-01 1
2013-02 1
2013-06 1
I have data as below:
Docdate Qty Amount
-----------------------------------
2014/08/01(Friday) 5 100
2014/08/03(Sunday) 5 100
2014/08/04(Monday) 8 100
2014/08/10(Sunday) 8 100
2014/08/11(Monday) 8 100
2014/08/17(Sunday) 8 100
2014/08/18(Monday) 8 100
2014/08/24(Sunday) 8 100
2014/08/25(Monday) 8 100
2014/08/31(Sunday) 8 100
I want to general a stored procedure with below template:
Item QtyWeek1(1-3) QtyWeek2(4-10) QtyWeek3(11-17) QtyWeek4(18-24) QtyWeek5(25-31)
A 10 16 16 16 16
B - - - - -
Is it possible to do that in stored procedure?
Is it possible to do that in Crystal Report? If yes, what stored procedure columns should be displayed?
Thanks
Try this solution, if you column Docdate DATETIME type
SELECT item, SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 1 AND 3 THEN Qty ELSE 0 END) AS qtyWeek1,
SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 4 AND 10 THEN Qty ELSE 0 END) AS qtyWeek2,
SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 11 AND 17 THEN Qty ELSE 0 END) AS qtyWeek3,
SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 18 AND 24 THEN Qty ELSE 0 END) AS qtyWeek4,
SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 25 AND 31 THEN Qty ELSE 0 END) AS qtyWeek5
FROM YouTable
GROUP BY item
It is not easy at all. I suppose you have one more column name item and you would like to have always five weeks, and so I had to consider an extra column YYYYMM to contains the month of the weeks belong.
Then the query would be:
select ITEM, MONTH_YEAR, [1] WEEK1, [2] WEEK2, [3] WEEK3, [4] WEEK4, [5] WEEK5
from (
select
ITEM,
YEAR(DATE1)*100+MONTH(DATE1) MONTH_YEAR,
1 + WEEK - WEEK0 WEEK,
sum(Qty) Qty
from Table1 t1
cross apply (SELECT cast(Replace(LEFT(DocDate,10),'/','-') as date)) N(DATE1)
cross apply (SELECT DATEPART(WEEK, DATEADD(DAY, -1, DATE1))) T(WEEK)
cross apply (SELECT DATEPART(WEEK, DateAdd(day, -1, DateAdd(month, MONTH(DATE1) - 1, DateAdd(Year, YEAR(DATE1)-1900, 0))))) U(WEEK0)
group by WEEK, YEAR(DATE1)*100+MONTH(DATE1), WEEK0, ITEM
) A
pivot (sum(QTY) FOR WEEK in ([1],[2],[3],[4],[5])) B
Glossary:
DATE1 represents the conversion of DocDate column in date type
WEEK represents the number of the week
WEEK0 represents the number of the first week of the month
SQL Fiddle
Since you have crystal reports DO NOT do the crosstab in the database. Do it in crystal report.
If you look at anyones regular advice on this it is: do the pivot in the reporting tool
General solution is:
Add a single column to your existing recordset called, say weeknumber,
Map your existing DocDate to the week number (you'll need to explain the rules, i.e. when does a week start, what is week 1 etc., then I can suggest some code)
In crystal reports create a crosstab report which pivots on the new column weeknumber.
That way if you have 2 or 20 columns, crystal will take care of it, rather than hard coding in a database object.
What I'm basically getting now is in a span of 6 months
PATIENT - Number of events in 6 months
1
2
3
4
5
What i want to get is
PATIENT - Number of events in 1st month - ... 2nd month - ... 3rd month .. till 6th month
1
2
3
4
5
Which ofc the results are the same, only that they're divided by month instead all in one column
CREATE PROCEDURE PMP.TOP5BonosVencidos
(
#pComienzo_Semestre datetime = null
)
AS
BEGIN
select TOP 5 PACIENTE_DOCUMENTO, PACIENTE_NOMBRE, PACIENTE_APELLIDO, COUNT(*)
from PMP.BONO_FARMACIA, PMP.PACIENTE, PMP.COMPRA_BONO
where CAST(FECHA_VENCIMIENTO AS DATE) >= CAST(#pComienzo_Semestre AS DATE) AND
CAST(FECHA_VENCIMIENTO AS DATE) < DATEADD(month,6,CAST(#pComienzo_Semestre AS DATE)) AND
PMP.PACIENTE.PACIENTE_ID = COMPRA_BONO_PACIENTE_ID AND
COMPRA_BONO_CANTIDAD_FARMACIA > 0 AND
COMPRA_BONO_ID = COMPRA_BONO
group by PACIENTE_DOCUMENTO, PACIENTE_NOMBRE, PACIENTE_APELLIDO
order by COUNT(*) DESC
END
GO
You can just add month to the grouping:
select TOP 5 PACIENTE_DOCUMENTO, PACIENTE_NOMBRE, PACIENTE_APELLIDO, DATEPART(Month, FECHA_VENCIMIENTO) as [Month], COUNT(*)
from PMP.BONO_FARMACIA, PMP.PACIENTE, PMP.COMPRA_BONO
where CAST(FECHA_VENCIMIENTO AS DATE) >= CAST(#pComienzo_Semestre AS DATE) AND
CAST(FECHA_VENCIMIENTO AS DATE) < DATEADD(month,6,CAST(#pComienzo_Semestre AS DATE)) AND
PMP.PACIENTE.PACIENTE_ID = COMPRA_BONO_PACIENTE_ID AND
COMPRA_BONO_CANTIDAD_FARMACIA > 0 AND
COMPRA_BONO_ID = COMPRA_BONO
group by PACIENTE_DOCUMENTO, PACIENTE_NOMBRE, PACIENTE_APELLIDO, DATEPART(Month, FECHA_VENCIMIENTO)
I removed ORDER BY as I'm not sure what the ordering should be in this case - please add the relevant ordering yourself.
I have a table of tickets. I am trying to calculate how many tickets were "open" at each month end over the course of the current year. As well, I am pushing this to a bar chart and I am needing out put this into an array through LINQ.
My SQL query to get my calculation is:
SELECT
(SELECT COUNT(*) FROM tblMaintenanceTicket t WHERE (CreateDate < DATEADD(MM, 1, '01/01/2012')))
-
(SELECT COUNT(*) FROM tblMaintenanceTicket t WHERE (CloseDate < DATEADD(MM, 1, '01/01/2012'))) AS 'Open #Month End'
My logic is the following: Count all tickets open between first and end of the month. Subtract that count from the tickets closed before the end of the month.
UPDATED:
I have updated my query with the comments below and it is not working with errors in the GROUP, but I am not truly understanding the logic I guess, my lack of skill in SQL is to blame.
I have added a SQL Fiddle example to show you my query: http://sqlfiddle.com/#!3/c9b638/1
Desired output:
-----------
| Jan | 3 |
-----------
| Feb | 4 |
-----------
| Mar | 0 |
-----------
Your SQL has several erros . . . are grouping by CreateDate but you don't have it as a column from the subqueries. And, you don't have a column alias on the count(*).
I think this is what you are trying to do:
select DATENAME(MONTH,CreateDate), DATEPART(YEAR,CreateDate),
(sum(case when CreateDate < DATEADD(MM, 1, '01/01/2012') then 1 else 0 end) -
sum(case when CloseDate < DATEADD(MM, 1, '01/01/2012') then 1 else 0 end)
)
from tblMaintenanceTicket
group by DATENAME(MONTH,CreateDate), DATEPART(YEAR,CreateDate)
Your comment seems to elucidate what you want clearer than your question (the explanation in the question is a bit buried). What you need is a driver table of months and then join this to your table. Something like:
select mons.yr, mons.mon, count(*) as OpenTickets
from (select month(CreateDate) as mon, year(CreateDate) as yr,
cast(min(CreateDate) as date) as MonthStart,
cast(max(CreateDate) as date) as monthEnd
from tblMaintenanceTicket
group by month(CreateDate), year(CreateDate)
) mons left outer join
tblMaintenanceTicket mt
on mt.CreateDate <= mons.MonthEnd and
(mt.CloseDate > mons.MonthEnd or mt.CloseDate is null)
group by mons.yr, mons.mon
I am assuming records are created on every day. This is a convenience so I don't have to think about getting the first and last day of each month using other SQL functions.
If your query is returning what you need, then simply use DATENAME(MONTH, yourDate) to retrieve the month and group by Month,Year:
SELECT SUM(*), DATENAME(MONTH,yourDate), DATEPART(YEAR,yourDate)
FROM
(
your actual query here
)
GROUP BY DATENAME(MONTH,yourDate), DATEPART(YEAR,yourDate)