I'm having a report which is called many times but I want the order to be different each time.
How can I change the "order by" depending on a variable.
For example
report print_label()
If is_reprint
then
order by rpt.item_code, rpt.description
else
order by rpt.description, rpt.item_code
end if
I tried passing in a variable when calling the report:
let scratch = "rpt.item_code, rpt.description"
start print_label(scratch)
And in the report I did:
order by scratch
But it didn't work...... Any other suggestions ??
Thank you!
The technique I've used for that type of problem is along the likes of
REPORT report_name(x)
DEFINE x RECORD
param1,param2, ..., paramN ...,
sort_method ...,
data ...
END RECORD
ORDER [EXTERNAL] BY x.param1, x.param2, ..., x.paramN
BEFORE GROUP OF x.param1
CASE
WHEN x.sort_method ...
PRINT ...
WHEN x.sort_method ...
PRINT ...
END CASE
BEFORE GROUP OF x.param2
# similar technique as above
...
BEFORE GROUP OF x.paramN
# similar technique as above
ON EVERY ROW
PRINT ...
AFTER GROUP OF x.paramN
# similar technique as above
...
AFTER GROUP OF x.param2
# similar technique as above
AFTER GROUP OF x.param1
# similar technique as above
... and then in the 4gl that calls the REPORT, populate x.param1, x.param2, ..., x.paramN with the desired parameters used for sorting e.g.
CASE x.sort_method
WHEN "product,branch"
LET x.param1 = x.data.product_code
LET x.param2 = x.data.branch_code
WHEN "branch,product"
LET x.param1 = x.data.branch_code
LET x.param2 = x.data.product_code
END CASE
OUTPUT TO REPORT report_name(x.*)
So as per my example, that's a technique I've seen and used for things like stock reports. The warehouse/branch/store manager wants to see things ordered by warehouse/branch/store, and then by product/sku/item, whilst a product manager wants to see things ordered by product/sku/item, and then warehouse/branch/store. More analytical reports with more potential parameters can be done using the same technique. I think the record I have seen is 6. So in that case, much better with 1 report covering all 6!=720 potential combinations, rather than writing a separate report for each possible order combination.
So probably similar to Jonathan option 1, although I don't have the same reservations about the complexity. I don't recall catching at code review any of my junior developers getting it badly wrong. In fact if the report is generic enough, you'll find that you don't need to touch it too often.
Short answer
The ORDER BY clause in an I4GL REPORT function has crucial effects on how the code implementing the report is generated. It is simply not feasible to rewire the generated code like that at run-time.
Therefore, you can't achieve your desired result directly.
Notes
Note that you should probably be using ORDER EXTERNAL BY rather than ORDER BY — the difference is that with EXTERNAL, the report can assume the data is presented in the correct order, but without, the report has to save up all the data (in a temporary table in the database), then select the data from the table in the required sorted order, making into into a two-pass report.
If you're brave and have the I4GL c-code compiler, you should take a look at the code generated for a report, but be aware it is some of the scariest code you're likely to encounter in a long time. It uses all sorts of tricks that you wouldn't dream of using yourself.
Workaround solutions — in outline
OK; so you can do it directly. What are your options? In my view, you have two options:
Use two parameters specifically for choosing the ordering, and then use an ORDER BY (without EXTERNAL) clause that always lists them in a fixed order. However, when it comes time to use the report, choose which sequence you want the arguments in.
Write two reports that differ only in the report name and the ORDER EXTERNAL BY clause. Arrange to call the correct report depending on which order you want.
Of these, option 2 is by far the simpler — except for the maintenance issue. Most likely, you'd arrange to generate the code from a single copy. That is, you'd save REPORT print_label_code_desc in one file, and then arrange to edit that into REPORT print_label_desc_code (use sed, for example) — and the edit would reverse the order of the names in the ORDER BY clause. This isn't all that hard to do in a makefile, though it requires some care.
Option 1 in practice
What does option 1 look like in practice?
DECLARE c CURSOR FOR
SELECT * FROM SomeTable
START REPORT print_label -- optional specification of destination, etc.
FOREACH c INTO rpt.*
IF do_item_desc THEN
OUTPUT TO REPORT print_label(rpt.item_code, rpt.description, rpt.*)
ELSE
OUTPUT TO REPORT print_label(rpt.description, rpt.item_code, rpt.*)
END IF
END FOREACH
FINISH REPORT print_label
The report function itself might look like:
REPORT print_label(col1, col2, rpt)
DEFINE col1 CHAR(40)
DEFINE col2 CHAR(40)
DEFINE rpt RECORD LIKE SomeTable.*
ORDER BY col1, col2
FORMAT
FIRST PAGE HEADER
…
BEFORE GROUP OF col1
…
BEFORE GROUP OF col2
…
ON EVERY ROW
…
AFTER GROUP OF col1
…
AFTER GROUP OF col2
…
ON LAST ROW
…
END REPORT
Apologies for any mistakes in the outline code; it is a while since I last wrote any I4GL code.
The key point is that the ordered by values are passed specially to the report and used solely for controlling its organization. You may need to
be able to print different details in the BGO (shorthand for BEFORE GROUP OF; AGO for AFTER GROUP OF) sections for the two columns. That will typically be handled by (gasp) global variables — this is I4GL and they are the normal way of doing business. Actually, they should be module variables rather than global variables if the report driver code (the code which calls START REPORT, OUTPUT TO REPORT and FINISH REPORT) is in the same file as the report itself. You need this because in general the reporting at the group levels (in the BGO and AGO blocks) will need different titles or labels depending on whether you're sorting code before description or vice versa. Note that the meaning of the group aggregates change depending on the order in the ORDER BY clause.
Note that not every report necessarily lends itself to such reordering. Simply running the BGO and AGO blocks in a different order is not sufficient to make the report output look sensible. In that case, you will fall back onto option 2 — or option 2A, which is write two separate reports that don't pretend to be just a reordering of the ORDER BY clause because the formatting of the data needs to be different depending on the ORDER BY clause.
As you can see, this requires some care — quite a bit more care than the alternative (option 2). If you use dynamic SQL to create the SELECT statement, you can arrange to put the right ORDER BY clause into the string that is then prepared so that the cursor will fetch the data in the correct order — allowing you to use ORDER EXTERNAL BY after all.
Summary
If you're a newcomer to I4GL, go with option 2. If your team is not reasonably experienced in I4GL, go with option 2. I don't like it very much, but it is the way that can be handled easily and is readily understood by yourself, your current colleagues, and those still to come.
If you're reasonably comfortable with I4GL and your team is reasonably experienced with I4GL — and the report layout really lends itself to being reorganized dynamically — then consider option 1. It is trickier, but I've done worse things in I4GL in times past.
You can have a case statement within the order by clause as follows :
order by
case
when 1 = 1 then
rpt.item_code, rpt.description
else
rpt.description, rpt.item_code
end
You can use prepare:
let query_txt="select ... "
If is_reprint then
let query_txt=query_txt clipped, " order by rpt.item_code,
rpt.description"
else
let query_txt=query_txt clipped, " order by rpt.description,
rpt.item_code"
end if
prepare statement1 from query_txt
declare cursor_name cursor for statement1
And now start report, use foreach etc, etc...
P.S. You must define query_txt as char long enough for whole text.
I am using a SQL query for a specified and faster search in my Rails app, currently I have the following query that works wonderfully for gathering the data from the initial search:
SELECT * FROM selected_tables
WHERE field1 LIKE '%#{present_param}'
AND field2 LIKE '%#{present_param2}'
And so on like that, with each LIKE line only appearing if the relevant parameter is present from the form.
So I am now able to get back a large amount of results from this query, but they're not ordered in any helpful way. I need some way of ordering the results based on their relevance to the original user input from the form, but I can't seem to find anything on google about it. Is there a way in SQL (specifically postgresql) that I can order the results based on this?
To be clear, when I say relevance I mean that a given search keyword should be in the title or company name for the result, not just present somewhere in the content.
For example: if you search "Sony" you get Sony Electronics first, not another listing containing Sony somewhere in the middle of its name.
I ended up using a series of Case/When statements that were weighted with various integer scores to apply priority to my results. They work wonderfully and turned out something like this:
SELECT title, company, user, CASE
WHEN upper(company_name) LIKE '%#{word[0].upcase}%' THEN 3
WHEN upper(company_name) LIKE '%#{company_name.upcase}%' THEN 2
ELSE 0 END as score
FROM selected_tables
WHERE company_name LIKE '%#{company_name}%'
ORDER BY score DESC;
I've gotten close, I believe. My current query is this
items = Item.select("items.icon, items.name, item_types.name AS type, items.level, items.rarity, items.vendor_value")
.joins(:item_type)
.where("item_types.name = '#{params[:item_type]}'")
This gets me an array of Item objects that at least respond to :type with the item_type.name.
What I am looking for is an array of arrays that look so:
[icon, name, item_type.name, level, rarity, vendor_value]
I've already had it working fairly easily, but it is important to me that this be done in one fell swoop via sql, instead of creating a map afterwards, because there are times where I need to respond with 40k+ items and need this to be as fast as possible.
Not sure how to go from the above to an array of attributes, without performing a map.
Thanks for your help!
The pluck method does precisely what you want. In your case, it would look like this:
items = Item.joins(:item_type)
.where("item_types.name = ?", params[:item_type])
.pluck("items.icon", "items.name", "item_types.name AS type",
"items.level", "items.rarity", "items.vendor_value")
I also changed the where call to use parameterization instead of string interpolation—interpolation isn't recommended, especially when you're getting a value from the user.
Further reading:
Official documentation for pluck
An in-depth explanation of how to use pluck
I want to delete my records from the database based on some ID's.
This is the statement written in my STORED PROCEDURE to delete records based on DeletedID.
DELETE FROM tms_activity where activity_id IN (DeletedID);
My DeletedID is a string with records comma seperated like("1,2,3")
Now when I am passing DeletedID in my Statement as a parameter it is taking the input as "1,2,3" and deleting the record with the first DeletedID it is getting(1 in this case).But I want to delete all the records based on the given parameter.
DeletedId must be passed like (1,2,3) rather than ("1,2,3") than only it can delete all the records based on provided ID's...Now how can I do that?
I consulted this question: MySQL wrong output with IN clause and parameter,
but couldn't understand how can I achieve my result.
Did you try this
DELETE FROM tms_activity where activity_id in
( SELECT ACTIVITY_ID FROM SOMETABLE WHERE FIELD = CRITERIA )
Or some more findings, if I were at this problem would select one of these solutions.
I investigated and found some very good links for you.
[http://johnhforrest.com/2010/10/parameterized-sql-queries-in-c/][1]
The Parametrized SQL queries have a benefit if you have to send a large number of parameters and want to run against one sql. It takes the sql in one chunk and all the parameters in other so keep the network traffic low.
You can search more in this topic
Thanks
QF
So simpledb has a kind of spreadsheet data model.
I have an app that simply needs to store keys against values. Except that a single key can have multiple values.
There will be multiple clients. Each client has an id with it's own set of keys.
I'd like to stick with a single domain if I can at this stage.
How can I map this onto simpleDB?
I was thinking
domain = mydomain
item = clientid
attribute.n.name = key_1 ... key_n
attribute.n.value = val1 ... valn
That would satisfy the ability to store multiple values for the same key.
But then I found that I need to either get ALL attributes in my select or know example
how many attributes I have. I will not know this up front.
Also I allow deleting a specific value from a key (or attribute). I will have to search for it first. It seems that in the select there is no attributeName() function, just the itemName() function.
Would it perhaps be better to make the item name a combination of id + key + _n ?
e.g. if the id is 'myid' and the key is 'boots' then the item name would be
'myidboots_1'
And then have a single attribute per item called say 'keyval'.
and I can do a
select 'keyval' where itemName like 'myidboots_%' ?
Still kindof cumbersome compared to a normal sql database.
Maybe I should try encoding the values like a comma separated list?
Except that it's probably more cumbersome and also I've read that there is a 1000 character limit.
Any other suggestions?
I'm not sure I totally follow your question, but I think it might be helpful to point out that SimpleDB lets you do classic SQL style queries like:
select * from foo where bar = '1'
This will return all the attributes/values for the resulting records.