How to create a User Defined Function for SQL server - sql

I have a fairly large stored procedure that I'm trying to simplify for readability.
It contains many union clauses with statements like the one shown below:
Fig.1
SELECT COUNT(1) AS Total
FROM Orders
WHERE (NOT EXISTS (
SELECT 1
FROM (
SELECT Id
FROM OrderLineItems
WHERE Orders.Id = Order_Id) AS Sub
WHERE EXISTS (
SELECT 1
FROM NormalizedLineItems
WHERE (Sub.Id = OrderLineItem_Id)
AND (OutOfStock = 1))))
AND (EXISTS (
SELECT 1 AS Total
FROM OrderShipments
WHERE (Orders.Id = Order_Id)
AND (CarrierApproved = 0)))
AND (IsQuote = 0)
AND (Cancelled = 0)
AND (Archived = 0)
AND (Completed = 0)
AND (Holding = 0)
However there are many reoccurring patterns in each statement.
The following pattern turns up several times Fig.2
Fig.2
WHERE (NOT EXISTS (
SELECT 1
FROM (
SELECT Id
FROM OrderLineItems
WHERE Orders.Id = Order_Id) AS Sub
WHERE EXISTS (
SELECT 1
FROM NormalizedLineItems
WHERE (Sub.Id = OrderLineItem_Id)
AND (OutOfStock = 1))))
I'm trying to (for readability purposes) reduce the code in the master stored procedure
So i thought id performance test a UDF, I've come up with the following Fig.3
Fig.3
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION TestFunction (#OrderId int)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
SELECT 1 AS Total
FROM (
SELECT OrderLineItems.Id AS Id
FROM OrderLineItems
WHERE #OrderId = Order_Id) AS Sub
WHERE EXISTS (
SELECT 1 AS Total
FROM NormalizedLineItems
WHERE (Sub.Id = OrderLineItem_Id)
AND (OutOfStock = 1)))
GO
All though the above compiles, I'm not really sure I'm on the right track, I'm having all sorts of problems trying to apply the above UDF to the original query.
I am seeking a concrete example of how to abstract Fig.2 from Fig.1 into a UDF so I can at least performance-test the solution to see if it's worthwhile.
Note: I do know user defined functions can be a performance nightmare, however I'm not even at a stage where I can test.

Create an order_ids table with only one column named order_id
Insert into order_ids
select order_id from
FROM Orders
WHERE (NOT EXISTS (
SELECT 1
FROM (
SELECT Id
FROM OrderLineItems
WHERE Orders.Id = Order_Id) AS Sub
WHERE EXISTS (
SELECT 1
FROM NormalizedLineItems
WHERE (Sub.Id = OrderLineItem_Id)
AND (OutOfStock = 1))))
Then you can simplify your Sql like this:
SELECT COUNT(1) AS Total
FROM Orders
join order_ids
on order_ids.order_id = Orders.order_id
...
If your reoccurring statement only in one query,
Common Table Expression is the best choice:
with CTE_order_ids as
(select order_id from
FROM Orders
WHERE (NOT EXISTS (
SELECT 1
FROM (
SELECT Id
FROM OrderLineItems
WHERE Orders.Id = Order_Id) AS Sub
WHERE EXISTS (
SELECT 1
FROM NormalizedLineItems
WHERE (Sub.Id = OrderLineItem_Id)
AND (OutOfStock = 1))))
)
SELECT COUNT(1) AS Total
FROM Orders
join CTE_order_ids
on order_ids.order_id = Orders.order_id
...

Related

How do I return the count for both matching column in a single return

I'm trying to get a number of count for exist and non-exist for my validation to insert my data. As refer to the image below, I'm inserting 3 data in which 1 data consist of same ID number as destination table, 1 data consist of same quotation number as destination table and the last data is a new entry.
Okay here's my requirement. I am sending my source table data to insert into the dest table. So before inserting, I want to do validation by mapping the entire dest table like this:
SELECT COUNT(*) FROM sourceTable WHERE exists(
SELECT * FROM sourceTable WHERE QuotationId IN
(SELECT A.QuotationId FROM sourceTable A
JOIN DestTable B ON A.QuotationId = B.QuotationId
JOIN DestTable C ON A.QuotationNum = C.QuotationNum)
Without further details on table structure and so on it's difficult to tell, but something like the following might do the trick:
WITH cteMatch AS(
SELECT s.QuotationID AS src, d.QuotationID as dst
FROM sourceTable s
LEFT JOIN destTable d
)
SELECT CASE WHEN dst IS NULL THEN N'NonExist' ELSE N'Exist' END AS ValExist, COUNT(*) cnt
FROM cteMatch
GROUP BY CASE WHEN dst IS NULL THEN N'NonExist' ELSE N'Exist' END
You seem to want:
select count(*)
from sourcetable s
where exists(
select 1
from desttable d
where d.quotationid = s.quotationid or d.quotationnum = s.quotationnum
)
This counts how many rows in the source table have a quotation id or num that exists in the target table. If you want the count of both existing and non-existing rows, I would recommend:
select sum(flag) as cnt_exists, sum(1 - flag) as cnt_not_exists
from (
select
case when exists (
select 1
from desttable d
where d.quotationid = s.quotationid or d.quotationnum = s.quotationnum
) then 1 else 0 end as flag
from sourcetable s
) t

Need help creating SQL query from example of data

I have a database table below.
And I want to get list of all DBKey that have: at least one entry with Staled=1, and the last entry is Staled=0
The list should not contain DBKey that has only Staled=0 OR Staled=1.
In this example, the list would be: DBKey=2 and DBKey=3
I think this should do the trick:
SELECT DISTINCT T.DBKey
FROM TABLE T
WHERE
-- checks that the DBKey has at least one entry with Staled = 1
EXISTS (
SELECT DISTINCT Staled
FROM TABLE
WHERE DBKey = T.DBKey
AND Staled = 1
)
-- checks that the last Staled entry for this DBKey is 0
AND EXISTS (
SELECT DISTINCT Staled
FROM TABLE
WHERE DBKey = T.DBKey
AND Staled = 0
AND EntryDateTime = (
SELECT MAX(EntryDateTime)
FROM TABLE
WHERE DBKey = T.DBKey
)
)
Here is a working SQLFiddle of the query, using your sample data.
The idea is to use EXISTS to look for those individual conditions that you've described. I've added comments to my code to explain what each does.
Should be done with a simple JOIN... Starting FIRST with any 1 qualifiers, joined to itself by same key AND 0 staled qualifier AND the 0 record has a higher date. Ensure you have an index on ( DBKey, Staled, EntryDateTime )
SELECT
YT.DBKey,
MAX( YT.EntryDateTime ) as MaxStaled1,
MAX( YT2.EntryDateTime ) as MaxStaled0
from
YourTable YT
JOIN YourTable YT2
ON YT.DBKey = YT2.DBKey
AND YT2.Staled = 0
AND YT.EntryDateTime < YT2.EntryDateTime
where
YT.Staled = 1
group by
YT.DBKey
having
MAX( YT.EntryDateTime ) < MAX( YT2.EntryDateTime )
Maybe this:
With X as
(
Select Row_Number() Over (Partition By DBKey Order By EntryDateTime Desc) RN, DBKey, Staled
From table
)
Select *
From X
Where rn = 1 and staled = 0 and
Exists (select 1 from x x2 where x2.dbkey = x.dbkey and Staled = 1)

SQL CASE / WHEN evaluating to two possible values

I have a list of items, Each item will have one of two options A or B. If the item has both options then A will always have a recipe of S (Standard) and B will always have a recipe of O (optional), However if there is ONLY the option B then B will have a recipe of S. I am a novice and can not begin to think on how to write this.
Item__Option__Recipe
1____A______S
1____B______O
2____B______S
3____A______S
3____B______O
4____A______S
5____A______S
6____B______S
So A will always be S and B will be S unless there is an A and then it will be O? If that's the logic then this should work:
SELECT
A.Item,
A.Option,
CASE
WHEN Option = 'A' THEN 'S' // A will always be S
WHEN Option = `B`
CASE
WHEN EXISTS(SELECT null FROM table WHERE Item = A.Item AND Option = 'A')
THEN 'O'
ELSE 'S'
END
END AS Recipe
FROM table
This assumes SQL Server syntax (for the table variable) but otherwise uses fairly standard (for these days) Common Table Expressions and a ROW_NUMBER():
declare #t table (Item int,[Option] char(1))
insert into #t(Item,[Option]) values
(1,'A'),(1,'B'),
(2,'B'),
(3,'A'),(3,'B'),
(4,'A'),
(5,'A'),
(6,'B')
;With Recipes as (
select 1 as rn,'S' as Recipe union all
select 2,'O'
), Ordered as (
select Item,[Option],
ROW_NUMBER() OVER (
PARTITION BY Item
ORDER BY [Option]
) as rn
from #t
)
select
Item,[Option],Recipe
from
Ordered o
inner join
Recipes r
on
o.rn = r.rn
order by Item,[Option]
And produces the result set you've shown.

Delete older from a duplicate select

I have been working on a query to search and delete duplicate column values. Currently I have this query (returns duplicates):
SELECT NUIP, FECHA_REGISTRO
FROM registros_civiles_nacimiento
WHERE NUIP IN (
SELECT NUIP
FROM registros_civiles_nacimiento
GROUP BY NUIP
HAVING (COUNT(NUIP) > 1)
) order by NUIP
This work returning a table like this:
NUIP FECHA_REGISTRO
38120100138 1975-05-30
38120100138 1977-08-31
40051800275 1980-09-24
40051800275 1999-11-29
42110700118 1972-10-26
42110700118 1982-04-22
44030700535 1982-10-19
44030700535 1993-05-05
46072300777 1991-01-17
46072300777 1979-03-30
The thing is that I need to delete the rows with duplicate column values. But I need to delete the row with the oldest date, for example, for the given result, once the needed query is performed, this is the list of result that must be kept:
NUIP FECHA_REGISTRO
38120100138 1977-08-31
40051800275 1999-11-29
42110700118 1982-04-22
44030700535 1993-05-05
46072300777 1991-01-17
How can I do this using plain SQL?
--PULL YOUR SELECT OF RECS WITH DUPES INTO A TEMP TABLE
--(OR CREATE A NEW TABLE SO THAT YOU CAN KEEP THEM AROUND FOR LATER IN CASE)
SELECT NUIP,FECHA_REGISTRO
INTO #NUIP
FROM SO_NUIP
WHERE NUIP IN (
SELECT NUIP
FROM SO_NUIP
GROUP BY NUIP
HAVING (COUNT(NUIP) > 1)
)
--CREATE FLAG FOR DETERMINIG DUPES
ALTER TABLE #NUIP ADD DUPLICATETOREMOVE bit
--USE `RANK()` TO SET FLAG
UPDATE #NUIP
SET DUPLICATETOREMOVE = CASE X.RANK
WHEN 1 THEN 1
ELSE 0
END
--SELECT *
FROM #NUIP A
INNER JOIN (SELECT NUIP,FECHA_REGISTRO,RANK() OVER (PARTITION BY [NUIP] ORDER BY FECHA_REGISTRO ASC) AS RANK
FROM #NUIP) X ON X.NUIP = A.NUIP AND X.FECHA_REGISTRO = A.FECHA_REGISTRO
--HERE IS YOUR DELETE LIST
SELECT *
FROM so_registros_civiles_nacimiento R
JOIN #NUIP N ON N.NUIP = R.NUIP AND N.FECHA_REGISTRO = R.FECHA_REGISTRO
WHERE N.DUPLICATETOREMOVE = 1
--HERE IS YOUR KEEP LIST
SELECT *
FROM so_registros_civiles_nacimiento R
JOIN #NUIP N ON N.NUIP = R.NUIP AND N.FECHA_REGISTRO = R.FECHA_REGISTRO
WHERE N.DUPLICATETOREMOVE = 0
--ZAP THEM AND COMMIT YOUR TRANSACTION, YOU'VE STILL GOT A REC OF THE DELETEDS FOR AS LONG AS THE SCOPE OF YOUR #NUIP
BEGIN TRAN --COMMIT --ROLLBACK
DELETE FROM so_registros_civiles_nacimiento
JOIN #NUIP N ON N.NUIP = R.NUIP AND N.FECHA_REGISTRO = R.FECHA_REGISTRO
WHERE N.DUPLICATETOREMOVE = 1
You can use analytical functions for this:
;WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY NUIP ORDER BY FECHA_REGISTRO DESC) RN
FROM registros_civiles_nacimiento
)
DELETE FROM CTE
WHERE RN > 1;
Use RANK() to create the result set ordered by date
Use WHERE EXISTS to delete from the source.
(Note: if you run the rank function over your duplicates, you should get your results. I've just referred to the whole table below)
This statement works in Oracle (replace the select * with delete if it works for you:
SELECT *
FROM registros_civiles_nacimiento ALL_
WHERE EXISTS
(SELECT * FROM
(SELECT * FROM
(SELECT NUIP,
FECHA_REGISTRO,
RANK() OVER (PARTITION BY NUIP ORDER BY FECHA_REGISTRO) AS ORDER_
FROM registros_civiles_nacimiento)
WHERE ORDER_ = 1) OLDEST
WHERE ALL_.NUIP = OLDEST.NUIP
AND ALL_.FECHA_REGISTRO = OLDEST.FECHA_REGISTRO);

SQL: Return "true" if list of records exists?

An alternative title might be:
Check for existence of multiple rows?
Using a combination of SQL and C# I want a method to return true if all products in a list exist in a table. If it can be done all in SQL that would be preferable. I have written a method that returns whether a single productID exists using the following SQL:
SELECT productID FROM Products WHERE ProductID = #productID
If this returns a row, then the c# method returns true, false otherwise.
Now I'm wondering if I have a list of product IDs (not a huge list mind you, normally under 20). How can I write a query that will return a row if all the product id's exist and no row if one or more product id's does not exist?
(Maybe something involving "IN" like:
SELECT * FROM Products WHERE ProductID IN ('1', '10', '100', 'ABC'))
EDIT:
How the result is expressed is not important to me. Whether the query returns a 1 or 0, an empty resultset or a non-empty one, true or false doesn't matter. I'd prefer the answer that is 1) easy to read and understand and 2) performant
I was envisioning concatenating the list of product id's with the SQL. Obviously this opens the code up to SQL injection (the product id's are actually varchar. in this case the chance is slim but still want to avoid that possibility). So if there is a way around this that would be better. Using SQL Server 2005.
Product ID's are varchar
Here's how I usually do it:
Just replace your query with this statement SELECT * FROM table WHERE 1
SELECT
CASE WHEN EXISTS
(
SELECT * FROM table WHERE 1
)
THEN 'TRUE'
ELSE 'FALSE'
END
Given your updated question, these are the simplest forms:
If ProductID is unique you want
SELECT COUNT(*) FROM Products WHERE ProductID IN (1, 10, 100)
and then check that result against 3, the number of products you're querying (this last part can be done in SQL, but it may be easier to do it in C# unless you're doing even more in SQL).
If ProductID is not unique it is
SELECT COUNT(DISTINCT ProductID) FROM Products WHERE ProductID IN (1, 10, 100)
When the question was thought to require returning rows when all ProductIds are present and none otherwise:
SELECT ProductId FROM Products WHERE ProductID IN (1, 10, 100) AND ((SELECT COUNT(*) FROM Products WHERE ProductID IN (1, 10, 100))=3)
or
SELECT ProductId FROM Products WHERE ProductID IN (1, 10, 100) AND ((SELECT COUNT(DISTINCT ProductID) FROM Products WHERE ProductID IN (1, 10, 100))=3)
if you actually intend to do something with the results. Otherwise the simple SELECT 1 WHERE (SELECT ...)=3 will do as other answers have stated or implied.
#Mark Hurd, thanks for pointing out the error.
this will work (if you are using Postgresql, Sql Server 2008):
create table products
(
product_id int not null
);
insert into products values(1),(2),(10),(100);
SELECT
CASE
WHEN EXISTS(
SELECT 1
FROM (values(1),(10),(100)) as x(id)
WHERE x.id NOT IN (select product_id from products))
THEN 0 --'NOT ALL'
ELSE 1 -- 'ALL'
END
If you are using MySQL, make a temporary memory table(then populate 1,10,100 there):
create table product_memory(product_id int) engine=MEMORY;
insert into product_memory values(1),(10),(100);
SELECT
CASE
WHEN EXISTS(
SELECT 1
FROM product_memory
WHERE product_memory.id NOT IN (select product_id from products))
THEN 0 -- 'NOT ALL'
ELSE 1 -- 'ALL'
END
On your C# code:
bool isAllExist = (int)(new SqlCommand(queryHere).ExecuteScalar()) == 1;
[EDIT]
How can I write a query that will
return a row if all the product id's
exist and no row if one or more
product id's does not exist?
Regarding, returning a row(singular) if all rows exists, and no row to be returned if one or more product id does not exists:
MySql:
SELECT 1
WHERE
NOT EXISTS(
SELECT 1
FROM product_memory
WHERE product_memory.id NOT IN (select product_id from products) )
Posgresql, Sql Server 2008:
SELECT 1
WHERE
NOT EXISTS(
SELECT 1
FROM (values(1),(10),(100)) as x(id)
WHERE x.id NOT IN (select product_id from products) )
Then on your C# code:
var da = new SqlDataAdapter(queryhere, connectionhere);
var dt = new DataTable();
da.Fill(dt);
if (dt.Rows.Count > 0)
return true;
else
return false;
Or just make the condition shorter:
return dt.Rows.Count > 0;
Assuming you're using SQL Server, the boolean type doesn't exist, but the bit type does, which can hold only 0 or 1 where 0 represents False, and 1 represents True.
I would go this way:
select 1
from Products
where ProductId IN (1, 10, 100)
Here, a null or no row will be returned (if no row exists).
Or even:
select case when EXISTS (
select 1
from Products
where ProductId IN (1, 10, 100)
) then 1 else 0 end as [ProductExists]
Here, either of the scalar values 1 or 0 will always be returned (if no row exists).
DECLARE #values TABLE (ProductId int)
INSERT #values (1)
INSERT #values (10)
INSERT #values (100)
SELECT CASE WHEN (SELECT COUNT(*) FROM #values v) =
(SELECT COUNT(*) FROM Products p WHERE p.ProductId IN
(SELECT v.ProductId FROM #values v))
THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END [AreAllFound]
I know this is old but I think this will help anyone else who comes looking...
SELECT CAST(COUNT(ProductID) AS bit) AS [EXISTS] FROM Products WHERE(ProductID = #ProductID)
This will ALWAYS return TRUE if exists and FALSE if it doesn't (as opposed to no row).
You can use a SELECT CASE statement like so:
select case when EXISTS (
select 1
from <table>
where <condition>
) then TRUE else FALSE end
It returns TRUE when your query in the parents exists.
For PostgreSQL:
SELECT COUNT(*) = 1 FROM (
SELECT 1 FROM $table WHERE $condition LIMIT 1
) AS t
// not familiar with C#, but C#'s equivalent of PHP's:
$count = count($productIds); // where $productIds is the array you also use in IN (...)
SELECT IF ((SELECT COUNT(*) FROM Products WHERE ProductID IN (1, 10, 100)) = $count, 1, 0)
If the IN clause is a parameter (either to SP or hot-built SQL), then this can always be done:
SELECT (SELECT COUNT(1)
FROM product_a
WHERE product_id IN (1, 8, 100)
) = (number of commas in product_id as constant)
If the IN clause is a table, then this can always be done:
SELECT (SELECT COUNT(*)
FROM product_a
WHERE product_id IN (SELECT Products
FROM #WorkTable)
) = (SELECT COUNT(*)
FROM #WorkTable)
If the IN clause is complex then either spool it into a table or write it twice.
If you have the IDs stored in a temp table (which can be done by some C# function or simple SQL) then the problem becomes easy and doable in SQL.
select "all exist"
where (select case when count(distinct t.id) = (select count(distinct id) from #products) then "true" else "false" end
from ProductTable t, #products p
where t.id = p.id) = "true"
This will return "all exists" when all the products in #products exist in the target table (ProductTable) and will not return a row if the above is not true.
If you are not willing to write to a temp table, then you need to feed in some parameter for the number of products you are attempting to find, and replace the temp table with an 'in'; clause so the subquery looks like this:
SELECT "All Exist"
WHERE(
SELECT case when count(distinct t.id) = #ProductCount then "true" else "false"
FROM ProductTable t
WHERE t.id in (1,100,10,20) -- example IDs
) = "true"
If you are using SQL Server 2008, I would create a stored procedure which takes a table-valued parameter. The query should then be of a particularly simple form:
CREATE PROCEDURE usp_CheckAll
(#param dbo.ProductTableType READONLY)
AS
BEGIN
SELECT CAST(1 AS bit) AS Result
WHERE (SELECT COUNT(DISTINCT ProductID) FROM #param)
= (SELECT COUNT(DISTINCT p.ProductID) FROM #param AS p
INNER JOIN Products
ON p.ProductID = Products.ProductID)
END
I changed this to return a row, as you seem to require. There are other ways to do this with a WHERE NOT EXISTS (LEFT JOIN in here WHERE rhs IS NULL):
CREATE PROCEDURE usp_CheckAll
(#param dbo.ProductTableType READONLY)
AS
BEGIN
SELECT CAST(1 AS bit) AS Result
WHERE NOT EXISTS (
SELECT * FROM #param AS p
LEFT JOIN Products
ON p.ProductID = Products.ProductID
WHERE Products.ProductID IS NULL
)
END
Your c# will have to do just a bit of work (counting the number of IDs passed in), but try this:
select (select count(*) from players where productid in (1, 10, 100, 1000)) = 4
Edit:
4 can definitely be parameterized, as can the list of integers.
If you're not generating the SQL from string input by the user, you don't need to worry about attacks. If you are, you just have to make sure you only get integers. For example, if you were taking in the string "1, 2, 3, 4", you'd do something like
String.Join(",", input.Split(",").Select(s => Int32.Parse(s).ToString()))
That will throw if you get the wrong thing. Then just set that as a parameter.
Also, be sure be sure to special case if items.Count == 0, since your DB will choke if you send it where ParameterID in ().
Where is this list of products that you're trying to determine the existence of? If that list exists within another table you could do this
declare #are_equal bit
declare #products int
SELECT #products =
count(pl.id)
FROM ProductList pl
JOIN Products p
ON pl.productId = p.productId
select #are_equal = #products == select count(id) from ProductList
Edit:
Then do ALL the work in C#. Cache the actual list of products in your application somewhere, and do a LINQ query.
var compareProducts = new List<Product>(){p1,p2,p3,p4,p5};
var found = From p in GetAllProducts()
Join cp in compareProducts on cp.Id equals p.Id
select p;
return compareProducts.Count == found.Count;
This prevents constructing SQL queries by hand, and keeps all your application logic in the application.
This may be too simple, but I always use:
SELECT COUNT(*)>0 FROM `table` WHERE condition;
Example:
SELECT iif(count(id)=0,'false','true') FROM table WHERE id = 19