Oracle SQL - Sum up and show text if > 0 - sql

I'm totally new with Oracle SQL and my question may seem stupid but I have some difficulties to solve my problem.
Current situation:
I have following tables: Supplier, Debtor, Invoice
Every Supplier has various Debtors and every Debtor has various Invoices.
I want to create an evaluation which shows me the following scenario:
A list of all Debtors from a Supplier. I also want to see in that list if the Debtor once haven't payed his invoice. I have this information within the table Invoice as the attribute called "payed" and possible values are 0 (payed) and 1 (not payed). I just want to see the debtor ONCE in the list so if there is only ONE invoice which is 1, it should show me "1" or "Not Payed" in the list. Right now when a debtor has 100 invoices it shows me 100 times the debtor with the info "0" or "1".
Currently:
SELECT company.company_id,
company.companyname_1,
supplier.supplier_id,
supplier.suppliername_1,
debtor.debtor_id_from_supplier,
debtor_ext.debtorname_1,
debtor_ext.street,
debtor_ext.street_number,
debtor_ext.postcode,
debtor_ext.city,
debtor.approved_limit,
debitor.limit_left,
debitor.limit_status,
debitor.limit_type,
debitor.prosecution,
CASE
WHEN invoice.payed = 0 THEN 'Yes'
ELSE 'No'
END as deb_payment
FROM debtor,
debtor_ext,
company,
supplier,
invoice
WHERE ( company.company_id = supplier.company_id ) and
( supplier.supplier_id = debtor.supplier_id ) and
( debtor.debtor_id = debtor_ext.debtor_id ) and
( debtor.supplier_id = invoice.supplier_id ) and
( debtor.debtor_id_from_supplier = invoice.debtor_id_from_supplier )
CODE CORRECTED!
Hope you guys can help me

First of all, In your query, you're doing cross join which is not required and also affects on the performance. I'll advice you to use Inner Join here.
Also,as the schema is not provided in question,I am giving solution for the below schema:
Supplier:
Supplier_Id (PK) | Supplier_Name
Debtor:
Debtor_Id (PK) | Debtor_Name | Supplier_Id (FK)
Invoice:
Invoice_Id (PK) | Supplier_Id (FK) | Debtor_Id (FK) | Amount | Payed
Query:
Select company.company_id,
company.companyname_1,
s.supplier_id,
s.suppliername_1,
d.debtor_id_from_supplier,
d.debtorname_1,
d.street,
d.street_number,
d.postcode,
d.city,
d.approved_limit,
d.limit_left,
d.limit_status,
d.limit_type,
d.prosecution,
tmp.deb_payment
From
(
SELECT d.supplier_id,
d.debtor_id,
(
CASE
WHEN min(i.payed) <> max(i.payed) Then 'Not Payed'
ELSE 'Payed'
END
)as deb_payment
FROM supplier s
inner join debtor d
on s.supplier_id = d.supplier_id
inner join invoice i
on i.supplier_id = d.supplier_id
and i.debtor_id = d.debtor_id
Group by d.supplier_id,d.debtor_id
) tmp
inner join supplier s
on s.supplier_id = tmp.supplier_id
inner join company
on company.company_id = s.company_id
inner join debtor d
on s.supplier_id = d.supplier_id
inner join debtor_ext de
d.debtor_id = de.debtor_id
;
Hope it helps!

Related

Select columns with multiple purcahses in a given date

table structure
I need to get names (FIO) of all the people that purchased product a and product b in any given day (but must be 2 purchases in a day) in april, or any other specified month.
What I tried to do is
with
purchased_items as
(
select customer_key
from purchase p
join product pr
on p.product_key = pr.product_key
where pr.name in ('Teddy bear', 'LEGO')
AND p.date_sold BETWEEN '01.04.2019' AND '30.04.2019'
group by customer_key
having count(distinct p.product_key) = 2
)
select *
from customer c
where exists (
select *
from purchased_items pui
where c.customer_key = pui.customer_key
);
But that only gives clients that bought 2 items in a month (not a single day).
I also suspect that it can be done by querying for Date, Client_name (FIO), array_agg(product.Name /*group by date */ ) , but I am not sure how to implemet it.
Thank you in advance for any help !
EDIT: figured it out.
with a as (
SELECT c.FIO, p.date_sold, pr.Name
FROM customer c
JOIN purchase p ON c.customer_key = p.customer_key
JOIN product pr ON p.product_key = pr.product_key
Where p.date_sold BETWEEN '01.04.2019' AND '30.04.2019' and (pr.name like '%LEGO%' OR pr.name like '%Teddy bear%') ),
count_name as (
select fio, count(distinct Name) as count, date_sold
from a
group by date_sold, fio)
select DISTINCT FIO
from count_name
where count=2
Although it's probably very subotimal

How to left join two tables on specific conditions

I have two tables Price(Type, Values) and Product(Seat) and some values.
Price | Product
-------------+---------
Type Values | Seat
S 4 | FO
P 6 | CA
| FA
I know that [FO] and [CA] belong to type [P], and [FA] belongs to type [S]. How can I join these tables and shows associated type and values:
Results
Seat Type Values
----- ----- -----------
FO P 6
CA P 6
FA S 4
You can join the tables like this:
select pr.seat, sum(p.value)
from price p join
product pr
on pr.seat in ('FO', 'CA') and p.type = 'P' or
pr.seat in ('FA') and p.type = 'S'
group by pr.seat;
That said, you should have a proper table that connects the seats to the products, probably called ProductSeats with one row per product and matching seat.
I would use a derived table to store the mapping between price and seat. This is easily extensible when new requirements come up.
SELECT pri.*, pro.*
FROM price pri
INNER JOIN (
SELECT 'FO' seat, 'P' price
UNION ALL SELECT 'CA' seat, 'P' price
UNION ALL SELECT 'FA' seat, 'S' price
) map ON map.pri = pri.price
INNER JOIN product pro ON pro.seat = map.pro
This can be simplified by using the VALUES() syntax:
SELECT pri.*, pro.*
FROM price pri
INNER JOIN (
VALUES('FO', 'P'), ('CA', 'P'), ('FA', 'S')
) AS map(seat, price) ON map.pri = pri.price
INNER JOIN product pro ON pro.seat = map.pro

Concatenating a column within the same column in SQL Server

Hi so I have a question on concatenating a column. I have two tables.
Table 1: Sales Order-> OrderID, ProductsOnHold (Should list product(s) on hold)
Table 2: Products-> ProductID, OnHold (boolean)
A Sales Order can have many products.
A product can have a hold, which would make the Sales Order be put on hold if that order has that product.
So a sale order can be on hold for have 1 or more products on hold.
I was able to get one product to display if the order is on hold,
but what if it has more products on hold -
how could I display them as well?
This is for a view I am creating.
This is what I have done so far:
(SELECT ProductName
FROM Products with (NOLOCK)
WHERE (OnHold = 1) AND (EXISTS
(SELECT CASE
WHEN (COUNT(DISTINCT Product)> 1) THEN (Product + ', ' + Product)
ELSE Product END AS ProductName
FROM SalesOrder WITH (NOLOCK)
GROUP BY OrderID ))) AS ProductsOnHold
Desired Output:
OrderID | ProductsOnHold
----------------------------------
1 | P1, P2, P7
2 |
3 | P1
4 | P1, P7, P8, P9, P15, P77
Anything I am missing in my sql query?
Assuming that you have 2 tables SalesOrder and Product with ProductID as the "relationship" column, you can join those 2 tables and then transpose using a "for xml" clause.
select s1.OrderID,
stuff(
(select ','+p.ProductName as [text()]
from SalesOrder s
join Product p on p.ProductID = s.ProductID and p.OnHold = true
where s.OrderID = s1.OrderID
group by p.ProductName
for xml path (''))
,1,1,'') as ProductsOnHold from SalesOrder s1 group by s1.OrderID
Hope this helps.
My solution [I am not sure if I understand your problem well]
user function
create function inonerow (#orderid int)
returns nvarchar(max)
begin
declare #data nvarchar(max)
SELECT #data =coalesce(#data + ',','')+ CONVERT(varchar,prodid) from SalesOrder
inner join product on product.prodid = salesorder.prodid where orderid=#orderid and product.oshold = 1
return #data
end
select distinct orderid, dbo.inonerow(orderid) from salesorder
inner join product on product.prodid = salesorder.prodid
where product.onhold = 1
Hope this works :)

Query Returning SUM of Quantity or 0 if no Quantity for a Product at a given date

I have the following 4 tables:
----------
tblDates:
DateID
30/04/2012
01/05/2012
02/05/2012
03/05/2012
----------
tblGroups:
GroupID
Home
Table
----------
tblProducts:
ProductID GroupID
Chair Home
Fork Table
Knife Table
Sofa Home
----------
tblInventory:
DateID ProductID Quantity
01/05/2012 Chair 2
01/05/2012 Sofa 1
01/05/2012 Fork 10
01/05/2012 Knife 10
02/05/2012 Sofa 1
02/05/2012 Chair 3
03/05/2012 Sofa 2
03/05/2012 Chair 3
I am trying to write a query that returns all Dates in tblDates, all GroupIDs in tblGroups and the total Quantity of items in each GroupID.
I manage to do this but only get Sum(Quantity) for GroupID and DateID that are not null. I would like to get 0 instead. For exemple for the data above, I would like to get a line "02/05/2012 Table 0" as there is no data for any product in "Table" group on 01/05/12.
The SQL query I have so far is:
SELECT tblDates.DateID,
tblProducts.GroupID,
Sum(tblInventory.Quantity) AS SumOfQuantity
FROM (tblGroup
INNER JOIN tblProducts ON tblGroup.GroupID = tblProducts.GroupID)
INNER JOIN (tblDates INNER JOIN tblInventory ON tblDates.DateID = tblInventory.DateID) ON tblProducts.ProductID = tblInventory.ProductID
GROUP BY tblDates.DateID, tblProducts.GroupID;
I reckon I should basically have the same Query on a table different from tblInventory that would list all Products instead of listed products with 0 instead of no line but I am hesitant to do so as given the number of Dates and Products in my database, the query might be too slow.
There is probably a better way to achieve this.
Thanks
To get all possible Groups and Dates combinations, you'll have to CROSS JOIN those tables. Then you can LEFT JOIN to the other 2 tables:
SELECT
g.GroupID
, d.DateID
, COALESCE(SUM(i.Quantity), 0) AS Quantity
FROM
tblDates AS d
CROSS JOIN
tblGroups AS g
LEFT JOIN
tblProducts AS p
JOIN
tblInventory AS i
ON i.ProductID = p.ProductID
ON p.GroupID = g.GroupID
AND i.DateID = d.DateID
GROUP BY
g.GroupID
, d.DateID
use the isnull() function. change your query to:
SELECT tblDates.DateID, tblProducts.GroupID,
**ISNULL( Sum(tblInventory.Quantity),0)** AS SumOfQuantity
FROM (tblGroup INNER JOIN tblProducts ON tblGroup.GroupID = tblProducts.GroupID)
INNER JOIN (tblDates INNER JOIN tblInventory ON tblDates.DateID = tblInventory.DateID)
ON tblProducts.ProductID = tblInventory.ProductID
GROUP BY tblDates.DateID, tblProducts.GroupID;
You don't say which database you're using but what I'm familiar with is Oracle.
Obviously, if you inner join all tbales, you will not get the rows containing nulls. If you use an outer join to the inventory table you also have a problem because the groupid column is not listed in tblinventory and you can only outer join to one table.
I'd say you have two choices. Use a function or have a duplicate of the groupid column in the inventory table.
So, a nicer but slower solution:
create or replace
function totalquantity( d date, g varchar2 ) return number as
result number;
begin
select nvl(sum(quantity), 0 )
into result
from tblinventory i, tblproducts p
where i.productid = p.productid
and i.dateid = d
and p.groupid = g;
return result;
end;
select dateid, groupid, totalquantity( dateid, groupid )
from tbldates, tblgroups
Or, if you include the groupid column in the inventory table. (You can still quarantee integrity using constraints)
select d.dateid, i.groupid, nvl(i.quantity, 0)
from tbldates d, tblinventory i
where i.dateid(+) = d.dateid
group by d.dateid, g.groupid
Hope this helps,
GĂ­sli

How to ensure outer join with filter still returns all desired rows?

Imagine I have two tables in a DB like so:
products:
product_id name
----------------
1 Hat
2 Gloves
3 Shoes
sales:
product_id store_id sales
----------------------------
1 1 20
2 2 10
Now I want to do a query to list ALL products, and their sales, for store_id = 1. My first crack at it would be to use a left join, and filter to the store_id I want, or a null store_id, in case the product didn't get any sales at store_id = 1, since I want all the products listed:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
WHERE store_id = 1 or store_id is null;
Of course, this doesn't work as intended, instead I get:
name sales
---------------
Hat 20
Shoes 0
No Gloves! This is because Gloves did get sales, just not at store_id = 1, so the WHERE clause has filtered them out.
How then can I get a list of ALL products and their sales for a specific store?
Here are some queries to create the test tables:
create temp table test_products as
select 1 as product_id, 'Hat' as name;
insert into test_products values (2, 'Gloves');
insert into test_products values (3, 'Shoes');
create temp table test_sales as
select 1 as product_id, 1 as store_id, 20 as sales;
insert into test_sales values (2, 2, 10);
UPDATE: I should note that I am aware of this solution:
SELECT name, case when store_id = 1 then sales else 0 end as sales
FROM test_products p
LEFT JOIN test_sales s ON p.product_id = s.product_id;
however, it is not ideal... in reality I need to create this query for a BI tool in such a way that the tool can simply add a where clause to the query and get the desired results. Inserting the required store_id into the correct place in this query is not supported by this tool. So I'm looking for other options, if there are any.
Add the WHERE condition to the LEFT JOIN clause to prevent that rows go missing.
SELECT p.name, coalesce(s.sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
AND s.store_id = 1;
Edit for additional request:
I assume you can manipulate the SELECT items? Then this should do the job:
SELECT p.name
,CASE WHEN s.store_id = 1 THEN coalesce(s.sales, 0) ELSE NULL END AS sales
FROM products p
LEFT JOIN sales s USING (product_id)
Also simplified the join syntax in this case.
I'm not near SQL, but give this a shot:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id AND store_id = 1
You don't want a where on the whole query, just on your join