Crosstab/cross over query - sql

Given data looks like
PLI_ID OWN_ID DCSF_ID SCH_NAME PREMIUM1 PREMIUM2 DESCRIPTION
901 2 1000 Dfe-School 1 86.40 7.20 Heads, Deps
902 2 1000 Dfe-School 2 403.30 8.40 Relief Bursar
903 2 1000 Dfe-School 3 327.00 8.40 £10.00
904 2 1000 Dfe-School 4 381.50 8.40 £11.00
905 2 1000 Dfe-School 5 152.60 8.40 Teaching staff
Expected data to look like
School £10.00 £11.00 Heads, Deps Relief Bursar Teaching staff Total
Dfe-School 1
Dfe-School 2
Dfe-School 3
Dfe-School 4
Total [Pre-Total]
I am not sure whether to write a Cross Tab or Cross Over query, as suggested. Let me know, if need more explanation.
My query uses around 6 tables and it looks like this:
SELECT PolicyLine.PLI_POS_Id, Policy.POL_OWN_Id, Policy.POL_DCSF, School.SCH_Name, PolicyLine.PLI_Premium, CoverPremium.CPR_Premium, StaffCategory.SCA_Description FROM
School
INNER JOIN Policy ON School.SCH_OWN_Id = Policy.POL_OWN_Id AND School.SCH_DCSF = Policy.POL_DCSF
INNER JOIN PolicyLine ON Policy.POL_Id = PolicyLine.PLI_POL_Id
INNER JOIN CoverOption ON PolicyLine.PLI_COP_Id = CoverOption.COP_Id
INNER JOIN CoverPremium ON CoverOption.COP_Id = CoverPremium.CPR_COP_Id AND Policy.POL_OWN_Id = CoverPremium.CPR_OWN_Id
RIGHT OUTER JOIN StaffCategory ON CoverOption.COP_SCA_Id = StaffCategory.SCA_

Based on your existing query you would use something like this:
SELECT School.SCH_Name school,
[£10.00], [£11.00],
[Heads, Deps], [Relief Bursar], [Teaching staff]
FROM
(
SELECT PolicyLine.PLI_POS_Id,
Policy.POL_OWN_Id,
Policy.POL_DCSF,
School.SCH_Name,
PolicyLine.PLI_Premium,
CoverPremium.CPR_Premium,
StaffCategory.SCA_Description
FROM School
INNER JOIN Policy
ON School.SCH_OWN_Id = Policy.POL_OWN_Id
AND School.SCH_DCSF = Policy.POL_DCSF
INNER JOIN PolicyLine
ON Policy.POL_Id = PolicyLine.PLI_POL_Id
INNER JOIN CoverOption
ON PolicyLine.PLI_COP_Id = CoverOption.COP_Id
INNER JOIN CoverPremium
ON CoverOption.COP_Id = CoverPremium.CPR_COP_Id
AND Policy.POL_OWN_Id = CoverPremium.CPR_OWN_Id
RIGHT OUTER JOIN StaffCategory
ON CoverOption.COP_SCA_Id = StaffCategory.SCA_
) x
PIVOT
(
sum(PLI_Premium)
for SCA_Description in([£10.00], [£11.00],
[Heads, Deps], [Relief Bursar], [Teaching staff])
) p
See SQL Fiddle with Demo

You probably want a PIVOT query, but from your example it's hard to tell what that should look like.
SELECT *
FROM yourtable
PIVOT
(SUM(Premium) FOR sch_name in ([£10.00],[£11.00],[Heads, Deps],[Relief Bursar],[Teaching staff]))

Related

T-SQL subselect statement is returning all rows instead of limiting to 1 based on subselect

I am trying to return just the first row where the BLOCK_STOP_ORDER = 2. What is wrong with my SQL? Why isn't WHERE SCHEDULE.BLOCK_STOP_ORDER = (SELECT MIN(S1.BLOCK_STOP_ORDER....
working? When I run the subselect on its own it returns the value '2' - doesn't that mean it should then limit the query result to only the row(s) where BLOCK_STOP_ORDER = 2?
SELECT ROUTE.ROUTE_ABBR, SCHEDULE.ROUTE_DIRECTION_ID, SCHEDULE.PATTERN_ID, SCHEDULE.BLOCK_STOP_ORDER,
SCHEDULE.SCHEDULED_TIME, GEO_NODE.GEO_NODE_ABBR, TRIP.TRIP_SEQUENCE AS TPST
FROM SCHEDULE
INNER JOIN GEO_NODE ON SCHEDULE.GEO_NODE_ID = GEO_NODE.GEO_NODE_ID
INNER JOIN ROUTE ON SCHEDULE.ROUTE_ID = ROUTE.ROUTE_ID
INNER JOIN TRIP ON SCHEDULE.TRIP_ID = TRIP.TRIP_ID
WHERE (SCHEDULE.CALENDAR_ID = '120221024') AND ROUTE.ROUTE_ABBR = '001'
AND SCHEDULE.ROUTE_DIRECTION_ID = '2' AND SCHEDULE.PATTERN_ID = '270082'
AND TRIP.TRIP_SEQUENCE = '18600'
AND SCHEDULE.BLOCK_STOP_ORDER =
(SELECT MIN(S1.BLOCK_STOP_ORDER)
FROM SCHEDULE S1
WHERE SCHEDULE.CALENDAR_ID = S1.CALENDAR_ID
AND SCHEDULE.ROUTE_ID = S1.ROUTE_ID
AND SCHEDULE.ROUTE_DIRECTION_ID = S1.ROUTE_DIRECTION_ID
AND SCHEDULE.PATTERN_ID = S1.PATTERN_ID
AND SCHEDULE.SCHEDULED_TIME = S1.SCHEDULED_TIME
AND SCHEDULE.GEO_NODE_ID = S1.GEO_NODE_ID
AND SCHEDULE.BLOCK_STOP_ORDER = S1.BLOCK_STOP_ORDER
AND SCHEDULE.TRIP_ID = S1.TRIP_ID
)
GROUP BY ROUTE.ROUTE_ABBR, SCHEDULE.ROUTE_DIRECTION_ID,
SCHEDULE.PATTERN_ID, SCHEDULE.SCHEDULED_TIME,
GEO_NODE.GEO_NODE_ABBR, SCHEDULE.BLOCK_STOP_ORDER, TRIP.TRIP_SEQUENCE
ORDER BY ROUTE.ROUTE_ABBR, SCHEDULE.ROUTE_DIRECTION_ID, TRIP.TRIP_SEQUENCE
Results:
ROUTE_ABBR
ROUTE_DIRECTION_ID
PATTERN_ID
BLOCK_STOP_ORDER
SCHEDULED_TIME
GEO_NODE_ABBR
TPST
001
2
270082
2
18600
1251
18600
001
2
270082
3
18600
1346
18600
001
2
270082
5
18720
1123
18600
001
2
270082
6
18720
11372
18600
001
2
270082
4
18720
1570
18600
001
2
270082
8
18780
11373
18600
This is probably better solved with the row_number() windowing function:
SELECT *
FROM (
SELECT DISTINCT r.ROUTE_ABBR, s.ROUTE_DIRECTION_ID, s.PATTERN_ID, s.BLOCK_STOP_ORDER,
s.SCHEDULED_TIME, g.GEO_NODE_ABBR, t.TRIP_SEQUENCE AS TPST,
row_number() over (order by SCHEDULE.BLOCK_STOP_ORDER) rn
FROM SCHEDULE s
INNER JOIN GEO_NODE g ON s.GEO_NODE_ID = g.GEO_NODE_ID
INNER JOIN ROUTE r ON s.ROUTE_ID = r.ROUTE_ID
INNER JOIN TRIP t ON s.TRIP_ID = t.TRIP_ID
WHERE s.CALENDAR_ID = '120221024' AND r.ROUTE_ABBR = '001'
AND s.ROUTE_DIRECTION_ID = '2' AND s.PATTERN_ID = '270082'
AND t.TRIP_SEQUENCE = '18600'
) t1
WHERE rn=1
ORDER BY t1.ROUTE_ABBR, t1.ROUTE_DIRECTION_ID, t1.TRIP_SEQUENCE
The problem with the original is the name SCHEDULE. For the full version of the query, the subquery is matching the name in the nested select with the instance of the table from the outer select. This correlates the results of the inner table with the outer, so only the item from that row of the outer table is eligible.
When you run the inner query by itself, separate from the outer query, there is only the one instance of the table. In that situation the WHERE conditions are matching the table to itself — they are always true — and you just get the smallest value of all the rows: 2.
This is why you should ALWAYS give ALL the tables in your queries an alias, and ONLY reference them by that alias (as I did in my answer). Do this, and the MIN() version can work... but will still be slower and more code than using row_number().
Finally, the use of DISTINCT / GROUP BY with every SELECT column is usually an indicator you don't fully understand the JOIN relationships used in the query, and in at least one case the join conditions are not sufficiently selective. I'd hesitate to move a query like that to production, even if it seems to be working, though I confess most of us have done it at some point anyway.

How to find non-matching records between 2 tables not having any unique keys

I have 2 tables tblBudget and tblActuals.
tblBudget
ProjID ExpenseType OrigBudget
101 Furniture 5000
102 Hardware 2000
102 Software 3500
tblActuals
ProjID ExpenseType ActualExpense
101 Furniture 4000
101 Hardware 2500
102 Hardware 1500
I want to pull the matching and non-matching records into a single query so that it shows like this
ProjID ExpenseType OriginalBudget ActualExpense
101 Furniture 5000 4000
101 Hardware 0 2500
102 Hardware 2000 1500
102 Software 3500 0
I tried a join query which successfully selects the matching records but having trouble in selecting the non-matching records. My table does not have primary keys nor can they be added since its part of a huge company db.
Any help from left join experts will be highly appreciated
In mysql use LEFT/RIGHT JOIN to emulate FULL OUTER JOIN
SQL DEMO
SELECT tB.ProjID, tB.ExpenseType, tB.OrigBudget as OrigianlBudget,
COALESCE(tA.ActualExpense,0) as ActualExpense
FROM tblBudget tB
LEFT JOIN tblActuals tA
ON tB.`ExpenseType` = tA.`ExpenseType`
AND tB.`ProjID` = tA.`ProjID`
UNION
SELECT COALESCE(tB.ProjID, tA.ProjID),
COALESCE(tB.ExpenseType, tA.ExpenseType),
COALESCE(tB.OrigBudget, 0),
tA.ActualExpense
FROM tblBudget tB
RIGHT JOIN tblActuals tA
ON tB.`ExpenseType` = tA.`ExpenseType`
AND tB.`ProjID` = tA.`ProjID`
ORDER BY `ProjID`
OUTPUT
MS Access does not make this easy. But here is one approach that should do what you want:
select b.ProjId, b.ExpenseType, b.OriginalBudget,
nz(a.ActualExpense, 0) as ActualExpense
from tblBudget as b left join
tblActual as a
on b.ProjID = a.ProjId and b.ExpenseType = a.ExpenseType
union all
select a.ProjId, a.ExpenseType, 0 as OriginalBudget, a.ActualExpense
from tblActual as a left join
tblBudget as b
on b.ProjID = a.ProjId and b.ExpenseType = a.ExpenseType
where b.ProjId is null

How to SUM an AS column in SQL

I have the following code...
SELECT WF.Word, WF.Frequency, WW.Weight, (WF.Frequency * WW.Weight) AS Product
FROM WF
INNER JOIN WW ON WW.Word = WF.word
Which outputs the following...
WORD | FREQUENCY | WEIGHT | PRODUCT
Fat 3 2 6
Ugly 2 4 8
Stupid 1 7 7
I also want to sum the product column at the same time. I understand how to sum an existing column but unsure how to sum a column i'm creating.
SELECT SUM(WF.Frequency * WW.Weight) AS Product
FROM WF
INNER JOIN WW ON WW.Word = WF.word
SELECT WF.Word, SUM(WF.Frequency) Frequency, SUM(WW.Weight) Weight, SUM(WF.Frequency * WW.Weight) AS Product
FROM WF
INNER JOIN WW ON WW.Word = WF.word
GROUP BY WF.Word
;
SELECT SUM(WF.Frequency * WW.Weight) Product
FROM WF
INNER JOIN
WW ON WW.Word = WF.word

Sum Values within 3 tables

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.

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;