How to write SQL sub-query in SQL? - sql

Here is sample data I am looking for total buying trade value and total selling trades value based on country.
Here are two tables, country, and trades
Table [companies]:
+-------------+--------------------+
| name| country |
+-------------+--------------------+
| Alice s.p. | Wonderland |
| Y-zap | Wonderland |
| Absolute | Mathlands |
| Arcus t.g. | Mathlands |
| Lil Mermaid | Underwater Kingdom |
| None at all | Nothingland |
+-------------+--------------------+
Table [trades]:
trades:
+----------+-------------+------------+-------+
| id | seller | buyer | value |
+----------+-------------+------------+-------+
| 20121107 | Lil Mermaid | Alice s.p. | 10 |
| 20123112 | Arcus t.g. | Y-zap | 30 |
| 20120125 | Alice s.p. | Arcus t.g. | 100 |
| 20120216 | Lil Mermaid | Absolute | 30 |
| 20120217 | Lil Mermaid | Absolute | 50 |
+----------+-------------+------------+-------+
Expected Output:
+--------------------+--------+--------+
| country| buyer | seller|
+--------------------+--------+--------+
| Mathlands | 180 | 30 |
| Nothingland | 0 | 0 |
| Underwater Kingdom | 0 | 90 |
| Wonderland | 40 | 100 |
+--------------------+--------+--------+
I am trying this: It gives only one value column and it doesn't show the 0 trade country that I want to show also.
select country, sum(value), sum(value)
from
(select a.buyer as export, a.seller as import, value, b.country as country
from trades as a
join companies as b
on a.seller=b.name)
group by country
order by country

Join country to distinct rows of trades which contain only buyer or seller and aggregate conditionally:
SELECT c.country,
SUM(CASE WHEN buyer IS NOT NULL THEN value ELSE 0 END) buyer,
SUM(CASE WHEN seller IS NOT NULL THEN value ELSE 0 END) seller
FROM country c
LEFT JOIN (
SELECT buyer, null seller, value FROM trades
UNION ALL
SELECT null, seller, value FROM trades
) t ON c.name IN (t.buyer, t.seller)
GROUP BY c.country
Or, with SUM() window function:
SELECT DISTINCT c.country,
SUM(CASE WHEN c.name = t.buyer THEN value ELSE 0 END) OVER (PARTITION BY c.country) buyer,
SUM(CASE WHEN c.name = t.seller THEN value ELSE 0 END) OVER (PARTITION BY c.country) seller
FROM country c LEFT JOIN trades t
ON c.name IN (t.buyer, t.seller)
See the demo.

Try CTE:
WITH sold AS (
SELECT sum(t.value) AS value, c.country FROM trades AS t INNER JOIN companies AS c ON (t.seller = c.name) GROUP BY c.country
), buyed AS (
SELECT sum(t.value) AS value, c.country FROM trades AS t INNER JOIN companies AS c ON (t.buyer = c.name) GROUP BY c.country
)
SELECT DISTINCT c.country, COALESCE(b.value, 0) AS buyer, COALESCE(s.value, 0) AS seller
FROM companies AS c
LEFT JOIN sold AS s ON (c.country = s.country)
LEFT JOIN buyed AS b ON (c.country = b.country)
https://www.db-fiddle.com/f/kgLezmhyiL9BKB2JUsaWYc/0

Related

Grouped Weighted Average in Access Query

I am trying to have a weighted average fee % of sales for each Client/Product/City combo from this data. I don't need the level of detail of sub product.
My data looks like this:
+--------+---------+-------+--------------+-------+----------------+
| Client | Product | City | Sub Product | Sales | Fee % of Sales |
+--------+---------+-------+--------------+-------+----------------+
| a | b | b | c | 1000 | 1% |
| a | b | b | d | 2000 | 2% |
| c | c | b | c | 3000 | 3% |
| d | c | b | c | 4000 | 4% |
+--------+---------+-------+--------------+-------+----------------+
I want to calculate the weighted average Fee % charged for each Client & Product combo. i.e. For Client 'a', Product 'b', City 'b': the fee% of sales would be (1,000/3,000)*1% + (2,000/3000 * 2%)
After I do this I will have another query that takes only the Client, Product,City Sales and new Weighted average field from the last query. I need another query because I will be using the results as part of a larger query.
This would have been easier done using window function, but since you are using ms-access... You can compute the sales subtotals per client/product/city in a subquery, and then JOIN in with the original table:
SELECT
t.client, t.product, t.city, SUM(t.sales * t.fee / t1.sales) res
FROM
mytable t
INNER JOIN (
SELECT client, product, city, SUM(sales) sales
FROM mytable
GROUP BY client, product, city
) t1
ON t1.client = t.client
AND t1.product = t.product
AND t1.city = t.city
GROUP BY t.client, t.product, t.city
This demo on DB Fiddle with your sample data returns:
| client | product | city | res |
| ------ | ------- | ---- | ------------------------------- |
| a | b | b | 0.016666666294137638 |
| c | c | b | 0.029999999329447746 |
| d | c | b | 0.03999999910593033 |
You can calculate the total sales & fee values as part of a subquery, then perform the division with the resulting values, e.g.:
select
q.client,
q.product,
q.city,
q.fee/q.totalsales as weightedfee
from
(
select
t.client,
t.product,
t.city,
sum(t.sales) as totalsales,
sum(t.sales*t.[fee % of sales]) as fee
from yourtable t
group by t.client, t.product, t.city
) q
Change yourtable to suit your table name.

Select Customers where not have value in other table with join

I need to select all Customers from the table Customer where Value in table Customer_Value is not 4.
Customers:
+------------+-------+
| Customer | ... |
+------------+-------+
| 312 | ... |
| 345 | ... |
| 678 | ... |
+------------+-------+
Customer_Value:
+------------+-------+
| Customer | Value |
+------------+-------+
| 312 | 1 |
| 312 | 2 |
| 345 | 1 |
| 345 | 2 |
| 345 | 3 |
| 678 | 1 |
| 678 | 2 |
| 678 | 4 |
+------------+-------+
To get my result I've used the following query:
SELECT C.Customer FROM [Customer] C
Left join Customer_Value V ON (C.Customer = V.Customer)
WHERE C.Customer NOT IN (SELECT Customer FROM [Customer_Value] WHERE Value = '4')
GROUP BY C.Customer
So my question is:
Is that a fast and good query? Or are there some other better solutions to get all the Customer Ids?
You can avoid Negative condition using Left Join and IS NULL Filter in where Condition.
SELECT C.Customer FROM [Customer] C
Left join Customer_Value V ON (C.Customer = V.Customer) and V.Value = '4'
WHERE V.Value is null
GROUP BY C.Customer
Your method is overkill; the JOIN is not necessary. I would use not exists:
select c.Customer
from Customer c
where not exists (select 1
from customer_value cv
where c.Customer = v.Customer and
cv.value = 4
);
You can also use aggregation, if you assume that all customers have at least one row in customer_value:
select cv.customer
from customer_value cv
group by cv.customer
having sum(case when cv.value = 4 then 1 else 0 end) = 0;
I would do
select * from customer c
join (
select distinct customer from customer_value where value!=4) v on c.customer = v.customer

How to query for SUM of multiple columns one-to-many

I have the following tables:
| Sales.Transaction
| ---------------
| Id
| Date
| BranchId
| Commission
|----------------
| Sales.TransactionItem
| ------------------
| Id
| Rate
| Pages
| TransactionId
|-------------------
| Sales.Branch
|-------------
| Id
| Name
|-------------
How can I get the total sales of each Branches, total number of transactions and total pages? I need to have a shape of data like this:
NOTE: Total amount of Transaction, can be computed by getting the sum of TransactionItems(Rate * Pages) - Commission
| Branches | Total Sales | No. of Transactions | Total Pages |
| Branch A | 10,500 | 14 | 17 |
| Branch B | 5,200 | 4 | 4 |
| Branch C | 400 | 2 | 2 |
| Branch D | 6,100 | 8 | 14 |
The problem with my query is that when the Transaction has a Commission and more than one TransactionItems, the Commission is being multiplied by the number of TransactionItems
select
b.Name as BranchName,
COUNT(t.Id) as Transactions,
SUM(ti.Pages * ti.Rate) - SUM(t.Commission) as TotalSales,
SUM(ISNULL(ti.Pages, 0)) as Pages
from
Sales.Branch b
left join Sales.[Transaction] t
on b.Id = t.BranchId
and t.Date >= '2017-11-01'
AND t.Date < '2017-12-01'
left join Sales.TransactionItem ti
on ti.TransactionId = t.Id
group by b.Name
order by b.Name ASC
This is tricky -- I think the solution is to aggregate the transaction items before joining the rest of the tables together:
select b.Name as BranchName,
count(t.Id) as Transactions,
sum(ti.total_minus_commission) - SUM(t.Commission) as TotalSales,
sum(ti.total_pages) as Pages
from Sales.Branch b left join
Sales.[Transaction] t
on b.Id = t.BranchId and
t.Date >= '2017-11-01'
t.Date < '2017-12-01' left join
(select ti.TransactionId,
sum(ti.Pages * ti.Rate) as total_minus_commission,
sum(ti.Pages) as total_pages
from Sales.TransactionItem ti
group by ti.TransactionId
) ti
on ti.TransactionId = t.Id
group by b.Name
order by b.Name ASC;
Note: I also think this correctly calculates Transactions.

Choose column based on max() of another column

Given the data below from the two tables cases and acct_transaction, how can I include just the acct_transaction.create_date of the largest acct_transaction amount whilst also calculating the sum of all amounts and the value of the largest amount? Platform is t-sql.
id amount create_date
---|----------|------------|
1 | 1.99 | 01/09/2009 |
1 | 2.99 | 01/13/2009 |
1 | 578.23 | 11/03/2007 |
1 | 64.57 | 03/03/2008 |
1 | 3.99 | 12/12/2012 |
1 | 31337.00 | 04/18/2009 |
1 | 123.45 | 05/12/2008 |
1 | 987.65 | 10/10/2010 |
Result set should look like this:
id amount create_date sum max_amount max_amount_date
---|----------|------------|----------|-----------|-----------
1 | 1.99 | 01/09/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 2.99 | 01/13/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 578.23 | 11/03/2007 | 33099.87 | 31337.00 | 04/18/2009
1 | 64.57 | 03/03/2008 | 33099.87 | 31337.00 | 04/18/2009
1 | 3.99 | 12/12/2012 | 33099.87 | 31337.00 | 04/18/2009
1 | 31337.00 | 04/18/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 123.45 | 05/12/2008 | 33099.87 | 31337.00 | 04/18/2009
1 | 987.65 | 10/10/2010 | 33099.87 | 31337.00 | 04/18/2009
This is what I have so far, I just don't know how to pull the date of the largest acct_transaction amount for max_amount_date column.
SELECT cases.id, acct_transaction.amount, acct_transaction.create_date AS 'create_date', SUM(acct_transaction.amount) OVER () AS 'sum', MIN(acct_transaction.amount) OVER () AS 'max_amount'
FROM cases INNER JOIN
acct_transaction ON cases.id = acct_transaction.id
WHERE (cases.id = '1')
;WITH x AS
(
SELECT c.id, t.amount, t.create_date,
s = SUM(t.amount) OVER(),
m = MAX(t.amount) OVER(),
rn = ROW_NUMBER() OVER(ORDER BY t.amount DESC)
FROM dbo.cases AS c
INNER JOIN dbo.acct_transaction AS t
ON c.id = t.id
)
SELECT x.id, x.amount, x.create_date,
[sum] = y.s,
max_amount = y.m,
max_amount_date = y.create_date
FROM x CROSS JOIN x AS y WHERE y.rn = 1;
You can just do a full outer join to the table which defines the aggregates:
select id, amount, create_date, x.sum, x.max_amount, x.max_amount_date
from table1
full outer join
(select sum(amount) as sum, max(amount) as max_amount,
(select top 1 create_date from table1 where amount = (select max(amount) from table1)) as max_amount_date
from table1) x
on 1 = 1
SQL Fiddle demo
Try this abomination of a query... I make no claims for its speed or elegance. It's likely I should pray that Cod have mercy on my soul.
Here is the out put of a join on the two tables that you mention but for which you do not provide schemas.
[SQL Fiddle][1]
SELECT A.case_id
,A.trans_id
,A.trans_amount
,A.trans_create_date
,A.trans_type
,B.max_amount
,B.max_amount_date
,E.sum_amount
FROM acct_transaction AS A
INNER JOIN (select C.case_id
,MAX(C.trans_amount) AS max_amount
,C.trans_create_date AS max_amount_date
FROM acct_transaction AS C group by C.case_id, C.trans_create_date ) AS B ON B.case_id = A.case_id
inner JOIN (select D.case_id, SUM(D.trans_amount) AS sum_amount FROM acct_transaction AS D GROUP BY D.case_id) AS E on E.case_id = A.case_id
WHERE (A.case_id = '1') AND (A.trans_type = 'F')
GROUP BY A.case_id
Thanks, that got me on the right track to this which is working:
,CAST((SELECT TOP 1 t2.create_date from acct_transaction t2
WHERE t2.case_sk = act.case_sk AND (t2.trans_type = 'F')
order by t2.amount, t2.create_date DESC) AS date) AS 'max_date'
It won't let me upvote because I have less than 15 rep :(

Joining 6 tables into single query?

Hey can anyone help me join the 5 tables below into a single query? I currently have the query below but is doesn't seem to work as if there are two products with the same ID inside the hires table all of the products are returned form the products table which is obviously wrong.
SELECT products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26-JAN-13'))
OR hires.prod_id IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
Table data:
PRODUCTS
--------------------------------------------
| Prod_ID | Title | Price | Type_ID |
|------------------------------------------|
| 1 | A | 5 | 1 |
| 2 | B | 7 | 1 |
| 3 | C | 3 | 2 |
| 4 | D | 3 | 3 |
|------------------------------------------|
PRODUCT_TYPES
----------------------
| Type_ID | Type |
|--------------------|
| 1 | TYPE_A |
| 2 | TYPE_B |
| 3 | TYPE_C |
| 4 | TYPE_D |
|--------------------|
PRODUCT_SUPPLIERS
-------------------------
| Prod_ID | Supp_ID |
|-----------------------|
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
|-----------------------|
SUPPLIERS
----------------------
| Supp_ID | Name |
|--------------------|
| 1 | SUPP_A |
| 2 | SUPP_B |
| 3 | SUPP_C |
| 4 | SUPP_D |
|--------------------|
HIRES
---------------------------------------------------------------
| Hire_ID | Prod_ID | Cust_ID | Hire_Start | Hire_End |
|-----------------------|------------|------------------------|
| 1 | 1 | 1 | 22-Jan-13 | 23-Jan-13 |
| 2 | 2 | 2 | 27-Jan-13 | 29-Jan-13 |
| 3 | 1 | 3 | 30-Jan-13 | 31-Jan-13 |
|-----------------------|------------|------------|-----------|
PRODUCTS
--------------------------------
| Cust_ID | Name | Phone |
|------------------------------|
| 1 | Cust_A | 555-666 |
| 2 | Cust_B | 444-234 |
| 3 | Cust_C | 319-234 |
| 4 | Cust_D | 398-092 |
|------------------------------|
The output from the query at the moment looks like this:
-------------------------------------------------------------
| Prod_ID | Title | Price | Type_ID | Suppliers |
|------------------------------------------|----------------|
| 1 | A | 5 | Type_A | SUPP_A,SUPP_B |
| 2 | B | 7 | Type_B | SUPP_B |
| 3 | C | 3 | Type_C | SUPP_C |
| 4 | D | 3 | Type_D | SUPP_D |
|------------------------------------------|----------------|
When it should look like this surely? as Prod_ID '1' is hired out between the dates in the query
-------------------------------------------------------------
| Prod_ID | Title | Price | Type_ID | Suppliers |
|------------------------------------------|----------------|
| 2 | B | 7 | Type_B | SUPP_B |
| 3 | C | 3 | Type_C | SUPP_C |
| 4 | D | 3 | Type_D | SUPP_D |
|------------------------------------------|----------------|
If anyone can help modify the query to output as suggested i would be really grateful. Because my understanding is that it should work as written?
Your issue is that Prod_Id 1 is both in and out of those date ranges. So instead, use a subquery to filter out which Prod_Id are in those ranges, and exclude those.
This is a much simplified version of your query:
SELECT P.Prod_ID
FROM Products P
LEFT JOIN (
SELECT Prod_ID
FROM Hires
WHERE hire_end >= To_Date('20130121', 'yyyymmdd') AND hire_start <= To_Date('20130126', 'yyyymmdd')
) H ON P.Prod_ID = H.Prod_ID
WHERE h.prod_id IS NULL
And the SQL Fiddle.
Assuming I copied and pasted correctly, this should be your query:
SELECT products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT JOIN (
SELECT Prod_ID
FROM Hires
WHERE hire_end >= To_Date('20130121', 'yyyymmdd') AND hire_start <= To_Date('20130126', 'yyyymmdd')
) H ON products.Prod_ID = H.Prod_ID
WHERE H.Prod_ID IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
Hope this helps.
Your left outer join will return null values when there is no match, meaning you still have a row (with no HIRE table data) when the results of this join query are Null:
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13')
OR hires.hire_start > to_date('26-JAN-13'))
OR hires.prod_id IS NULL
Try adding a select from the hires table (eg. hire.Hire_Start) to see this happening, then switch it to an inner join as well and I think your problem will be solved.
OR add a WHERE clause on the full query with something like hire.Hire_Start is not null
EDIT
If you change your original query to:
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
OR hires.prod_id IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
What comes back in the Hire_Start column?
Then if you add it to the where clause do you get the expected result:
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
OR hires.prod_id IS NULL
WHERE hires.Hire_Start is not null
GROUP BY products.prod_id, products.title, products.price, product_types.name
Finally, dropping the Outer Join altogether, does this work as expected?
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
INNER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
GROUP BY products.prod_id, products.title, products.price, product_types.name
And note: is the OR Hires.prod_ID sopposed to indicate that if the result returns no hire information it is available, in which case you need to write the query more like the other answer provided.
Here is some code that may help you:
SELECT L.V_PRODUCT_ID "PROD_ID" , L.TITLE "TITLE" , L.PRICE "PRICE" , L.TYPE "TYPE" , S.NAME "SUPPLIERS"
FROM
(SELECT V_PRODUCT_ID , TITLE , PRICE , TYPE , SUPPLIER_ID FROM
((select p.prod_id v_product_id , p.title TITLE , p.price PRICE , t.type TYPE
from products p , products_types t
where p.type_id = t_type_id) A
JOIN
(SELECT PROD_ID VV_PRODUCT_ID , SUPP_ID SUPPLIER_ID
FROM PRODUCTS_SUPPLIERS) H
ON (A.V_PRODUCT_ID = H.VV_PRODUCT_ID))) L
JOIN
SUPLLIERS S
ON (L.SUPPLIER_ID = S.SUPP_ID);
SELECT Emp.Empid, Emp.EmpFirstName, Emp.EmpLastName, Dept.DepartmentName
FROM Employee Emp
INNER JOIN Department dept
ON Emp.Departmentid=Dept.Departmenttid