I have a column which contains category values like
11,2,3
114,3,2
etc.
Want to select top category from these unique numbers. I've tried to select 11th category with
select *
from product
where category like '%11%'
but this one select bot 11 and like 114. How can I only select the 11?
Declare #Search varchar(25) = '11'
Select * from product where category+',' like '%'+#Search+',%'
DECLARE #x VARCHAR(MAX) = '11,2,3 114,3,2'
SELECT 1
WHERE ',' + #x + ',' LIKE '%,11,%'
You can do it in a set based way and this is faster than like for bigger sets..Using one of the functions from here..
declare #x='11,2,3 114,3,2'
;With cte
as
(select * from dbo.substring_numbers(#x,',')
)
select * from cte where item=11
Related
I have an issue which I can not find anywhere. It is a strange scenario.
Imagine:
DECLARE #P INT
SET #P = 5
SELECT * FROM tbl WHERE ID = #P --THIS WILL SELECT 1 ROW.
But if #P is null, I don't want it to do this.
Now one could do
IF #P IS NULL BEGIN
SELECT * FROM tbl
ELSE
SELECT * FROM tbl WHERE ID = #P
END
But when you have a very big select script 50 lines, you don't want to repeat it twice.
It would be far easy to do something like this.
SELECT *
FROM tbl
WHERE ID IN ISNULL(#P, *) -- if "*" returns everything
Can you do this in SQL Server?
You can do this:
SELECT * FROM tbl WHERE (#P IS NULL OR ID = #P);
Another scenario which might be more readable if you have many things in your Where clause
SELECT *
FROM tbl
WHERE ID = Coalesce(#P, ID)
I am working in SQL Server 2008. I have a table with all columns set up as varchar(255), which is necessary since I do data validation on this table. I have the following query on this table:
SELECT
col_1
FROM
table_A
WHERE
col_1 NOT LIKE '%[^.0123456789]%'
AND CAST(col_1 AS float) <= 2.5
I'm getting an error stating that it can't convert one of my table values to data type float. The offending value is '3269e+'. I don't understanding why this value causes an error. Wouldn't this value have been excluded by the first condition in the WHERE clause? If I'm doing something wrong, how should I re-write this query?
Instead of trying to parse out the string using a like statement, you can use ISNUMERIC. This does have some false positives, which people discuss on the comments of the MSDN page. In your example, it could be:
SELECT
col_1
FROM
table_A
WHERE
ISNUMERIC(col_1) = 1
AND CAST(col_1 AS float) <= 2.5
You can do this with a cte to first separate the valid rows, then apply your math.
with ValidRows as
(
SELECT
col_1
FROM table_A
WHERE col_1 NOT LIKE '%[^.0123456789]%'
)
select *
from ValidRows
WHERE CAST(col_1 AS float) <= 2.5
No it wont be taken out by the first condition. that order of execution is done by query optimizer.
Tf you want to make it work you could use a subquery like this:
SELECT *
FROM (
SELECT col_1
FROM table_A
WHERE col_1 NOT LIKE '%[^.0123456789]%' ) t
WHERE CAST(col_1 AS float) <= 2.5
Here is a working version with data similar to yours:
DECLARE #value AS VARCHAR(MAX) = '1'
DECLARE #value1 AS VARCHAR(MAX) = '2.1'
DECLARE #value2 AS VARCHAR(MAX) = '1.5'
DECLARE #value3 AS VARCHAR(MAX) = '32344'
DECLARE #value4 AS VARCHAR(MAX) = '23324e+'
DECLARE #value5 AS VARCHAR(MAX) = '23434334e+'
select * from(
select #value as v
UNION
select #value1
UNION
select #value2
UNION
select #value3
UNION
select #value4
UNION
select #value5
) t
where t.v NOT LIKE '%[^.0123456789]%'
AND CAST(t.v AS float) <= 2.5
Environment: Sql Server 2008
have rows of a column that contains comma separated values.
what to get the row even if a single product exists in that csv.
this is how i can do it but was just wondering if another way to write it?
SELECT * FROM REWARDS
WHERE ProductCsv like '%banana%'
or ProductCsv like '%strawberry%'
or ProductCsv like '%orange%'
Your current query doesn't seem to accurately capture the results you want. What if we have a row like this:
bananaberry cream pie,strawberry shortcake,orange juice
Should this match your query or not? I think this would be more accurate:
WHERE ',' + ProductCsv + ',' LIKE '%,banana,%'
OR ',' + ProductCsv + ',' LIKE '%,strawberry,%'
OR ',' + ProductCsv + ',' LIKE '%,orange,%'
If you're just trying to find one item, this is probably much more efficient:
WHERE ',' + ProductCsv + ',' LIKE '%,banana,%'
You probably want to use a split function. For example:
CREATE FUNCTION dbo.SplitStrings_XML
(
#List NVARCHAR(MAX),
#Delim NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>' + REPLACE(#List, #Delim, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
So now you can say:
SELECT * FROM dbo.REWARDS AS r
WHERE EXISTS
(
SELECT 1 FROM dbo.REWARDS AS r2
CROSS APPLY dbo.SplitStrings_XML(r2.ProductCsv, ',') AS x
WHERE x.Item IN ('orange','strawberry','banana')
AND r2.[key] = r.[key]
);
Or more simply:
SELECT DISTINCT r.ProductCsv --, other columns
FROM dbo.REWARDS AS r
CROSS APPLY dbo.SplitStrings_XML(r.ProductCsv, ',') AS x
WHERE x.Item IN ('orange','strawberry','banana');
The XML approach is a little brittle depending on the kinds of strings can be stored in the table, but there are many alternatives (including passing in your set via a TVP instead of as a list or separate values). For some much deeper background on split functions see these blog posts:
http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings
http://www.sqlperformance.com/2012/08/t-sql-queries/splitting-strings-follow-up
http://www.sqlperformance.com/2012/08/t-sql-queries/splitting-strings-now-with-less-t-sql
That all said, I don't know if this is any better than what you have now.
Have a look at the following example using XML
DECLARE #Table TABLE(
CommaList VARCHAR(MAX)
)
INSERT INTO #Table SELECT 'banana,test,hello'
INSERT INTO #Table SELECT 'homer,banana,test,hello'
INSERT INTO #Table SELECT 'homer,banana'
INSERT INTO #Table SELECT '1,2,3'
;WITH XMLValues AS (
SELECT *,
CAST('<d>' + REPLACE(CommaList, ',', '</d><d>') + '</d>' AS XML) XMLValue
FROM #Table t
)
, SplitValues AS (
SELECT xv.*,
T2.Loc.value('.','VARCHAR(MAX)') SplitValue
FROM XMLValues xv
CROSS APPLY XMLValue.nodes('/d') as T2(Loc)
)
SELECT DISTINCT
CommaList
FROM SplitValues
WHERE SplitValue = 'banana'
xml Data Type Methods
nodes() Method (xml Data Type)
value() Method (xml Data Type)
Using Common Table Expressions
You could store all the values to be compared in a table
DECLARE #Product_list TABLE(
products VARCHAR(50)
)
Insert into #Product_list values
('banana'),
('strawberry'),
('orange')
SELECT * FROM REWARDS
join #Product_list
on ProductCsv like '%'+products+'%'
You could use a split function (there are many around on the web) to split ProductsCsv into individual elements and then compare that against banana, strawberry etc.
Ideally, you wouldn't store CSV data in a column, but instead have a separate table for that.
I am writing a cursor to populate data in new table from main table which contains data in below manner
Item
Colors
Shirt
Red,Blue,Green,Yellow
I want to populate new Table data by fetching the Item and then adding it in row, according to each color it contains
Item
Color
Shirt
Red
Shirt
Blue
Shirt
Green
Shirt
Yellow
I am stuck in how to
Delimit/Split "Colors" string
To save it in an array
To use it in cursor
as I am going to use Nested cursor for this purpose.
Using Sql Server 2005+ and the XML datatype, you can have a look at the following
DECLARE #Table TABLE(
Item VARCHAR(250),
Colors VARCHAR(250)
)
INSERT INTO #Table SELECT 'Shirt','Red,Blue,Green,Yellow'
INSERT INTO #Table SELECT 'Pants','Black,White'
;WITH Vals AS (
SELECT Item,
CAST('<d>' + REPLACE(Colors, ',', '</d><d>') + '</d>' AS XML) XmlColumn
FROM #Table
)
SELECT Vals.Item,
C.value('.','varchar(max)') ColumnValue
FROM Vals
CROSS APPLY Vals.XmlColumn.nodes('/d') AS T(C)
The article Faking Arrays in Transact SQL details SEVERAL techniques to solve this problem, ranging from using the PARSENAME() function (limit to 5 items) to writing CLR functions.
The XML answer is one of the detailed techniques that can be chosen to a specific scenario.
Combining some of the tips, I solved my string split problem like this:
SET NOCOUNT ON;
DECLARE #p NVARCHAR(1000), #len INT;
SET #p = N'value 1,value 2,value 3,value 4,etc';
SET #p = ',' + #p + ',';
SET #len = LEN(#p);
-- Remove this table variable creation if you have a permanent enumeration table
DECLARE #nums TABLE (n int);
INSERT INTO #nums (n)
SELECT A.n FROM
(SELECT TOP 1000 ROW_NUMBER() OVER (ORDER BY TableKey) as n FROM dbo.Table) A
WHERE A.n BETWEEN 1 AND #len;
SELECT SUBSTRING(#p , n + 1, CHARINDEX( ',', #p, n + 1 ) - n - 1 ) AS "value"
FROM #nums
WHERE SUBSTRING( #p, n, 1 ) = ',' AND n < #len;
Note that, considering 1000 your string length limit, you must have a table with 1000 or more rows (dbo.Table on the sample tsql) to create the table variable #nums of this sample. On the article, they have a permanent enumeration table.
For those who like to keep it simple:
-- Here is the String Array you want to convert to a Table
declare #StringArray varchar(max)
set #StringArray = 'First item,Second item,Third item';
-- Here is the table which is going to contain the rows of each item in the String array
declare ##mytable table (EachItem varchar(50))
-- Just create a select statement appending UNION ALL to each one of the item in the array
set #StringArray = 'select ''' + replace(#StringArray, ',', ''' union all select ''') + ''''
-- Push the data into your table
insert into ##mytable exec (#StringArray)
-- You now have the data in an an array inside a table that you can join to other objects
select * from ##mytable
I just accomplished something like this to create staging tables to replicate the source tables using the INFORMATION_SCHEMA views on a linked server. But this is a modified version to create the results you are look for. Just remember to remove the last two characters from the Colors column when displaying it.
SELECT
t.Item
, (
SELECT
x.Color + ', ' AS [data()]
FROM
Items x
WHERE
x.Item = t.Item
FOR XML PATH(''), TYPE
).value('.', 'varchar(max)') AS Colors
FROM
Items t
GROUP BY
t.Item
I would like to know if there is a way to use an order by clause when updating a table. I am updating a table and setting a consecutive number, that's why the order of the update is important. Using the following sql statement, I was able to solve it without using a cursor:
DECLARE #Number INT = 0
UPDATE Test
SET #Number = Number = #Number +1
now what I'd like to to do is an order by clause like so:
DECLARE #Number INT = 0
UPDATE Test
SET #Number = Number = #Number +1
ORDER BY Test.Id DESC
I've read: How to update and order by using ms sql The solutions to this question do not solve the ordering problem - they just filter the items on which the update is applied.
Take care,
Martin
No.
Not a documented 100% supported way. There is an approach sometimes used for calculating running totals called "quirky update" that suggests that it might update in order of clustered index if certain conditions are met but as far as I know this relies completely on empirical observation rather than any guarantee.
But what version of SQL Server are you on? If SQL2005+ you might be able to do something with row_number and a CTE (You can update the CTE)
With cte As
(
SELECT id,Number,
ROW_NUMBER() OVER (ORDER BY id DESC) AS RN
FROM Test
)
UPDATE cte SET Number=RN
You can not use ORDER BY as part of the UPDATE statement (you can use in sub-selects that are part of the update).
UPDATE Test
SET Number = rowNumber
FROM Test
INNER JOIN
(SELECT ID, row_number() OVER (ORDER BY ID DESC) as rowNumber
FROM Test) drRowNumbers ON drRowNumbers.ID = Test.ID
Edit
Following solution could have problems with clustered indexes involved as mentioned here. Thanks to Martin for pointing this out.
The answer is kept to educate those (like me) who don't know all side-effects or ins and outs of SQL Server.
Expanding on the answer gaven by Quassnoi in your link, following works
DECLARE #Test TABLE (Number INTEGER, AText VARCHAR(2), ID INTEGER)
DECLARE #Number INT
INSERT INTO #Test VALUES (1, 'A', 1)
INSERT INTO #Test VALUES (2, 'B', 2)
INSERT INTO #Test VALUES (1, 'E', 5)
INSERT INTO #Test VALUES (3, 'C', 3)
INSERT INTO #Test VALUES (2, 'D', 4)
SET #Number = 0
;WITH q AS (
SELECT TOP 1000000 *
FROM #Test
ORDER BY
ID
)
UPDATE q
SET #Number = Number = #Number + 1
The row_number() function would be the best approach to this problem.
UPDATE T
SET T.Number = R.rowNum
FROM Test T
JOIN (
SELECT T2.id,row_number() over (order by T2.Id desc) rowNum from Test T2
) R on T.id=R.id
update based on Ordering by the order of values in a SQL IN() clause
Solution:
DECLARE #counter int
SET #counter = 0
;WITH q AS
(
select * from Products WHERE ID in (SELECT TOP (10) ID FROM Products WHERE ID IN( 3,2,1)
ORDER BY ID DESC)
)
update q set Display= #counter, #counter = #counter + 1
This updates based on descending 3,2,1
Hope helps someone.
I had a similar problem and solved it using ROW_NUMBER() in combination with the OVER keyword. The task was to retrospectively populate a new TicketNo (integer) field in a simple table based on the original CreatedDate, and grouped by ModuleId - so that ticket numbers started at 1 within each Module group and incremented by date. The table already had a TicketID primary key (a GUID).
Here's the SQL:
UPDATE Tickets SET TicketNo=T2.RowNo
FROM Tickets
INNER JOIN
(select TicketID, TicketNo,
ROW_NUMBER() OVER (PARTITION BY ModuleId ORDER BY DateCreated) AS RowNo from Tickets)
AS T2 ON T2.TicketID = Tickets.TicketID
Worked a treat!
I ran into the same problem and was able to resolve it in very powerful way that allows unlimited sorting possibilities.
I created a View using (saving) 2 sort orders (*explanation on how to do so below).
After that I simply applied the update queries to the View created and it worked great.
Here are the 2 queries I used on the view:
1st Query:
Update MyView
Set SortID=0
2nd Query:
DECLARE #sortID int
SET #sortID = 0
UPDATE MyView
SET #sortID = sortID = #sortID + 1
*To be able to save the sorting on the View I put TOP into the SELECT statement. This very useful workaround allows the View results to be returned sorted as set when the View was created when the View is opened. In my case it looked like:
(NOTE: Using this workaround will place an big load on the server if using a large table and it is therefore recommended to include as few fields as possible in the view if working with large tables)
SELECT TOP (600000)
dbo.Items.ID, dbo.Items.Code, dbo.Items.SortID, dbo.Supplier.Date,
dbo.Supplier.Code AS Expr1
FROM dbo.Items INNER JOIN
dbo.Supplier ON dbo.Items.SupplierCode = dbo.Supplier.Code
ORDER BY dbo.Supplier.Date, dbo.Items.ID DESC
Running: SQL Server 2005 on a Windows Server 2003
Additional Keywords: How to Update a SQL column with Ascending or Descending Numbers - Numeric Values / how to set order in SQL update statement / how to save order by in sql view / increment sql update / auto autoincrement sql update / create sql field with ascending numbers
SET #pos := 0;
UPDATE TABLE_NAME SET Roll_No = ( SELECT #pos := #pos + 1 ) ORDER BY First_Name ASC;
In the above example query simply update the student Roll_No column depending on the student Frist_Name column. From 1 to No_of_records in the table. I hope it's clear now.
IF OBJECT_ID('tempdb..#TAB') IS NOT NULL
BEGIN
DROP TABLE #TAB
END
CREATE TABLE #TAB(CH1 INT,CH2 INT,CH3 INT)
DECLARE #CH2 INT = NULL , #CH3 INT=NULL,#SPID INT=NULL,#SQL NVARCHAR(4000)='', #ParmDefinition NVARCHAR(50)= '',
#RET_MESSAGE AS VARCHAR(8000)='',#RET_ERROR INT=0
SET #ParmDefinition='#SPID INT,#CH2 INT OUTPUT,#CH3 INT OUTPUT'
SET #SQL='UPDATE T
SET CH1=#SPID,#CH2= T.CH2,#CH3= T.CH3
FROM #TAB T WITH(ROWLOCK)
INNER JOIN (
SELECT TOP(1) CH1,CH2,CH3
FROM
#TAB WITH(NOLOCK)
WHERE CH1 IS NULL
ORDER BY CH2 DESC) V ON T.CH2= V.CH2 AND T.CH3= V.CH3'
INSERT INTO #TAB
(CH2 ,CH3 )
SELECT 1,2 UNION ALL
SELECT 2,3 UNION ALL
SELECT 3,4
BEGIN TRY
WHILE EXISTS(SELECT TOP 1 1 FROM #TAB WHERE CH1 IS NULL)
BEGIN
EXECUTE #RET_ERROR = sp_executesql #SQL, #ParmDefinition,#SPID =##SPID, #CH2=#CH2 OUTPUT,#CH3=#CH3 OUTPUT;
SELECT * FROM #TAB
SELECT #CH2,#CH3
END
END TRY
BEGIN CATCH
SET #RET_ERROR=ERROR_NUMBER()
SET #RET_MESSAGE = '#ERROR_NUMBER : ' + CAST(ERROR_NUMBER() AS VARCHAR(255)) + '#ERROR_SEVERITY :' + CAST( ERROR_SEVERITY() AS VARCHAR(255))
+ '#ERROR_STATE :' + CAST(ERROR_STATE() AS VARCHAR(255)) + '#ERROR_LINE :' + CAST( ERROR_LINE() AS VARCHAR(255))
+ '#ERROR_MESSAGE :' + ERROR_MESSAGE() ;
SELECT #RET_ERROR,#RET_MESSAGE;
END CATCH