Building a dynamic no-fail where clause - mysql

I have built an interface, where the user fetches records from the database. User has the option of specifying 0 or more of the filters. There are four filters A, B, C, D (let's say these are the fields in a certain table)
Here's what my query should look like:
select * from table
where (A = v1 or B = v2 or C = v3) and D = v4
I am trying to come with a way to formulate the query, whereas when a specific filter is specified, it is applied, if it isn't, it is ignored. But this should hold for all the sixteen cases.
What I have been able to come up with so far are these methods:
select * from table
where (
(A = v1 and 1)
or (B = v2 and 1)
or (C = v3 and 1)
)
and D = v4
v1 or other values are set to -1 when they are not specified. So in case they are not specified, they're simply ignored, because then the other filter (from among A, B, C) is used. But this fails in the case when none of A, B, and C are specified. In that case, false is Anded with D, and D is not applied.
Is there a way to come with a where clause for this case? I am open to programmatic solutions to this one as well, where I add or not add clauses through code, but I would prefer it this way. And I would really not want to have a lot of if-else statements.
Thank you!

What about using case construct
select *
from table
where (A = CASE WHEN v1 IS NOT NULL THEN v1 else '' END)
OR (B = CASE WHEN v2 IS NOT NULL THEN v2 else '' END)
OR (C = CASE WHEN v3 IS NOT NULL THEN v3 else '' END)
OR (CASE WHEN v1 is null and v2 is null and v3 is null then 1 else 0 end)
and D = v4

if v1-v4 are the values you're searching for and all of them are -1 if not specified, you can do this:
SELECT
*
FROM
table
WHERE
(
(A = v1 OR -1 = v1)
or
(B = v2 OR -1 = v2)
or
(C = v3 OR -1 = v3)
)
AND
(D = v4 OR -1 = v4)

Databases have a hard time optimizing dynamic where clauses. Typically, they'll produce a plan that's optimal for the first invocation. So if your first search is for filter A and B, the query plan will be optimized for that. The next query will also use that plan, even if it's using filters C and D. Adding where clauses in code tends to perform much better.
But it is possible, for example:
where (
A = #FilterAValue
or B = #FilterBValue
or C = #FilterCValue
)
and D = coalesce(#FilterDValue, D)
And then you can toggle the filters with the FilterXValue parameters. If the filter for A, B or C is null, the other parts of the or will still be evaluated. A = null or B = 1 is the same as unknown or B = 1 which is only true when B = 1.

Related

How to remove an element in jsonb integer array in PostgreSQL

On Postgres, in a table called "photo" I have a jsonb column called "id_us" containing a json integer array, simply like this one [1,2,3,4]
I would like to find the query to remove the element 3 for example.
The closer I could get is this
SELECT jsonb_set(id_us, ''
, (SELECT jsonb_agg(val)
FROM jsonb_array_elements(p.id_us) x(val)
WHERE val <> jsonb '3')
) AS id_us
FROM photo p;
Any idea how to solve this?
Thank you!
You can use a subquery containing JSONB_AGG() function while filtering out the index value 3(by starting indexing from 1) such as
WITH p AS
(
SELECT JSONB_AGG(j) AS js
FROM photo
CROSS JOIN JSONB_ARRAY_ELEMENTS(id_us)
WITH ORDINALITY arr(j,idx)
WHERE idx != 3
)
UPDATE photo
SET id_us = js
FROM p
Demo
Edit : If you need to remove the value but not index as mentioned in the comment, just use the variable j casted as numeric
WITH p AS
(
SELECT JSONB_AGG(j) AS js
FROM photo
CROSS JOIN JSONB_ARRAY_ELEMENTS(id_us)
WITH ORDINALITY arr(j,idx)
WHERE j::INT != 18
)
UPDATE photo
SET id_us = js
FROM p
Demo
P.S: using JSONB_SET(), the comma-seperated place for the removed element along with quotes will still remain in such a way that in the following
WITH p AS
(
SELECT ('{'||idx-1||'}')::TEXT[] AS idx
FROM photo
CROSS JOIN JSONB_ARRAY_ELEMENTS(id_us)
WITH ORDINALITY arr(j,idx)
WHERE j::INT = 18
)
UPDATE photo
SET id_us = JSONB_SET(id_us,idx,'""')
FROM p;
SELECT * FROM photo;
id_us
-----------------
[127, 52, "", 44]
I've run across a similar issue, and it stems from the - operator. This operator is overloaded to accept either text or integer, but acts differently for each type. Using text will remove by value, and using an integer will remove by index. But what if your value IS an integer? Well then you're shit outta luck...
If possible, you can try changing your jsonb integer array to a jsonb string array (of integers), and then the - operator should work smoothly.
e.g.
'{1,2,3}' - 2 = '{1,2}' -- removes index 2
'{1,2,3}' - '2' = '{1,2,3}' -- removes values == '2' (but '2' != 2)
'{"1","2","3"}' - 2 = '{"1","2"}' -- removes index 2
'{"1","2","3"}' - '2' = '{"1","3"}' -- removes values == '2'

Update column depending on other column by calculation

seems like a stupid question...
I have a mysql table where I want to modify column A to a number 0 or 1 depending on the condition of another column B
So: if( B > 500 ) A = 1 ELSE A = 0
Column A = INT
Column B = DOUBLE
How do you do something like this in sql?
Thanks,
Erik
Try the following statement,
UPDATE tableName
SET A = (B > 500)
SQLFiddle Demo
(B > 500) is a boolean arithmetic in mysql which returns 1 and 0 for true and false , respectively.
You can also use CASE for much more RDBMS friendly,
UPDATE tableName
SET A = CASE WHEN B > 500 THEN 1 ELSE 0 END

How to get the intersection of two columns

For instance I have table A and table B
a.data = {1,2,3,4,5,6}
b.data = {4,5,7}
If you want to lookup one value in a.data or b.data you can use FIND_IN_SET(3, b.data).
But I want to know if at least all the values of b.data are in a.data, or else if I can find
at least the intersection between b.data and a.data. So in this case {4,5}.
WHERE INTERSECT(a.data, b.data) ... something like that. How should I do this in MySQL?
update
The b.data {4,5,7} is the column data of one 1 record, so joining a.data on b.data won't work.
table A
=======
ID DATA
1 {1,2,3,4,5,6}
2 {7,9,12}
table B
=======
ID DATA
1 {4,5,7}
2 {9,10,11,12}
You can take interection of tables using INNER JOIN
have a look at Visual explaination of joins
SELECT fn_intersect_string(a.data, b.data) AS result FROM table_name;
also you can write a user defined function as:
CREATE FUNCTION fn_intersect_string(arg_str1 VARCHAR(255), arg_str2 VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
SET arg_str1 = CONCAT(arg_str1, ",");
SET #var_result = "";
WHILE(INSTR(arg_str1, ",") > 0)
DO
SET #var_val = SUBSTRING_INDEX(arg_str1, ",", 1);
SET arg_str1 = SUBSTRING(arg_str1, INSTR(arg_str1, ",") + 1);
IF(FIND_IN_SET(#var_val, arg_str2) > 0)
THEN
SET #var_result = CONCAT(#var_result, #var_val, ",");
END IF;
END WHILE;
RETURN TRIM(BOTH "," FROM #var_result);
END;
You get the intersection from an inner join:
SELECT a.data FROM a, b WHERE a.data = b.data
To decide whether b is a subset of a, you can do
SELECT b.data FROM b LEFT JOIN a ON a.data = b.data WHERE a.data IS NULL
This will compute the difference: all values from b which are not contained in a. If it is empty, then b is a subset of a.
You can use both of these approaches as subqueries inside a larger query.
If your column is of type SET, then it is stored as a number internally, and will auto-convert to that number where appropriate. The operations you describe correspond to bit-wise logical operations on those numbers. For example, the intersection can be computed using the bit-wise and of the values from two columns.
a.data & b.data AS intersection,
(a.data & b.data) <> 0 AS aAndBIntersect,
(a.data & b.data) == b.data AS bIsSubsetOfA
This requires that the type of both columns is the same, so that the same strings correspond to the same bits. To turn the result back into a string, you'd could use ELT, but with all the combination that's likely to get ugly. As an alternative, you could save the result in a temporary table with the same data type, storing it as a number and later retrieving it as a string.

IF condition in mysql

I have a contact table I wish to query when a certain condition exists. I tried the query below but am getting a syntax error.
SELECT *
FROM contact_details
WHERE contactDeleted` =0
AND IF ( contactVisibility = "private"
, SELECT * FROM contact_details
WHERE contactUserId = 1
, IF( contactVisibility = "group"
, SELECT * FROM contact_details
WHERE contactGroup = 3
)
)
If I'm understanding your question correctly (which is difficult with the lack of info you've provided. Sample datasets and expected outcomes are typically helpful), then I don't believe you need IFs at all for what you want. The following will return contacts that are not deleted and who either have (visibility = "private" and userId = 1) OR (visibility = "group" and group = 3)
SELECT *
FROM contact_details
WHERE contactDeleted = 0
AND (
(contactVisibility = "public")
OR
(contactVisibility = "private" AND contactUserId = 1)
OR
(contactVisibility = "group" AND contactGroup = 3)
)
I am assuming you want to use the IF() function and not the statement which is for stored functions..
Refer to this link for more information on that.
Notice that you have put 2 select statements in there, where the custom return values are supposed to be. So you are returning a SELECT *... now notice that in your upper level sql statement you have an AND.. so you basically writing AND SELECT *.. which will give you the syntax error.
Try using .. AND x IN (SELECT *) .. to find if x is in the returned values.
Let me also list this link to make use of an existing and well written answer which may also applicable to your question.

SQL Server 2008: Error converting data type nvarchar to float

Presently troubleshooting a problem where running this SQL query:
UPDATE tblBenchmarkData
SET OriginalValue = DataValue, OriginalUnitID = DataUnitID,
DataValue = CAST(DataValue AS float) * 1.335
WHERE
FieldDataSetID = '6956beeb-a1e7-47f2-96db-0044746ad6d5'
AND ZEGCodeID IN
(SELECT ZEGCodeID FROM tblZEGCode
WHERE(ZEGCode = 'C004') OR
(LEFT(ZEGParentCode, 4) = 'C004'))
Results in the following error:
Msg 8114, Level 16, State 5, Line 1
Error converting data type nvarchar to float.
The really odd thing is, if I change the UPDATE to SELECT to inspect the values that are retrieved are numerical values:
SELECT DataValue
FROM tblBenchmarkData
WHERE FieldDataSetID = '6956beeb-a1e7-47f2-96db-0044746ad6d5'
AND ZEGCodeID IN
(SELECT ZEGCodeID
FROM tblZEGCode WHERE(ZEGCode = 'C004') OR
(LEFT(ZEGParentCode, 4) = 'C004'))
Here are the results:
DataValue
2285260
1205310
Would like to use TRY_PARSE or something like that; however, we are running on SQL Server 2008 rather than SQL Server 2012. Does anyone have any suggestions? TIA.
It would be helpful to see the schema definition of tblBenchmarkData, but you could try using ISNUMERIC in your query. Something like:
SET DataValue = CASE WHEN ISNUMERIC(DataValue)=1 THEN CAST(DataValue AS float) * 1.335
ELSE 0 END
Order of execution not always matches one's expectations.
If you set a where clause, it generally does not mean the calculations in the select list will only be applied to the rows that match that where. SQL Server may easily decide to do a bulk calculation and then filter out unwanted rows.
That said, you can easily write try_parse yourself:
create function dbo.try_parse(#v nvarchar(30))
returns float
with schemabinding, returns null on null input
as
begin
if isnumeric(#v) = 1
return cast(#v as float);
return null;
end;
So starting with your update query that's giving an error (please forgive me for rewriting it for my own clarity):
UPDATE B
SET
OriginalValue = DataValue,
OriginalUnitID = DataUnitID,
DataValue = CAST(DataValue AS float) * 1.335
FROM
dbo.tblBenchmarkData B
INNER JOIN dbo.tblZEGCode Z
ON B.ZEGCodeID = Z.ZEGCodeID
WHERE
B.FieldDataSetID = '6956beeb-a1e7-47f2-96db-0044746ad6d5'
AND (
Z.ZEGCode = 'C004' OR
Z.ZEGParentCode LIKE 'C004%'
)
I think you'll find that a SELECT statement with exactly the same expressions will give the same error:
SELECT
OriginalValue,
DataValue NewOriginalValue,
OriginalUnitID,
DataUnitID OriginalUnitID,
DataValue,
CAST(DataValue AS float) * 1.335 NewDataValue
FROM
dbo.tblBenchmarkData B
INNER JOIN dbo.tblZEGCode Z
ON B.ZEGCodeID = Z.ZEGCodeID
WHERE
B.FieldDataSetID = '6956beeb-a1e7-47f2-96db-0044746ad6d5'
AND (
Z.ZEGCode = 'C004' OR
Z.ZEGParentCode LIKE 'C004%'
)
This should show you the rows that can't convert:
SELECT
B.*
FROM
dbo.tblBenchmarkData B
INNER JOIN dbo.tblZEGCode Z
ON B.ZEGCodeID = Z.ZEGCodeID
WHERE
B.FieldDataSetID = '6956beeb-a1e7-47f2-96db-0044746ad6d5'
AND (
Z.ZEGCode = 'C004' OR
Z.ZEGParentCode LIKE 'C004%'
)
AND IsNumeric(DataValue) = 0
-- AND IsNumeric(DataValue + 'E0') = 0 -- try this if the prior doesn't work
The trick in the last commented line is to tack on things to the string to force only valid numbers to be numeric. For example, if you wanted only integers, IsNumeric(DataValue + '.0E0') = 0 would show you those that aren't.