DB2 - handling columns defined as null - db2

Let's say you have three textboxes that you can use to search for data. Each textbox will correspond to a column on the DB2 table. The search string you enter will be inserted into the where clause. For example, you have First-Name, Last-Name, and Phone Number. If you don't enter data into a particular textbox, I default its value in the where clause to '_', the wildcard character to select everything. Also, lets say Phone Number is defined as NULL on the table.
Cursor1 will be used if the user has entered a Phone number to search for. So the where clause will look something like this:
Where FIRST_NAME like :firstname AND
LAST_NAME like :lastname AND
PHONE_NBR like :number
This works when data is entered for phone number. But if a search is done for First Name only, the cursor returns partial or no results because the :number host variable will be populated with the "_' wildcard. PHONE_NBR like '_' will only return the rows that have a real value. If there is a null for PHONE_NBR on a row that matches the First Name you searched for, that row won't show up. So I created a second cursor.
Cursor2 will be used if the user HAS NOT entered a Phone number to search for. The Where clause looks something like this.
Where FIRST_NAME like :firstname AND
LAST_NAME like :lastname AND
(PHONE_NBR like :number OR
PHONE_NBR IS NULL)
So again, if a search was done for a first name only, and some values in PHONE_NBR have data, some are null, EVERYTHING that matches the first name that is searched for will show in the results - which is good. For the rows with values in PHONE_NBR, PHONE_NBR like '_' will get those. For the rows with null in PHONE_NBR, PHONE_NBR IS NULL will get those.
This is a minor yet necessary difference. Because of this minor difference, I would like to combine these two cursors into one. How can that be done to achieve the same results?

Ian, i think the difference is if the user supplies a number he doesn't want to return rows with a null. using cursor 2 all the time would return rows with null along with matching numbers.
You could try a CASE statement based on :number...though i'm not sure if you can use a CASE with "is null" syntax. i know you could if you were just checking for different values (equal to, less than, etc).

The way I'd recommend handling this is by building the query to supply conditions only on the columns where the user enters data. That is:
If the user enters something in the First_Name text box, you have a condition such as:
FIRST_NAME LIKE '...'
If the user enters something in the Last_Name text box, you have a condition such as:
LAST_NAME LIKE '...'
If the user enters something in the Phone_Nbr text box, you have a condition such as:
PHONE_NBR LIKE '...'
In each case, the 3 dots represent a string derived from the information typed into the text box, and the function that does that conversion is fully aware of quoting (to avoid SQL injection).
If the user types in two or three of the text boxes, the independent conditions are joined by an AND. If the user types nothing, you can generate a tautology such as 1 = 1 as the condition.
You then append that condition to the WHERE clause of the SQL statement, and then arrange to execute it.
This is the technique made available by the CONSTRUCT statement in IBM Informix 4GL; it has been available there since 1986. It allows for conditions other than just LIKE, such as equals, less than, greater than or equal to, ranges, or even a list of alternatives (for an IN ('val1', 'val2', ...) condition), and it can be used for all data types.

Related

Doctrine : PostgreSQL group by different than select

I have two tables :
user and activityHistory (that has a key to the user)
I am trying to query activityHistory while grouping by user and postgreSQL does not allow me to do that WHILE Sqlite does allow me
return $qb
->select('a.id')
->leftJoin('a.user', 'user')
->leftJoin(
ActivityHistory::class,
'b',
'WITH',
'a.id = b.id AND a.createdAt > b.createdAt'
)
->groupBy('user.id')
->orderBy( 'a.createdAt','ASC' )
->getQuery()->getArrayResult();
I am getting this error only with postgreSQL : Grouping error: 7 ERROR: column "a0_.id" must appear in the GROUP BY clause or be used in an aggregate function
The point is I don't want to groupBy activityHistory id, I only want it to be selected, how can I do ? (I heard about aggregate but I think this works only with functions like SUM etc)
First of all, let's clarify how aggregation works. Aggregation is the act of grouping by certain field(s) and selecting either those fields or calling aggregation functions and passing ungrouped fields.
You misunderstand how this works - hence the question -, but let me provide you a few very simple examples:
Example 1
Let's consider that there is a town and there are individuals living in that town. Each individual has an eye color, but, if you are wondering what the eye color of the people of the town is, then your question does not make sense, because the group itself does not have an eye color, unless specified otherwise.
Example 2
Let's modify the example above by grouping the people of the town by eye color. Such an aggregation will have a row for all existent eye colors and you can select the eye color, along with the average age, number of individuals, etc. as you like, because you are grouping by eye color
Your example
You have users and they are performing actions. So, an activity is performed by a single user, but a user may perform many activities. So, if you want to group by your user id, then the "eye color" that you are not grouping by here is the history id.
You will have a single record for any user, so you are grouping multiple history items into the same row and after the grouping, asking about the history item's id does not exist.
But, you can use string_agg(some_column, ',') which will take all the values you have and put them all into a string of values separated by comma.
You can explode(',', '$yourvalues) in PHP to convert such a value into an array.

Using MYSQLI to select rows in which part of a column matches part of an input

I have a database in which one of the columns contains a series of information 'tags' about the row that are stored as a comma-separated list (a string) of dynamic length. I am using mysqli within PHP, and I want to select rows in which any of these items match any of the items in an input string.
For example, there could be a row describing an apple, containing the tags: "tasty, red, fruit, sour, sweet, green." I want this to show up as a result in a query like: "SELECT * FROM table WHERE info tags IN ('blue', 'red', 'yellow')", because it has at least one item ("red") overlapping. Kind of like "array_intersect" in PHP.
I think I could use IN if each row had only one tag, and I could use LIKE if I used only one input tag, but both are of dynamic length. I know I can loop over all the input tags, but I was hoping to put this in a single query. Is that possible? If not, can I use a different structure to store the tags in the database to make this possible (something other than a comma separated string)?
I think the best would be to create tags table (id + label) then separate "table_tags" table which holds table_id and tag_id.
that means using JOINS to get the final result.
another (but lazy) solution would be to prefix and suffix tags with commas so the full column contains something like:
,tasty,red,fruit,sour,sweet,green,
and you can do a LIKE search without being worried about overlapping words (i.e red vs bored) and still get a proper match by using LIKE '%,WORD,%'

How to get best matching products by number of matches in postgres

Postgres 9.1 shopping cart contains product table
create table products (
id char(30) primary key,
name char(50),
description text );
Cart has search field. If something is entered into it, autocomplete dropdown must show best matching products ordered by number products matched to this criteria.
How to implement such query in postgres 9.1 ? Search should performed by name and description fields in products table.
It is sufficient to use substring match. Full text search with sophisticated text match is not strictly required.
Update
Word joote can be part of product name or description.
For example for first match in image text may contain
.. See on jootetina ..
and for other product
Kasutatakse jootetina tegemiseks ..
and another with upper case
Jootetina on see ..
In this case query should return word jootetina and matching count 3.
How to make it working like auotcomplete which happens when search term is typed in Google Chrome address bar ?
How to implement this ?
Or if this is difficult, how to return word jootetina form all those texts which matches search term joote ?
select word, count(distinct id) as total
from (
select id,
regexp_split_to_table(name || ' ' || description, E'\\s+') as word
from products
) s
where position(lower('joote') in lower(word)) > 0
group by word
order by 2 desc, 1
First of all, do not use the data type char(n). That's a misunderstanding, you want varchar(n) or just text. I suggest text.
Any downsides of using data type "text" for storing strings?
With that fixed, you need a smart index-based approach or this is a performance nightmare. Either trigram GIN indexes on the original columns or a text_pattern_ops btree index on a materialized view of individual words (with count).
Pattern matching with LIKE, SIMILAR TO or regular expressions in PostgreSQL
The MV approach is probably superior for many repetitions among words.

Web2py multi-digit value from dropdown gets treated as a list of values

I have an SQLFORM.factory field:
Field('course', requires=IS_IN_SET(course_query, multiple=True), widget=SQLFORM.widgets.multiple.widget),
that gets its contents from a query:
course_query = external_db.executesql("SELECT course_id, course_title FROM course")
I want the user to be able to select one or more courses, which they can. Upon submit, the course_id(s) they submitted are captured by:
courses = request.vars.course
then I loop through the returned course_id's and insert them into a table:
if form.process().accepted:
for course in courses:
external_db.enrolment.insert(student_id=student, course_id=course)
response.flash = 'Record saved'
This works fine when the user selects more than one course. Each submitted record gets inserted into the database with the correct course_id. But if the user selects only one course, which happens to have a 2-digit ID, only the first digit gets inserted.
I have found that if a single 2-digit course_id value is submitted Web2py treats it as a list, with each digit as an individual element.
How do I make it treat double-digit values from request.vars as a single value instead of a list of values?
Thanks all.
The integer values returned from the browser are actually strings, and when you have just a single value selected, you end up with a single string rather than a list of strings. In Python, when you iterate over a string, you iterate over the individual characters in the string. In this case, a double-digit value is just a two-character string, so your for loop will run once for each digit.
A simple solution is to use form.vars.course instead of request.vars.course. The former will be a list even when only a single value has been selected, so you will be iterating over a list containing a single item rather than iterating over a two-character string.
Note, form.vars will not be populated with the processed form values until after you have called form.process(), so you may need to adjust the order of your code.
As an aside, there is no need to specify the widget argument, as you will get that widget automatically by virtue of using the IS_IN_SET(..., multiple=True) validator.

Reporting Services and Dynamic Fields

I'm new to reporting services so this question might be insane. I am looking for a way to create an empty 'template' report (that is basically a form letter) rather than having to create one for every client in our system. Part of this form letter is a section that has any number of 25 specific fields. The section is arranged as such:
Name: Jesse James
Date of Birth: 1/1/1800
Address: 123 Blah Blah Street
Anywhere, USA 12345
Another Field: Data
Another Field2: More Data
Those (and any of the other fields the client specifies) could be arranged in any order and the label on the left could be whatever the client decides (example: 'DOB' instead of 'Date of Birth'). IDEALLY, I'd like to be able to have a web interface where you can click on the fields you want, specify the order in which they'll appear, and specify what the custom label is. I figured out a way to specify the labels and order them (and load them 'dynamically' in the report) but I wanted to take it one step further if I could and allow dynamic field (right side) selection and ordering. The catch is, I want to do this without using dynamic SQL. I went down the path of having a configuration table that contained an ordinal, custom label text, and the actual column name and attempting to join that table with the table that actually contains the data via information_schema.columns. Maybe querying ALL of the potential fields and having an INNER JOIN do my filtering (if there's a match from the 'configuration' table, etc). That doesn't work like I thought it would :) I guess I was thinking I could simulate the functionality of a dataset (it having the value and field name baked in to the object). I realize that this isn't the optimal tool to be attempting such a feat, it's just what I'm forced to work with.
The configuration table would hold the configuration for many customers/reports and I would be filtering by a customer ID. The config table would look somthing like this:
CustID LabelText ColumnName Ordinal
1 First Name FName 1
1 Last Name LName 2
1 Date of Birth DOBirth 3
2 Client ID ClientID 1
2 Last Name LName 2
2 Address 1 Address1 3
2 Address 2 Address2 4
All that to say:
Is there a way to pull off the above mentioned query?
Am I being too picky about not using dynamic SQL as the section in question will only be pulling back one row? However, there are hundreds of clients running this report (letter) two or three times a day.
Also, keep in mind I am not trying to dynamically create text boxes on the report. I will either just concatenate the fields into a single string and dump that into a text box or I'll have multiple reports each with a set number of text boxes expecting a generic field name ("field1",etc). The more I type, the crazier this sounds...
If there isn't a way to do this I'll likely finagle something in custom code; but my OCD side wants to believe there is SQL beyond my current powers that can do this in a slicker way.
Not sure why you need this all returned in one row: it seems like SSRS would want this normalized further: return a row for every row in the configuration table for the current report. If you really need to concatenate then do that in Embedded code in the report, or consider just putting a table in the form letter. The query below makes some assumptions about your configuration table. Does it only hold the cofiguration for the current report, or does it hold the config for many customers/reports at once? Also you didn't give much info about how you'll filter to the appropriate record, so I just used a customer ID.
SELECT
config.ordinal,
config.LabelText,
CASE config.ColumnName
WHEN 'FName' THEN DataRecord.FirstName
WHEN 'LName' THEN DataRecord.LastName
WHEN 'ClientID' THEN DataRecord.ClientID
WHEN 'DOBirth' THEN DataRecord.DOB
WHEN 'Address' THEN DataRecord.Address
WHEN 'Field' THEN DataRecord.Field
WHEN 'Field2' THEN DataRecord.Field2
ELSE
NULL
END AS response
FROM
ConfigurationTable AS config
LEFT OUTER JOIN
DataTable AS DataRecord
ON config.CustID = DataRecord.CustomerID
WHERE DataRecord.CustomerID = #CustID
ORDER BY
config.Ordinal
There are other ways to do this, in SSRS or in SQL, depends on more details of your requirements.