Sum Values within 3 tables - sql

Table 1
jh."job-hdr"
job-date job-disp job-dept job-route job-id job-no
01/04/2013 6467 abc 123 22 81088
01/04/2013 6468 abc 987 36 82568
Table 2
rh."rec-charge"
charge-type rec-id base-sales-value
XYZ 22 700
Table 3
rc."rec-cost"
charge-type rec-id base-cost-value
XYZ 22 300
I need to be able to get the profit from this jobid of
700 - 300 = 400
This is where I have gotten up to
SELECT jh."job-date", jh."job-disp", jh."job-dept", jh."job-route", rc."charge-type",rh."charge-type",
SUM(rc."base-cost-value") as COSTS,
SUM(rh."base-sales-value") as SALES,
SUM(rh."base-sales-value") - SUM(rc."base-cost-value") as PROFIT
FROM MSN.PUB."rec-chg" rh, PUB."job-hdr" jh, pub."rec-cost" rc
WHERE jh."job-date" between '2013-04-01' and '2013-04-30'
and jh."job-id" = rc."rec-id"
and rc."rec-id" = rh."rec-id"
and jh."grp-id" = '0'
and jh."job-status"<>'D'
and jh."job-no" = '81088'
and rc."charge-type" = rh."charge-type"
Group by jh."job-date", jh."job-disp", jh."job-dept", jh."job-route",rc."charge- type",rh."charge-type"
This is not giving me great results at all and I know I am way off. I just need to be put in the right direction.

Update profit to:
SUM(rh."base-sales-value" - rc."base-cost-value") as PROFIT
And update your group by to:
group by jh."job-id", rc."rec-id", rh."rec-id"
This should give your the desired result (hopefully). Sorry didnt not have time to test it myself. The main focus is on group by, which should be applied on a field that would return multiple results for other fields you want to run the sum on.

Your question appears is a little ambiguous, as to whether you want the results by job or by charge type. In either case, you need to aggregate the results before doing the join. The following query does this at the job level:
SELECT jh."job-date", jh."job-disp", jh."job-dept", jh."job-route",
COSTS, SALES, SALES - COSTS as PROFIT
FROM PUB."job-hdr" jh left outer join
(select rh."rec-id", SUM(rh."base-sales-value") as SALES
from MSN.PUB."rec-chg" rh
group by rh."rec-id"
) rh
on jh."job-id" = rh."rec-id" left outer join
(select rc."rec-id", SUM(rc."base-cost-value") as COSTS
from pub."rec-cost" rc
group by rc."rec-id"
) rc
on jh."job-id" = rc."rec-id"
WHERE jh."grp-id" = '0' and
jh."job-status" <> 'D' and
jh."job-no" = '81088';
Notice that I replaced your implicit join syntax with explicit join syntax. The explicit version is much better, so you should learn to use it.

Related

Advanced SQL: Sum of sales between a date range

I'm trying to get the sum of sales for a specific product between a date range. Unfortunately, the sum of sales from the results for both dates are the same. By right the total sales on 2019/01/01 is 5000 and the total sales on following day 2019/01/02 is 3000. The results showed total sales which is 8000 for both days. Which is wrong. Any expert can help to improve is this query?
Declare #BusinessDate datetime ='2019-01-01'
Declare #end datetime ='2019-01-02'
DECLARE #StoreId int = 100
SELECT [Terminals].[Id] AS [TerminalId],
[Terminals].[StoreId],
[EOD].[Id] AS [EODId],
SUM([Sales].[SalesAmount]) AS [SalesAmount],
[EOD].BusinessDate
FROM [CEPP]..[Stores] WITH (NOLOCK)
INNER JOIN [CEPP]..[Terminals] WITH (NOLOCK)
ON [Stores].[Id] = [Terminals].[StoreId]
AND [Terminals].[MWorkFlowStatusId] = 2
AND ([Terminals].[MStatusId] = 1
OR ([Terminals].[MStatusId] = 0
AND [Terminals].[SuspendedDate] > #BusinessDate ))
LEFT JOIN [EndOfDays] AS [EOD] WITH (NOLOCK)
ON [Terminals].[Id] = [EOD].[TerminalId]
AND [EOD].[BusinessDate] >= #BusinessDate and [EOD].[BusinessDate]<=#end
CROSS APPLY (
SELECT SUM([Products].[Deno]) AS [SalesAmount]
FROM [SalesOrders] AS [SO] WITH (NOLOCK)
INNER JOIN [SalesTransactions] AS [ST] WITH (NOLOCK)
ON [SO].[Id] = [ST].[SalesOrderId]
LEFT JOIN [VoidOrders] AS [VO] WITH (NOLOCK)
INNER JOIN [VoidTransactions] AS [VT] WITH (NOLOCK)
ON [VO].[Id] = [VT].[VoidOrderId]
ON [SO].[DealerId] = [VO].[DealerId]
AND [SO].[StoreId] = [VO].[StoreId]
AND [SO].[TerminalId] = [VO].[TerminalId]
AND [ST].[ProductId] = [VT].[ProductId]
AND [ST].[SerialNo] = [VT].[SerialNo]
AND [ST].[BusinessDate] = [VT].[BusinessDate]
AND [VT].[MVoidTypeId] = 1
INNER JOIN [CEPP].[dbo].[Products] WITH (NOLOCK)
ON [ST].[ProductId] = [Products].[Id]
WHERE [EOD].[Id] IS NOT NULL
AND [VT].[SerialNo] IS NULL
AND [SO].[TerminalId] = [Terminals].[Id]
AND [ST].[BusinessDate] >= #BusinessDate and [ST].[BusinessDate] <= #end
) AS [Sales]
WHERE [Stores].[DealerId] = 1 AND (#StoreId IS NULL OR [Terminals].[StoreId] = #StoreId)
GROUP BY [Terminals].[Id], [Terminals].[StoreId], [EOD].[Id], [Stores].[Code], [Terminals].[Code],[EOD].BusinessDate
ORDER BY ISNULL([EOD].[Id], 0), [Stores].[Code], [Terminals].[Code]
The unexpected results I got is :
TerminalId StoreId EODId SalesAmount BusinessDate
21598 100 5427531 8000.00 2019-01-01 00:00:00.000
21598 100 5427532 8000.00 2019-01-02 00:00:00.000
The results should be like this:
TerminalId StoreId EODId SalesAmount BusinessDate
21598 100 5427531 5000.00 2019-01-01 00:00:00.000
21598 100 5427532 3000.00 2019-01-02 00:00:00.000
From what I can see at a glance and without test data, is that the SUM([Products].[Deno]) is being performed in the CROSS APPLY irrespective of any GROUP BY you have in the outer query. Hence why you're getting SUM([Sales].[SalesAmount]) to equal 8000 for each output row.
Refactor the CROSS APPLY subquery to aggregate SUM([Products].[Deno]) with respect to a GROUP BY and join back to the main table by the GROUP BY predicates to your outer query.
AND [EOD].[BusinessDate] >= #BusinessDate and [EOD].[BusinessDate]<=#end
This part looks very suspicious to me. I think it should be something like
[EOD].[BusinessDate] = [Sales].[Date]
If that does not resolve your problem, please provide us with scripts for creation of tables and test data. That way it's much easier to investigate query.

Access SQL LEFT JOIN alternative

I'm trying something in Access via SQL using a LEFT JOIN but it doesn't seem to be working. Access keeps generating the error "JOIN expression not supported.".
What I'm trying to accomplish is as follows. I have a table with job cards and another table with costs as below.
JOBCARDS
ID JOBNAME
1 Job one
2 Job two
3 Job three
COSTS
ID TYPE COST JOB
1 PART 15.01 1
2 LABOUR 20.00 1
3 LABOUR 40.00 2
4 PART 34.54 3
5 PART 84.67 3
I'm trying to formulate an SQL query that will give me the following result:
QUERY
ID JOBNAME PARTS LABOUR
1 Job one 15.01 20.00
2 Job two 0.00 40.00
3 Job three 119.21 0.00
What I came up with:
SELECT
CARDS.[ID] AS [ID],
CARDS.[JOBNAME] AS [JOBNAME],
SUM (COSTS1.[COST]) AS [PARTS],
SUM (COSTS2.[COST]) AS [LABOUR]
FROM
(([JOBCARDS] CARDS LEFT JOIN [COSTS] COSTS1 ON COSTS1.[JOB]=CARDS.[ID] AND COSTS1.[TYPE]='PART')
LEFT JOIN [COSTS] COSTS2 ON COSTS2.[JOB]=CARDS.[ID] AND COSTS2.[TYPE]='LABOUR')
GROUP BY
CARDS.[ID], CARDS.[JOBNAME];
Access seems to be having problems with the part "COSTS1.[TYPE]='PART'".
Is there any way I can accomplish what I'm trying to do without using a LEFT JOIN?
Or does anyone spot the error?
This SQL will give the result.
The 0 values will be Null, but you could use NZ to replace that with a 0.
SELECT JobCards.ID
,JobName
,SUM(C2.Cost) AS Parts
,SUM(C1.Cost) AS Labour
FROM (JobCards LEFT JOIN Costs C1 ON (JobCards.ID = C1.Job AND C1.Type = 'Labour'))
LEFT JOIN Costs C2 ON (JobCards.ID = C2.Job AND C2.Type = 'Part')
GROUP BY JobCards.ID
,JobName
Edit:
Re-reading your SQL - you just forgot to put the brackets after the ON statements in your Join:
(COSTS1.[JOB]=CARDS.[ID] AND COSTS1.[TYPE]='PART')
SELECT
CARDS.[ID] AS [ID],
CARDS.[JOBNAME] AS [JOBNAME],
SUM (COSTS1.[COST]) AS [PARTS],
SUM (COSTS2.[COST]) AS [LABOUR]
FROM
(([JOBCARDS] CARDS LEFT JOIN [COSTS] COSTS1 ON (COSTS1.[JOB]=CARDS.[ID] AND COSTS1.[TYPE]='PART'))
LEFT JOIN [COSTS] COSTS2 ON (COSTS2.[JOB]=CARDS.[ID] AND COSTS2.[TYPE]='LABOUR'))
GROUP BY
CARDS.[ID], CARDS.[JOBNAME];
Edit 2: There's no need to put brackets around everything, or alias the field names if they're the same as the source field, or use the table names in the SELECT & WHERE clauses unless the field name appears in more than one table.
Here is a correlated sub-query example:
SELECT JOBCARDS.ID,
JOBCARDS.JOBNAME,
(SELECT SUM([COST]) FROM COSTS WHERE COSTS.JOB = JOBCARDS.ID AND COSTS.TYPE = 'PART') AS PARTS,
(SELECT SUM([COST]) FROM COSTS WHERE COSTS.JOB = JOBCARDS.ID AND COSTS.TYPE = 'LABOUR') AS LABOUR
FROM JOBCARDS;

How get specific rows in grouped result

i need to group some data but because there are 4 store images , sql query return 4 result for every store. How can i get only one for a store by using sql query ?
select s.name,si.SHOP_IMG_PATH,count(*) amount from stab t
inner join shop s on (s.shop_id = t.shop_id)
inner join SHOP_IMG si on (s.shop_id= si.SHOP_ID)
where t.acct_id = 111 and t.CR_DATE >= sysDate - 1
group by s.name,si.SHOP_IMG_PATH
order by 3 desc,1 asc
As you see below image there a re 4 images so query can give random image
You are grouping by s.name, si.SHOP_IMG_PATH it will consider all possible combination of s.name, si.SHOP_IMG_PATH as separate you need to keep group by only s.name
Try this
SELECT a.NAME, a.PATH, a.AMOUNT
FROM (select
s.name AS 'NAME', si.SHOP_IMG_PATH AS 'PATH', count(*) AS 'AMOUNT',
ROW_NUMBER() OVER(PARTITION BY s.name
ORDER BY type si.SHOP_IMG_PATH) AS rk
from
stab t
inner join shop s on (s.shop_id = t.shop_id)
inner join SHOP_IMG si on (s.shop_id= si.SHOP_ID)
where t.acct_id = 111 and t.CR_DATE >= sysDate - 1
group by s.name
order by 3 desc,1 asc) a
WHERE a.rk = 1;
Alternative
You will get result but this is just a workaround and easy alternative to your problem but not a good one.
select s.name AS 'NAME', min(si.SHOP_IMG_PATH) AS 'PATH', count(*) AS 'AMOUNT',
from
stab t
inner join shop s on (s.shop_id = t.shop_id)
inner join SHOP_IMG si on (s.shop_id= si.SHOP_ID)
where t.acct_id = 111 and t.CR_DATE >= sysDate - 1
group by s.name
order by 3 desc,1 asc
This second query will return result as per your need
group by s.name, si.SHOP_IMG_PATH
You're telling it to differentiate them according to SHOP_IMG_PATH. Hence, it shows 4 results, one for each of those.
You'll have to drop SHOP_IMG_PATH from the select clause, if you won't let it use it.
Edit
Got your comment. What you're looking for is random aggregation. This is achieved diferently on different SQL engines. Google around for the one you're using.
If it's Oracle, as indicated by the question tag, here
I solved my problem by using below query,
select s.name,t.shop_id,(select min(SHOP_IMG_PATH) from SHOP_IMG si where shop_id =t.shop_id),count(*) amount from stab t
inner join shop s on (s.shop_id = t.shop_id)
where t.acct_id = 111 and t.CR_DATE >= sysDate - 1
group by s.name,t.shop_id
order by 4 desc,1 asc

Get percentages of larger group

The query below is kind of an ugly one so I hope I've got it spaced well enough to make it readable. The query finds the percentage of people that visit a given hospital if they are from a certain area. For instance, if 100 people live in county X and 20 go to hospital A and 80 go to hospital B the query outputs. How the heck is this sort of thing done? Let me know if I need to document the query or whatever I can do to make it clearer.
hospital A 20
hospital B 80
The query below works exactly like I want it to, but it give me thinking: how could this be done for every county in my table?
select hospitalname, round(cast(counts as float)/cast(fayettestrokepop as float)*100,2)as percentSeen
from
(
SELECT tblHospitals.hospitalname, COUNT(tblHospitals.hospitalname) AS counts, tblStateCounties_1.countyName,
(SELECT COUNT(*) AS Expr1
FROM Patient INNER JOIN
tblStateCounties ON Patient.stateCode = tblStateCounties.stateCode AND Patient.countyCode = tblStateCounties.countyCode
WHERE (tblStateCounties.stateCode = '21') AND (tblStateCounties.countyName = 'fayette')) AS fayetteStrokePop
FROM Patient AS Patient_1 INNER JOIN
tblHospitals ON Patient_1.hospitalnpi = tblHospitals.hospitalnpi INNER JOIN
tblStateCounties AS tblStateCounties_1 ON Patient_1.stateCode = tblStateCounties_1.stateCode AND Patient_1.countyCode = tblStateCounties_1.countyCode
WHERE (tblStateCounties_1.stateCode = '21') AND (tblStateCounties_1.countyName = 'fayette')
GROUP BY tblHospitals.hospitalname, tblStateCounties_1.countyName
) as t
order by percentSeen desc
EDIT: sample data
The sample data below is without the outermost query (the as t order by part).
The countsInTheCounty column is the (select count(*)..) part after 'tblStateCounties_1.countyName'
hospitalName hospitalCounts countyName countsInTheCounty
st. james 23 X 300
st. jude 40 X 300
Now with the outer query we would get
st james 0.076 (23/300)
st. jude 0.1333 (40/300)
Here is my guess. You'll have to test against your data or provide proper DDL + sample data.
;WITH totalCounts AS
(
SELECT StateCode, countyCode, COUNT(*) AS totalcount
FROM dbo.Patient GROUP BY StateCode, countyCode
)
SELECT
h.hospitalName,
hospitalCounts = COUNT(p.hospitalnpi),
c.countyName,
countsInTheCounty = tc.totalCount,
percentseen = CONVERT(DECIMAL(5,2), COUNT(p.hospitalnpi)*100.0/tc.totalCount)
FROM
dbo.Patient AS p
INNER JOIN
dbo.tblHospitals AS h
ON p.hospitalnpi = h.hospitalnpi
INNER JOIN
totalCounts AS tc
ON p.StateCode = tc.StateCode
AND p.countyCode = tc.countyCode
INNER JOIN
dbo.tblStateCounties AS c
ON tc.StateCode = c.stateCode
AND tc.countyCode = c.countyCode
GROUP BY
h.hospitalname,
c.countyName,
tc.totalcount
ORDER BY
c.countyName,
percentseen DESC;

Rollup / recursive addition SQL Server 2008

I have a query with rollup that outputs data like (the query is a little busy, but I can post if necessary)
range subCounts Counts percent
1-9 3 100 3.0
10-19 13 100 13.0
20-29 30 100 33.0
30-39 74 100 74.0
NULL 100 100 100.0
How is it possible to keep a running summation total of percent? Say I need to find the bottom 15 percentile, in this case 3+13=16 so I would like for the last row to be returned read
range subCounts counts percent
10-19 13 100 13.0
EDIT1: here the query
select '$'+cast(+bin*10000 + ' ' as varchar(10)) + '-' + cast(bin*10000+9999 as varchar(10)) as bins,
count(*) as numbers,
(select count(distinct patient.patientid) from patient
inner join tblclaims on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on tblhospitals.hospitalnpi = patient.hospitalnpi
where (tblhospitals.hospitalname = 'X')
) as Totals
, round(100*count(*)/cast((select count(distinct patient.patientid) from patient
inner join tblclaims on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on tblhospitals.hospitalnpi = patient.hospitalnpi
where (tblhospitals.hospitalname = 'X')) as float),2) as binsPercent
from
(
select tblclaims.patientid, sum(claimsmedicarepaid) as TotalCosts,
cast(sum(claimsmedicarePaid)/10000 as int) as bin
from tblclaims inner join patient on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on patient.hospitalnpi = tblhospitals.hospitalnpi
where tblhospitals.hospitalname = 'X'
group by tblclaims.patientid
) as t
group by bin with rollup
OK, so for whomever might use this for reference I figured out what I needed to do.
I added row_number() over(bin) as rownum to the query and saved all of this as a view.
Then I used
SELECT *,
SUM(t2.binspercent) AS SUM
FROM t t1
INNER JOIN t t2 ON t1.rownum >= t2.rownum
GROUP BY t1.rownum,
t1.bins, t1.numbers, t1.uktotal, t1.binspercent
ORDER BY t1.rownum
by joining t1.rownum >=t2.rownum you can get the rolling count sort of thing.
This isn't exactly what i was looking for, but it's on the same track:
http://blog.tallan.com/2011/12/08/sql-server-2012-windowing-functions-part-1-of-2-running-and-sliding-aggregates/ and http://blog.tallan.com/2011/12/19/sql-server-2012-windowing-functions-part-2-of-2-new-analytic-functions/ - check out PERCENT_RANK
CUME_DIST
PERCENTILE_CONT
PERCENTILE_DISC
Sorry for the lame answer