SQL attribute variant within the same statement - sql

Having the following statement gives me 4 columns (location,description,parent,count) depending on the status:
SELECT
KINCIDENT.location, LOCATIONS.description,
LOCHIERARCHY.parent, count(KINCIDENT.incnum)
FROM
KINDICENT, LOCATIONS, LOCHIERARCHY
WHERE
KINCIDENT.location = LOCATIONS.location
and LOCATIONS.location = LOCHIERARCHY.location
AND KINCIDENT.status = 'ECCAPR'
GROUP BY
KINCIDENT.location, LOCATIONS.description, LOCHIERARCHY.parent
ORDER BY
parent;
However, I'd like a 5th column that gives me the count but when KINCIDENT.status='FSAPR' instead. How can I specify which status each count column takes?

You want conditional aggregation. You should also learn to use explicit joins:
SELECT k.location, l.description, h.parent,
SUM(CASE WHEN k.status = 'ECCAPR' THEN 1 ELSE 0 END) as cnt_ECCAPR,
SUM(CASE WHEN k.status = 'FSAPR' THEN 1 ELSE 0 END) as cnt_FSAPR
FROM KINDICENT k JOIN
LOCATIONS l
ON k.location = l.location JOIN
LOCHIERARCHY h
ON l.location = h.location
GROUP BY k.location, l.description, h.parent
ORDER BY parent;
I also introduced table aliases which generally make queries easier to write and to read.

Related

Return a Count of 0 When No Rows

OK, I've looked this up and tried a number of solutions, but can't get it to work. I'm a bit of a novice. Here's my original query - how can I get it to return 0 for an account when there are no results in the student table?
SELECT a.NAME
,count(s.student_sid)
FROM account a
JOIN inst i ON a.inst_sid = i.root_inst_sid
JOIN inst_year iy ON i.inst_sid = iy.inst_sid
JOIN student s ON iy.inst_year_sid = s.inst_year_sid
WHERE s.demo = 0
AND s.STATE = 1
AND i.STATE = 1
AND iy.year_sid = 16
AND a.account_sid IN (
20187987
,20188576
,20188755
,52317128
,20189249
)
GROUP BY a.NAME;
Use an outer join, moving the condition on that table into the join:
select a.name, count(s.student_sid)
from account a
join inst i on a.inst_sid = i.root_inst_sid
join inst_year iy on i.inst_sid = iy.inst_sid
left join student s on iy.inst_year_sid = s.inst_year_sid
and s.demo = 0
and s.state = 1
where i.state = 1
and iy.year_sid = 16
and a.account_sid in (20187987, 20188576, 20188755, 52317128, 20189249)
group by a.name;
count() does not count null values, which s.student_sid will be if no rows join from student.
You need to LEFT JOIN and then SUM() over the group where s.student_sid is not null:
select
a.name,
sum(case when s.student_sid is null then 0 else 1 end) as student_count
from account a
join inst i on a.inst_sid = i.root_inst_sid
join inst_year iy on i.inst_sid = iy.inst_sid
left join student s
on iy.inst_year_sid = s.inst_year_sid
and s.demo = 0
and s.state = 1
where i.state = 1
and iy.year_sid = 16
and a.account_sid in (20187987, 20188576, 20188755, 52317128, 20189249)
group by a.name;
This is assuming that all of the fields in the student table that you are filtering on are optional. If you don't want to enforce removal of records where, say, s.state does not equal 1, then you need to move the s.state=1 predicate into the WHERE clauses.
If, for some reason, you are getting duplicate student IDs and students are being counted twice, then you can change the aggregate function to this:
count(distinct s.student_id) as student_count
...which is safe to do as count(distinct ...) ignores null values.

how to include several count functions and group byes in one line

I am trying to get the number of customers by their types and groups all in line as such:
GroupName | GroupNotes | Count(Type1) | Count(Type2) | Count(Type3)
but instead I can only get the groupid ,the typeid and the number of types in the group by using the following query
SELECT
CustomersGroups.idCustomerGroup , Customers.type , COUNT(*)
FROM
CustomersGroups
inner Join CustomersInGroup on CustomersGroups.idCustomerGroup = CustomersInGroup.idCustomerGroup
inner Join Customers on Customers.idCustomer = CustomersInGroup.idCustomer
Group by
CustomersGroups.idCustomerGroup, Customers.type
is there a way to show them in a single line , (and show the name of the group?)
This is a "pivot" query. Some databases directly support pivot syntax. In all, you can use conditional aggregation.
Perhaps more importantly, you should learn to use table aliases. These make queries easier to write and to read:
select cg.idCustomerGroup,
sum(case when c.type = 'Type1' then 1 else 0 end) as num_type1,
sum(case when c.type = 'Type2' then 1 else 0 end) as num_type2,
sum(case when c.type = 'Type3' then 1 else 0 end) as num_type3
from CustomersGroups cg inner Join
CustomersInGroup cig
on cg.idCustomerGroup = cig.idCustomerGroup inner Join
Customers c
on c.idCustomer = cig.idCustomer
Group by cg.idCustomerGroup;

SQL: Split rows with same ID into columns + left join

I have a cs cart database and I am trying to select all the attributes for all the products, the problem is that for each separate attribute for a product, my query creates a new row, I want to to have a single row for each products that has all the attributes into columns.
This is my query right now:
SELECT a.product_id, b.variant, c.description, d.product_code
FROM cscart_product_features_values a
LEFT JOIN cscart_product_feature_variant_descriptions b ON a.variant_id = b.variant_id
LEFT JOIN cscart_product_features_descriptions c ON a.feature_id = c.feature_id
LEFT JOIN cscart_products d ON a.product_id = d.product_id
After I run the query, I get the following result:
product_id;"variant";"description";"product_code"
38;"1st";"Grade Level";"750"
38;"Math";"Subject Area";"750"
38;"Evan-Moor";"Publisher";"750"
etc next product
What I want is this:
product_id;"product_code";"Grade Level";"Subject Area";"Publisher"
38;"750";"1st";"Math";"Evan-Moor"
etc next product
We only have 3 type of attributes: Grade Level, Subject Area and Publisher.
Any ideas how to improve my query and achieve this? I would be happy even with concatenating all 3 attributes in one column, delimited by ",".
This is a generic SQL solution using GROUP BY and MAX(case expression) to achieve the transformation of 3 rows into a single row with the 3 columns.
SELECT
v.product_id
, p.product_code
, MAX(CASE WHEN fd.description = 'Grade Level' THEN vd.variant END) AS GradeLevel
, MAX(CASE WHEN fd.description = 'Subject Area' THEN vd.variant END) AS SubjectArea
, MAX(CASE WHEN fd.description = 'Publisher' THEN vd.variant END) AS Publisher
FROM cscart_products p
LEFT JOIN cscart_product_features_values v ON p.product_id = v.product_id
LEFT JOIN cscart_product_feature_variant_descriptions vd ON v.variant_id = vd.variant_id
LEFT JOIN cscart_product_features_descriptions fd ON v.feature_id = fd.feature_id
GROUP BY
v.product_id
, p.product_code
This approach should work on just about any SQL database.
Note also that I have changed the order of tables because I presume there has to be a row in cscart_products, but there might not be related rows in the other tables.
I have also changed the aliases, personally I do not care for aliaes based on the order of use in a query (e.g. I just changed the order so I had to change all references). I have use 'p' = product, 'v' = variant, 'vd' = variant description & 'fd' = feature description' - with such a convention for aliases I can re-arrange the query without changing every reference.

To compute sum regarding to a constraint

I'm using PostgreSQL 8.4.
I have the following sql-query:
SELECT p.partner_id,
CASE WHEN pa.currency_id = 1 THEN SUM(amount) ELSE 0 END AS curUsdAmount,
CASE WHEN pa.currency_id = 2 THEN SUM(amount) ELSE 0 END AS curRubAmount,
CASE WHEN pa.currency_id = 3 THEN SUM(amount) ELSE 0 END AS curUahAmount
FROM public.player_account AS pa
JOIN player AS p ON p.id = pa.player_id
WHERE p.partner_id IN (819)
GROUP BY p.partner_id, pa.currency_id
The thing is that query does not what I expected. I realize that, but now I want to understand what exactly that query does. I mean, what SUM will be counted after the query executed. Could you clarify?
I think you have the conditions backwards in the query:
SELECT p.partner_id,
SUM(CASE WHEN pa.currency_id = 1 THEN amount ELSE 0 END) AS curUsdAmount,
SUM(CASE WHEN pa.currency_id = 2 THEN amount ELSE 0 END) AS curRubAmount,
SUM(CASE WHEN pa.currency_id = 3 THEN amount ELSE 0 END) AS curUahAmount
FROM public.player_account pa JOIN
player p
ON p.id = pa.player_id
WHERE p.partner_id IN (819)
GROUP BY p.partner_id;
Note that I also removed currency_id from the group by clause.
Maybe one row per (partner_id, currency_id) does the job. Faster and cleaner that way:
SELECT p.partner_id, pa.currency_id, sum(amount) AS sum_amount
FROM player_account pa
JOIN player p ON p.id = pa.player_id
WHERE p.partner_id = 819
AND pa.currency_id IN (1,2,3) -- may be redundant if there are not other
GROUP BY 1, 2;
If you need 1 row per partner_id, you are actually looking for "cross-tabulation" or a "pivot table". In Postgres use crosstab() from the additional module tablefunc , which is very fast. (Also available for the outdated version 8.4):
SELECT * FROM crosstab(
'SELECT p.partner_id, pa.currency_id, sum(amount)
FROM player_account pa
JOIN player p ON p.id = pa.player_id
WHERE p.partner_id = 819
AND pa.currency_id IN (1,2,3)
GROUP BY 1, 2
ORDER BY 1, 2'
,VALUES (1), (2), (3)'
) AS t (partner_id int, "curUsdAmount" numeric
, "curRubAmount" numeric
, "curUahAmount" numeric); -- guessing data types
Adapt to your actual data types.
Detailed explanation:
PostgreSQL Crosstab Query

Trouble Aggregating Rows with SQL Statement

I am attempting to pivot some data out of the Vendor column in this table, creating new columns for each of my vendors. Ideally, I would have 1 row for each ContractDate, and then 2 values. However, I'm ending up with 2 rows of a distinct ContractDate.
I believe i may need some sort of temp table query to do this...i'm not sure though.
SELECT [ContractDate],
CASE WHEN Vendor = 'AirDat'
THEN (sum(wf.Temppop) / sum(wf.Population)) END as 'AirDat',
CASE WHEN Vendor = 'CWG'
THEN (sum(wf.Temppop) / sum(wf.Population)) END as 'CWG'
FROM [ECPDB].[dbo].[weather.forecast] as wf
INNER JOIN ecpdb.[lookup].[newWeatherStation] as ws
ON wf.[Station_ID] = ws.[Station ID]
INNER JOIN ecpdb.[lookup].[CountyNew] as c
ON ws.[County FIPS] = c.[County FIPS]
WHERE tradedate = '7/2/2012'
AND [BENTEK Cell] = 'Northeast'
GROUP BY [ContractDate], vendor
You can do this using a subquery;
Select ContractDate,
max(case when Vendor = 'AirDat' THEN Vendor_Average End) as AirDAT,
max(case when Vendor = 'CWG' THEN Vendor_Average End) as CWG
from (
SELECT [ContractDate] , Vendor, (sum(wf.Temppop) / sum(wf.Population)) as Vendor_Average
FROM [ECPDB].[dbo].[weather.forecast] as wf
inner join ecpdb.[lookup].[newWeatherStation] as ws
on wf.[Station_ID] = ws.[Station ID]
inner join ecpdb.[lookup].[CountyNew] as c
on ws.[County FIPS] = c.[County FIPS]
where tradedate = '7/2/2012' and [BENTEK Cell] = 'Northeast'
group by ContractDate, Vendor
) as Subquery
group by Contractdate
This way, the query runs and finds the values you need, and then you pick the rows you want without needing to group.