Dynamic substitute variable - sql

I'm looking to find out if it is possible for substitute variables to be 'dynamic'.
For example, I want to use a popup for a repeatable script that prompts the user to enter two variables: colour and number. However, I need the script to still run even if the user does not enter one variable. In other words it sets the variable to IS NOT NULL if left blank.
Is this even possible?
select user
from table
where colour = '&colour'
and number = &number

This isn't really something Oracle is meant to do (substitution variables are generally just for quick ad-hoc things, and serious reporting should be handled on the front-end).
As a bit of a hack, you could do something like this:
select user
from table
where (colour = '&&colour' OR '&&colour' IS NULL)
and (number = to_number('&&number') OR to_number('&&number') IS NULL);
Just as a side-note, I was a bit surprised to find that the empty string is treated as NULL in Oracle.
Apparently, '' IS NULL evaluates to true, and '' = '' evaluates to false (which makes sense, since any equality check to NULL is false)

For these kinds of queries, I always use the nvl function; example as folows:
select user
from table
where colour = nvl('&colour', colour)
and number = nvl(&number,number)
;

Related

Array contains nulls PostgreSQL

I have SQL query in PostgreSQL which filters particular fields in the form of an array
for being in bigint range. I would like to add the possibility not to filter out null values. With existing queries, null values for all of the fields are filtered out:
select *
from table_test
where '[0,2147483647]'::int8range #> ALL(ARRAY[fields])
And I would like to do something like this, only here I check against the whole array while I would want to check against each field:
select count(*) from dbm.inventory_source where '[0,2147483647]'::int8range #> ALL(ARRAY[id, exchange_id, min_cpm_micros])
or (array[id, exchange_id, min_cpm_micros]) is null
Also, I would not want to check each field for null instead I would like to check nulls for the whole array of fields.
I pass the names of the fields like one string into query (called fields) and it is the reason I do not want to check each field separately. Such implementation was created to have more generic queries for multiple tables.
How can I fix this query?
I would like to add the possibility not to filter out null values.
Based on this, I would expect logic like:
where '[0,2147483647]'::int8range #> ALL(ARRAY[field_1, field_2, field_3]) or
(field_1 is null and field_2 is null and field_3 is null)
I am unclear if you want to allow all values to be NULL or any of them. The above is for all of them. If you want any, change the ands to ors.
If I understand correctly, presumably you're looking for something like this:
SELECT *
FROM table_test
WHERE '[0,2147483647]'::int8range #> ALL(ARRAY[fields]) IS NOT FALSE
(yeah, sorry, all I did was add three words and capitalize your keywords)
What's this doing? Let's start from the top.
Let's look at what we want from all the conditional stuff. Specifically, it seems we want the condition to return TRUE for every array wherein each value of the array satisfies one of these two conditions:
The value falls within the range [0,2147483647]
The value is NULL
It's useful here to keep in mind the exact meaning of NULL in SQL: it's a value that we don't know. This is why NULL propagates in most operations, and thinking of it that way makes it easier to predict how the database will treat it. In fact, let's replace it with ? for some examples. Why doesn't ? = ? return TRUE? It's because we don't know what either of those values are, so we don't know if they're equal, so the expression evaluates to some unknown value, NULL. What about something like ? + 1? Well, we don't know what the sum is, so it's also NULL. Similarly, ? AND TRUE depends entirely on what the first value is, and we don't know what it is, so we write NULL.
This is where it gets fun: ? AND FALSE will always be false, no matter what our unknown value is, so it evaluates to FALSE instead of NULL. Similarly, ? OR TRUE must evaluate to TRUE.
Now, revisiting our two conditions, we see that your code already checked for condition 1. What about condition 2? Well, think about how ALL works, and what it's really telling you. It's basically evaluating your condition for each entry in your array, then combining all of those with AND to tell you whether or not it's true for all of the entries. This means that your test, specifically the expression
'[0,2147483647]'::int8range #> ALL(ARRAY[fields])
returns TRUE, FALSE, or NULL for each of the entries in the array, then combines those results using AND. Since we know that
TRUE AND TRUE returns TRUE
NULL AND TRUE returns NULL
x AND FALSE returns FALSE for any x
we can safely say that your code will return FALSE if and only if the array contains a value outside of your given range; otherwise, it will return TRUE or NULL. On the other hand, we want to get TRUE regardless of whether your code says TRUE or NULL; in other words, whenever it evaluates to anything other than FALSE. Luckily, there's a predicate for that:
IS NOT NULL
Well! That was pretty complicated to think about, but after finding the solution, it seems almost offensively simple! Way to make me feel stupid.
Check it out here.

Can 2 character length variables cause SQL injection vulnerability?

I am taking a text input from the user, then converting it into 2 character length strings (2-Grams)
For example
RX480 becomes
"rx","x4","48","80"
Now if I directly query server like below can they somehow make SQL injection?
select *
from myTable
where myVariable in ('rx', 'x4', '48', '80')
SQL injection is not a matter of length of anything.
It happens when someone adds code to your existing query. They do this by sending in the malicious extra code as a form submission (or something). When your SQL code executes, it doesn't realize that there are more than one thing to do. It just executes what it's told.
You could start with a simple query like:
select *
from thisTable
where something=$something
So you could end up with a query that looks like:
select *
from thisTable
where something=; DROP TABLE employees;
This is an odd example. But it does more or less show why it's dangerous. The first query will fail, but who cares? The second one will actually work. And if you have a table named "employees", well, you don't anymore.
Two characters in this case are sufficient to make an error in query and possibly reveal some information about it. For example try to use string ')480 and watch how your application will behave.
Although not much of an answer, this really doesn't fit in a comment.
Your code scans a table checking to see if a column value matches any pair of consecutive characters from a user supplied string. Expressed in another way:
declare #SearchString as VarChar(10) = 'Voot';
select Buffer, case
when DataLength( Buffer ) != 2 then 0 -- NB: Len() right trims.
when PatIndex( '%' + Buffer + '%', #SearchString ) != 0 then 1
else 0 end as Match
from ( values
( 'vo' ), ( 'go' ), ( 'n ' ), ( 'po' ), ( 'et' ), ( 'ry' ),
( 'oo' ) ) as Samples( Buffer );
In this case you could simply pass the value of #SearchString as a parameter and avoid the issue of the IN clause.
Alternatively, the character pairs could be passed as a table parameter and used with IN: where Buffer in ( select CharacterPair from #CharacterPairs ).
As far as SQL injection goes, limiting the text to character pairs does preclude adding complete statements. It does, as others have noted, allow for corrupting the query and causing it to fail. That, in my mind, constitutes a problem.
I'm still trying to imagine a use-case for this rather odd pattern matching. It won't match a column value longer (or shorter) than two characters against a search string.
There definitely should be a canonical answer to all these innumerable "if I have [some special kind of data treatment] will be my query still vulnerable?" questions.
First of all you should ask yourself - why you are looking to buy yourself such an indulgence? What is the reason? Why do you want add an exception to your data processing? Why separate your data into the sheep and the goats, telling yourself "this data is "safe", I won't process it properly and that data is unsafe, I'll have to do something?
The only reason why such a question could even appear is your application architecture. Or, rather, lack of architecture. Because only in spaghetti code, where user input is added directly to the query, such a question can be ever occur. Otherwise, your database layer should be able to process any kind of data, being totally ignorant of its nature, origin or alleged "safety".

Example of three valued logic in SQL Server

I understand that SQL uses three valued logic but I am having trouble understanding how to use this in practice, especially why TRUE || NULL = True and FALSE && NULL = False instead of evaluating to null.
Here are the three valued truth tables that apply to SQL Server:
I found a couple explanations of three valued logic online but I cannot find any real code examples of this in use. Can someone show me a code example using three valued logic to help me understand this a little better?
An example of TRUE || NULL = True would be
declare #x as int = null;
if 1=1 or #x/1=1
print 'true'
An example of FALSE && NULL = False would be
declare #x as int = null;
if not(1=2 and #x/1=1)
print 'false'
True && NULL is neither True or False. It's just NULL.
Whether that will evaluate as True, False, or an Error in a boolean expression depends on what happens on your system when you evaluate NULL by itself as a boolean. Sql Server will do everything it can to avoid choosing, but when forced you'll pretty much never see a positive (True) result.
Generally speaking from a user standpoint, you don't want a Boolean expression to evaluate to NULL.
Writing SQL typically involves writing queries to explicitly avoid NULL values in Boolean expressions. IMX, developers would consider using three valued logic intentionally would be considered an abuse of three valued logic. A properly written query should handle NULLs and understand them. You don't write them in such a way that they happen to work right when something is NULL. Usually this involves COALESCE() or IS NULL or IS NOT NULL somewhere.
It is, however, vital that you understand the logic, because NULLs exist and are unavoidable for most real-world data.
For example, let's say I'm working on a table of students. The table has First, Middle, and Last name fields. I want to know the list of students that don't have a middle name. Now, some applications will store an empty string, '', and some applications will store a NULL value, and some applications might do both (and some RDBMSs like Oracle treat empty strings as NULLs). If you were unsure, you could write it as:
SELECT *
FROM Student
WHERE MiddleName = ''
OR MiddleName IS NULL;
The other common scenario is when you're OUTER JOINing to another table. Let's say you're comparing the paychecks for teachers. You have a table for Checks, and a table for CheckDetail. You want to know how much teachers pay for Benefits. Your report needs to list all teachers, even if they're contractors who don't pay for benefits because they don't get any:
SELECT Check.Employee_Id,
SUM(CheckDetail.Amount) AS BenefitsDeductions
FROM Check
LEFT JOIN CheckDetail
ON Check.Id = CheckDetail.CheckId
AND CheckDetail.LineItemType = 'Benefits'
GROUP BY Check.Employee_Id;
You run your report, and you notice that your contractor teachers show NULL for BenefitsDeductions. Oops. You need to make sure that shows up as a zero:
SELECT Check.Employee_Id,
COALESCE(SUM(CheckDetail.Amount),0) AS BenefitsDeductions
FROM Check
LEFT JOIN CheckDetail
ON Check.Id = CheckDetail.CheckId
AND CheckDetail.LineItemType = 'Benefits'
GROUP BY Check.Employee_Id;
So you try that, and it works. No NULL values! But... a few days later, your users report that teachers who used to be contractors are showing up with 0s even though they're paying for benefits now. You've got to COALESCE before the SUM to keep those amounts:
SELECT Check.Employee_Id,
SUM(COALESCE(CheckDetail.Amount,0)) AS BenefitsDeductions
FROM Check
LEFT JOIN CheckDetail
ON Check.Id = CheckDetail.CheckId
AND CheckDetail.LineItemType = 'Benefits'
GROUP BY Check.Employee_Id;
Finding these kinds of corner cases and exceptions is what writing SQL is all about.
The code example by user4955163 is a great visualization of this, however I just wanted to step back in to address the first segment of the question:
...especially why TRUE || NULL = True and FALSE && NULL = False instead
of evaluating to null...
TRUE || NULL = True
This is because the or operator will short-circuit if one operand is already known to be true. No matter what the second operand is (even if unknown, ie. "NULL"), it wouldn't make the expression false since we already know the other operand is true. or only needs one operand to be true, to evaluate to true.
FALSE && NULL = False
This is because the and operator will short-circuit if one operand is already known to be false. No matter what the second operand is (even if unknown, ie. "NULL"), it wouldn't make the expression true since we already know the other operand is false. and needs both operands to be true to evaluate to true.
To use a nullable variable you just need to check NULL conditions (using IS NULL) before checking the value.
e.g. IF #a IS NOT NULL AND #a = 1

How to create list as a parameter in SSRS?

I have a report in 2005 SSRS which I want to add a parameter to. The parameter would be comprised of a group of zip codes, but be selected as a single item in the list.
For example, I would like to have 5 zip codes as one selection in the list and 3 for another, etc:
Select 11111,22222,33333,44444,55555,66666 AS Boondock
Select 77777,88888,99999 AS Timbuck
Select Zip Codes NOT IN (11111-99999) AS Everything Else
So my selections in the dropdown would be:
Boondock
Timbuck
Everything Else
Can anyone help me with how I should go about creating this parameter?
Create a simple string parameter to present to the user. Let's call it ZipCodeSet.
Create a dataset that examines the #ZipCodeSet parameter and returns the appropriate list of zip codes. Call it ZipCodeSelection.
Create an internal multivaue parameter that uses ZipCodeSelection as both its Available Values and Default Values. Call it SelectedZipCodes.
Use SelectedZipCodes in your report's datasets.
The easiest solution here would probably to use a Calculated Field on your dataset, called LocationDescription, for example:
=SWITCH(Fields!ZipCode >= 11111 and Fields!ZipCode <= 66666, "Boondock", Fields!ZipCode >= 77777 and Fields!ZipCode <= 99999, "Timbuck",True, "Everywhere Else")
The lone true statement at the end is due to the SWITCH expression reading left-to-right and exiting once it evaluates one of the switches as TRUE. This way for each of the items in your table of ZipCodes you will always end up with a TRUE result.
I assume you're evaluating a range of ZipCodes, and not exact values of 11111,22222, and so on? If so, the switch will have more values. A sample of your data would help if you want an exact answer.
Once you have built your Calculated Field, you can then set up a Parameter (called #LocationParameter) with available values based on a query of your LocationDescription field, then just filter your dataset using:
Expression:
= Fields!LocationDescription
Operator: =
Value:
#LocationParameter
(if you want multiple selections on your parameter, change the operator to IN)
Hope that helps.

SQL - Conditionally joining two columns in same table into one

I am working with a table that contains two versions of stored information. To simplify it, one column contains the old description of a file run while another column contains the updated standard for displaying ran files. It gets more complicated in that the older column can have multiple standards within itself. The table:
Old Column New Column
Desc: LGX/101/rpt null
null Home
Print: LGX/234/rpt null
null Print
null Page
I need to combine the two columns into one, but I also need to delete the "Print: " and "Desc: " string from the beginning of the old column values. Any suggestions? Let me know if/when I'm forgetting something you need to know!
(I am writing in Cache SQL, but I'd just like a general approach to my problem, I can figure out the specifics past that.)
EDIT: the condition is that if substr(oldcol,1,5) = 'desc: ' then substr(oldcol,6)
else if substr(oldcol,1,6) = 'print: ' then substr(oldcol,7) etc. So as to take out the "desc: " and the "print: " to sanitize the data somewhat.
EDIT2: I want to make the table look like this:
Col
LGX/101/rpt
Home
LGX/234/rpt
Print
Page
It's difficult to understand what you are looking for exactly. Does the above represent before/after, or both columns that need combining/merging.
My guess is that COALESCE might be able to help you. It takes a bunch of parameters and returns the first non NULL.
It looks like you're wanting to grab values from new if old is NULL and old if new is null. To do that you can use a case statement in your SQL. I know CASE statements are supported by MySQL, I'm not sure if they'll help you here.
SELECT (CASE WHEN old_col IS NULL THEN new_col ELSE old_col END) as val FROM table_name
This will grab new_col if old_col is NULL, otherwise it will grab old_col.
You can remove the Print: and Desc: by using a combination of CharIndex and Substring functions. Here it goes
SELECT CASE WHEN CHARINDEX(':',COALESCE(OldCol,NewCol)) > 0 THEN
SUBSTRING(COALESCE(OldCol,NewCol),CHARINDEX(':',COALESCE(OldCol,NewCol))+1,8000)
ELSE
COALESCE(OldCol,NewCol)
END AS Newcolvalue
FROM [SchemaName].[TableName]
The Charindex gives the position of the character/string you are searching for.
So you get the position of ":" in the computed column(Coalesce part) and pass that value to the substring function. Then add +1 to the position which indicates the substring function to get the part after the ":". Now you have a string without "Desc:" and "Print:".
Hope this helps.