Related
I have 2 tables, table A and Table B, and I want to check if Table B matches quantity of items that exists on Table A, so, for example:
Table A
S_O
ITEM
QTY
1
ITA
1
3
ITB
2
4
ITC
3
6
ITD
0
Table B
S_O
ITEM
QTY
1
ITA
1
3
ITB
2
4
ITC
3
6
ITD
5
7
ITE
2
8
ITF
1
My first thought was to use an except between the two tables, but then I was asked to check if the quantity was OK or if it was shortage to generate a preview like:
Result from comparing the two tables
S_O
ITEM
STATUS
1
ITA
OK
3
ITB
OK
4
ITC
OK
6
ITD
SHORTAGE
And it needs to ignore items "ITE" and "ITF" because they don't exist in Table A
I'm pretty new with sql server queries, I think I could use a SELECT CASE but I don't know how to do it, I'd appreciate some help in this matter
In those tables my unique identifier is S_O, so it would need to match the S_O, item and quantity for both tables
Here's how you can "pre summarise" table a and b to make item unique, then join:
select
A.item,
A.qty as qtya,
B.qty as qtyb,
A.qty - B.qty as shortageamt,
case
when A.qty = B.qty then 'OK'
else 'Shortage'
end as status
from
(
select item, sum(qty) as qty
from tablea
group by item
) as A
inner join
(
select item, sum(qty) as qty
from tableb
group by item
) as B
on A.item = B.item
You'll only get an item listed in the result if it's in both tables - is that what you want? if ITG is in tablea but not tableb, do you want to see it (with qty 0)? That requires an outer join.
I think you should go with a LEFT JOIN. Also, the first answer does not JOIN on S_O, which you state in your question you need.
"In those tables my unique identifier is S_O, so it would need to match the S_O, item and quantity for both tables"
DECLARE #ta TABLE (S_O INT, ITEM VARCHAR(20), QTY INT)
INSERT INTO #ta
VALUES
(1, 'ITA', 1),
(3, 'ITB', 2),
(4, 'ITC', 3),
(6, 'ITD', 0)
DECLARE #tb TABLE (S_O INT, ITEM VARCHAR(20), QTY INT)
INSERT INTO #tb
VALUES
(1, 'ITA', 1),
(3, 'ITB', 2),
(4, 'ITC', 3),
(6, 'ITD', 5),
(7, 'ITE', 2),
(8, 'ITF', 1);
SELECT ta.S_O,
ta.ITEM,
CASE WHEN ta.ITEM = tb.ITEM AND SUM(ta.QTY) >= SUM(tb.QTY) THEN 'OK' ELSE 'SHORTAGE' END AS 'STATUS'
FROM #ta ta
LEFT JOIN #tb tb ON ta.S_O = tb.S_O
GROUP BY ta.S_O, ta.ITEM, tb.ITEM
S_O
ITEM
STATUS
1
ITA
OK
3
ITB
OK
4
ITC
OK
6
ITD
SHORTAGE
i have a bq table i record product information. each row has a nested column called categories and each product might have more than one category. how can i select products that are not in the category i specified
i tried following code but i still get all the product in the table, i think because each products have other categories than 'Unwanted Category'. how should i rewrite my select ?
SELECT * FROM xx.products,
unnest(categories) as categories
WHERE
categories.name not in ('Unwanted Category')
LIMIT 1000
Below is for BigQuery Standard SQL
#standardSQL
WITH `project.dataset.products` AS (
SELECT 1 product_id, ['Unwanted Category', 'category A'] categories UNION ALL
SELECT 2, ['category A', 'category B'] UNION ALL
SELECT 3, ['category C']
)
SELECT *
FROM `project.dataset.products`
WHERE NOT 'Unwanted Category' IN UNNEST(categories)
with result
Row product_id categories
1 2 category A
category B
2 3 category C
I just realized, you might have slightly different schema, thus modified approach - as in below example
#standardSQL
WITH `project.dataset.products` AS (
SELECT 1 product_id, [STRUCT<id INT64, name STRING>(1, 'Unwanted Category'), (2, 'category A')] categories UNION ALL
SELECT 2, [STRUCT<id INT64, name STRING>(2, 'category A'), (3, 'category B')] UNION ALL
SELECT 3, [STRUCT<id INT64, name STRING>(4, 'category C')]
)
SELECT *
FROM `project.dataset.products`
WHERE NOT 'Unwanted Category' IN (SELECT name FROM UNNEST(categories))
with output
Row product_id categories.id categories.name
1 2 2 category A
3 category B
2 3 4 category C
I am using SQL Server 2008 and I have a SALES_ORDER table.
Each row of the table is one material. Many rows (materials) can belong to the same ORDER. I want to select the materials of many orders under two conditions:
All materials under the same order will be selected (orders will not be split).
The total number of selected rows will not exceed a maximum predefined number ROW_MAX.
For example, let's say we have
4 materials under order_1
3 materials under order_2
5 materials under order_3
2 materials under order_4
and ROW_MAX = 7
the query must return
all materials of the orders order_1 and order_2 (total 7 materials)
OR
all materials of orders order_1 and order_4 (total 6 materials)
OR
any other combination of orders so the total number or selected rows does not exceed 7.
Any idea how to do it with SQL script?
I believe it's a bit more tricky than that. I guess the following code answers the requisite:
any other combination of orders so the total number or selected rows does not exceed 7
There's little work to do before you can cross apply.
First we create a data sample in which order1 + order2 is bigger than 7 (so that we can test and doesn't stops looking after order2):
-- A sample table in which order1 + order 2 count is bigger than 7
IF OBJECT_ID('tempdb..#sample') is not null
DROP TABLE #sample
CREATE TABLE #sample (
OrderID INT,
OrderDesc VARCHAR(50)
)
INSERT INTO #sample VALUES
(1, 'Order 1 customer 1'),
(1, 'Order 2 customer 1'),
(1, 'Order 3 customer 1'),
(1, 'Order 4 customer 1'),
(2, 'Order 1 customer 2'),
(2, 'Order 2 customer 2'),
(2, 'Order 3 customer 2'),
(2, 'Order 4 customer 2'),
(2, 'Order 5 customer 2'),
(3, 'Order 1 customer 3'),
(3, 'Order 2 customer 3'),
(4, 'Order 1 customer 4')
Then we count the number of row for each order:
-- Counting rows for each order
IF OBJECT_ID('tempdb..#samplecount') is not null
DROP TABLE #samplecount
SELECT OrderID, COUNT(*) as OrderCount
INTO #samplecount
FROM #sample
GROUP BY OrderID
We order the orders according that count:
--Numbering each row
IF OBJECT_ID('tempdb..#samplecountordered') is not null
DROP TABLE #samplecountordered
SELECT *, ROW_NUMBER() OVER(ORDER BY OrderCount) AS OrderNumber
INTO #samplecountordered
from #samplecount
After that we can use cross apply on that organised repository:
select sa.*
from #samplecountordered so cross apply
(select SUM(OrderCount) as running_count
from #samplecountordered so2
where so2.OrderNumber <= so.OrderNumber
) so2
INNER JOIN #sample sa
ON sa.OrderID = so.orderID
where running_count <= 7;
This solution requires further testing, but I guess it is the spirit.
I use temporary tables so that it is easier to follow in terms of construction.
Sorry, I used customers instead of material.
You are looking for a cumulative sum. In SQL Server 2008, you can do this with apply:
select so.*
from sales_order so cross apply
(select count(*) as running_count
from sales_order so2
where so2.order_id <= so.order_id
) so2
where running_count <= 7;
It's possible to create a unique index across tables, basically using a view and a unique index.
I have a problem though.
Given two (or three) tables.
Company
- Id
- Name
Brand
- Id
- CompanyId
- Name
- Code
Product
- Id
- BrandId
- Name
- Code
I want to ensure uniqueness that the combination of:
Company / Brand.Code
and
Company / Brand.Product/Code
are unique.
CREATE VIEW TestView
WITH SCHEMABINDING
AS
SELECT b.CompanyId, b.Code
FROM dbo.Brand b
UNION ALL
SELECT b.CompanyId, p.Code
FROM dbo.Product p
INNER JOIN dbo.Brand b ON p.BrandId = b.BrandId
The creation of the view is successful.
CREATE UNIQUE CLUSTERED INDEX UIX_UniquePrefixCode
ON TestView(CompanyId, Code)
This fails because of the UNION
How can I solve this scenario?
Basically Code for both Brand/Product cannot be duplicated within a company.
Notes:
Error that I get is:
Msg 10116, Level 16, State 1, Line 3 Cannot create index on view
'XXXX.dbo.TestView' because it contains one or more UNION, INTERSECT,
or EXCEPT operators. Consider creating a separate indexed view for
each query that is an input to the UNION, INTERSECT, or EXCEPT
operators of the original view.
Notes 2:
When I'm using the sub query I get the following error:
Msg 10109, Level 16, State 1, Line 3 Cannot create index on view
"XXXX.dbo.TestView" because it references derived table "a"
(defined by SELECT statement in FROM clause). Consider removing the
reference to the derived table or not indexing the view.
**Notes 3: **
So given the Brands:
From #spaghettidba's answer.
INSERT INTO Brand
(
Id,
CompanyId,
Name,
Code
)
VALUES
(1, 1, 'Brand 1', 100 ),
(2, 2, 'Brand 2', 200 ),
(3, 3, 'Brand 3', 300 ),
(4, 1, 'Brand 4', 400 ),
(5, 3, 'Brand 5', 500 )
INSERT INTO Product
(
Id,
BrandId,
Name,
Code
)
VALUES
(1001, 1, 'Product 1001', 1 ),
(1002, 1, 'Product 1002', 2 ),
(1003, 3, 'Product 1003', 3 ),
(1004, 3, 'Product 1004', 301 ),
(1005, 4, 'Product 1005', 5 )
The expectation is, the Brand Code + Company or Product Code + Company is unique, if we expand the results out.
Company / Brand|Product Code
1 / 100 <-- Brand
1 / 400 <-- Brand
1 / 1 <-- Product
1 / 2 <-- Product
1 / 5 <-- Product
2 / 200 <-- Brand
3 / 300 <-- Brand
3 / 500 <-- Brand
3 / 3 <-- Product
3 / 301 <-- Brand
There's no duplicates. If we have a brand and product with the same code.
INSERT INTO Brand
(
Id,
CompanyId,
Name,
Code
)
VALUES
(6, 1, 'Brand 6', 999)
INSERT INTO Product
(
Id,
BrandId,
Name,
Code
)
VALUES
(1006, 2, 'Product 1006', 999)
The product belongs to a different Company, so we get
Company / Brand|Product Code
1 / 999 <-- Brand
2 / 999 <-- Product
This is unique.
But if you have 2 brands, and 1 product.
INSERT INTO Brand
(
Id,
CompanyId,
Name,
Code
)
VALUES
(7, 1, 'Brand 7', 777)
(8, 1, 'Brand 8', 888)
INSERT INTO Product
(
Id,
BrandId,
Name,
Code
)
VALUES
(1007, 8, 'Product 1008', 777)
This would produce
Company / Brand|Product Code
1 / 777 <-- Brand
1 / 888 <-- Brand
1 / 777 <-- Product
This would not be allowed.
Hope that makes sense.
Notes 4:
#spaghettidba's answer solved the cross-table problem, the 2nd issue was duplicates in the Brand table itself.
I've managed to solve this by creating a separate index on the brand table:
CREATE UNIQUE NONCLUSTERED INDEX UIX_UniquePrefixCode23
ON Brand(CompanyId, Code)
WHERE Code IS NOT NULL;
I blogged about a similar solution back in 2011. You can find the post here:
http://spaghettidba.com/2011/08/03/enforcing-complex-constraints-with-indexed-views/
Basically, you have to create a table that contains exactly two rows and you will use that table in CROSS JOIN to duplicate the rows that violate your business rules.
In your case, the indexed view is a bit harder to code because of the way you expressed the business rule. In fact, checking uniqueness on the UNIONed tables through an indexed view is not permitted, as you already have seen.
However, the constraint can be expressed in a different way: since the companyId is implied by the brand, you can avoid the UNION and simply use a JOIN between product and brand and check uniqueness by adding the JOIN predicate on the code itself.
You didn't provide some sample data, I hope you won't mind if I'll do it for you:
CREATE TABLE Company (
Id int PRIMARY KEY,
Name varchar(50)
)
CREATE TABLE Brand (
Id int PRIMARY KEY,
CompanyId int,
Name varchar(50),
Code int
)
CREATE TABLE Product (
Id int PRIMARY KEY,
BrandId int,
Name varchar(50),
Code int
)
GO
INSERT INTO Brand
(
Id,
CompanyId,
Name,
Code
)
VALUES (1, 1, 'Brand 1', 100 ),
(2, 2, 'Brand 2', 200 ),
(3, 3, 'Brand 3', 300 ),
(4, 1, 'Brand 4', 400 ),
(5, 3, 'Brand 5', 500 )
INSERT INTO Product
(
Id,
BrandId,
Name,
Code
)
VALUES
(1001, 1, 'Product 1001', 1 ),
(1002, 1, 'Product 1002', 2 ),
(1003, 3, 'Product 1003', 3 ),
(1004, 3, 'Product 1004', 301 ),
(1005, 4, 'Product 1005', 5 )
As far as I can tell, no rows violating the business rules are present yet.
Now we need the indexed view and the two rows table:
CREATE TABLE tworows (
n int
)
INSERT INTO tworows values (1),(2)
GO
And here's the indexed view:
CREATE VIEW TestView
WITH SCHEMABINDING
AS
SELECT 1 AS one
FROM dbo.Brand b
INNER JOIN dbo.Product p
ON p.BrandId = b.Id
AND p.code = b.code
CROSS JOIN dbo.tworows AS t
GO
CREATE UNIQUE CLUSTERED INDEX IX_TestView ON dbo.TestView(one)
This update should break the business rules:
UPDATE product SET code = 300 WHERE code = 301
In fact you get an error:
Msg 2601, Level 14, State 1, Line 1
Cannot insert duplicate key row in object 'dbo.TestView' with unique index 'IX_TestView'. The duplicate key value is (1).
The statement has been terminated.
Hope this helps.
I am having trouble writing a script which can delete all the rows which match on the first three columns and where the Quantities sum to zero?
I think the query needs to find all Products that match and then within that group, all the Names which match and then within that subset, all the currencies which match and then, the ones which have quantities netting to zero.
In the below example, the rows which would be deleted would be rows 1&2,4&6.
Product, Name, Currency, Quantity
1) Product A, Name A, GBP, 10
2) Product A, Name A, GBP, -10
3) Product A, Name B, GBP, 10
4) Product A, Name B, USD, 10
5) Product A, Name B, EUR, 10
6) Product A, Name B, USD, -10
7) Product A, Name C, EUR, 10
Hope this makes sense and appreciate any help.
Try this:
DELETE
FROM [Product]
WHERE Id IN
(
SELECT Id
FROM
(
SELECT Id, SUM(Quantity) OVER(PARTITION BY a.Product, a.Name, a.Currency) AS Sm
FROM [Product] a
) a
WHERE Sm = 0
)
You may want to break this problem into parts.
First create a view that lists those combinations which sum to zero
CREATE VIEW vw_foo AS
SELECT product,name, currency, sum(quantity) as net
FROM foo
GROUP BY product, name, currency
HAVING sum(quantity)=0;
At this point, you need to make sure this view has the data you expect to delete. In you example, the view should have only 2 records: ProductA/NameA/GBP and ProductA/NameB/USD
Step 2. Delete the data where the fields match:
DELETE FROM foo
WHERE EXISTS
(SELECT *
FROM vw_foo
WHERE vw_foo.product = product
AND vw_foo.name = name
AND vw_currency = currency);
One way to simplify the SQL is to just concatente the 3 columns into one and apply some grouping:
delete from product
where product + name + currency in (
select product + name + currency
from product
group by product + name + currency
having sum(quantity) = 0)
I am assuming this is a accounting problem with offsetting pairs of entries in the ledger.
If there are for instance three entries for combination (A, A, GBP) this code and some of the example above will not work.
I create a temporary test table, loaded it with your data, used a CTE - common table expression - to find the duplicate pattern and joined it to the table to select the rows.
Just change the 'select *' to 'delete'.
Again, this only works for equal offsetting pairs. It will cause havoc with odd number of entries.
Do you have only even number of entries?
Sincerely
John
-- create sample table
create table #products
(
product_id int identity(1,1),
product_txt varchar(16),
name_txt varchar(16),
currency_cd varchar(16),
quantity_num int
);
go
-- add data 2 table
insert into #products
(product_txt, name_txt, currency_cd, quantity_num)
values
('A', 'A', 'GBP', 10),
('A', 'A', 'GBP', -10),
('A', 'B', 'GBP', 10),
('A', 'B', 'USD', 10),
('A', 'B', 'EUR', 10),
('A', 'B', 'USD', -10),
('A', 'C', 'EUR', 10);
go
-- show the data
select * from #products;
go
-- use cte to find combinations
with cte_Ledger_Offsets (product_txt, name_txt, currency_cd)
as
(
select product_txt, name_txt, currency_cd
from #products
group by product_txt, name_txt, currency_cd
having sum(quantity_num) = 0
)
select * from #products p inner join cte_Ledger_Offsets c
on p.product_txt = c.product_txt and
p.name_txt = c.name_txt and
p.currency_cd = c.currency_cd;