Reduce the oracle PL/SQL statement - sql

I am a new in PL/SQL. After hours of learning, I have some
I have a few curious questions. Example I have a SQL statement like this:
SELECT ShipperName, OrderID, NumberOfOrders, ...
FROM Orders
Can we declare a variable that hold the "list select" like list_select = 'ShipperName, OrderID, NumberOfOrders, ... '
And then replace the select statement with the "list select" we was declared?
SELECT list_select
FROM Orders
And other question, Can we reduce the SQL statement like the Image . I was try this in Oracle SQL Developer, the compiler is ok but when I ran the test I was got an error "by segment".
Any help is appreciated and thank for help.

For your first question, You may search for "EXECUTE IMMEDIATE" statement which is used for dynamic query execution. So you may try something like below -
EXECUTE IMMEDIATE 'SELECT ' || YOUR_COMMA_DELIMITED_COLUMNS_LIST || ' FROM Orders';
For 2nd, Please post the sample data and expected result.

For your second question: You don't want joins here, because you only select from table1. You rather want [NOT] EXISTS or [NOT] IN. E.g.:
SELECT orderid, clientid
FROM orders o
WHERE EXISTS -- We are looking for orders containing expensive items
(
SELECT null
FROM orderposition op
WHERE op.orderid = o.orderid
AND op.price > 1000
)
AND -- and the client ...
(
NOT EXISTS -- ... must have paid all their invoices (there exists no unpaid invoice)
(
SELECT null
FROM invoices i
WHERE i.clientid = o.clientid
AND i.paid_date IS NULL
)
OR clientid IN -- ... or be known as reliable client (they are in the set of reliable ones)
(
SELECT c.clientid
FROM clients c
WHERE c.reliable = 'YES'
)
);

Related

SQL Conditional join inside a function

This question has been asked many times on SO but I never quite found the answer to it, they are mostly solutions to avoid the problem altogether.
I'm working with SQL MS and I'm trying to build a query inside a function (for security reasons) that will either return a table or it's unnested version by country.
meaning that the function should either be
SELECT * FROM SALES AS S
or
SELECT
S.*,
C.Country,
C.CountryPercentage * S.AmountWithouthVAT as CountryValue
FROM SALES AS S
INNER JOIN CountryAllocation AS C ON S.CountryAllocationID = C.CountryAllocationID
(the fact that this join will make a single row into many rows is why I don't simply use the above one. And the reason why I don't make the join outside the function is because the person running the function will not have access to either of the tables. Also note that because of the way permissions in SQL Server work a dynamic query will require permission evaluation, meaning that is not a feasible option unless I'm to develop a structure around certificates)
So, now I got 2 problems:
The output table might or might not have the columns Country and CountryValue causing problems when defining the output type of the function
The actual way to have a function parameter to switch between the 2 versions of the table.
I've got a solution, but this code pains my eyes to look upon:
CREATE FUNCTION [dbo].[fn_I_view] (#Type int)
RETURNS #OutTable TABLE
(
SaleID int,
AmountWithouthVAT decimal(18, 2),
Country varchar(50),
AlocationPercentage decimal(18, 2)
)
AS
BEGIN
WITH
Out1 AS
(
SELECT
S.*,
NULL as Country,
NULL as AlocationPercentage
FROM Sales AS S
WHERE #Type = 1
),
Out2 AS
(
SELECT
S.*,
C.Country,
C.CountryPercentage * S.AmountWithouthVAT as CountryValue
FROM SALES AS S
INNER JOIN CountryAllocation AS C ON S.CountryAllocationID = C.CountryAllocationID
WHERE #Type = 2
)
INSERT INTO #OutTable
SELECT * FROM Out1
UNION ALL
SELECT * FROM Out2
RETURN
END
GO
so, I can't exactly fix the first problem, only worked around it by making SELECT * from [INV].[fn_I_ViewAllMyInvoices](1) still return those 2 extra columns with NULL and I didn't fix the second problem either, as I'm calculating both queries when I only needed 1 of them (and as you can expect this is a demo code, the real deal is way more complex)
Is there any way to improve this code?/solve the problem in a different way? performance, readability as well as maintenance improvements are all welcome
You don't need to calculate both. Just do:
BEGIN
IF #type = 1
BEGIN
INSERT INTO #OutTable
SELECT S.*, NULL as Country, NULL as AlocationPercentage
FROM Sales s;
END;
ELSE
BEGIN
INSERT INTO #OutTable
SELECT S.*, C.Country, C.CountryPercentage * S.AmountWithouthVAT as CountryValue
FROM SALES S JOIN
CountryAllocation C
ON S.CountryAllocationID = C.CountryAllocationID;
END;
RETURN;
END;

Selecting all data if the string is empty in SQL IN operator

I have a stored procedure for the Filters of products in my website which goes like:
ALTER PROCEDURE [dbo].[sp_product_get_by_filters]
(#brand_names nvarchar(max),
#type nvarchar(max))
AS
BEGIN
SELECT
tbl_product.product_code,
tbl_product.brand_name,
tbl_product.subcategory_code,
tbl_product.product_name,
tbl_product.product_photo_1,
tbl_product.filter_code,
(select filter_name from tbl_filter where filter_code = tbl_product.filter_code )as filter_name,
(select AVG(CAST(rating AS DECIMAL(10,2))) from tbl_review where product_code = tbl_product.product_code) as Rating,
(select TOP 1 sub_product_price from tbl_sub_product where product_code = tbl_product.product_code) as product_price,
(select TOP 1 size from tbl_sub_product where product_code = tbl_product.product_code) as size,
(select TOP 1 sub_product_code from tbl_sub_product where product_code = tbl_product.product_code) as sub_product_code
FROM
tbl_product
WHERE
tbl_product.brand_name IN (SELECT * FROM dbo.splitstring(#brand_names))
AND tbl_product.filter_code IN (SELECT * FROM dbo.splitstring(#type))
END
#brand_names here is a string of the name of brands separated by comma for example
Apple,Samsung,Nokia
and #type is the filter of the products which is like
'Watch,Mobile,Tablet'
The dbo.splitstring function separates each value from the concatenated string and return the list as a table. So the problem when a User select both Brand Name and Type the query returns the values but if a user select only Brand Name or Type the query doesn't return anything. I want to make the query to return the products if the user select both Brand Name and Type or don't select any of them (You know like filters in every e-commerce website). If the user doesn't select any filter I am passing an empty string in variables like if user doesn't select any brand then #brand_names will be #brand_names = ''.
For example if a user select Brand Name Apple the query must return all the products related to this brand. And again if the user select the Type watch then the query must return the Watches from brand Apple. I am using SQL Server 2008.
Thank you for the Help.
For this kind of "optional parameter" query, an option recompile at the end can improve performance by quite a lot.
If an "unselected" parameter is an empty string, then you can do:
WHERE
(#brand_names = '' or tbl_product.brand_name IN (SELECT * from dbo.splitstring(#brand_names)))
and (#type = '' or tbl_product.filter_code IN (SELECT * from dbo.splitstring(#type)))
option (recompile)
The option (recompile) tells SQL to build a new plan for this statement every time the procedure runs. So, for example, if you pass an empty string for #brand_names, the engine doesn't even need to evaluate the or tbl_product.brand_name in ... part of that predicate. If you don't do this, then SQL will - as always - build a plan for the first execution, and then reuse that plan on subsequent executions. That's not great when different parameter values can make such a big difference to the result.

Update table setting the value of a field to the result of a query

I have the following table(let's call it Sales) structure:
What I would like to do is to fill the field "SalesID" with values resulting from a query I have created in the same DB. This query reflects the table structure so I was thinking to connect the two using the ID.
Basically, I am trying with scripts of this forms:
UPDATE Sales
SET SalesID = ( SELECT SalesIDCalc FROM (SELECT Sales.ID, Sales.[Email Address], Sales.[Points], IIf([Points] >80,"Super","Normal") AS SalesIDCalc FROM Sales) AS q
WHERE Sales.ID = q.ID);
but I am nowhere near the solution. Do you have any idea on how to proceed?
EDIT: Now I get the Error 'Operation must use an updateable table'
I think you want UPDATE with a correlated subquery:
UPDATE Sales
SET SalesID = (SELECT SalesIDCalc
FROM (MyQuery) as q
WHERE Sales.ID = q.ID
);

How to use Join with like operator and then casting columns

I have 2 tables with these columns:
CREATE TABLE #temp
(
Phone_number varchar(100) -- example data: "2022033456"
)
CREATE TABLE orders
(
Addons ntext -- example data: "Enter phone:2022033456<br>Thephoneisvalid"
)
I have to join these two tables using 'LIKE' as the phone numbers are not in same format. Little background I am joining the #temp table on the phone number with orders table on its Addons value. Then again in WHERE condition I am trying to match them and get some results. Here is my code. But my results that I am getting are not accurate. As its not returning any data. I don't know what I am doing wrong. I am using SQL Server.
select
*
from
order_no as n
join
orders as o on n.order_no = o.order_no
join
#temp as t on t.phone_number like '%'+ cast(o.Addons as varchar(max))+'%'
where
t.phone_number = '%' + cast(o.Addons as varchar(max)) + '%'
You can not use LIKE statement in the JOIN condition. Please provide more information on your tables. You have to convert the format of one of the phone field to compile with other phone field format in order to join.
I think your join condition is in the wrong order. Because your question explicitly mentions two tables, let's stick with those:
select *
from orders o JOIN
#temp t
on cast(o.Addons as varchar(max)) like '%' + t.phone_number + '%';
It has been so long since I dealt with the text data type (in SQL Server), that I don't remember if the cast() is necessary or not.
Instead of trying to do everything in a single top-level query, you should apply a transformation projection to your orders table and use that as a subquery, which will make the query easier to understand.
Using the CHARINDEX function will make this a lot easier, however it does not support ntext, you will need to change your schema to use nvarchar(max) instead - which you should be doing anyway as ntext is deprecated, fortunately you can use CONVERT( nvarchar(max), someNTextValue ), though this will reduce performance as you won't be able to use any indexes on your ntext values - but this query will run slowly anyway.
SELECT
orders2.*,
CASE WHEN orders2.PhoneStart > 0 AND orders2.PhoneEnd > 0 THEN
SUBSTRING( orders2.Addons, orders2.PhoneStart, orders2.PhoneEnd - orders2.PhoneStart )
ELSE
NULL
END AS ExtractedPhoneNumber
FROM
(
SELECT
orders.*, -- never use `*` in production, so replace this with the actual columns in your orders table
CHARINDEX('Enter phone:', Addons) AS PhoneStart,
CHARINDEX('<br>Thephoneisvalid', AddOns, CHARINDEX('Enter phone:', Addons) ) AS PhoneEnd
FROM
orders
) AS orders2
I suggest converting the above into a VIEW or CTE so you can directly query it in your JOIN expression:
CREATE VIEW ordersWithPhoneNumbers AS
-- copy and paste the above query here, then execute the batch to create the view, you only need to do this once.
Then you can use it like so:
SELECT
* -- again, avoid the use of the star selector in production use
FROM
ordersWithPhoneNumbers AS o2 -- this is the above query as a VIEW
INNER JOIN order_no ON o2.order_no = order_no.order_no
INNER JOIN #temp AS t ON o2.ExtractedPhoneNumber = t.phone_number
Actually, I take back my previous remark about performance - if you add an index to the ExtractedPhoneNumber column of the ordersWithPhoneNumbers view then you'll get good performance.

Fastest way to determine if record exists

As the title suggests... I'm trying to figure out the fastest way with the least overhead to determine if a record exists in a table or not.
Sample query:
SELECT COUNT(*) FROM products WHERE products.id = ?;
vs
SELECT COUNT(products.id) FROM products WHERE products.id = ?;
vs
SELECT products.id FROM products WHERE products.id = ?;
Say the ? is swapped with 'TB100'... both the first and second queries will return the exact same result (say... 1 for this conversation). The last query will return 'TB100' as expected, or nothing if the id is not present in the table.
The purpose is to figure out if the id is in the table or not. If not, the program will next insert the record, if it is, the program will skip it or perform an UPDATE query based on other program logic outside the scope of this question.
Which is faster and has less overhead? (This will be repeated tens of thousands of times per program run, and will be run many times a day).
(Running this query against M$ SQL Server from Java via the M$ provided JDBC driver)
EXISTS (or NOT EXISTS) is specially designed for checking if something exists and therefore should be (and is) the best option. It will halt on the first row that matches so it does not require a TOP clause and it does not actually select any data so there is no overhead in size of columns. You can safely use SELECT * here - no different than SELECT 1, SELECT NULL or SELECT AnyColumn... (you can even use an invalid expression like SELECT 1/0 and it will not break).
IF EXISTS (SELECT * FROM Products WHERE id = ?)
BEGIN
--do what you need if exists
END
ELSE
BEGIN
--do what needs to be done if not
END
SELECT TOP 1 products.id FROM products WHERE products.id = ?; will outperform all of your suggestions as it will terminate execution after it finds the first record.
Nothing can beat -
SELECT TOP 1 1 FROM products WHERE id = 'some value';
You don't need to count to know if there is a data in table. And don't use alias when not necessary.
SELECT CASE WHEN EXISTS (SELECT TOP 1 *
FROM dbo.[YourTable]
WHERE [YourColumn] = [YourValue])
THEN CAST (1 AS BIT)
ELSE CAST (0 AS BIT) END
This approach returns a boolean for you.
Don't think anyone has mentioned it yet, but if you are sure the data won't change underneath you, you may want to also apply the NoLock hint to ensure it is not blocked when reading.
SELECT CASE WHEN EXISTS (SELECT 1
FROM dbo.[YourTable] WITH (NOLOCK)
WHERE [YourColumn] = [YourValue])
THEN CAST (1 AS BIT)
ELSE CAST (0 AS BIT) END
You can also use
If EXISTS (SELECT 1 FROM dbo.T1 WHERE T1.Name='Scot')
BEGIN
--<Do something>
END
ELSE
BEGIN
--<Do something>
END
Below is the simplest and fastest way to determine if a record exists in database or not
Good thing is it works in all Relational DB's
SELECT distinct 1 products.id FROM products WHERE products.id = ?;
SELECT COUNT(*) FROM products WHERE products.id = ?;
This is the cross relational database solution that works in all databases.
For those stumbling upon this from MySQL or Oracle background - MySQL supports the LIMIT clause to select a limited number of records, while Oracle uses ROWNUM.
For MySql you can use LIMIT like below (Example shows in PHP)
$sql = "SELECT column_name FROM table_name WHERE column_name = 'your_value' LIMIT 1";
$result = $conn->query($sql);
if ($result -> num_rows > 0) {
echo "Value exists" ;
} else {
echo "Value not found";
}
create or replace procedure ex(j in number) as
i number;
begin
select id into i from student where id=j;
if i is not null then
dbms_output.put_line('exists');
end if;
exception
when no_data_found then
dbms_output.put_line(i||' does not exists');
end;
I've used this in the past and it doesn't require a full table scan to see if something exists. It's super fast...
UPDATE TableName SET column=value WHERE column=value
IF ##ROWCOUNT=0
BEGIN
--Do work
END
SQL SERVER 2012+
SELECT IIF((SELECT TOP 1 1 FROM dbo.[YourTable] WHERE [YourColumn] = [YourValue]) IS NULL, 0, 1)
May you wanna try my way:
IF (SELECT TOP 1 1 FROM [TableName]) = 1
BEGIN
--Do work
END