How to subtract one table sum from other - sql

I have two tables: Issue and Return. Both tables have columns article_id, person_id and quantity. I need to subtract Return table from Issue table for one person and group the result by article name.
First select looks like this:
SELECT Article.Name, SUM(Issue.Quantity)
FROM Issue LEFT JOIN Article ON (Issue.ArticleID = Article.ID)
WHERE Issue.PersonID = 2
GROUP BY Article.Name
ArticleName | Quantity
------------------------
Shoes | 5
Coats | 3
Hats | 3
Second like this:
SELECT Article.Name, SUM(Return.Quantity)
FROM Return LEFT JOIN Article ON (Return.ArticleID = Article.ID)
WHERE Return.PersonID = 2
GROUP BY Article.Name
ArticleName | Quantity
------------------------
Shoes | 3
Coats | 2
Hats | 0
Question is, how do I subtract second select from the first (Return - Issue) to get a table like this:
ArticleName | Quantity
------------------------
Shoes | 2
Coats | 1
Hats | 3
The Quantity in the final result should be the number of remaining articles to be returned.

This may work:
(heavily edited - my first answer did not have a chance of working...)
SELECT Article.Name
, (SELECT SUM(Issue.Quantity) FROM Issue WHERE Issue.ArticleID = Article.ID AND Issue.PersonID = 2)
- (SELECT SUM(Return.Quantity) FROM Return WHERE Return.ArticleID = Article.ID AND Return.PersonID = 2)
FROM Article

Related

How to count number of rows that corresponds to the ID given two tables in SQL?

I have two tables: 1) Places 2) Reviews
Table examples are below:
PLACES
ID | NAME
============
1 | Joe
2 | Cat
3 | Dog
REVIEWS
PLACE_ID | REVIEW_ID| REVIEW_CONTENT
====================================
1 | 1000 | "it's good"
1 | 1001 | "aweful place"
3 | 1002 | "good place"
PLACE_ID is my foreign key and I want to count number of review contents per each ID in PLACES table.
As you can see,
there are 2 review contents in REVIEWS table for place id 1 ("Joe")
there are 0 review contents in REVIEWS table for place id 2 ("Cat")
there are 1 review contents in REVIEWS table for place id 3 ("Dog")
The result should look like
RESULT
PLACE_ID | NAME | COUNT
=======================
1 | Joe | 2
2 | Cat | 0
3 | Dog | 1
Can someone please help how to count number of rows (e.g number of review contents) that has same foreign key (e.g. PLACE_ID), given two tables?
This is basic SQL. Please do some reading on simple aggregations.
SELECT P.ID as PLACE_ID,
P.NAME as NAME,
COUNT(R.ID) as COUNT
FROM PLACES P
LEFT JOIN REVIEWS R
ON P.ID = R.PLACE_ID
You can try the below - using left join and aggregation
SELECT p.id, p.name,count(r.id) as cnt
from place p left join reviews ON p.id = r.place_id
group by p.id, p.name
Simple Join both the tables and perform a aggregation to count the number of reviews for each ID available in Place table. You can find the code below.
Select A.PLACE_ID,
A.Name,
count(REVIEW_ID) COUNT
From Places A
Left Join Reviews B
on A.ID = B.PLACE_ID
group by A.PLACE_ID,
A.Name

Find SQL table rows where there are multiple different values

I want to be able to filter out groups where the values aren't the same. When doing the query:
SELECT
category.id as category_id,
object.id as object_id,
object.value as value
FROM
category,
object
WHERE
category.id = object.category
We get the following results:
category_id | object_id | value
-------------+-----------+-------
1 | 1 | 1
1 | 2 | 2
1 | 3 | 2
2 | 4 | 3
2 | 5 | 2
3 | 6 | 1
3 | 7 | 1
The goal: Update the query so that it yields:
category_id
-------------
1
2
In other words, find the categories where the values are different from the others in that same category.
I have tried many different methods of joining, grouping and so on, to no avail.
I know it can be done with multiple queries and then filter with a little bit of logic, but this is not the goal.
You can use aggregation:
SELECT o.category as category_id
FROM object o
GROUP BY o.category
HAVING MIN(o.value) <> MAX(o.value);
You have left the FROM clause out of your query. But as written, you don't need a JOIN at all. The object table is sufficient -- because you are only fetching the category id.

In a query (no editing of tables) how do I join data without any similarities?

I Have a query that finds a table, here's an example one.
Name |Age |Hair |Happy | Sad |
Jon | 15 | Black |NULL | NULL|
Kyle | 18 |Blonde |YES |NULL |
Brad | 17 | Blue |NULL |YES |
Name and age come from one table in a database, hair color comes from a second which is joined, and happy and sad come from a third table.My goal would be to make the first line of the chart like this:
Name |Age |Hair |Happy |Sad |
Jon | 15 |Black |Yes |Yes |
Basically I want to get rid of the rows under the first and get the non NULL data joined to the right. The problem is that there is no column where the Yes values are on the Jon row, so I have no idea how to get them there. Any suggestions?
PS. With the data I am using I can't just put a 'YES' in the 'Jon' row and call it a day, I would need to find the specific value from the lower rows and somehow get that value in the boxes that are NULL.
Do you just want COALESCE()?
COALESCE(Happy, 'Yes') as happy
COALESCE() replaces a NULL value with another value.
If you want to join on a NULL value work with nested selects. The inner select gets an Id for NULLs, the outer select joins
select COALESCE(x.Happy, yn_table.description) as happy, ...
from
(select
t1.Happy,
CASE WHEN t1.Happy is null THEN 1 END as happy_id
from t1 ...) x
left join yn_table
on x.xhappy_id = yn_table.id
If you apply an ORDER BY to the query, you can then select the first row relative to this order with WHERE rownum = 1. If you don't apply an ORDER BY, then the order is random.
After reading your new comment...
the sense is that in my real data the yes under the other names will be a number of a piece of equipment. I want the numbers of the equipment in one row instead of having like 8 rows with only 4 ' yes' values and the rest null.
... I come to the conclusion that this a XY problem.
You are asking about a detail you think will solve your problem, instead of explaining the problem and asking how to solve it.
If you want to store several pieces of equipment per person, you need three tables.
You need a Person table, an Article table and a junction table relating articles to persons to equip them. Let's call this table Equipment.
Person
------
PersonId (Primary Key)
Name
optional attributes like age, hair color
Article
-------
ArticleId (Primary Key)
Description
optional attributes like weight, color etc.
Equipment
---------
PersonId (Primary Key, Foreign Key to table Person)
ArticleId (Primary Key, Foreign Key to table Article)
Quantity (optional, if each person can have only one of each article, we don't need this)
Let's say we have
Person: PersonId | Name
1 | Jon
2 | Kyle
3 | Brad
Article: ArticleId | Description
1 | Hat
2 | Bottle
3 | Bag
4 | Camera
5 | Shoes
Equipment: PersonId | ArticleId | Quantity
1 | 1 | 1
1 | 4 | 1
1 | 5 | 1
2 | 3 | 2
2 | 4 | 1
Now Jon has a hat, a camera and shoes. Kyle has 2 bags and one camera. Brad has nothing.
You can query the persons and their equipment like this
SELECT
p.PersonId, p.Name, a.ArticleId, a.Description AS Equipment, e.Quantity
FROM
Person p
LEFT JOIN Equipment e
ON p.PersonId = e.PersonId
LEFT JOIN Article a
ON e.ArticleId = a.ArticleId
ORDER BY p.Name, a.Description
The result will be
PersonId | Name | ArticleId | Equipment | Quantity
---------+------+-----------+-----------+---------
3 | Brad | NULL | NULL | NULL
1 | Jon | 4 | Camera | 1
1 | Jon | 1 | Hat | 1
1 | Jon | 5 | Shoes | 1
2 | Kyle | 3 | Bag | 2
2 | Kyle | 4 | Camera | 1
See example: http://sqlfiddle.com/#!4/7e05d/2/0
Since you tagged the question with the oracle tag, you could just use NVL(), which allows you to specify a value that would replace a NULL value in the column you select from.
Assuming that you want the 1st row because it contains the smallest age:
- wrap your query inside a CTE
- in another CTE get the 1st row of the query
- in another CTE get the max values of Happy and Sad of your query (for your sample data they both are 'YES')
- cross join the last 2 CTEs.
with
cte as (
<your query here>
),
firstrow as (
select name, age, hair from cte
order by age
fetch first row only
),
maxs as (
select max(happy) happy, max(sad) sad
from cte
)
select f.*, m.*
from firstrow f cross join maxs m
You can try this:
SELECT A.Name,
A.Age,
B.Hair,
C.Happy,
C.Sad
FROM A
INNER JOIN B
ON A.Name = B.Name
INNER JOIN C
ON A.Name = B.Name
(Assuming that Name is the key columns in the 3 tables)

join multiple row in table by filed value

i have a table company row like this :
id(int) |name(string) |maincategory(int) |subcategory(string)
1 |Google |1 |1,2,3
2 |yahoo |4 |4,1
and other table category like:
id(int) |name(string)
1 |Search
2 |Email
3 |Image
4 |Video
i want to join tow table by company.subcategory = category.id
is it possible in sql ?
Start by splitting your subcategory column. In the end you should have an additional company_category table with company_id and category_id as columns.
company_id(int) |category_id(int)
1 |1
1 |2
1 |3
2 |4
2 |1
Your design is invalid. You shoud have another table called companySubcategories or something like that.
This table shoud have two columns companyId an categoryId.
Then your select would look like this:
select <desired fields> from
company c
join companySubcategories cs on cs.companyId = cs.id
join category ct on ct.id = cs.categoryId
you can do like below...
select * from
company c, category cc
where c. subcategory like '%'||cc.id||'%';
it is working as expected in oracle database ..
You could introduce a new table company_subcategory to keep track of subcategories
id (int) | subcategory(int)
1 | 1
1 | 2
1 | 3
2 | 1
2 | 4
then you would be able to run select as
select company.name AS company, category.name AS category
FROM company
JOIN company_subcategory
ON company.id = company_subcategory.company
JOIN category
ON company_subcategory.subcategory = category.id;
to get
+---------+----------+
| company | category |
+---------+----------+
| google | search |
| google | email |
| google | image |
| yahoo | search |
| yahoo | video |
+---------+----------+
SELECT *
FROM COMPANY CMP, CATEGORY CT
WHERE (SELECT CASE
WHEN INSTR(CMP.SUB_CATEGORY, CT.ID) > 0 THEN
'TRUE'
ELSE
'FALSE'
END
FROM DUAL) = 'TRUE'
This query looks for the ID in the SUB_CATEGORY, using the INSTR function.
In case it does exist, the row is returned.
The output is as below
ID NAME MAIN_CATEGORY SUB_CATEGORY ID NAME
1 Google 1 1,2,3 1 Search
1 Google 1 1,2,3 2 Email
1 Google 1 1,2,3 3 Image
2 yahoo 2 4,1 1 Search
2 yahoo 2 4,1 4 Video
Hope it helps.
However, I suggest you avoid this type of entries, as an ID should have separate entries and not combined entries. This may create problems in future, so it would be better to avoid it now.

Problem with advanced distinct SQL query

Ok this one is realy tricky :D
i have a this table
bills_products:
- bill_id - product_id - action -
| 1 | 4 | add |
| 1 | 5 | add |
| 2 | 4 | remove |
| 2 | 1 | add |
| 3 | 4 | add |
as you can see product with the id 4 was added at bill 1 then removed in bill 2 and added again in bill 3
All Bills belong to a bill_group. But for the simplicity sake let's assume all the bills are in the same group.
Now i need a SQL Query that shows all the products that are currently added at this group.
In this example that would be 5, 1 and 4. If we would remove the bill with id 3 that would be 5 and 1
I've tried to do this with DISTINCT but it's not powerful enough or maybe I'm doing it wrong.
This seems to work in SQL Server at least:
select product_id
from (
select product_id,
sum((case when action='add' then 1 else -1 end)) as number
from bills_products
group by product_id
) as counts
where number > 0
SELECT DISTINCT product_id FROM bills_products WHERE action = 'add';
GSto almost had it, but you have to ORDER BY bill_id DESC to ensure you get the latest records.
SELECT DISTINCT product_id FROM bills_products
WHERE action = 'add'
ORDER BY bill_id DESC;
(P.S. I think most people would say it's a best practice to have a timestamp column on tables like this where you need to be able to know what the "newest" row is. You can't always rely on ids only ascending.)