One column as parameter both having and where (excluding) - sql

I have such two queries:
First:
select p.prodid, p.name, max(b.ldate) as lastsale
from prod p, buy b
where p.id = b.idprod and b.id<>0 and b.wskus=0 and b.bufor=0
group by p.prodid, p.name
HAVING sum(b.curNo)=0
order by p.name asc
Second
select p.prodid, p.name, min(b.buydate) as oldest_buy
from prod p, buy b
where p.id = b.idprod and b.id<>0 and b.wskus=0 and b.bufor=0 and b.curNo>0
group by p.prodid, p.name
order by p.name asc
How can I make JOIN for them to have as a result columns:
| p.prodid | p.name | lastsale | oldest_buy |
| 1 | ex1 | 1.1.18 | NULL |
| 2 | ex2 | NULL | 1.1.18 |
as HAVING sum(b.curNo)=0 from first query is exclusive to WHERE parameter from second query b.curNo>0 I have problem how to make this work.

Without your input data it's hard to tell, but it's possible this will work for you...
SELECT
p.prodid,
p.name,
MIN(CASE WHEN b.curNo > 0 THEN b.buydate END) AS oldest_buy, -- MIN(buydate) WHERE curno>0
CASE WHEN SUM(b.curNo) = 0 THEN MAX(b.ldate) END AS lastsale -- MAX(ldate) HAVING SUM(curNo) = 0
FROM
prod p
INNER JOIN -- Don't use "," use "JOIN"s, the standard for about 25 years...
buy b
ON p.id = b.idprod
WHERE
b.id <> 0
AND b.wskus = 0
AND b.bufor = 0
GROUP BY
p.prodid,
p.name
ORDER BY
p.name ASC
It's possible that moving the b.curNo > 0 or the SUM(b.curNo) = 0 in to the CASE statements will give extra rows, depending on the behavior of your data. It's impossible to tell without more details or example data.
The values in the two calculations will be okay, but I can't speak for the number of rows.
To be more explicit about it you could do...
SELECT
p.prodid,
p.name,
CASE WHEN MAX(b.curNo) > 0 THEN MIN(CASE WHEN b.curNo > 0 THEN b.buydate END) END AS oldest_buy,
CASE WHEN SUM(b.curNo) = 0 THEN MAX(b.ldate) END AS lastsale
FROM
prod p
INNER JOIN -- Don't use "," use "JOIN"s, the standard for about 25 years...
buy b
ON p.id = b.idprod
WHERE
b.id <> 0
AND b.wskus = 0
AND b.bufor = 0
GROUP BY
p.prodid,
p.name
HAVING
SUM(b.curNo) = 0
OR MAX(b.curNo) > 0
ORDER BY
p.name ASC
Another possibility (Again because you didn't give example data) is to aggregate then join.
This is based on the notion that you mean p.curNo rather than b.curNo...
SELECT
p.prodid,
p.name,
CASE p.curNo > 0 THEN b.oldest_buy END AS oldest_buy,
CASE p.curNo = 0 THEN b.last_sale END AS lastsale
FROM
prod p
INNER JOIN -- Don't use "," use "JOIN"s, the standard for about 25 years...
(
SELECT
idprod,
MIN(buydate) AS oldest_buy,
MAX(ldate) AS last_sale
FROM
buy
WHERE
b.id <> 0
AND b.wskus = 0
AND b.bufor = 0
)
b
ON p.id = b.idprod
ORDER BY
p.name ASC

Put the first query in a subquery before union all. Try this:
select
t.prodid, t.name, t.lastsale, null as oldest_buy
from (select p.prodid, p.name,
max(b.ldate) as lastsale
from prod p, buy b
where p.id = b.idprod and b.id<>0 and
b.wskus=0 and b.bufor=0
group by p.prodid, p.name
HAVING sum(b.curNo)=0 ) t
union all
( select p.prodid, p.name,
null as lastsale, min(b.buydate) as oldest_buy
from prod p, buy b
where p.id = b.idprod and b.id<>0 and b.wskus=0
and b.bufor=0 and b.curNo>0
group by p.prodid, p.name )
order by 2 asc

Related

Order by and limit on a multiple left join - PostgresSQL and python

I have the following relations:
A Product have multiple Images
A Product can have multiple Categories
A Category can have multiple Products
I want to get:
only the 'short_name' from the first category
only the first image url order_by another parameter
I have the following SQL, in PostgreSql:
SELECT DISTINCT ON(I.product_id) P.id, P.name, P.short_description,
CAT.short_name AS category, I.url
FROM products_product AS P
LEFT JOIN products_product_categories AS RPC ON P.id = RPC.product_id
LEFT JOIN categories_category AS CAT ON RPC.category_id = CAT.id
LEFT JOIN products_productimage AS I ON I.product_id = P.id
WHERE (P.is_active = TRUE)
My issue is that I don't know to limit left join and order by, I try to add LIMIT 1
LEFT JOIN categories_category AS CAT ON RPC.category_id = CAT.id LIMIT 1
but it is not working, I receive a code error 'syntax error at or near "LEFT"'
Category table
id | category_name | category_short_name
1 catA A
2 catB B
3 catC C
Product table
id | product_name | product_desc
1 P1 lorem1
2 P2 lorem2
3 P3 lorem3
ManytoMany: product_category
id product_id category_id
1 1 1
2 2 1
3 1 2
4 3 3
5 3 3
Image table
id url product_id order
1 lo1 1 4
2 lo2 1 0
3 lo3 1 1
4 lo4 2 0
For Product with id1 I expect to get:
name: P1, desc 'lorem1', category short_name : cat A, image url lo2
DISTINCT ON makes no sense without ORDER BY. As you want two different orders (on i.order for images and on cat.id for categories), you must do this in separate subqueries.
select p.id, p.name, p.short_description, c.short_name, i.url
from products_product p
left join
(
select distinct on (pcat.product_id) pcat.product_id, cat.short_name
from products_product_categories pcat
join categories_category cat on cat.id = pcat.category_id
order by pcat.product_id, cat.id
) c on c.product_id = p.id
left join
(
select distinct on (product_id) product_id, url
from products_productimage
order by product_id, order
) i on i.product_id = p.id
where p.is_active
order by p.id;
Two alternatives to write this query are:
subqueries with fetch first row only in the select clause
lateral left joins on subqueries with fetch first row only

Select all categories with COUNT of sub-categories

I need to select all categories with count of its sub-categories.
Assume here are my tables:
categories
id | title
----------
1 | colors
2 | animals
3 | plants
sub_categories
id | category_id | title | confirmed
------------------------------------
1 1 red 1
2 1 blue 1
3 1 pink 1
4 2 cat 1
5 2 tiger 0
6 2 lion 0
What I want is :
id | title | count
------------------
1 colors 3
2 animals 1
3 plants 0
What I have tried so far:
SELECT c.id, c.title, count(s.category_id) as count from categories c
LEFT JOIN sub_categories s on c.id = s.category_id
WHERE c.confirmed = 't' AND s.confirmed='t'
GROUP BY c.id, c.title
ORDER BY count DESC
The only problem with this query is that this query does not show categories with 0 sub categories!
You also can check that on SqlFiddle
Any help would be great appreciated.
The reason you don't get rows with zero counts is that WHERE clause checks s.confirmed to be t, thus eliminating rows with NULLs from the outer join result.
Move s.confirmed check into join expression to fix this problem:
SELECT c.id, c.title, count(s.category_id) as count from categories c
LEFT JOIN sub_categories s on c.id = s.category_id AND s.confirmed='t'
WHERE c.confirmed = 't'
GROUP BY c.id, c.title
ORDER BY count DESC
Adding Sql Fiddle: http://sqlfiddle.com/#!17/83add/13
I think you can try this too (it evidence what column(s) you are really grouping by):
SELECT c.id, c.title, RC
from categories c
LEFT JOIN (SELECT category_id, COUNT(*) AS RC
FROM sub_categories
WHERE confirmed= 't'
GROUP BY category_id) s on c.id = s.category_id
WHERE c.confirmed = 't'
ORDER BY RC DESC

Postgres - Get sum of the values in a column on condition of another column

I have a table in Postgres (custdata) that looks like this:
customerid | mID | count
1 3 13
1 2 17
1 2 3
1 5 14
1 4 7
And a query like this:
SELECT
c.customerid,
c.mID,
c.count,
SUM (c.count)
FROM custdata c
JOIN tableB b
ON b.customerid = c.customerid
WHERE c.mID <> 2
AND b.starttimestamp = ? and b.endtimestamp = ?
I want to get the sum of the values in the count column of the values whose mID does not equal 2. In this case, the correct sum would be 34. But in my case, the result is returning 2 different rows with the incorrect sum. How can I get the sum of the values in the count column where the mID is not 2? Any help would be appreciated.
Hoping, i understood correctly.
Please check below query
SELECT
c.customerid,
c.mID,
c.count,
SUM(case when c.mID<>2 then c.count else 0 end) over(partition by c.customerid order by c.customerid) sum_col
FROM custdata c
JOIN tableB b
ON b.customerid = c.customerid
WHERE
--c.mID <> 2 AND
b.starttimestamp = ? and b.endtimestamp = ?
With window function you can group with customerID:
SELECT
c.customerid,
c.mID,
c.count,
SUM(c.count) over(partition by c.customerid)
FROM custdata c
JOIN tableB b using(customerid)
WHERE c.mID <> 2
group by c.customerid,
c.mID,
c.count

"If one-to-many table has value" as column in SELECT

I have one table with cars, and another table with fuel types. A third table tracks which cars can use which fuel types. I need to select all data for all cars, including which fuel types they can use:
Car table has Car_ID, Car_Name, etc
Fuel table has Fuel_ID, Fuel_Name
Car_Fuel table has Car_ID, Fuel_ID (one car can have multiple Fuel options)
What I want to return:
SELECT
*
, Can_Use_Gas
, Can_Use_Diesel
, Can_Use_Electric
FROM Car
The Can_Use columns are a BIT value, indicating if the car has a matching Fuel entry in the Car_Fuel table.
I can do this with multiple SELECT statements, but this looks painfully messy (and possibly very inefficient?). I'm hoping there's a better way:
SELECT
c.*
, (SELECT COUNT(*) FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 1) AS Can_Use_Gas
, (SELECT COUNT(*) FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 2) AS Can_Use_Diesel
, (SELECT COUNT(*) FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 3) AS Can_Use_Electric
FROM Car c
Presumably you have no duplicates in Car_fuel, so you don't need aggregation. Hence you can do:
SELECT c.*,
ISNULL((SELECT TOP 1 1 FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 1), 0) AS Can_Use_Gas
ISNULL((SELECT TOP 1 1 FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 2), 0) AS Can_Use_Diesel
ISNULL((SELECT TOP 1 1 FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 3), 0) AS Can_Use_Electric
FROM Car c;
This is one case where ISNULL() has a performance advantage over COALESCE(), because COALESCE() evaluates the first argument twice.
Although not a perfect solution, you could use the pivot clause:
select *
from ( select car_name, fuel_name
from Car
inner join Car_Fuel on Car.car_id = Car_Fuel.car_id
inner join Fuel on Car_Fuel.fuel_id = Fuel.fuel_id
) as data
pivot (
count(fuel_name)
for fuel_name in (Gas, Diesel, Electric)
) as pivot_table;
See this fiddle, which outputs a table like this:
| car_name | Gas | Diesel | Electric |
|----------|-----|--------|----------|
| Jaguar | 0 | 1 | 0 |
| Mercedes | 0 | 1 | 1 |
| Volvo | 1 | 0 | 1 |
The SQL statement still has the hard-coded list in the for clause of the pivot part, but when the number of fuel types increases, this might be easier to manage and have better performance.
Generating the SQL dynamically
If you use an application server, you could first execute this query:
SELECT stuff( ( SELECT ',' + fuel_name
FROM Fuel FOR XML PATH('')
), 1, 1, '') columns
This will return the list of columns as one comma-separated value, for example:
Gas,Diesel,Electric
You would grab that result and inject it in the first query in the FOR clause.
I would suspect using counts would be inefficient as there would be a large number of sub queries running to total all the counts.
Below is an alternative using self joins. It's not as short as your example but may be easier to maintain and read and should be more efficient.
select car.car_id, car.car_name,
-- Select fuel variables
CASE WHEN lpg.fuel_id IS NULL THEN 0 ELSE 1 END AS LPG,
CASE WHEN unleaded.fuel_id IS NULL THEN 0 ELSE 1 END AS Unleaded,
CASE WHEN electric.fuel_id IS NULL THEN 0 ELSE 1 END AS Electric,
CASE WHEN diesel.fuel_id IS NULL THEN 0 ELSE 1 END AS Diesel
FROM car
-- Self Join fuel records
LEFT join car_fuel as lpg on car.car_id = lpg.car_id and lpg.fuel_id = 1
LEFT join car_fuel as unleaded on car.car_id = unleaded.car_id and unleaded.fuel_id = 2
LEFT join car_fuel as electric on car.car_id = electric.car_id and electric.fuel_id = 3
LEFT join car_fuel as diesel on car.car_id = diesel.car_id and diesel.fuel_id = 4
The self join will return a NULL if the car doesn't use that fuel type. The CASE returns 1 if the join found a record for that car/fuel and 0 if it didn't.
I hope this help.
You could use conditional aggregation.
Do an outer join to the Car_Fuel table, and do a GROUP BY Car_ID to collapse the rows.
For each row from Car_Fuel, return a 1 if the Fuel_ID matches the one you are checking for, otherwise return a 0. And use a MAX() aggregate to filter the rows, finding out if any of them returned a 1.
For example:
SELECT c.Car_ID
, c.Car_Name
, MAX(CASE WHEN f.Fuel_ID=1 THEN 1 ELSE 0 END) AS Can_Use_Gas
, MAX(CASE WHEN f.Fuel_ID=2 THEN 1 ELSE 0 END) AS Can_Use_Diesel
, MAX(CASE WHEN f.Fuel_ID=3 THEN 1 ELSE 0 END) AS Can_Use_Electric
FROM Car c
LEFT
JOIN Car_Fuel f
ON f.Car_ID = c.Car_ID
GROUP
BY c.Car_ID
, c.Car_Name
With SQL Server, you'd need to repeat every non-aggregate expression in the SELECT list in the GROUP BY clause. If you add more columns from the Car table to SELECT list, you'll have to copy those down to the GROUP BY.
If that's too painful, you could do the aggregation in an inline view instead, and then do the JOIN. To make sure a NULL doesn't get returned, you can replace a NULL value with a 0, in the outer query:
For example:
SELECT c.Car_ID
, c.Car_Name
, ISNULL(u.Can_Use_Gas,0) AS Can_Use_Gas
, ISNULL(u.Can_Use_Diesel,0) AS Can_Use_Diesel
, ISNULL(u.Can_Use_Electric,0) AS Can_Use_Electric
FROM Car c
LEFT
JOIN ( SELECT f.Car_ID
, MAX(CASE WHEN f.Fuel_ID=1 THEN 1 ELSE 0 END) AS Can_Use_Gas
, MAX(CASE WHEN f.Fuel_ID=2 THEN 1 ELSE 0 END) AS Can_Use_Diesel
, MAX(CASE WHEN f.Fuel_ID=3 THEN 1 ELSE 0 END) AS Can_Use_Electric
FROM Car_Fuel f
GROUP BY f.Car_ID
) u
ON u.Car_ID = c.Car_ID

SQL - Group By Distinct Values

My question, is there a faster way to the following query?
I'm using ORACLE 10g
Say i have a table Manufacturer and Car, and i want to count all occurrences of the column 'Car.Name'. here is How i'd do it:
SELECT manuf.Name, COUNT(car1.Name), COUNT(car2.Name), COUNT(car3.Name)
FROM Manufacturer manuf
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari1') car1 ON manuf.PK = car1.ManufPK
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari2') car2 ON manuf.PK = car2.ManufPK
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari3') car3 ON manuf.PK = car3.ManufPK
GROUP BY manuf.Name
Wanted Results:
Manufacturer | Ferrari1 | Ferrari2 | Ferrari3
----------------------------------------------
Fiat | 1 | 0 | 5
Ford | 2 | 3 | 0
I tried this with few LEFT JOINs, and it worked fine. But when i added a lot (like 90+), it was ultra slow (more than 1 minute).
My question, is there a faster way to do this query?
If you are happy to see the cars counted down the page, try:
select m.Name manufacturer_name,
c.Name car_name,
count(*)
from Manufacturer m
left join Car c
on m.PK = c.ManufPK and c.Name in ('Ferrari1','Ferrari2','Ferrari3')
group by m.Name, c.Name
If you need to see individual cars across the page, try:
select m.Name manufacturer_name,
sum(case c.Name when 'Ferrari1' then 1 else 0 end) Ferrari1_Count,
sum(case c.Name when 'Ferrari2' then 1 else 0 end) Ferrari2_Count,
sum(case c.Name when 'Ferrari3' then 1 else 0 end) Ferrari3_Count
from Manufacturer m
left join Car c
on m.PK = c.ManufPK and c.Name in ('Ferrari1','Ferrari2','Ferrari3')
group by m.Name
SELECT manuf.Name, COUNT(DISTINCT c.Name)
FROM Manufacturer manuf
LEFT JOIN Car c ON manuf.PK = c.ManufPK
GROUP BY manuf.Name
OR depending on your needs
SELECT manuf.Name, c.Name, COUNT(*) Cnt
FROM Manufacturer manuf
LEFT JOIN Car c ON manuf.PK = c.ManufPK
GROUP BY manuf.Name, c.Name
PS: Your question is not very clear. Provide some wanted resultset to refine the answer
You can also try this:
SELECT manuf.Name
, car1.cnt AS Ferrari1
, car2.cnt AS Ferrari2
, car3.cnt AS Ferrari3
FROM
Manufacturer AS manuf
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari1'
GROUP BY ManufPK
) AS car1
ON car1.ManufPK = manuf.PK
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari2'
GROUP BY ManufPK
) AS car2
ON car2.ManufPK = manuf.PK
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari3'
GROUP BY ManufPK
) AS car3
ON car3.ManufPK = manuf.PK
ORDER BY manuf.Name