How to delete rows that fail sqlite constraint? - sql

I have a table that looks like this:
orders (
user_id INTEGER
item_id INTEGER
quantity INTEGER
... (more columns and constraints)
CHECK(quantity > 0)
)
I want to decrease the quantity of an order by one, and delete it if that would make the quantity zero.
Is it possible to do this in one statement?
Right now I have:
UPDATE orders SET quantity = quantity - 1 WHERE *blah blah complicated clause*
However when the quantity is 1, this fails and leaves the quantity at 1, because setting it to 0 would be a constraint error.
I want it to instead just delete the row when the quantity is 1, because the order is now empty. How can I do this?

I'd suggest deleting rows, according to the complicated clause with AND quantity < 2 prior to doing the UPDATE.
e.g. (where user_id = 1 represents the complicated clause)
DROP TABLE IF EXISTS orders;
CREATE TABLE IF NOT EXISTS orders (user_id INTEGER, order_id INTEGER, quantity INTEGER, CHECK(quantity > 0) );
INSERT INTO orders VALUES (1,1,10),(1,2,1),(1,3,2),(2,1,3),(2,2,2),(2,3,5);
SELECT * FROM orders;
DELETE FROM orders WHERE user_id = 1 AND quantity < 2;
UPDATE orders SET quantity = quantity -1 WHERE user_id = 1;
SELECT * FROM orders;
results in (all rows before anything is done) :-
and then :-
i.e. orders 1 and 3 have been updated whilst order 2 (circled in above) has been deleted.

Related

How do I add MAX(RowID)+1 when adding multiple records?

Tables:
Orders -- this stored RowID
ExternalOrders -- this is where I get some important order data from
I'm trying to add orders into the Orders table using data from ExternalOrders. However, I want to add multiple records at the same time and ensure that a new RowID number gets assigned to each new row.
For example, if the last RowID that currently exists in the Orders table is 1 then let the next row have a RowID of 2, then 3, 4...etc.
I've tried the following code:
CREATE TABLE Orders
(
RowID BIGINT,
OrderID VARCHAR(200)
)
INSERT INTO Orders (RowID, OrderID)
SELECT
MAX(O.RowID) + 1,
E.Order_ID
FROM
[DatabaseName].[dbo].[ExternalOrders] E
LEFT JOIN
Orders O ON O.SaleDate = E.Sale_Date
The error I get is:
Msg 8120, Level 16, State 1, Line 9
Column 'DatabaseName.dbo.ExternalOrders.Order_ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I also tried using the following as RowID:
DECLARE #NewRowID INT = (SELECT MAX(RowID) FROM Orders) + 1
but this makes all records I insert have the same new RowID.
Important: there are already records in the Orders table so I want to resume RowID numbering from the last one present.
You should likely be using an IDENTITY column for your auto incrementing ID. You do not provide a value for these columns when inserting into the table.
DECLARE #Orders TABLE (RowID INT IDENTITY, OrderDate DATETIME, CustomerID INT)
INSERT INTO #Orders (OrderDate, CustomerID) VALUES
(GETUTCDATE(), 1), (DATEADD(HOUR,-1,GETUTCDATE()), 2)
SELECT *
FROM #Orders
https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property?view=sql-server-ver16

How to update rows in a table based on a range from two rows in another table

I'm working on researching this myself but I wanted to get some input from the community in the meantime. I have a SQL Server 2012 database with a couple of tables like so:
DISCOUNT
Id Quantity DiscountAmount
--------------------------------
1 500 6
2 1000 8
3 1500 10
I have another table called Reviews with thousands of entries in it. The schema of that table is not important.
What I want to do is loop through the entries in the Discounts table and apply an UPDATE statement to all of the records in the Reviews table where the row number of that review falls between the current Discount row's quantity and the next quantity - 1.
So in pseudo-code:
For each row in the discount table order by quantity asc
BEGIN
UPDATE Review SET Discount=DiscountAmount WHERE [ROW NUMBER]
BETWEEN Quantity[CURRENT_ROW] AND Quantity[NEXT_ROW]-1
END
Effectively this sets the discount amount for the first 499 rows to 0 (which is the default for Discount), then for 500-999 they get a discount of 6, for 1000-1499 they get a discount of 8 and for 1500+ they get a discount of 10.
Using simple MAX:
SqlFiddleDemo
/* Preparing data */
CREATE TABLE Review(id INT IDENTITY(1,1), val INT, discount INT NULL);
INSERT INTO Review(val)
VALUES (12), (400), (600), (1100), (1550);
CREATE TABLE Discount(Id INT IDENTITY(1,1), Quantity INT, DiscountAmount INT);
INSERT INTO Discount(Quantity, DiscountAmount)
VALUES (500, 6), (1000, 8), (1500, 10);
/* Main */
UPDATE rev
SET Discount = (SELECT ISNULL(MAX(d.DiscountAmount), 0)
FROM Discount d
WHERE rev.val >= d.Quantity)
FROM Review rev;
If you have only 4 tiers for discount you can simply write an update statement with a case expression in it.
UPDATE Review
SET Discount = CASE
WHEN Quantity < 500 THEN 0
WHEN Quantity >= 500 AND Quantity <= 999 THEN 6
WHEN Quantity >= 1000 AND Quantity <= 1499 THEN 8
WHEN Quantity >= 1500 THEN 10
END

Select without - values from table SQL

I'm taking items [I1] from this SQL Server table and returning to this table as - QTY and - TOTAL ..
http://i.stack.imgur.com/bdnPF.png
After that I have another row with the same Item ID and - QTY AND -Total
http://i.stack.imgur.com/4vUNP.png
I need to filter every time without returned product,
In this case I already returned the I1, when I'm going to return sale invoice .. I need to select the none returned products,
SELECT
DEL_PurchasesLines.ItemIdentityCode,
SUM(DEL_PurchasesLines.Qty) As Qty,
SUM(DEL_PurchasesLines.Total) As Total
FROM DEL_PurchasesLines
WHERE InvoiceNo = '1' AND DealerCode = 'S0002M'
GROUP BY
DEL_PurchasesLines.ItemIdentityCode
HAVING SUM(DEL_PurchasesLines.Total) > 0 AND SUM(DEL_PurchasesLines.Qty) > 0
I always like to create some test data in tempdb.
--
-- Create sample data
--
use tempdb;
go
if object_id('items') > 0
drop table items
go
create table items
(
id varchar(8),
qty int,
total int
);
go
insert into items values
('I1', 2, 4),
('I2', 3, 6),
('I3', 5, 10),
('I1', -2, -4);
go
select * from items;
go
One way to solve this problem is to group by the id, summing both the qty and total columns. Display only rows that have > 0.
--
-- Show lines in which qty * total > 0
--
select id, sum(qty) as sum_qty, sum(total) as sum_total
from items
group by id
having sum(qty) > 0 and sum(total) > 0;
Another way to think of this is to show all orders that do not have returns.
--
-- Show rows that do not have any returns
--
select *
from items i left join
(
select id
from items
where qty < 0 or total < 0
) r on i.id = r.id
where r.id is null

Postgres: GROUP BY several column

I have two table in this example.
( example column name )
First is the product
product_id | product_text
Second table is Price.
price_productid | price_datestart | price_price
Let's just say I have multiple datestart with the same product. How can I get the actual price ?
If I use GROUP BY in Postgres, with all the selected column, 2 row may come for the same product. Because the column price_datestart is different.
Example :
product_id : 1
product_text : "Apple Iphone"
price_productid : 1
price_datestart :"2013-10-01"
price_price :"99"
price_productid : 1
price_datestart :"2013-12-01"
price_price :"75"
If I try this :
SELECT price_productid,price_datestart,price_price,product_text,product_id
WHERE price_datestart > now()
GROUP BY price_productid,price_datestart,price_price,product_text,product_id
ORDER BY price_datestart ASC
It will give me a result, but two rows and I need one.
Use distinct on syntax. If you want current price:
select distinct on (p.productid)
p.productid, pr.product_text, p.price, p.datestart
from Price as p
left outer join Product as pr on pr.productid = p.productid
where p.datestart <= now()
order by p.productid, p.datestart desc
sql fiddle demo
You have a few problems, but GROUP BY is not one of them.
First, although you have a datestart you don't have a dateend. I'd change datestart to be a daterange, for example:
CREATE TABLE product
(
product_id int
,product_text text
);
CREATE TABLE price
(
price_productid int
,price_daterange TSRANGE
,price_price NUMERIC(10,2)
);
The TSRANGE allows you to set up validity of your price over a given range, for example:
INSERT INTO product VALUES(1, 'phone');
INSERT INTO price VALUES(1, '[2013-08-01 00:00:00,2013-10-01 00:00:00)', 199);
INSERT INTO price VALUES(1, '[2013-10-01 00:00:00,2013-12-01 00:00:00)', 99);
INSERT INTO price VALUES(1, '[2013-12-01 00:00:00,)', 75);
And that makes your SELECT much more simple, for example:
SELECT price_productid,price_daterange,price_price,product_text,product_id
FROM product, price
WHERE price_daterange #> now()::timestamp
AND product_id = price_productid
This also has the benefit of allowing you to query for any arbitrary time by swapping out now() for another date.
You should read up on ranges in PostgresQL as they are very powerful. The example above is not complete in that it should also have indices on price_daterange to ensure that you do not have overlaps for any product.
SQL fiddle with above solution

Update query with 'not exists' check causes primary key violation

The following tables are involved:
Table Product:
product_id
merged_product_id
product_name
Table Company_Product:
product_id
company_id
(Company_Product has a primary key on both the product_id and company_id columns)
I now want to run an update on Company_Product to set the product_id column to a merged_ product_id. This update could cause duplicates which would trigger a primary key violation, so therefore I added a 'not exists' check in the where clause and my query looks like this:
update cp
set cp.product_id = p.merged_product_id
from Company_Product cp
join Product p on p.product_id = cp.product_id
where p.merged_product_id is not null
and not exists
(select * from Company_Product cp2
where cp2.company_id = cp.company_id and
cp2.product_id = p.merged_product_id)
But this query fails with a primary key violation.
What I think might happen is that because the Product table contains multiple rows with the same merged_product_id, it will succeed the for the first product, but when going to the next product with the same merged_product_id, it'll fail because the 'not exists' subquery does not see the first change, as the query has not finished and committed yet.
Am I right in thinking this, and how would I change the query to make it work?
[EDIT] Some data examples:
Product:
product_id merged_product_id
23 35
24 35
25 12
26 35
27 NULL
Company_Product:
product_id company_id
23 2
24 2
25 2
26 3
27 4
[EDIT 2] Eventually I went with this solution, which uses a temporary table to to the update on and then inserts the updated data into the original Company_Product table:
create table #Company_Product
(product_id int, company_id int)
insert #Company_Product select * from Company_Product
update cp
set cp.product_id = p.merged_product_id
from #Company_Product cp
join Product p on p.product_id = cp.product_id
where p.merged_product_id is not null
delete from Company_Product
insert Company_Product select distinct * from #Company_Product
drop table #Company_Product
A primary key is supposed to be three things:
Non-null
Unique
Unchanging
By altering part of the primary key you're violating requirement #3.
I think you'd be better off creating a new table, populating it, then drop the constraints, drop the original table, and rename the new table to the desired name (then of course, re-apply the original constraints). In my experience this gives you the chance to check out the 'new' data before making it 'live'.
Share and enjoy.
You can use MERGE if you are on SQL 2008 at least.
Otherwise you're going to have to choose a criteria to establish which merged_product_id you want in and which one you leave out:
update cp
set cp.product_id = p.merged_product_id
from Company_Product cp
cross apply (
select top(1) merged_product_id
from Product
where product_id = cp.product_id
and p.merged_product_id is not null
and not exists (
select * from Company_Product cp2
where cp2.company_id = cp.company_id and
cp2.product_id = merged_product_id)
order by <insert diferentiating criteria here>) as p
Note that this is not safe if multiple concurrent requests are running the merge logic.
I can't quite see how your structure is meant to work or what this update is trying to achieve. You seem to be updating Company_Product and setting a (new) product_id on an existing row that apparently has a different product_id; e.g., changing the row from one product to another. This seems...an odd use case, I'd expect you to be inserting a new unique row. So I think I'm missing something.
If you're converting Company_Product to using a new set of product IDs instead of an old set (the name "merged_product_id" makes me speculate this), are you sure that there is no overlap between the old and new? That would cause a problem like what you're describing.
Without seeing your data, I believe your analysis is correct - the entire set is updated and then the commit fails since it results in a constraint violation. An EXISTS is never re-evaluated after "partial commit" of some of the UPDATE.
I think you need to more precisely define your rules regarding attempting to change multiple products to the same product according to the merged_product_id and then make those explicit in your query. For instance, you could exclude any products which would fall into that category with a further NOT EXISTS with appropriate query.
I think you are correct on why the update is failing. To fix this, run a delete query on your company_product table to remove the extra product_ids where the same merged_prduct_id will be applied.
here is a stab at what the query might be
delete company_product
where product_id not in (
select min(product_id)
from product
group by merged_product_id
)
and product_id not in (
select product_id
from product
where merged_product_id is null
)
-- Explanation added in resonse to comment --
What this tries to do is to delete rows that will be duplicates after the update. Since you have products with multiple merged ids, you really only need one of those products (for each company) in the table when you are done. So, my query (if it works...) will keep the min original product id for each merged product id - then your update will work.
So, let's say you have 3 product ids which will map to 2 merged ids: 1 -> 10, 2 -> 20, 3 -> 20. And you have the following company_product data:
product_id company_id
1 A
2 A
3 A
If you run your update against this, it will try to change both the second and third rows to product id 20, and it will fail. If you run the delete I suggest, it will remove the third row. After the delete and the update, the table will look like this:
product_id company_id
10 A
20 A
Try this:
create table #Company_Product
(product_id int, company_id int)
create table #Product (product_id int,merged_product_id int)
insert into #Company_Product
select 23, 2
union all select 24, 2
union all select 25, 2
union all select 26, 3
union all select 27, 4
insert into #product
Select 23, 35
union all select 24, 35
union all select 25, 12
union all select 26, 35
union all select 27, NULL
update cp
set product_id = merged_product_id
from #company_product cp
join
(
select min(product_id) as product_id, merged_product_id
from #product where merged_product_id is not null
group by merged_product_id
) a on a.product_id = cp.product_id
delete cp
--select *
from #company_product cp
join #product p on cp.product_id = p.product_id
where cp.product_id <> p.merged_product_id
and p.merged_product_id is not null