PGSQL - Combining many AND + OR in WHERE clause - sql

I have this table format:
| productid | price |
| ----------| -------------- |
| 1 | 10 |
| 2 | 20 |
| 3 | 30 |
| 4 | 40 |
Let's say I want to select all rows where:
(productid is 1 and price is over 50) OR
(productid is 2 and price is over 100) OR
(productid is 3 and price is over 20)
Is there a better generic way to achieve it (something with like arrays with indexes or something) other that do one at a time like:
select * from table where (productid = 1 and price > 50) OR
(productid = 2 and price > 100) OR
(productid = 3 and price > 20)

I would use a values clause:
select *
from the_table t
join (
values (1, 50),
(2, 100),
(3, 20)
) as p(productid, price)
on t.productid = p.productid
and t.price > p.price;

with conditions as (
select a[1]::int as product_id, a[2]::int as min_price
from (select regexp_split_to_array(unnest(string_to_array('1,50;2,100;3,20', ';')),',')) as dt(a)
)
select t.* from my_table t
inner join conditions c on t.product = c.product_id
and t.price >= c.min_price
test data:
drop table if exists my_table;
create temp table if not exists my_table(product int, price int, note text);
insert into my_table
select 1,10, 'some info 1:10' union all
select 1,20, 'some info 1:20' union all
select 1,50, 'some info 1:50' union all
select 2,20, 'some info 2:10' union all
select 2,100, 'some info 2:100:1' union all
select 2,100, 'some info 2:100:2' union all
select 3,30, 'some info 3:30' union all
select 4,40, 'some info 4:40';
result:
1 50 "some info 1:50"
2 100 "some info 2:100:1"
2 100 "some info 2:100:2"
3 30 "some info 3:30"
unnest(string_to_array('1,50;2,100;3,20', ';'))-- split CSV to rows
regexp_split_to_array(...., ',') -- split CSV to columns

Related

How to convert similar record as one record & reflect single time in oracle table

In one table tbl1 data is like-
ID Desc. Amount Org. Date
1001 abc 125 Equity 12-Mar-18
1001 abc 50 Equity 12-Mar-18
1001 def 150 Market 12-Mar-18
1001 def 200 Market 12-Mar-18
1001 def 100 Market 12-Mar-18
But due to data repetition I want for the same organization, amount would be SUM and the data would be single times only & reflect into the table like wise-
ID Desc. Amount Org. Date
1001 abc 175 Equity 12-Mar-18
1001 def 450 Market 12-Mar-18
I want that these above 5 data replace with below 2 data and store into the table. Means 5 data replace with these 2 data & only these 2 data reflect on the table.
This is called aggregation, which is what you haven't done yet
select ID, Desc, sum(Amount) Amount, Org, Date
from table t
group by ID, Desc, Org, Date
Storing the records temporarily, truncating and then re-inserting would be an option, especially if your data is huge.
Another approach as a two step process, without truncating or creating DDLs would be to update all the amount to the required value and then deleting the duplicates.
UPDATE tbl1 t1
SET Amount = (
SELECT sum(Amount)
FROM tbl1 t2
WHERE t2.ID = t1.ID
AND t2.Descr = t1.Descr
AND t2.Org = t1.Org
AND t2.Date_t = t1.Date_t
);
DELETE
FROM tbl1
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM tbl1
GROUP BY ID
,Descr
,Amount
,Org
,Date_t
) ;
Demo
You can use a MERGE statement and analytic functions to do it in a single statement:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( ID, "Desc", Amount, Org, "Date" ) AS
SELECT 1001, 'abc', 125, 'Equity', DATE '2018-03-12' FROM DUAL UNION ALL
SELECT 1001, 'abc', 50, 'Equity', DATE '2018-03-12' FROM DUAL UNION ALL
SELECT 1001, 'def', 150, 'Market', DATE '2018-03-12' FROM DUAL UNION ALL
SELECT 1001, 'def', 200, 'Market', DATE '2018-03-12' FROM DUAL UNION ALL
SELECT 1001, 'def', 100, 'Market', DATE '2018-03-12' FROM DUAL;
Query 1:
MERGE INTO table_name dst
USING (
SELECT ROWID rid,
SUM( Amount ) OVER (
PARTITION BY ID, "Desc", Org, "Date"
) AS Amount,
ROW_NUMBER() OVER (
PARTITION BY ID, "Desc", Org, "Date"
ORDER BY ROWNUM
) AS rn
FROM table_name
) src
ON ( src.rid = dst.ROWID )
WHEN MATCHED THEN
UPDATE SET Amount = src.Amount
DELETE WHERE rn > 1
Query 2:
SELECT * FROM table_name
Results:
| ID | Desc | AMOUNT | ORG | Date |
|------|------|--------|--------|----------------------|
| 1001 | abc | 175 | Equity | 2018-03-12T00:00:00Z |
| 1001 | def | 450 | Market | 2018-03-12T00:00:00Z |

get all products that are in nested categories

I would really appreciate some help on this. my teacher couldn't help me.
Anyway I have 3 tables
tbl_product:
PID | productname
1 | product 1
2 | product 2
3 | product 3
4 | product 4
..
tbl_categories, motherCategory allows me to nest categories:
CID | categoriename | motherCategory
1 | electronics | NULL
2 | clothing | NULL
3 | Arduino | 1
4 | Casings, extra's | 3
..
tbl_productInCategory PID and CID are foreign keys to PID and CID in tbl_product and tbl_categories respectively. A product can have multiple categories assigned to it so PID can occur more than once in this table.
PID | CID
1 | 3
2 | 3
3 | 4
4 | 4
I want to select all products that are in a given category + it's subcategories. For instance if I give it the parameter CID = 1 (Electronics) it should also return the products in arduino and Casings, extra's.
I can't figure out how to do this and any help or pointers are appreciated.
something like a recursive WITH
with recur AS
(
SELECT CID,motherCategory FROM tbl_categories WHERE CID = #YourId
UNION ALL
SELECT r2.CID, r2.motherCategory FROM tbl_categoriesr2 WHERE r2.motherCategory = recur.CID
)
SELECT * FROM tbl_product WHERE PID IN (SELECT CID FROM recur)
You could use a Common Table Expression like this:
declare #tbl_product table (
PID int,
productname nvarchar(50)
)
insert into #tbl_product(PID, productname)
select 1, 'product 1'
union
select 2, 'product 2'
union
select 3, 'product 3'
union
select 4, 'product 4'
union
select 5, 'product 5'
declare #tbl_categories table (
CID int,
categoriename nvarchar(50),
motherCategory int
)
insert into #tbl_categories(CID, categoriename, motherCategory)
select 1,'electronics', NULL
union
select 2, 'clothing', NULL
union
select 3, 'Arduino', 1
union
select 4, 'Casings, extra''s', 3
declare #tbl_productInCategory table (
PID int,
CID int
)
insert into #tbl_productInCategory(PID, CID)
select 1, 3
union
select 2, 3
union
select 3, 4
union
select 4, 4
-- COMMON TABLE EXPRESSION
;with category_cte (CID, categoriname, motherCategory)
AS
(
select CID, categoriename, motherCategory
from #tbl_categories
where CID = 1 -- THE CID YOU WANT TO USE
UNION ALL
select c.CID, c.categoriename, c.motherCategory
from #tbl_categories c
inner join category_cte cte on c.motherCategory = cte.CID
)
select p.*
from #tbl_product p
inner join #tbl_productInCategory pic on p.PID = pic.PID
Note that there is a comment in the SQL where the CID is being used.

Inner Join + select the most recent

I have been trying to do the bellow query but honestly it's driving me crazy.
I have 2 Tables on MS SQL CE 4.0
Table 1 Name: Items
ID
Item_Code
Logged_by
Description
ID | Item_Code | Logged_by | Description
1 | A | Pete | just an A
2 | B | Mary | Seams like a B
3 | C | Joe | Obviously this is a C
4 | D | Pete | This is another A
Table 2 Name: Item_Comments
ID
Item_Code
Comment
Date
ID | Item_Code | Comment | Date
1 | B | Done | 2014/08/08
2 | A | Nice A | 2014/08/08
3 | B | Send 1 More | 2014/08/09
4 | C | Done | 2014/08/10
5 | D | This is an A | 2014/08/10
6 | D | Opps Sorry | 2014/08/11
The wanted result: I'm looking to join the most recent comment from Item_Comments to the Items Table
ID | Item_Code | Logged_by | Description | Comment
1 | A | Pete | just an A | Nice A
2 | B | Mary | Seams like a B | Send 1 More
3 | C | Joe | Obviously this is a C | Done
4 | D | Pete | This is another A | Opps Sorry
I did this query but I'm getting all the information =( mixed.
SELECT *
FROM Items t1
JOIN
(SELECT Item_Code, Comment, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code, Comment, Date
) t2
ON Item_Code= Item_Code
ORDER BY t1.Item_Code;
Do you know any way to do this ?
Try:
select x.*, z.comment
from items x
join (select item_code, max(date) as latest_dt
from item_comments
group by item_code) y
on x.item_code = y.item_code
join item_comments z
on y.item_code = z.item_code
and y.latest_dt = z.date
Fiddle test: http://sqlfiddle.com/#!6/d387f/8/0
You were close with your query but in your inline view aliased as t2 you were grouping by comment, leaving the max function to not actually aggregate anything at all. In t2 you should have just selected item_code and max(date) and grouped only by item_code, then you can use that to join into item_comments (y and z in my query above).
This is a second way of doing this using a subquery, however I would stick to the above (a join w/ an inline view):
select i.*, c.comment
from items i
join item_comments c
on i.item_code = c.item_code
where c.date = (select max(x.date)
from item_comments x
where x.item_code = c.item_code)
order by i.id
Fiddle test: http://sqlfiddle.com/#!6/d387f/11/0
Note if you run this inside piece you get every single record:
SELECT Item_Code, Comment, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code, Comment, Date
You want only the most recent comment. Assuming this is SQL Server 2008 or earlier, this get's you the most recent date for each Item_Code:
SELECT Item_Code, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code
Now you need to join that back and look up the comment on that date:
SELECT C.*
FROM Item_Comments C
INNER JOIN
(SELECT Item_Code, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code
) t2
ON C.Item_Code= t2.Item_Code
AND C.date = t2.MyDate
Now you can use that to join back to your original table:
SELECT t1.*, LatestComment.*
FROM Items t1
INNER JOIN
(
SELECT C.*
FROM Item_Comments C
INNER JOIN
(SELECT Item_Code, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code
) t2
ON C.Item_Code= t2.Item_Code
AND C.date = t2.MyDate
) LatestComment
On LatestComment.Item_Code = t1.Item_Code
Depending on the actual database you are using, this can get much simpler. Thats why you need to tag your database and version.
Try this,
create table items (id int, item_code char(1), logged_by varchar(10), description varchar(30));
insert into items values (1, 'A', 'Pete', 'just an A');
insert into items values (2, 'B', 'Mary', 'Seams like a B');
insert into items values (3, 'C', 'Joe', 'Obviously this is a C');
insert into items values (4, 'D', 'Pete', 'This is another A');
create table item_comments (id int, item_code char(1), comment varchar(20), date date);
insert into item_comments values (1, 'B', 'Done', '2014/08/08');
insert into item_comments values (2, 'A', 'Nice A', '2014/08/08');
insert into item_comments values (3, 'B', 'Send 1 More', '2014/08/09');
insert into item_comments values (4, 'C', 'Done', '2014/08/10');
insert into item_comments values (5, 'D', 'This is an A', '2014/08/10');
insert into item_comments values (6, 'D', 'Opps Sorry', '2014/08/11');
select * from items;
select * from item_comments;
select * from (select i.logged_by,i.id,i.item_code,i.description,ic.comment
,row_number() over(partition by i.id order by i.id )as Rnk
from items i inner join item_comments ic
on i.item_code=ic.item_code and i.id in(1,3)) x
where x.Rnk=1
union
select * from (select i.logged_by,i.id,i.item_code,i.description,ic.comment
,row_number() over(partition by i.id order by i.id )as Rnk
from items i inner join item_comments ic
on i.item_code=ic.item_code and i.id in(2,4)
) x where x.Rnk=2 order by item_code

Update statement using select and group by throwing missing expression

I tried to update a table column deletiondate with data from another table but I get "missing expression" error. Can someone help me resolve this issue?
Basically I want to update deletiondate column in a table by joining the key fields with another table and doing a group by. If the date is 01-JAN-0001 and count of records is > 1 then need to update 01-JAN-0001 else I need to update the maximum deletion date value.
The update Statement I used:
update table1 db1 set deletiondate =
SELECT
CASE WHEN count(*)>1 and (
(select 1
from table2 b
where b.loginid = a.loginid
and a.creationdate = b.creationdate
and b.deletiondate = '01-JAN-0001'
) = 1) THEN '01-JAN-0001' ELSE to_char(MAX(deletiondate),'DD-MON-YYYY') END as deletiondate1
FROM table2 a
GROUP BY a.loginid, a.creationdate
WHERE db1.userloginid = a.loginid and db1.usercreationdate = a.creationdate
Use this format: http://www.sqlfiddle.com/#!4/c46a6/2
update product set
(total_qty,max_qty) =
(
select sum(qty) as tot, max(qty) as maxq
from inv
where product_id = product.product_id
group by product_id
) ;
Sample data:
create table product(
product_id int primary key,
product_name varchar(100),
total_qty int,
max_qty int
);
create table inv(
inv_id int primary key,
product_id int references product(product_id),
qty int
);
insert into product(product_id,product_name)
select 1,'KB' from dual
union
select 2, 'MSE' from dual
union
select 3, 'CPU' from dual;
insert into inv(inv_id,product_id,qty)
select 1,1,4 from dual
union
select 2,2,1 from dual
union
select 3,3, 3 from dual
union
select 4,1,1 from dual
union
select 5,2,2 from dual
union
select 6,1,5 from dual;
Query output:
| PRODUCT_ID | PRODUCT_NAME | TOTAL_QTY | MAX_QTY |
|------------|--------------|-----------|---------|
| 1 | KB | 10 | 5 |
| 2 | MSE | 3 | 2 |
| 3 | CPU | 3 | 3 |

Multiple Ranks in one table

I need the following, Can anyone please help me do it.
Rank Cust_Type Cust_Name Revenue
1 Top A 10000
2 Top B 9000
3 Top C 8000
1 Bottom X 5000
2 Bottom Y 6000
3 Bottom Z 7000
I need separate ranks for Top and Bottom Cust_Type and all this is in MySQL.
This is a bit tricky. You may want to use variables, such as in the following example:
SELECT (
CASE cust_type
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := cust_type END
) + 1 AS rank,
cust_type,
cust_name,
revenue
FROM sales,
(SELECT #curRow := 0, #curType := '') r
ORDER BY cust_type DESC, revenue DESC;
The (SELECT #curRow := 0, #curType := '') r part allows the variable initialization without requiring a separate SET command.
Test case:
CREATE TABLE sales (cust_type varchar(10), cust_name varchar(10), revenue int);
INSERT INTO sales VALUES ('Top', 'A', 10000);
INSERT INTO sales VALUES ('Top', 'B', 9000);
INSERT INTO sales VALUES ('Top', 'C', 8000);
INSERT INTO sales VALUES ('Bottom', 'X', 5000);
INSERT INTO sales VALUES ('Bottom', 'Y', 6000);
INSERT INTO sales VALUES ('Bottom', 'Z', 7000);
Result:
+------+-----------+-----------+---------+
| rank | cust_type | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Top | A | 10000 |
| 2 | Top | B | 9000 |
| 3 | Top | C | 8000 |
| 1 | Bottom | Z | 7000 |
| 2 | Bottom | Y | 6000 |
| 3 | Bottom | X | 5000 |
+------+-----------+-----------+---------+
6 rows in set (0.00 sec)
Another test case:
CREATE TABLE sales (cust_type varchar(10), cust_name varchar(10), revenue int);
INSERT INTO sales VALUES ('Type X', 'A', 7000);
INSERT INTO sales VALUES ('Type X', 'B', 8000);
INSERT INTO sales VALUES ('Type Y', 'C', 5000);
INSERT INTO sales VALUES ('Type Y', 'D', 6000);
INSERT INTO sales VALUES ('Type Y', 'E', 4000);
INSERT INTO sales VALUES ('Type Z', 'F', 4000);
INSERT INTO sales VALUES ('Type Z', 'G', 3000);
Result:
+------+-----------+-----------+---------+
| rank | cust_type | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Type Z | F | 4000 |
| 2 | Type Z | G | 3000 |
| 1 | Type Y | D | 6000 |
| 2 | Type Y | C | 5000 |
| 3 | Type Y | E | 4000 |
| 1 | Type X | B | 8000 |
| 2 | Type X | A | 7000 |
+------+-----------+-----------+---------+
7 rows in set (0.00 sec)
You can obviously order the cust_type in ascending order instead of descending. I used descending just to have Top before Bottom in the original test case.
I found a problem with the solution using CASE, #curRow, and #curType. It depends on the execution plan MySQL uses to process the query. For example, it shows up if you add a join to the query. Then there is no guarantee that the rank is going to be computed correctly.
Making a slight change to the answer:
CREATE TABLE sales (cust_type_id int, cust_name varchar(10), revenue int);
CREATE TABLE cust_type (cust_type_id int, type_name varchar(10));
INSERT INTO cust_type VALUES (1, 'Bottom');
INSERT INTO cust_type VALUES (2, 'Top');
INSERT INTO sales VALUES (2, 'A', 10000);
INSERT INTO sales VALUES (2, 'B', 9000);
INSERT INTO sales VALUES (2, 'C', 8000);
INSERT INTO sales VALUES (1, 'X', 5000);
INSERT INTO sales VALUES (1, 'Y', 6000);
INSERT INTO sales VALUES (1, 'Z', 7000);
If I query only the sales table I get the rank in the correct order, but if I join to the cust_type table the rank values are no longer correct
SELECT (
CASE s.cust_type_id
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := s.cust_type_id END
) AS rank,
t.type_name,
s.cust_name,
s.revenue
FROM sales s,
cust_type t,
(SELECT #curRow := 0, #curType := 0) r
WHERE s.cust_type_id = t.cust_type_id
ORDER BY t.type_name DESC, s.revenue DESC;
Result:
+------+-----------+-----------+---------+
| rank | type_name | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Top | A | 10000 |
| 2 | Top | B | 9000 |
| 3 | Top | C | 8000 |
| 3 | Bottom | Z | 7000 |
| 2 | Bottom | Y | 6000 |
| 1 | Bottom | X | 5000 |
+------+-----------+-----------+---------+
MySQL is running the initial query into a temp table and then the ORDER BY is executing against the temp table after a rank was already computed.
This is similar to Thomas's answer, but a little simpler:
SELECT (SELECT COUNT(Cust_Type) FROM sales
WHERE Cust_Type = S.Cust_Type AND Revenue >= S.Revenue) AS Rank,
Cust_Type,
Cust_Name,
Revenue
FROM sales AS S
ORDER BY Cust_Type DESC, Rank;
I'm not sure how the performance compares to the Daniel's solution, particularly on very large data sets, or if you have to use complex joins.
What is not exactly clear is how the items should be ranked (I assumed by Revenue) or whether you are only pulling a certain number of values (e.g. the top 3 and the bottom 3) so I assumed you wanted all values. Given those assumptions,
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue > T.Revenue ) + 1 As Rank
From Table As T
Where Cust_Type = 'Top'
Union All
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue < T.Revenue ) + 1 As Rank
From Table As T
Where Cust_Type = 'Bottom'
If you were trying to do this in a single non-union query you could do:
Select Cust_Name, Cust_Type
, Case Z.Cust_Type
When 'Top' Then Z.TopRank
Else Z.BottomRank
End As Rank
From (
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue > T.Revenue ) + 1 As TopRank
, (Select Count(*)
From Table As T1
Where T1.Revenue < T.Revenue ) + 1 As BottomRank
From Table As T
) As Z
This works for me by keeping ranking of sales revenue and ordering separate .
SELECT
(Select count(s1.revenue)+1 from sales s1 where s.cust_type_id = s1.cust_type_id and s1.revenue > s.revenue)
As rank,
t.type_name,
s.cust_name,
s.revenue
FROM sales s LEFT JOIN
cust_type t USING(cust_type_id)
Group by t.type_name,s.cust_name,s.revenue DESC order by s.revenue DESC;
For JOIN tables problem, I found a solution.
I create a temporary table, this way MySQL maintain the order of value that I want to rank.
DROP TEMPORARY TABLE IF EXISTS tmp_mytable;
CREATE TEMPORARY TABLE tmp_mytable ENGINE = MEMORY
SELECT mytable.id AS id,
mytable.login AS Login,
cliente.myrank_id AS id_myrank,
mytable.rankvalue AS rankvalue
FROM mytable
INNER JOIN myjoin ON (mytable.id_myjoin = myjoin.id)
ORDER BY 3, 4 DESC;
SELECT id, login, IFNULL(id_myrank, 0) AS id_myrank, rankvalue,
#rank := IF(#prev_born = IFNULL(id_myrank, 0), #rank + 1, 1) AS ranking,
#prev_Born := IFNULL(id_myrank, 0) AS fake_field
FROM tmp_mytable, (select #prev_born := 0, #rank := 0) r
-- HAVING ranking < 20;
*PS: I tried with View creation, but insn't work too.
Here are solutions for MySQL 5.7 and MySQL 8.0
MySQL 5.7 solution
SELECT cust_type,
cust_name,
revenue,
#new_rank := IF(#current_type = cust_type, #new_rank + 1, 1),
#current_type := cust_type
FROM rank_test,
(SELECT #new_rank := null, #current_type := null) r
ORDER BY cust_type DESC, revenue DESC;
MySQL 8.0 solution
SELECT cust_type,
cust_name,
revenue,
RANK() OVER (
PARTITION BY
cust_type
ORDER BY
revenue DESC
) new_rank
FROM rank_test;
Here is schema to try on dbfiddle
CREATE TABLE rank_test (
cust_type varchar(20),
cust_name varchar(20),
revenue numeric(19, 6)
);
INSERT INTO rank_test VALUES ('TOP', 'A', 10000);
INSERT INTO rank_test VALUES ('TOP', 'B', 9000);
INSERT INTO rank_test VALUES ('TOP', 'C', 8000);
INSERT INTO rank_test VALUES ('BOTTOM', 'A', 10000);
INSERT INTO rank_test VALUES ('BOTTOM', 'B', 9000);
INSERT INTO rank_test VALUES ('BOTTOM', 'C', 8000);