sql select sum conditions - sql

I'm studying sql (by myself) and I would like to know how I would do for these examples:
1- i'd create this 3 tables bellow:
CREATE TABLE Business (
Id INT,
Category INT,
Business_Name VARCHAR(30),
City_Id INT,
Billing INT
);
INSERT INTO business (Id, Category, Business_Name, City_Id, Billing) VALUES(1, 1, 'Bread', 1, 50);
INSERT INTO business (Id, Category, Business_Name, City_Id, Billing) VALUES(2, 2, 'Oreo', 2, 10);
INSERT INTO business (Id, Category, Business_Name, City_Id, Billing) VALUES(3, 2, 'Pizza', 3, 15);
INSERT INTO business (Id, Category, Business_Name, City_Id, Billing) VALUES(4, 2, 'Beer', 4, 25);
INSERT INTO business (Id, Category, Business_Name, City_Id, Billing) VALUES(5, 1, 'Steak', 1, 80);
CREATE TABLE City (
Id INT,
City_Name VARCHAR(30)
);
INSERT INTO City (Id, City_Name) VALUES(1, 'Paris');
INSERT INTO City (Id, City_Name) VALUES(2, 'New York');
INSERT INTO City (Id, City_Name) VALUES(3, 'Tokio');
INSERT INTO City (Id, City_Name) VALUES(4, 'Vancouver');
INSERT INTO City (Id, City_Name) VALUES(5, 'Cairo');
CREATE TABLE Category (
Id INT,
Category_Name VARCHAR(30)
);
INSERT INTO Category (Id, Category_Name) VALUES(1, 'Bar');
INSERT INTO Category (Id, Category_Name) VALUES(2, 'Pub');
INSERT INTO Category (Id, Category_Name) VALUES(3, 'Pizza');
2- I want to make these SQL queries:
a) Total Value of Billing (Billing) all stores, like this table:
-----------------------
|Business_Name | Total |
|--------------+-------|
|Total | 180 |
------------------------
b) All Total Billing by Category_Name like this table:
-------------------
|Category | Total |
|---------+-------|
|Bar | 130 |
|---------+-------|
|Pub | 50 |
|---------+-------|
|Pizza | 5 |
----------+--------
c)List the Business_Name with min billing, showing the: Category_Name, Business_Name, and Billing like this table:
----------------------------------------
|Category_Name | Business_Name | Total |
|--------------+---------------+-------|
|Pub | Beer | 5 |
|--------------+---------------+--------
d) All Total of Billing by City, showing the: Category_Name, Business_Name, City_Name and Billing like this table
--------------------------
|City | Total |
|----------------+-------|
|Cairo | 0 |
|----------------+-------|
|New York | 10 |
|----------------+-------|
|Paris | 130 |
|----------------+-------|
|Tokio | 15 |
-----------------+--------
|Vancouver | 25 |
-----------------+--------
Any body with a little more knowledge that could be help me, please? =)

First thing is first, all of these are basic queries and i have to point out that a simple google search for tutorials(ex1, ex2, ex3) would have answered most of these. as we are here to provide help and guidance i hope you take it to heart and read the tutorials before going over the answers.
with that said to be able to help you out i will walk through each query and provide an overview of what is happening.
a) you need an aggregate operation here to sum up the values. You would use the sum key word. normally you need a group by, but in this case since we only have a hard coded column with the word "Total" in it, it is not required. we also give each of the columns an alias as per your table. this is after the column name.
select 'Total' as business_Name,
sum(billing) Total
from business
b) This one is almost an exact copy of a, but requires a grouping. in this case you have to use the group by key word for all columns that are not in aggregates. in this case it is only the category name. it is good practice to not use the ordinal position in group by and order by statements, you should always spell out the columns you are using when able.
select c.category_name,
sum(billing) total
from business b
inner join category c
on b.category = c.id
group by c.category_name
c) We continue to build onto the query and add another column in the select statement and then add a column to the group by to allow grouping.
select c.category_name,
b.business_name,
sum(billing) total
from business b
inner join category c
on b.category = c.id
group by c.category_name, b.business_name
d) For this query its very similar to b, but instead of category_name we do a join on city with city id.
select c.city_name
,sum(billing) as total
from business b
inner join city c on c.id = b.city_id
group by c.city_name
with all of this said, several of your examples do not match your expected output. but these queries do match the expected output with the data you provided.
I really do recommend going through some tutorials to grasp the basics of sql better.

Here is answer to one of the queries. But I'd recommend you to read online basic sql tutorials and you will be able to write them yourself easily.
b)
select c.category_name
,sum(billing)
from business b
join category c
on b.category = c.id
group by 1

Related

Custom table join

Suppose I have 4 tables in Postgresql DB:
users {
id: int
}
cars {
id: int
}
usage_items {
id: int,
user_id: int,
car_id: int,
start: date,
end: date
}
prices {
id: int,
car_id: int,
price: int
}
When a user is renting a car I am creating a usage_item record to track the rental time. At the end of the month, I am sending him an invoice with calculated costs. The SQL is pretty simple here:
SELECT usage_items.start, usage_items.end, prices.price
FROM usage_items
JOIN prices ON prices.car_id = usage_items.car_id
(I omitted here WHERE clause with dates comparison, the rest of calculations I do in my Ruby code)
The problem I struggle with now is that some of my users have custom contracts with me ensuring lower prices for them. I am looking for a way to express this logic in my DB.
I came up with an idea to add user_id column to the prices table but this way I would need to create prices for every single user. So I decided to implement the following logic: if car_id in prices row is null it means that it is the default price for all the users. Otherwise, it is specific to a user. But I have no idea how to write SQL for this case, because:
SELECT usage_items.start, usage_items.end, prices.price
FROM usage_items
JOIN prices ON prices.car_id = usage_items.car_id
WHERE prices.user_id IS NULL OR prices.user_id = usage_items.user_id
returns rows for both prices. And I need only the one with an associated group or if it does not exist the one with null group_id.
Can you help me fix this SQL? Or maybe my design is bad and I should change it somehow?
Given your existing schema, this is one way to accomplish what you want:
Setup:
CREATE TABLE usage_items (user_id INTEGER, car_id INTEGER);
CREATE TABLE prices (user_id INTEGER, car_id INTEGER, price INTEGER);
INSERT INTO usage_items VALUES (1, 10), (2, 11), (3, 12);
INSERT INTO prices VALUES
(1, 10, 101),
(2, 11, 102),
(4, 12, 104),
(NULL, 10, 201),
(NULL, 11, 202),
(NULL, 12, 304);
Query (I'm not using start/end but it's the same thing):
SELECT DISTINCT ON (u.user_id, u.car_id) u.user_id, u.car_id, p.price
FROM usage_items u
LEFT JOIN prices p
ON u.car_id = p.car_id
AND (u.user_id = p.user_id OR p.user_id IS NULL)
ORDER BY u.user_id, u.car_id, CASE WHEN p.user_id IS NOT NULL THEN 1 ELSE 2 END
Result:
| user_id | car_id | price |
| ------- | ------ | ----- |
| 1 | 10 | 101 |
| 2 | 11 | 102 |
| 3 | 12 | 304 |
As you can see, the records in usage_items with a corresponding record for their car AND user_id in prices get their custom price rather than the NULL version; user 3 who does not have a custom price gets the NULL version (and not the custom price for a different customer).
Test here https://www.db-fiddle.com/f/wPVWEY3r22n22iKpDMrcMC/0

Postgres: Using WITH RECURSIVE to build list of item and their parents

I have a schema that looks like this:
CREATE TABLE category (
id SERIAL PRIMARY KEY,
parent INTEGER REFERENCES category(id) DEFERRABLE,
name TEXT NOT NULL UNIQUE );
SET CONSTRAINTS ALL DEFERRED;
The rows I have added are:
INSERT INTO category VALUES (1, NULL, 'animal');
INSERT INTO category VALUES (2, 1, 'dog');
INSERT INTO category VALUES (3, 1, 'cat');
INSERT INTO category VALUES (4, 3, 'siamese');
INSERT INTO category VALUES (5, 3, 'persian');
INSERT INTO category VALUES (6, 7, 'scary1');
INSERT INTO category VALUES (7, 6, 'scary2');
I'm trying to figure out how to recursively query this using WITH RECURSIVE to get a result that looks as such:
ids | names
---------+------------------------
{1} | animal
{2,1} | dog, animal
{3,1} | cat, animal
{4,3,1} | siamese, cat, animal
{5,3,1} | persian, cat, animal
{6,7} | scary1, scary2
{7,6} | scary2, scary1
Where ids is an array containing each parent until the root, and where names is a string with each name separated by commas.
I also need to handle the paradox condition without hanging.
Can somebody point me in the right direction?
Because of circular dependencies in your data you have to check whether id of the next referenced row does not exist already in the ids array. Use distinct on (id) in the final query and select rows with largest ids array.
with recursive categories as (
select id, parent, array[id] as ids, name as names
from category
union all
select cs.id, c.parent, ids || c.id, concat(names, ', ', name)
from categories cs
join category c on cs.parent = c.id
where c.id <> all(ids)
)
select distinct on (id) ids, names
from categories
order by id, cardinality(ids) desc;
ids | names
---------+----------------------
{1} | animal
{2,1} | dog, animal
{3,1} | cat, animal
{4,3,1} | siamese, cat, animal
{5,3,1} | persian, cat, animal
{6,7} | scary1, scary2
{7,6} | scary2, scary1
(7 rows)

SQL split column

I made an easy exemple to understand better what is my question.
So, i have 2 table and i have a selection based on inner join which is into a selection.
create table students(
id_student number,
name_student varchar2(15),
id_advisor number,
money number
);
insert into students values(1, 'Student_1', 1, 100);
insert into students values(2, 'Student_2', 8,-200);
insert into students values(4, 'Student_4', 7, 256);
insert into students values(5, 'Student_5', 3, -305);
----------------
create table advisors(
id_advisor number,
name_advisor varchar2(15)
);
insert into advisors values(1, 'advisor_1');
insert into advisors values(3, 'advisor_3');
insert into advisors values(5, 'advisor_5');
------------------------------------------
SELECT name_advisor, money as money_pozitive
FROM(
select name_student, name_advisor, money from students
inner join advisors on students.id_advisor = advisors.id_advisor)
WHERE money > 0
With this code i have the following result:
name_advisor | money_pozitive
------------------------------------
advisor_1 | 100
My question is, how I add an extra column named money_negative with of course negative values ? like this:
name_advisor | money_pozitive | money_negative
---------------------------------------------------------
advisor_1 | 100 | -305
Just use case:
select name_student, name_advisor,
(case when money > 0 then money end) as money_positive,
(case when money < 0 then money end) as money_negative
from students s inner join
advisors a
on s.id_advisor = a.id_advisor;
Notes:
A subquery is not necessary.
Use table aliases. These make a query easier to write and to read.
If you have multiple table names, it is a good habit to qualify all column names (i.e., use the table aliases for the column names).
And you don't need a where clause.

Postgresql aggregate array

I have a two tables
Student
--------
Id Name
1 John
2 David
3 Will
Grade
---------
Student_id Mark
1 A
2 B
2 B+
3 C
3 A
Is it possible to make native Postgresql SELECT to get results like below:
Name Array of marks
-----------------------
'John', {'A'}
'David', {'B','B+'}
'Will', {'C','A'}
But not like below
Name Mark
----------------
'John', 'A'
'David', 'B'
'David', 'B+'
'Will', 'C'
'Will', 'A'
Use array_agg: http://www.sqlfiddle.com/#!1/5099e/1
SELECT s.name, array_agg(g.Mark) as marks
FROM student s
LEFT JOIN Grade g ON g.Student_id = s.Id
GROUP BY s.Id
By the way, if you are using Postgres 9.1, you don't need to repeat the columns on SELECT to GROUP BY, e.g. you don't need to repeat the student name on GROUP BY. You can merely GROUP BY on primary key. If you remove the primary key on student, you need to repeat the student name on GROUP BY.
CREATE TABLE grade
(Student_id int, Mark varchar(2));
INSERT INTO grade
(Student_id, Mark)
VALUES
(1, 'A'),
(2, 'B'),
(2, 'B+'),
(3, 'C'),
(3, 'A');
CREATE TABLE student
(Id int primary key, Name varchar(5));
INSERT INTO student
(Id, Name)
VALUES
(1, 'John'),
(2, 'David'),
(3, 'Will');
What I understand you can do something like this:
SELECT p.p_name,
STRING_AGG(Grade.Mark, ',' ORDER BY Grade.Mark) As marks
FROM Student
LEFT JOIN Grade ON Grade.Student_id = Student.Id
GROUP BY Student.Name;
EDIT
I am not sure. But maybe something like this then:
SELECT p.p_name, 
    array_to_string(ARRAY_AGG(Grade.Mark),';') As marks
FROM Student
LEFT JOIN Grade ON Grade.Student_id = Student.Id
GROUP BY Student.Name;
Reference here
You could use the following:
SELECT Student.Name as Name,
(SELECT array(SELECT Mark FROM Grade WHERE Grade.Student_id = Student.Id))
AS ArrayOfMarks
FROM Student
As described here: http://www.mkyong.com/database/convert-subquery-result-to-array/
Michael Buen got it right. I got what I needed using array_agg.
Here just a basic query example in case it helps someone:
SELECT directory, ARRAY_AGG(file_name)
FROM table
WHERE type = 'ZIP'
GROUP BY directory;
And the result was something like:
| parent_directory | array_agg |
+-------------------------+----------------------------------------+
| /home/postgresql/files | {zip_1.zip,zip_2.zip,zip_3.zip} |
| /home/postgresql/files2 | {file1.zip,file2.zip} |
This post also helped me a lot: "Group By" in SQL and Python Pandas.
It basically says that it is more convenient to use only PSQL when possible, but that Python Pandas can be useful to achieve extra functionalities in the filtering process.

SQL: Fetching total shares of a customer through multiple tables

I have 3 tables customer(cid, name, phone) and transactions (cid (reference), fundid, date, shares) and fund (fundid, fund_name).
I am trying to write an sql query that would get me the total number of shares for each customer for each fund.
Here are the sample inserts:
INSERT INTO CUSTOMER(1, 'Alex', '123456678');
INSERT INTO CUSTOMER(2, 'Bill', '6323450236');
INSERT INTO CUSTOMER(3, 'Marie', '8568289912');
INSERT INTO FUND (1, 'Docotel');
INSERT INTO FUND (2, 'Armen');
INSERT INTO FUND (3, 'TD');
INSERT INTO TRANSACTIONS(1, 2, '2010-2-12', 234); (means shares bought)
INSERT INTO TRANSACTIONS(3, 1, '2010-4-2', 192);
INSERT INTO TRANSACTIONS(1, 2, '2010-4-22', -45); (the '-' means shares sold)
INSERT INTO TRANSACTIONS(1, 3, '2010-4-26', 220);
INSERT INTO TRANSACTIONS(3, 2, '2010-7-21', 170);
I want the sql result to look something like this:
Name| Fund_Name | Total_Shares |
Alex Docotel 189
Alex TD 220
Marie Docotel 192
Marie Armen 170
Thanks
Try this:
SELECT customer.name, fund.fund_name, T1.total_shares
FROM
(
SELECT cid, fundid, SUM(shares) AS total_shares
FROM transactions
GROUP BY cid, fundid
) T1
JOIN customer ON T1.cid = customer.cid
JOIN fund ON T1.fundid = fund.fundid
ORDER BY customer.name