Postgres - Query to select fields from multiple tables as columns - sql

I have the following tables
Table 1 : Product
id name
1 Bread
2 Bun
3 Cake
Table 2: Expense Items
product| quantity
1 | 100
2 | 150
3 | 180
1 | 25
2 | 30
Table 3: Income Items
product| quantity
1 | 100
2 | 150
3 | 180
1 | 25
2 | 30
Now I want the results like this
product | sum of quantity of expenseitem | sum of quantity of income item
1 | 125 | 125
2 | 180 | 180
3 | 180 | 180
What is the query to get this result ?
Thanks

You can try to use UNION ALL in a subquery with the condition in the aggregate function
Schema (PostgreSQL v9.6)
CREATE TABLE Product(
id int,
name varchar(50)
);
INSERT INTO Product VALUES (1,'Bread');
INSERT INTO Product VALUES (2,'Bun');
INSERT INTO Product VALUES (3,'Cake');
CREATE TABLE ExpenseItems(
product int,
quantity int
);
INSERT INTO ExpenseItems VALUES (1,100);
INSERT INTO ExpenseItems VALUES (2,150);
INSERT INTO ExpenseItems VALUES (3,180);
INSERT INTO ExpenseItems VALUES (1,25);
INSERT INTO ExpenseItems VALUES (2,30);
CREATE TABLE IncomeItems(
product int,
quantity int
);
INSERT INTO IncomeItems VALUES (1,100);
INSERT INTO IncomeItems VALUES (2,150);
INSERT INTO IncomeItems VALUES (3,180);
INSERT INTO IncomeItems VALUES (1,25);
INSERT INTO IncomeItems VALUES (2,30);
Query #1
SELECT p.id,
SUM(CASE WHEN grp = 1 THEN quantity END) SUMExpenseItems,
SUM(CASE WHEN grp = 2 THEN quantity END) SUMIncomeItems
FROM (
SELECT product, quantity,1 grp
FROM ExpenseItems
UNION ALL
SELECT product, quantity,2
FROM IncomeItems
) t1 JOIN Product p on p.id = t1.product
GROUP BY p.id;
| id | sumexpenseitems | sumincomeitems |
| --- | --------------- | -------------- |
| 1 | 125 | 125 |
| 2 | 180 | 180 |
| 3 | 180 | 180 |
View on DB Fiddle

Similar to #D-Shih's answer, PostgreSQL 9.4+ supports the FILTER() clause for conditional aggregation in place of CASE statements:
SELECT p.id,
SUM(quantity) FILTER (WHERE grp = 1) SUMExpenseItems,
SUM(quantity) FILTER (WHERE grp = 2) SUMIncomeItems
FROM
-- ...same union all query...
GROUP BY p.id

Related

how to insert row without duplicate values in two differents table ( header and detail)

I have the follow scenario, I need to insert header and details, they are two different tables.
The field order should be insert unique order without duplicate values and the detail get the id from the header
the data received by csv is:
order, number, qty, price
-------------------------
1000,a1000,1,2.0
1000,a1001,2,3.0
1001,a1000,1,3.0
1001,a1001,1,3.0
1001,a1000,1,3.0
I have the follow function in pgsql:
this query does not work, it is duplicating the records. How I can solve this problem?
INSERT INTO public.HeaderTable ( order )
SELECT
order
FROM
public.HeaderTable
WHERE
NOT EXISTS (
SELECT
idpo
FROM
public.HeaderTable
WHERE
order = '1000'
)
LIMIT 1;
This second query I don't know how to make to insert the detail, get the id and all this thing if not exist the row else not insert the row...
INSERT INTO public.DetailsTable ( idh, product, qty )
SELECT
order
FROM
public.HeaderTable
WHERE
NOT EXISTS (
SELECT
idpo
FROM
public.HeaderTable
WHERE
order = '1000'
) LIMIT 1;
expected result:
note: this is the expected result insert
HeaderTable:
id | order
------------
1 | 1000
2 | 1001
DetailsTable:
id | idh | product | qty
----------------------------
1 | 1 | a1000 | 2.0
2 | 1 | a1001 | 3.0
3 | 2 | a1000 | 3.0
4 | 2 | a1001 | 3.0

case when statement in oracle across tables

Hi apologies for formatting but im stumped and frustrated and i just need some help.
I've got two tables. I have made a good faith attempt to follow community standards but just in case it doesnt work, Table A has 3 columns 'ID', to identify a sales rep, 'Start' to indicate what company term they started, and 'Sales' to indicate their sales in that first term. Table B is just an expansion of Table A where it lists all terms (i marked it as quarters) a sales person was there and their sales.
Table A
+----+---------+-------+
| ID | Quarter | Sales |
+----+---------+-------+
| 1 | 141 | 30 |
| 2 | 151 | 50 |
| 3 | 151 | 80 |
+----+---------+-------+
Table B
+----+---------+-------+
| ID | Quarter | Sales |
+----+---------+-------+
| 1 | 141 | 30 |
| 1 | 142 | 25 |
| 1 | 143 | 45 |
| 2 | 151 | 50 |
| 2 | 152 | 60 |
| 2 | 153 | 75 |
| 3 | 151 | 80 |
| 3 | 152 | 50 |
| 3 | 153 | 70 |
+----+---------+-------+
My desired output is a table with ID, start term, sales from that term, second term, sales from that term, etc. for the first 6 terms an employee is there
my code is this
select a.id, start, a.sales,
case when a.start+1 = b.quarter then sales end as secondquartersales,
case when a.start+2 = b.quarter then sales end as thridquartersales,.....
from tablea a
left join tableb b
on a.id = b.id;
it gives nulls for all case when statements. please help
maybe try GROUP BY
create table a ( id number, strt number, sales number);
create table b (id number, quarter number , sales number);
insert into a values (1,141,30);
insert into a values (2,151,50);
insert into a values (3,151,80);
insert into b values ( 1,141,30);
insert into b values ( 1,142,25);
insert into b values ( 1,143,45);
insert into b values ( 2,151,50);
insert into b values ( 2,152,60);
insert into b values ( 2,153,75);
insert into b values ( 3,151,80);
insert into b values ( 3,152,50);
insert into b values ( 3,153,70);
select a.id, a.strt, a.sales,
max(case when a.strt+1 = b.quarter then b.sales end ) as secondquartersales,
max(case when a.strt+2 = b.quarter then b.sales end ) as thridquartersales
from a, b
where a.id = b.id
group by a.id, a.strt, a.sales;
OR PIVOT
select * from (
select a.id,
case when a.strt+1 = b.quarter then 'Q2'
when a.strt+2 = b.quarter then 'Q3'
when a.strt+3 = b.quarter then 'Q4'
when a.strt = b.quarter then 'Q1'end q,
b.sales sales
from a, b
where a.id = b.id)
pivot ( max(nvl(sales,0)) for Q in ('Q1', 'Q2', 'Q3', 'Q4'));
This is valid ANSI 92 SQL, as it is an inner join. The whole ANSI style version is just syntax candy.

Get records having the same value in 2 columns but a different value in a 3rd column

I am having trouble writing a query that will return all records where 2 columns have the same value but a different value in a 3rd column. I am looking for the records where the Item_Type and Location_ID are the same, but the Sub_Location_ID is different.
The table looks like this:
+---------+-----------+-------------+-----------------+
| Item_ID | Item_Type | Location_ID | Sub_Location_ID |
+---------+-----------+-------------+-----------------+
| 1 | 00001 | 20 | 78 |
| 2 | 00001 | 110 | 124 |
| 3 | 00001 | 110 | 124 |
| 4 | 00002 | 3 | 18 |
| 5 | 00002 | 3 | 25 |
+---------+-----------+-------------+-----------------+
The result I am trying to get would look like this:
+---------+-----------+-------------+-----------------+
| Item_ID | Item_Type | Location_ID | Sub_Location_ID |
+---------+-----------+-------------+-----------------+
| 4 | 00002 | 3 | 18 |
| 5 | 00002 | 3 | 25 |
+---------+-----------+-------------+-----------------+
I have been trying to use the following query:
SELECT *
FROM Table1
WHERE Item_Type IN (
SELECT Item_Type
FROM Table1
GROUP BY Item_Type
HAVING COUNT (DISTINCT Sub_Location_ID) > 1
)
But it returns all records with the same Item_Type and a different Sub_Location_ID, not all records with the same Item_Type AND Location_ID but a different Sub_Location_ID.
This should do the trick...
-- some test data...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
BEGIN DROP TABLE #TestData; END;
CREATE TABLE #TestData (
Item_ID INT NOT NULL PRIMARY KEY,
Item_Type CHAR(5) NOT NULL,
Location_ID INT NOT NULL,
Sub_Location_ID INT NOT NULL
);
INSERT #TestData (Item_ID, Item_Type, Location_ID, Sub_Location_ID) VALUES
(1, '00001', 20, 78),
(2, '00001', 110, 124),
(3, '00001', 110, 124),
(4, '00002', 3, 18),
(5, '00002', 3, 25);
-- adding a covering index will eliminate the sort operation...
CREATE NONCLUSTERED INDEX ix_indexname ON #TestData (Item_Type, Location_ID, Sub_Location_ID, Item_ID);
-- the actual solution...
WITH
cte_count_group AS (
SELECT
td.Item_ID,
td.Item_Type,
td.Location_ID,
td.Sub_Location_ID,
cnt_grp_2 = COUNT(1) OVER (PARTITION BY td.Item_Type, td.Location_ID),
cnt_grp_3 = COUNT(1) OVER (PARTITION BY td.Item_Type, td.Location_ID, td.Sub_Location_ID)
FROM
#TestData td
)
SELECT
cg.Item_ID,
cg.Item_Type,
cg.Location_ID,
cg.Sub_Location_ID
FROM
cte_count_group cg
WHERE
cg.cnt_grp_2 > 1
AND cg.cnt_grp_3 < cg.cnt_grp_2;
You can use exists :
select t.*
from table t
where exists (select 1
from table t1
where t.Item_Type = t1.Item_Type and
t.Location_ID = t1.Location_ID and
t.Sub_Location_ID <> t1.Sub_Location_ID
);
Sql server has no vector IN so you can emulate it with a little trick. Assuming '#' is illegal char for Item_Type
SELECT *
FROM Table1
WHERE Item_Type+'#'+Cast(Location_ID as varchar(20)) IN (
SELECT Item_Type+'#'+Cast(Location_ID as varchar(20))
FROM Table1
GROUP BY Item_Type, Location_ID
HAVING COUNT (DISTINCT Sub_Location_ID) > 1
);
The downsize is the expression in WHERE is non-sargable
I think you can use exists:
select t1.*
from table1 t1
where exists (select 1
from table1 tt1
where tt1.Item_Type = t1.Item_Type and
tt1.Location_ID = t1.Location_ID and
tt1.Sub_Location_ID <> t1.Sub_Location_ID
);

How to copy rows into a new a one to many relationship

I'm trying to copy a set of data in a one to many relationship to create a new set of the same data in a new, but unrelated one to many relationship. Lets call them groups and items. Groups have a 1-* relation with items - one group has many items.
I've tried to create a CTE to do this, however I can't get the items inserted (in y) as the newly inserted groups don't have any items associated with them yet. I think I need to be able to access old. and new. like you would in a trigger, but I can't work out how to do this.
I think I could solve this by introducing a previous parent id into the templateitem table, or maybe a temp table with the data required to enable me to join on that, but I was wondering if it is possible to solve it this way?
SQL Fiddle Keeps Breaking on me, so I've put the code here as well:
DROP TABLE IF EXISTS meta.templateitem;
DROP TABLE IF EXISTS meta.templategroup;
CREATE TABLE meta.templategroup (
templategroup_id serial PRIMARY KEY,
groupname text,
roworder int
);
CREATE TABLE meta.templateitem (
templateitem_id serial PRIMARY KEY,
itemname text,
templategroup_id INTEGER NOT NULL REFERENCES meta.templategroup(templategroup_id)
);
INSERT INTO meta.templategroup (groupname, roworder) values ('Group1', 1), ('Group2', 2);
INSERT INTO meta.templateitem (itemname, templategroup_id) values ('Item1A',1), ('Item1B',1), ('Item2A',2);
WITH
x AS (
INSERT INTO meta.templategroup (groupname, roworder)
SELECT distinct groupname || '_v1' FROM meta.templategroup where templategroup_id in (1,2)
RETURNING groupname, templategroup_id, roworder
),
y AS (
Insert INTO meta.templateitem (itemname, templategroup_id)
Select itemname, x.templategroup_id
From meta.templateitem i
INNER JOIN x on x.templategroup_id = i.templategroup_id
RETURNING *
)
SELECT * FROM y;
Use an auxiliary column templategroup.old_id:
ALTER TABLE meta.templategroup ADD old_id int;
WITH x AS (
INSERT INTO meta.templategroup (groupname, roworder, old_id)
SELECT DISTINCT groupname || '_v1', roworder, templategroup_id
FROM meta.templategroup
WHERE templategroup_id IN (1,2)
RETURNING templategroup_id, old_id
),
y AS (
INSERT INTO meta.templateitem (itemname, templategroup_id)
SELECT itemname, x.templategroup_id
FROM meta.templateitem i
INNER JOIN x ON x.old_id = i.templategroup_id
RETURNING *
)
SELECT * FROM y;
templateitem_id | itemname | templategroup_id
-----------------+----------+------------------
4 | Item1A | 3
5 | Item1B | 3
6 | Item2A | 4
(3 rows)
It's impossible to do that in a single plain sql query without an additional column. You have to store the old ids somewhere. As an alternative you can use plpgsql and anonymous code block:
Before:
select *
from meta.templategroup
join meta.templateitem using (templategroup_id);
templategroup_id | groupname | roworder | templateitem_id | itemname
------------------+-----------+----------+-----------------+----------
1 | Group1 | 1 | 1 | Item1A
1 | Group1 | 1 | 2 | Item1B
2 | Group2 | 2 | 3 | Item2A
(3 rows)
Insert:
do $$
declare
grp record;
begin
for grp in
select distinct groupname || '_v1' groupname, roworder, templategroup_id
from meta.templategroup
where templategroup_id in (1,2)
loop
with insert_group as (
insert into meta.templategroup (groupname, roworder)
values (grp.groupname, grp.roworder)
returning templategroup_id
)
insert into meta.templateitem (itemname, templategroup_id)
select itemname || '_v1', g.templategroup_id
from meta.templateitem i
join insert_group g on grp.templategroup_id = i.templategroup_id;
end loop;
end $$;
After:
select *
from meta.templategroup
join meta.templateitem using (templategroup_id);
templategroup_id | groupname | roworder | templateitem_id | itemname
------------------+-----------+----------+-----------------+-----------
1 | Group1 | 1 | 1 | Item1A
1 | Group1 | 1 | 2 | Item1B
2 | Group2 | 2 | 3 | Item2A
3 | Group1_v1 | 1 | 4 | Item1A_v1
3 | Group1_v1 | 1 | 5 | Item1B_v1
4 | Group2_v1 | 2 | 6 | Item2A_v1
(6 rows)

group values in sql server view

Hi i have a view that contains this:
sku quantity price discount
123 4 10 YES
123 1 10 YES
123 1 10 NO
the table have a lot of fields thats just a example what im trying to achieve is to group the sku by refering the quantity and discount column so it will look like this:
sku quantity price discount
123 5 10 YES
123 1 10 NO
so in this case there was 5 items with discount= YES same sku but other one with same sku 123 but quantity 1 and discount= NO, how i can group or sum that to make this work?
thank you
You could use window functions depending on your version of SQL Server:
Create Sample Table:
CREATE TABLE mytable (
sku INT
,quantity INT
,price MONEY
,discount VARCHAR(5)
);
insert into mytable values
(123, 4, 10, 'YES'),
(123, 1, 10, 'YES'),
(123, 1, 10, 'NO');
Query:
select distinct sku,
sum(quantity) OVER (partition by sku,discount order by sku) as quantity,
max(price) OVER (partition by sku,discount order by sku) as price,
discount
from mytable
Result:
+-----+----------+-------+----------+
| sku | quantity | price | discount |
+-----+----------+-------+----------+
| 123 | 1 | 10 | NO |
| 123 | 5 | 10 | YES |
+-----+----------+-------+----------+
SQL Fiddle Demo