SQL Server query brings unmatched data with BETWEEN filter - sql

I'm querying on my products table for all products with code between a range of codes, and the result brings a row that should't be there.
This is my SQL query:
select prdcod
from products
where prdcod between 'F-DH1' and 'F-FMS'
order by prdcod
and the results of this query are:
F-DH1
F-DH2
F-DH3
FET-RAZ <-- What is this value doing here!?
F-FMC
F-FML
F-FMS
How can this odd value make it's way into the query results?
PS: I get the same results if I use <= and >= instead of between.

According to OP request promoted next comment to answer:
Seems like your collation excludes '-' sign - this way results make sense, FE is between FD and FM.
:)

between and >= and <= are primarily used for numeric operations (including dates). You're trying to use this for strings, which are difficult at best to determine how those operators will interpret the each string.
Now, while I think I understand your goal here, I'm not entirely sure it's possible using SQL Server queries. This may be some business logic (thanks to the product codes) that needs implemented in code. Something like the Entity Framework or Linq-to-SQL may be better suited to get you the data you're looking for.

How about adding AND LEFT(prdcod, 2) = 'F-'?

Try replacing the "-" with a space so the order is what you would expect:
DECLARE #list table(word varchar(50))
--create list
INSERT INTO #list
SELECT 'F-DH1'
UNION ALL
SELECT 'F-DH2'
UNION ALL
SELECT 'F-DH3'
UNION ALL
SELECT 'FET-RAZ'
UNION ALL
SELECT 'F-FMC'
UNION ALL
SELECT 'F-FML'
UNION ALL
SELECT 'F-FMS'
--original order
SELECT * FROM #list order by word
--show how order changes
SELECT *,replace(word,'-',' ') FROM #list order by replace(word,'-',' ')
--show between condition
SELECT * FROM #list where replace(word,'-',' ') between 'F DH1' and 'F FMS'

Related

Search for a particular value in a string with commas

I have a TEXT column in my Table T and contains some values separated by Commas.
Example
Columns BNFT has text values such as
B20,B30,B3,B13,B31,B14,B25,B29,B1,B2,B4,B5
OR
B1,B2,B34,B31,B8,B4,B5,B33,B30,B20,B3
I want to return result in my query only if B3 is present.
It should not consider B30-B39 or B[1-9]3 (i.e. B13, B23 .... B93).
I tried with below query, but want to implement REGEXP or REGEXP_LIKE/INSTR etc. Haven't used them before and unable to understand also.
Select *
FROM T
Where BNFT LIKE '%B3,%' or BNFT LIKE '%B3'
Pls advise
Procedures will not work. Query must start with Select as 1st statement.
The first advice is to fix your data structure. Storing lists of ids in strings is a bad idea:
You are storing numbers as strings. That is the wrong representation.
You are storing multiple values in a string column. That is not using SQL correctly.
These values are probably ids. You cannot declare proper foreign key relationships.
SQL does not have particularly strong string functions.
The resulting query cannot take advantage of indexes.
That said, sometimes we are stuck with other people's bad design decisions.
In SQL Server, you would do:
where ',' + BNFT + ',' LIKE '%,33,%'
This question was originally tagged MySQL, which offers find_in_set() for this purpose:
Where find_in_set(33, BNFT) > 0
Select *
FROM T
Where ',' + BNFT + ',' LIKE '%,B3,%';
or
Select *
FROM T
Where CHARINDEX (',B3,',',' + BNFT + ',') > 0;
This can be easily achieve by CTE, REGEXP/REGEXP_Like/INSTR works better with oracle, for MS SQL Server you can try this
DECLARE #CSV VARCHAR(100) ='B2,B34,B31,B8,B4,B5,B33,B30,B20,B3';
SET #CSV = #CSV+',';
WITH CTE AS
(
SELECT SUBSTRING(#CSV,1,CHARINDEX(',',#CSV,1)-1) AS VAL, SUBSTRING(#CSV,CHARINDEX(',',#CSV,1)+1,LEN(#CSV)) AS REM
UNION ALL
SELECT SUBSTRING(A.REM,1,CHARINDEX(',',A.REM,1)-1)AS VAL, SUBSTRING(A.REM,CHARINDEX(',',A.REM,1)+1,LEN(A.REM))
FROM CTE A WHERE LEN(A.REM)>=1
) SELECT VAL FROM CTE
WHERE VAL='B3'

With as in Oracle SQL

I would like to know if is it possible to use the clause "with as" with a variable and/or in a block begin/end.
My code is
WITH EDGE_TMP
AS
(select edge.node_beg_id,edge.node_end_id,prg_massif.longueur,prg_massif.lgvideoupartage,prg_massif.lgsanscable from prg_massif
INNER JOIN edge on prg_massif.asset_id=edge.asset_id
where prg_massif.lgvideoupartage LIKE '1' OR prg_massif.lgsanscable LIKE '1')
,
journey (TO_TOWN, STEPS,DISTANCE,WAY)
AS
(SELECT DISTINCT node_beg_id, 0, 0, CAST(&&node_begin AS VARCHAR2(2000))
FROM EDGE_TMP
WHERE node_beg_id = &&node_begin
UNION ALL
SELECT node_end_id, journey.STEPS + 1
, journey.DISTANCE + EDGE_TMP.longueur,
CONCAT(CONCAT(journey.WAY,';'), EDGE_TMP.node_end_id
)
It create a string as output separated by a ; but i need to get it back as variable or table do you know how? I used a concat to retrieve data in a big string. Can i use a table to insert data
,
A need to use the result to proceed more treatment.
Thank you,
mat
No, WITH is a part of an SQL statement only. But if you describe why you need it in pl/sql, we'll can advice you something.
Edit: if you have SQL statement which produces result you need, you can assign it's value to pl/sql variable. There are several methods to do this, simpliest is to use SELECT INTO statement (add INTO variable clause into your select).
You can use WITH clause as a part of SELECT INTO statement (at least in not-too-very-old Oracle versions).

sql cte if statement

This might be a bad idea but just wondering?
;with Products
AS
(
/* can you have an if statement in here? */
select * from products
/* or */
select * from products
where condition
)
what i'm thinking is what if sometimes you have a search string or not. How do you make that a consideration in cte?
Or maybe it would be a better idea to have 2 cte in one procedure?
If you're passing in a search string as a parameter, you can check that it is null or not all in one statement. For example:
select *
from MyTable
where MyColumn = #SearchString
or #SearchString is null;
This will return records that match when the parameter is not null, and return all records when it is null.
As another option, you can always put case statements in your where clause.
Beyond that, if you truly need different queries, you can certainly branch with if BUT your query must be the very next statement after you declare the CTE. Thus you'd have to have a copy or your CTE and query in each branch of the if statement.
If you're thinking of passing an entire where clause and running it all as dynamic SQL (edit: meaning a non-parameterized concatenated string, not ORM-type sp_executesql), I would try refactoring to use any of the above methods first, as there are inherent problems with dynamic SQL. Dynamic SQL often looks clever and elegant at the outset, but should more often be seen as a last resort only when other options somehow turn out to be worse.
table variable might also help you. Check if it helps..
DECLARE #tbl TABLE(id int,name varchar(500), .... )
if <#booleanexpression = 1>
INSERT INTO #tbl select * from products
else
INSERT INTO #tbl select * from products where condition..
with cte as
( select * from #tbl )
select * from cte

SQL mass string manipulation

I'm working with an oracle DB and need to manipulate a string column within it. The column contains multiple email addresses in this format:
jgooooll#gmail.com;dhookep#gmail.com;amoore#outlook.com
What I want to do is take out anything that does not have '#gmail.com' at the end (in this example amoore#outlook.com would be removed) however amoore#outlook.com may be the first email in the next row of the column so in this way there is no real fixed format, the only format being that each address is seperated by a semi-colon.
Is there anyway of implementing this through one command to run through every row in the column and remove anything thats not #gmail.com? I'm not really sure if this kind of processing is possible in SQL. Just looking for your thoughts!!
Thanks a lot you guys. Look forward to hearing from you!
Applicable to Oracle 11g (11.2) onward only. Because listagg function is supported only in 11.2 onward. If you are using 10.1 onward up to 11.1, you can write your own string aggregate function or take this one.
with T1 as (
select 1 id, 'jhd#jk.com;jgooooll#gmail.com;dhookep#gmail.com;amoore#outlook.com' emails from dual union all
select 2 id, 'jhd#jk.com;jgooooll#gmail.com;dhookep#gmail.com;amoore#outlook.com' emails from dual
)
select id
, listagg(email, ';') within group(order by id) emails
from (select id
, regexp_substr(emails,'[^;]+', 1, rn) email
from t1
cross join (select rownum rn
from(select max (regexp_count(emails, '[^;]+')) ml
from t1
)
connect by level <= ml
)
)
where email like '%#gmail.com%'
group by id
Id Emails
--------------------------------------
1 dhookep#gmail.com;jgooooll#gmail.com
2 dhookep#gmail.com;jgooooll#gmail.com
Here is a Demo
This answer is actually for SQL Server, as that is what I know. That being said, perhaps having an example of how to do it in one system will give you an idea of how to do it in yours. Or maybe there is a way to convert the code into the same type of thing in Oracle.
First, the thought process: In SQL Server combining the FOR XML PATH and STUFF functionality allows you to make a comma separated list. I'm adding a WHERE Split.SplitValue LIKE ... clause into this to filter it to only gmail addresses. I'm cross applying this whole thing to the main table, and that turns it into a filtered email list. You could then further filter the main table to run this on a more targeted set of rows.
Second, the SQL Server implementation:
SELECT
*
FROM #Table Base
CROSS APPLY
(
SELECT
STUFF(
(SELECT
';' + Split.SplitValue AS [text()]
FROM dbo.fUtility_Split(Base.Emails, ';') Split
WHERE Split.SplitValue LIKE '%#gmail.com'
FOR XML PATH (''))
, 1, 1, '') Emails
) FilteredEmails
EDIT: I forgot to mention that this answer requires you have some sort of function to split a string column based on a separator value. If you don't have that already, then google for it. There are tons of examples.

Use sql to select data, then insert row in first position of result set only (not update database)

If I select a column in a table, say 10 rows, can I use SQL to insert a 'please select' row into the first position using SQL only? I want to insert it in the result set only - not the database.
First you should know this is a bad idea.
You are confusing your presentation layer and your database layer. Forcing SQL to do things like output status messages or feedback to users is an antipattern to be avoided.
That being said, if the column is of a string type (char, varchar, etc), you can do something like:
SELECT 'Please Select'
UNION ALL
SELECT TOP 10 Varcharfield
FROM Mytable
If it's numeric then no unless you cast it to a string type.
Here's something you can try:
SELECT u.YourVarcharColumnName
FROM (
SELECT 1 AS rID, 'Please select' AS YourVarcharColumnName
UNION
SELECT 2 AS rID, YourVarcharColumnName
FROM YourTableName
) u
ORDER BY u.rID;
I placed rID in place and sorted by it as an extreme-case-measure when your intended first rowset does not come out on top...
However, you should keep in mind that YourVarcharColumnName should be (as it says) a string. You'll have to convert it to a string if it's a non-string column.
As #JNK mentioned it.. I thought I should edit my post as well:
Please first try:
SELECT 'Please select' AS YourVarcharColumnName
UNION
SELECT YourVarcharColumnName
FROM YourTableName
Which is similar to what the others have posted. If you ever experience what I've been unfortunate to encounter and 'Please select' doesn't come out on top, please refer to the query I posted at the top.. Thanks!
SELECT '(Please select)'
UNION
SELECT ColumnName FROM TableName