Reputation: 2642
My company uses media management software which stores all metadata for media in 1 table (dimensions, titles, captions, tags, etc...). Because of this, all values are of type VARCHAR
. It's not a huge problem because each row has a field called MetaName
which defines what type of data exists in that row. For instance, MetaName 1 is the title field and MetaName 3 is height in pixels.
I'm attempting to find images between certain dimensions in pixels. Since the values are stored as type VARCHAR
such as 1200px
, I need to replace the px
and cast to an INT
before I test a size range like so: CAST(REPLACE(VALUE, 'px', '') AS INT)
.
Here's a simplified schema and an example to easier understand what I'm talking about:
CREATE TABLE Media (
ID INT IDENTITY(1,1) PRIMARY KEY,
ImagePath VARCHAR(MAX)
);
CREATE TABLE MetaData (
ID INT IDENTITY(1,1) PRIMARY KEY,
MediaId INT REFERENCES Media(ID),
MetaName INT NOT NULL,
Value VARCHAR(MAX) NOT NULL DEFAULT ''
);
And test values + my query:
INSERT INTO Media (ImagePath)
VALUES
('C:/mypath/myimage.png'),
('C:/mypath/otherimage.jpg');
INSERT INTO MetaData (MediaId, MetaName, Value)
VALUES
(1, 1, 'Title1'), (2, 1, 'Title2'),
(1, 2, 'Description1'), (2, 2, 'Description2'),
(1, 3, '1260px'), (2, 3, '1100px'),
(1, 4, '800px'), (2, 4, '1900px');
SELECT * FROM MetaData
WHERE
(MetaName = 3 AND CAST(REPLACE(Value, 'px', '') AS INT) BETWEEN 800 AND 1200)
OR
(MetaName = 4 AND CAST(REPLACE(Value, 'px', '') AS INT) BETWEEN 800 AND 1200)
This should work fine, however, the CAST
call fails because the query tries to cast ALL of the Value columns so when it runs into "Title1" or "Description1", an error occurs.
How can I cast ONLY on fields with MetaName = 3 or 4? I figured the MetaName = 3
and the MetaName = 4
at the beginning of the conditional statements before the AND
would prevent the CAST
+ REPLACE
from executing but it happens on all columns regardless.
Upvotes: 3
Views: 206
Reputation: 25122
You can use try_convert on SQL Server 2012 onward
SELECT * FROM MetaData
WHERE
(MetaName = 3 AND try_convert(int,REPLACE(Value, 'px', '')) BETWEEN 800 AND 1200)
OR
(MetaName = 4 AND try_convert(int,REPLACE(Value, 'px', '')) BETWEEN 800 AND 1200)
Or try_cast
SELECT * FROM MetaData
WHERE
(MetaName = 3 AND try_cast(REPLACE(Value, 'px', '') as int) BETWEEN 800 AND 1200)
OR
(MetaName = 4 AND try_cast(REPLACE(Value, 'px', '') as int) BETWEEN 800 AND 1200)
Or, you can wrap that cast in a CASE
SELECT * FROM MetaData
where
(MetaName = 3 AND case when MetaName = 3 then CAST(REPLACE(Value, 'px', '') AS INT) end BETWEEN 800 AND 1200)
OR
(MetaName = 4 AND case when MetaName = 4 then CAST(REPLACE(Value, 'px', '') AS INT) end BETWEEN 800 AND 1200)
Or, use IIF
SELECT * FROM MetaData
where
(MetaName = 3 AND iif(MetaName = 3,CAST(REPLACE(Value, 'px', '') AS INT),null) BETWEEN 800 AND 1200)
OR
(MetaName = 4 AND iif(MetaName = 4,CAST(REPLACE(Value, 'px', '') AS INT),null) BETWEEN 800 AND 1200)
Or, break up the filtering using a CTE
.
;with cte as(
SELECT * FROM MetaData
where MetaName in (3,4))
select *
from cte
where CAST(REPLACE(Value, 'px', '') AS INT) BETWEEN 800 AND 1200
Upvotes: 3
Reputation: 743
SELECT * FROM MetaData
WHERE
( case when (MetaName = 3 or MetaName = 4) then CAST(REPLACE(Value, 'px', '') AS INT) else NULL end )
BETWEEN 800 AND 1200
Upvotes: 2
Reputation: 41
What you can do is use TRY_CAST instead of CAST.
SELECT * FROM MetaData
WHERE
(MetaName = 3 AND TRY_CAST(REPLACE(Value, 'px', '') AS INT) BETWEEN 800 AND 1200)
OR
(MetaName = 4 AND TRY_CAST(REPLACE(Value, 'px', '') AS INT) BETWEEN 800 AND 1200)
When I do this it only returns MetaNames 3 and 4 as part of the result set. I believe this functionality was introduced in SQL Server 2012.
Upvotes: 2