SQL - Simple Pivot Table while using Join statements? - sql

I'm trying to transpose my results from the following code which is joining multiple tables together. I know i need to use a PIVOT for this and it may be a simple fix, but i'm having huge difficultly getting the code to work. My code is as follows:
SELECT F.SetValue, D.Name FROM Device D
INNER JOIN Location L ON D.LocationId = L.LocationId
INNER JOIN Fitting F ON L.LocationId = F.LocationId
INNER JOIN LocationTypeFitting LTF ON F.LocationTypeFittingId = LTF.LocationTypeFittingId
WHERE D.DeviceName = 'Device 1' AND LTF.Name LIKE '%Television%';
which prints the following results:
SetValue | Name
===========================
1 | TV_Power
1 | TV_Volume
1 | TV_Source
I need to return the values as below:
TV_Power | TV_Volume | TV_Source
================================
1 | 1 | 1
I know i'll also need a GROUP BY statement, but the the joining of additional tables is making this particular query increasingly difficult. Any help would be very much appreciated.

I would do the following two things:
Wrap the whole query in a sub-query and apply the pivot syntax.
Add another column, such as DeviceName from the Device table (or some other table), so that you can differentiate the rows once the pivot has been executed (I assume there will be more than one row).
it also shows where the group by would go in the comments.
select post_pivot.*
from (
SELECT F.SetValue, D.Name, D.DeviceName FROM Device D
INNER JOIN Location L ON D.LocationId = L.LocationId
INNER JOIN Fitting F ON L.LocationId = F.LocationId
INNER JOIN LocationTypeFitting LTF ON F.LocationTypeFittingId = LTF.LocationTypeFittingId
WHERE D.DeviceName = 'Device 1' AND LTF.Name LIKE '%Television%'
--group by (if needed)
) as pre_pivot
pivot (max(pre_pivot.set_value) for pre_pivot.Name in ([TV_Power], [TV_Volume], [TV_Source])) as post_pivot

Hopefully this will be sufficient or will give you enough to go on.
SELECT 'DeviceType' as DeviceTYpe,* FROM
(
SELECT D.Name, F.SetValue FROM Device D
INNER JOIN Location L ON D.LocationId = L.LocationId
INNER JOIN Fitting F ON L.LocationId = F.LocationId
INNER JOIN LocationTypeFitting LTF ON F.LocationTypeFittingId = LTF.LocationTypeFittingId
WHERE D.DeviceName = 'Device 1' AND LTF.Name LIKE '%Television%'
) AS SourceTable
PIVOT
(
MAX(SetValue)
FOR Name in ([TV_Power], [TV_Volume], [TV_Source])
) As PivotTable

Related

How to subtract from another table in SQL

SELECT
COUNT(ca.Plate) as 'OccupiedElectricSlots'
FROM cities C
JOIN ParkingHouses HS on C.Id = hs.CityId
JOIN ParkingSlots PS on HS.Id = ps.ParkingHouseId
LEFT JOIN Cars Ca on PS.Id = Ca.ParkingSlotsId
WHERE ps.ElectricOutlet = 1
GROUP BY hs.HouseName, C.CityName
SELECT
MAX(Ps.SlotNumber) as 'ParkingSlotTotal'
,MAX(PS.SlotNumber) - Count(ca.Plate) as 'FreeSlots'
,SUM(CAST(PS.ElectricOutlet AS INT)) as 'ElectricOutlet'
,Hs.HouseName
,C.CityName
FROM Cities C
JOIN ParkingHouses HS on C.Id = hs.CityId
JOIN ParkingSlots PS on HS.Id = ps.ParkingHouseId
LEFT JOIN Cars Ca on PS.Id = Ca.ParkingSlotsId
GROUP BY hs.HouseName, C.CityName
How can I subtract the first tables numbers on the second one?
I want to see how many free slots that have electric outlet.
Like this Column ElectricOutlet - OccupiedElectricSlots = result
I'm quite new at SQL, but I have tried to outer apply (don't fully understand it), and I tried to join them both tables togheter. Tried different where conditions but I'm stuck atm.
Your queries are almost identical as far as I can see. You can change your first query to:
SELECT COUNT(CASE WHEN ps.ElectricOutlet = 1 THEN ca.Plate END) as 'OccupiedElectricSlots'
FROM cities C
JOIN ParkingHouses HS on C.Id = hs.CityId
JOIN ParkingSlots PS on HS.Id = ps.ParkingHouseId
LEFT JOIN Cars Ca on PS.Id = Ca.ParkingSlotsId
GROUP BY hs.HouseName, C.CityName
I.e., instead of filtering on ps.ElectricOutlet you just ignore those rows in COUNT. Now you can just:
SELECT
[...]
,SUM(CAST(PS.ElectricOutlet AS INT)) - COUNT(CASE WHEN ...) AS result
[...]
FROM Cities C
JOIN ParkingHouses HS
ON C.Id = hs.CityId
JOIN ParkingSlots PS
ON HS.Id = ps.ParkingHouseId
LEFT JOIN Cars Ca
ON PS.Id = Ca.ParkingSlotsId
GROUP BY hs.HouseName, C.CityName
The MINUS operator is used to subtract the result set obtained by first SELECT query from the result set obtained by second SELECT query.
MINUS compares the data in two tables and returns only the rows of data using the specified columns that exist in the first table but not the second.

CALCULATE PERCENT BY DEVIDING AGREGATED VALUE ON TOTAL

I'm trying to write a query that aggregates a column by a category and then find a percent of that category from total categories
WITH USERS_ENERGY AS
(SELECT D.REGION_ID,E.YEAR_DESC,HOME_TYPE_ID,OWNERSHIP_TYPE_ID,B.ELECTRICITY_TYPE_KEY,SUM(WEIGHT_FINAL) HH_BY_ELECTRICITY_TYPE
FROM
R_FACT_HOUSING_UNIT B
JOIN
RR_DIM_SAMPLE C ON B.SAMPLE_FORM_ID=C.SAMPLE_FORM_ID
JOIN
R_DIM_PLACES D ON C.FRAM_PLACE_ID=D.PLACE_ID
JOIN
R_DIM_YEAR E ON B.ROUND_YEAR=E.ID
JOIN
R_DIM_HOME_TYPE F ON F.HOME_TYPE_ID=B.HOME_TYPE_KEY
JOIN
R_DIM_OWNERSHIP_TYPE G ON G.OWNERSHIP_TYPE_ID=B.OWNERSHIP_TYPE_KEY
WHERE B.ELECTRICITY_TYPE_KEY IN(8200002,8200001)
GROUP BY D.REGION_ID,E.YEAR_DESC,HOME_TYPE_ID,OWNERSHIP_TYPE_ID,B.ELECTRICITY_TYPE_KEY),
ALL_HH AS
(
SELECT REGION_ID,YEAR_DESC,HOME_TYPE_ID,OWNERSHIP_TYPE_ID,SUM(B.WEIGHT_FINAL) TOTAL_HH
FROM
R_FACT_HOUSING_UNIT B
JOIN
RR_DIM_SAMPLE C ON B.SAMPLE_FORM_ID=C.SAMPLE_FORM_ID
JOIN
R_DIM_PLACES D ON C.FRAM_PLACE_ID=D.PLACE_ID
JOIN
R_DIM_YEAR E ON B.ROUND_YEAR=E.ID
JOIN
R_DIM_HOME_TYPE F ON F.HOME_TYPE_ID=B.HOME_TYPE_KEY
JOIN
R_DIM_OWNERSHIP_TYPE G ON G.OWNERSHIP_TYPE_ID=B.OWNERSHIP_TYPE_KEY
WHERE B.ELECTRICITY_TYPE_KEY IN(8200002,8200001)
GROUP BY REGION_ID,YEAR_DESC,HOME_TYPE_ID,OWNERSHIP_TYPE_ID
)
SELECT A."REGION_ID",A."YEAR_DESC",A."HOME_TYPE_ID",A."OWNERSHIP_TYPE_ID",A."ELECTRICITY_TYPE_KEY",A."HH_BY_ELECTRICITY_TYPE",TOTAL_HH FROM USERS_ENERGY A,ALL_HH B WHERE A.REGION_ID=B.REGION_ID AND A.YEAR_DESC=B.YEAR_DESC AND A.HOME_TYPE_ID=B.HOME_TYPE_ID AND A.OWNERSHIP_TYPE_ID=B.OWNERSHIP_TYPE_ID
that was the view I created.
when I tried to test it gave me wrong results
this was my test
SELECT ELECTRICITY_TYPE_KEY,SUM(HH_BY_ELECTRICITY_TYPE),SUM(TOTAL_HH)FROM MASDAR_HEN_3_A_A T
GROUP BY ELECTRICITY_TYPE_KEY
ORDER BY ELECTRICITY_TYPE_KEY
the result was:
but it supposes to be:
so if you devide values on total and add them it supposes to give 100%
Your query is almost impossible to follow. Some helpful advice for your query-writing:
Always use MEANINGFUL table alases, such as y for dim_year. Arbitrary letters are hard to follow.
Always use proper, explicit, standard JOIN syntax. Never use commas in the FROM clause.
As for your view, I think you just need analytic functions. A simpler version (assuming I haven't make mistakes on the table aliases):
SELECT p.REGION_ID, y.YEAR_DESC, ht.HOME_TYPE_ID, ot.OWNERSHIP_TYPE_ID, hu.ELECTRICITY_TYPE_KEY,
SUM(WEIGHT_FINAL) as HH_BY_ELECTRICITY_TYPE
SUM(SUM(WEIGHT_FINAL)) OVER (REGION_ID,YEAR_DESC, HOME_TYPE_ID, OWNERSHIP_TYPE_ID) as TOTAL_HH
FROM R_FACT_HOUSING_UNIT hu JOIN
RR_DIM_SAMPLE
ON hu.SAMPLE_FORM_ID = s.SAMPLE_FORM_ID JOIN
R_DIM_PLACES p
ON s.FRAM_PLACE_ID= p.PLACE_ID JOIN
R_DIM_YEAR y
ON hu.ROUND_YEAR = y.ID JOIN
R_DIM_HOME_TYPE ht
ON ht.HOME_TYPE_ID = hu.HOME_TYPE_KEY JOIN
R_DIM_OWNERSHIP_TYPE ot
ON ot.OWNERSHIP_TYPE_ID= hu.OWNERSHIP_TYPE_KEY
WHERE hu.ELECTRICITY_TYPE_KEY IN (8200002, 8200001)
GROUP BY p.REGION_ID, y.YEAR_DESC, ht.HOME_TYPE_ID, ot.OWNERSHIP_TYPE_ID, hu.ELECTRICITY_TYPE_KEY;
Oracle provides a RATIO_TO_REPORT analytic function to compute % of total for you.
SELECT
d.region_id,
e.year_desc,
home_type_id,
ownership_type_id,
b.electricity_type_key,
SUM(weight_final) hh_by_electricity_type,
100*RATIO_TO_REPORT(SUM(weight_final) OVER ( PARTITION BY REGION_ID,YEAR_DESC, HOME_TYPE_ID, OWNERSHIP_TYPE_ID ) pct_of_total
FROM
r_fact_housing_unit b
JOIN rr_dim_sample c ON b.sample_form_id = c.sample_form_id
JOIN r_dim_places d ON c.fram_place_id = d.place_id
JOIN r_dim_year e ON b.round_year = e.id
JOIN r_dim_home_type f ON f.home_type_id = b.home_type_key
JOIN r_dim_ownership_type g ON g.ownership_type_id = b.ownership_type_key
WHERE
b.electricity_type_key IN (
8200002,
8200001
)
GROUP BY
d.region_id,
e.year_desc,
home_type_id,
ownership_type_id,
b.electricity_type_key;
Here is another example, using the Oracle data dictionary for input data, for readers who do not have data model to test with:
SELECT owner,
sum(bytes) total_bytes,
100*ratio_to_report(sum(bytes)) over () pct_of_total
FROM dba_segments
GROUP BY owner;

sql inner join over multiple tables

I've given this relational scheme and following task:
Inner Join: Return a list of professors, which gives
'lehrveranstaltung' of the 'fachbereich' with the name 'informatik'.
* print 'vorname', 'ho_name', 'lv_name'
* output should sort surnames in ascending order and if they're the same in descending order
* identical lines should online shown once
now I came up with following query:
select distinct
v.vorname,
h.ho_name,
l.lv_name
--print wanted, only once
from
vorname v,
hochschulangehoeriger h,
lehrveranstaltung l
-- from these tables
inner join fachbereich f on f.fb_name = 'Informatik'
-- only the 'informatik' events
inner join prof_haelt_lv on l.lv_nr = pl.lv_nr
-- make sure 'lehrveranstaltung' is from a professor
inner join mitarbeiter mit on pl.pers_Nr = mit.pers_Nr
-- make sure dude is a prof
where
mit.ho_nr = h.ho_nr
and
mit.ho_nr = v.ho_nr -- give only names from prof
order by
2 asc,
3 desc; -- order rules
I think this works for me (can't test it properly). But when I look at it I'll wish that I came up for a bether solution since this looks kinda ugly and wrong for me.
Is there a bether way of doing this? (Have to use inner join)
Based on the table you have, you may use the following SQL statement
SELECT DISTINCT v.vorname,
h.ho_name,
l.lv_name
FROM vorname v
INNER JOIN hochschulangehoeriger h
ON v.ho_nr = h.ho_nr
INNER JOIN mitarbeiter m
ON m.ho_nr = h.ho_nr
INNER JOIN fachbereich f
ON f.fb_nr = m.fb_nr
AND f.fb_name = 'Informatik'
INNER JOIN lehrveranstaltung l
ON l.fb_nr = f.nb_nr
INNER JOIN professor p
ON p.pers_nr = m.pers_nr
INNER JOIN prof_haelt_lv pl
ON pl.pers_nr = p.pers_nr
AND pl.lv_nr = l.lv_nr
ORDER BY 2,
3 DESC;
Also, these section on your SQL, this has no connection to any table in your SQL
inner join fachbereich f on f.fb_name = 'Informatik'
-- only the 'informatik' events
you forgot the alias for prof_haelt_lv
inner join prof_haelt_lv on l.lv_nr = pl.lv_nr
-- make sure 'lehrveranstaltung' is from a professor

Aggregate to 'plain' query

I have a query which uses aggregate functions to assign the maximum absolute of the values to another column in the table. The problem is that it takes whole lot of time (apprx. adds upto 10-15 seconds) to query completion time. This is what the query looks like:
UPDATE calculated_table c
SET tp = (SELECT MAX(ABS(s.tp))
FROM ts s INNER JOIN tc t ON s.id = t.id
GROUP BY s.id);
Where id is not unique, hence the grouping. tp is a numeric whole number field. Here is what the tables look like:
TABLE ts
PID(primary) | id (FKEY) | tp (integer)
--------------------+-----------------------------+------------------------------------------------------
1 | 2 | -100
2 | 2 | -500
3 | 2 | -1000
TABLE tc
PID(primary) | id (FKEY)
--------------------+-----------------------------+-------------------------
1 | 2
I want the output to look like:
TABLE c
PID(primary) | tp (integer)
--------------------+-----------------------------+--------
1 | 1000
I tried to make it work like this:
UPDATE calculated_table c
SET tp = (SELECT s.tp
FROM ts s INNER JOIN tc t ON s.id = t.id
ORDER BY s.tp DESC
LIMIT 1);
Though it improved the performance, however the results are incorrect.. any help would be appreciated?
I did manage to modify the query, turnsout nesting aggregate functions is not a good option. However, if it helps anyone, here is what I ended up doing:
UPDATE calculated_table c
SET tp = (SELECT ABS(s.trade_position)
FROM ts s INNER JOIN tc t ON s.id = t.id
WHERE c.id = s.id
ORDER BY ABS(s.tp) DESC
LIMIT 1);
Though it improved the performance, however the results are incorrect.
The operation was a success, but the patient died.
The problem with your query is that
SELECT MAX(ABS(s.tp))
FROM ts s INNER JOIN tc t ON s.id = t.id
GROUP BY s.id);
doesn't produce a scalar value; it produces a column of values, one for each s.id. Your DBMS really should raise a syntax error. In terms of performance, I think you're sequentially applying each row produced by the subquery to each row in the target table. It's probably both slow and wrong.
What you want is to correlate your select output with the table you're updating, and limit the rows updated to those correlated. Here's ANSI syntax to update one table from another:
UPDATE calculated_table
SET tp = (SELECT MAX(ABS(s.tp))
FROM ts s INNER JOIN tc t ON s.id = t.id
where s.id = calculated_table.id)
where exists ( select 1 from ts join tc
on ts.id = tc.id
where ts.id = calculated_table.id )
That should be close to what you want.
BTW, it's tempting to interpret correlated subqueries literally, to think that the subquery is run N times, once for each row in the target table. And that's the right way to picture it, logically. The DBMS won't implement it that way, though, in all likelihood, and performance should be much better than that picture would suggest.
Try:
UPDATE calculated_table c
SET tp = (SELECT greatest( MAX( s.tp ) , - MIN( s.tp ))
FROM ts s INNER JOIN tc t ON s.id = t.id
WHERE c.id = s.id
);
Also try to create a multicolumn index on ts( id, tp )
I hope the below sql will be helpful to you, I tested in netezza, but not postgresql. Also, I didn't put update on top of it.
SELECT ABS(COM.TP)
FROM TC C LEFT OUTER JOIN
(SELECT ID,TP
FROM TS A
WHERE NOT EXISTS (SELECT 1
FROM TS B
WHERE A.ID = B.ID
AND ABS(B.TP)>ABS(A.TP))) COM
ON C.ID = COM.ID

SQL joins "going up" two tables

I'm trying to create a moderately complex query with joins:
SELECT `history`.`id`,
`parts`.`type_id`,
`serialized_parts`.`serial`,
`history_actions`.`action`,
`history`.`date_added`
FROM `history_actions`, `history`
LEFT OUTER JOIN `parts` ON `parts`.`id` = `history`.`part_id`
LEFT OUTER JOIN `serialized_parts` ON `serialized_parts`.`parts_id` = `history`.`part_id`
WHERE `history_actions`.`id` = `history`.`action_id`
AND `history`.`unit_id` = '1'
ORDER BY `history`.`id` DESC
I'd like to replace `parts`.`type_id` in the SELECT statement with `part_list`.`name` where the relationship I need to enforce between the two tables is `part_list`.`id` = `parts`.`type_id`. Also I have to use joins because in some cases `history`.`part_id` may be NULL which obviously isn't a valid part id. How would I modify the query to do this?
Here is some sample date as requested:
history table:
(source: ianburris.com)
serialized_parts table:
(source: ianburris.com)
parts table:
(source: ianburris.com)
part_list table:
(source: ianburris.com)
And what I want to see is:
id name serial action date_added
4 Battery 567 added 2010-05-19 10:42:51
3 Antenna Board 345 added 2010-05-19 10:42:51
2 Main Board 123 added 2010-05-19 10:42:51
1 NULL NULL created 2010-05-19 10:42:51
This would at least be on the right track...
If you're looking to NOT show any parts with an invalid ID, simply change the LEFT JOINs to INNER JOINs (they will restrict NULL values)
SELECT `history`.`id`
, `parts`.`type_id`
, `part_list`.`name`
, `serialized_parts`.`serial`
, `history_actions`.`action`
, `history`.`date_added`
FROM `history_actions`
INNER JOIN `history` ON `history`.`action_id` = `history_actions`.`id`
LEFT JOIN `parts` ON `parts`.`id` = `history`.`part_id`
LEFT JOIN `serialized_parts` ON `serialized_parts`.`parts_id` = `history`.`part_id`
LEFT JOIN `part_list` ON `part_list`.`id` = `parts`.`type_id`
WHERE `history`.`unit_id` = '1'
ORDER BY `history`.`id` DESC
Boy, these backticks make my eyes hurt.
SELECT
h.id,
p.type_id,
pl.name,
sp.serial,
ha.action,
h.date_added
FROM
history h
INNER JOIN history_actions ha ON ha.id = h.action_id
LEFT JOIN parts p ON p.id = h.part_id
LEFT JOIN serialized_parts sp ON sp.parts_id = h.part_id
LEFT JOIN part_list pl ON pl.id = p.type_id
WHERE
h.unit_id = '1'
ORDER BY
history.id DESC