Update statement using select and group by throwing missing expression - sql

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 |

Related

PGSQL - Combining many AND + OR in WHERE clause

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

How to Oracle SQL return where values when now records found.?

I have a customers table
What I want is when I run the query some thing like below
SELECT (<what should be the query here?>)
WHERE CustomerID in (2,3,13,18);
it should return the values used in where the condition for which it didn't find any records, something like below
Customer ID CustomerName ContactName Address
13 null null null
18 null null null
You can use left join -- but you need a table with the values. Here is one way to construct that table:
select *
from (select 2 as customerid from dual union all
select 3 as customerid from dual union all
select 13 as customerid from dual union all
select 18 as customerid from dual
) c left join
customers c
using (customerid)
where c.customerid is null;
Use a table collection expression and LEFT OUTER JOIN it to your table:
SELECT t.COLUMN_VALUE AS customer_id,
c.CustomerName,
c.ContactName,
c.Address
FROM TABLE( SYS.ODCINUMBERLIST( 2, 3, 13, 18 ) ) t
LEFT OUTER JOIN Customers c
ON ( t.COLUMN_VALUE = c.Customer_ID )
WHERE c.Customer_ID IS NULL;
So, for the sample data:
CREATE TABLE Customers ( customer_id, customername, contactname, address ) AS
SELECT LEVEL,
'Customer' || LEVEL,
'Contact' || LEVEL,
'Address' || LEVEL
FROM DUAL
CONNECT BY LEVEL <= 5;
This outputs:
CUSTOMER_ID | CUSTOMERNAME | CONTACTNAME | ADDRESS
----------: | :----------- | :---------- | :------
13 | null | null | null
18 | null | null | null
db<>fiddle here

How can I use the self join to find equal values within a group?

I am looking to find instances of GROUPID where all price values are 0. The following is a simplified version of what I am looking at
--------------------------------
| Groupid | Price | Customer|
--------------------------------
| 001 | 9 | 4 |
| 001 | 0 | 4 |
| 002 | 4 | 4 |
| 002 | 4 | 4 |
| 003 | 0 | 4 |
| 003 | 0 | 4 |
| 004 | 4 | 4 |
| 004 | 7 | 4 |
--------------------------------
I am attempting to use the following query to find all GROUPID where both PRICE values for that particular group = 0.
SELECT * FROM MYTABLE WHERE GROUPID IN
(SELECT TB1.GROUPID FROM MYTABLE TB1 JOIN MYTABLE TB2 ON TB1.GROUPID = TB2.GROUPID
AND TB1.PRICE = 0 AND TB2.PRICE = 0)
and CUSTOMER = 4
ORDER BY GROUPID;
This query returns:
| Groupid | Price | Customer|
--------------------------------
| 001 | 9 | 4 |
| 001 | 0 | 4 |
| 003 | 0 | 4 |
| 003 | 0 | 4 |
--------------------------------
In my case, I only need it to return GROUPID 003.
I'd also like to ask for assistance in modifying the query to return all non 0 equal PRICE values within a groupid. It doesn't have to be in the same query as above. For example the return would be:
| Groupid | Price | Customer|
--------------------------------
| 002 | 4 | 4 |
| 002 | 4 | 4 |
Any help would be appreciated. Thank you for your time.
If all the prices are zero, then look at the minimum and maximum price for the groupid:
select groupid
from mytable t
group by groupid
having min(price) = 0 and max(price) = 0;
I should point out that no self-join is required for this.
Try this:
SELECT * FROM MYTABLE as m1 where Price = 0
and not exists (
select 1
from MYTABLE as m2
where m2.Groupid = m1.Groupid
and m2.Price <> 0
)
You can list the the rows, whose group_id has no rows with non-zero price
select groupid, price, customer from mytable t
where not exists (
select 1 from mytable where group_id = t.group_id
and price != 0
);
I would do it by counting the number of distinct prices over each group (and I'm assuming that each group will have the same customer) and then filtering on the price(s) you're interested in.
For example, for prices that are 0 for all rows in each groupid+customer:
WITH mytable AS (SELECT '001' groupid, 9 price, 4 customer FROM dual UNION ALL
SELECT '001' groupid, 0 price, 4 customer FROM dual UNION ALL
SELECT '002' groupid, 4 price, 4 customer FROM dual UNION ALL
SELECT '002' groupid, 4 price, 4 customer FROM dual UNION ALL
SELECT '003' groupid, 0 price, 4 customer FROM dual UNION ALL
SELECT '003' groupid, 0 price, 4 customer FROM dual UNION ALL
SELECT '004' groupid, 4 price, 4 customer FROM dual UNION ALL
SELECT '004' groupid, 7 price, 4 customer FROM dual)
SELECT groupid,
customer,
price
FROM (SELECT groupid,
customer,
price,
COUNT(DISTINCT price) OVER (PARTITION BY groupid, customer) num_distinct_prices
FROM mytable)
WHERE num_distinct_prices = 1
AND price = 0;
GROUPID CUSTOMER PRICE
------- ---------- ----------
003 4 0
003 4 0
Just change the and price = 0 to and price != 0 if you want the groups which have the same non-zero price for all rows. Or simply remove that predicate altogether.
EDIT
Gordon's is the best solution for the first part:
SELECT groupid
FROM mytable t
GROUP BY GroupID
HAVING (MAX(price) = 0 and MIN(price) = 0)
And for the second part:
SELECT groupid
FROM mytable t
GROUP BY GroupID
HAVING MIN(price) <> 0 AND (MAX(price) = MIN(price))
My original one:
SELECT groupid
FROM mytable t
GROUP BY GroupID
HAVING SUM(Price) =0
This assumes, there are no negative prices.
To the second part of your question:
SELECT groupid
FROM mytable t
WHERE Price > 0
GROUP BY GroupID, Price
HAVING COUNT(price) > 1
In your sample data, you have only one customer. I assume if you had more than one customer, you would still want to return the rows where the groupid has the same price, across all rows and all customers. If so, you could use the query below. It is almost the same as Boneist's - I just use min(price) and max(price) instead of count(distinct), and I don't include customer in partition by.
If the price may be NULL, it will be ignored in the computation of max price and min price; if all the NON-NULL prices are equal for a groupid, all the rows for that group will be returned. If price can be NULL and this is NOT the desired behavior, that can be changed easily - but the OP will have to clarify.
The query below retrieves all the cases when there is a single price for the groupid. To retrieve only the groups where the price is 0 (an additional condition), add and price = 0 to the WHERE clause of the outer query. I added more test data to illustrate some of the cases the query covers.
with
test_data ( groupid, price, customer ) as (
select '001', 9, 4 from dual union all
select '001', 0, 4 from dual union all
select '002', 4, 4 from dual union all
select '002', 4, 4 from dual union all
select '003', 0, 4 from dual union all
select '003', 0, 4 from dual union all
select '004', 4, 4 from dual union all
select '004', 7, 4 from dual union all
select '002', 4, 8 from dual union all
select '005', 2, 8 from dual union all
select '005', null, 8 from dual
),
prep ( groupid, price, customer, min_price, max_price) as (
select groupid, price, customer,
min(price) over (partition by groupid),
max(price) over (partition by groupid)
from test_data
)
select groupid, price, customer
from prep
where min_price = max_price
;
GROUPID PRICE CUSTOMER
------- --------- ---------
002 4 8
002 4 4
002 4 4
003 0 4
003 0 4
005 8
005 2 8
This may be what you want:
SELECT * FROM MYTABLE
WHERE GROUPID NOT IN (
SELECT GROUPID
FROM MYTABLE
WHERE Price <> 0)
and just change the last line for the other query:
SELECT * FROM MYTABLE
WHERE GROUPID NOT IN (
SELECT GROUPID
FROM MYTABLE
WHERE Price = 0)
I would do it very similarly to what Gordon posted
SELECT groupId
FROM MyTable
GROUP BY groupId
HAVING SUM(price) = 0

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.

How do you order a group of records then insert their order placement too?

I have a table of logs that contain a ID and TIMESTAMP. I want to ORDER BY ID and then TIMESTAMP.
For example, this is what the result set would look like:
12345 05:40
12345 05:50
12345 06:22
12345 07:55
12345 08:33
Once that's done, I want to INSERT a order value in a third column that signifies it's placement in the group from earliest to latest.
So, you would have something like this:
12345 05:40 1 <---First entry
12345 05:50 2
12345 06:22 3
12345 07:55 4
12345 08:33 5 <---Last entry
How can I do that in a SQL statement? I can select the data and ORDER BY ID, TIMESTAMP. But, I can't seem to INSERT a order value based on the groupings. :(
Try this update not an insert:
Fiddle demo here:
;with cte as(
select id, yourdate, row_number() over(order by id,yourdate) rn
from yourTable
)
Update ut Set thirdCol = rn
From yourTable ut join cte on ut.Id = cte.id and ut.yourdate = cte.yourdate
NOTE: if you need to get the thirdColumn updated per id basis, please partition your rownumber by using row_number() over (partition by id, order by order by id,yourdate)
Results:
| ID | YOURDATE | THIRDCOL |
|-------|----------|----------|
| 12345 | 05:40 | 1 |
| 12345 | 05:50 | 2 |
| 12345 | 06:22 | 3 |
| 12345 | 07:55 | 4 |
| 12345 | 08:33 | 5 |
Using a derived table and an update.
IF OBJECT_ID('tempdb..#TableOne') IS NOT NULL
begin
drop table #TableOne
end
CREATE TABLE #TableOne
(
SomeColumnA int ,
LetterOfAlphabet varchar(12) ,
PositionOrdinal int not null default 0
)
INSERT INTO #TableOne ( SomeColumnA , LetterOfAlphabet )
select 123 , 'x'
union all select 123 , 'b'
union all select 123 , 'z'
union all select 123 , 't'
union all select 123 , 'c'
union all select 123 , 'd'
union all select 123 , 'e'
union all select 123 , 'a'
Select 'pre' as SpaceTimeContinium , * from #TableOne order by LetterOfAlphabet
Update
#TableOne
Set PositionOrdinal = derived1.rowid
From
( select SomeColumnA , LetterOfAlphabet , rowid = row_number() over (order by LetterOfAlphabet asc) from #TableOne innerT1 )
as derived1
join #TableOne t1
on t1.LetterOfAlphabet = derived1.LetterOfAlphabet and t1.SomeColumnA = derived1.SomeColumnA
Select 'post' as SpaceTimeContinium, * from #TableOne order by LetterOfAlphabet
IF OBJECT_ID('tempdb..#TableOne') IS NOT NULL
begin
drop table #TableOne
end
To get the order you desire without doing an insert and an update, you can set your clustered index to handle it for you. The example below creates a clustered primary key.
To do this you must remove any clustered index that you already have on the table because you can only have one clustered index per table.
CREATE TABLE dbo.Table_1
(
ID int NOT NULL,
DTStamp datetime NOT NULL
)
ALTER TABLE dbo.Table_1 ADD CONSTRAINT
PK_Table_1 PRIMARY KEY CLUSTERED
(
ID,
DTStamp
)
Insert some random data to test with...
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,getdate());
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,dateadd(mi,1,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,dateadd(mi,2,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,dateadd(mi,3,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12346,dateadd(mi,4,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12340,dateadd(mi,5,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12340,dateadd(mi,6,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12340,dateadd(mi,7,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12340,dateadd(mi,8,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,1,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,2,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,3,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,4,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,5,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,6,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,7,getdate()));
INSERT INTO [dbo].[Table_1]([ID],[DTStamp])VALUES(12344,dateadd(mi,8,getdate()));
Now query your table and check out the order...
SELECT [ID] ,[DTStamp] FROM [Table_1]
If you need the order to display in a query, you can add the row number with an over clause.
SELECT [ID] ,[DTStamp],row_number() over (partition by [ID] order by [ID] ,[DTStamp]) as SortOdr FROM [Table_1]