Double junction arguments swapping sides - raku

I'm confused about how double junctions are supposed to work.
This makes some sense:
say all('a', 'b', 'c') ~ any('d', 'e');
gives
all(any(ad, ae), any(bd, be), any(cd, ce))
This doesn't make sense:
say any('a', 'b', 'c') ~ all('d', 'e');
gives
all(any(da, db, dc), any(ea, eb, ec))
It confuses me because the letter 'a' which I would expect to be on the left of the letter 'd' is now on the right.

This was indeed a bug. This has been fixed with https://github.com/rakudo/rakudo/commit/4ef8433aa2 .
Thank you for your question / bug report! The next Rakudo compiler release / next Rakudo Star release will contain this fix.

Related

DECODE Statement to SQL

I have a nested DECODE statement that I am trying to rewrite from Oracle to SQL Server and it is giving me fits. Can anyone please help me to rewrite this line:
DECODE(UR.UR_REG_IND, 'P', 'P', 'Y') = DECODE(:UR_REG_IND, 'B', DECODE(UR.UR_REG_IND, 'P', 'P', 'Y'), :UR_REG_IND)
I understand the :UR_REG_IND is an input variable #UR_REG_IND and I also understand the DECODE is a type of CASE in SQL, but I am having all kinds of trouble trying to rewrite this without a nested CASE statement, if its even possible.
Something along these lines. The parentheses are not needed but they may help readability.
case ur.ur_reg_ind when 'P' then 'P' else 'Y' end =
case :ur_reg_ind when 'B' then (case ur.ur.reg_ind when 'P' then 'P' else 'Y' end)
else :ur_reg_ind end
Note that this translation should work equally well in Oracle itself - it is actually how I would write that condition. It is longer, perhaps, but to me it seems much easier to read and understand (and therefore to maintain).

Check if number in varchar2 is greater than n

I've got a Varchar2 field which usually holds two alphabetic characters (such as ZH, SZ, AI,...). Let's call it FOO.
Certain datasets save A or A1 - A9 into the same field. I need to select all rows except exactly those.
I used the function substr to separate the number from the A. So far so good, < or > don't seem to work correctly with the "number-string".
How can I achieve this without converting it to a number? Is there an easier solution?
I haven't found anything on the internet and I reached my limit trying it myself.
This is my WHERE clause so far:
WHERE (substr(FOO, 0, 1) != 'A'
or (substr(FOO, 0, 1) = 'A' AND substr(FOO, 1, 1) > '9'));
It returns all the rows without restrictions.
The only solution I found:
WHERE (FOO NOT IN ('A', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'));
But this is not optimal if, somewhere in the future, there will be A1 - A50. I would have to add 51 strings to my WHERE clause. And, since the query is in source code, also the code readability would get worse.
The solution should work on ORACLE and SQL Server.
Thanks in advance
(substr(FOO, 0, 1) = (substr(FOO, 1, 1) - Oracle starts with 1 (not 0).
So you should use substr(FOO, 2, 1) to get the second symbol.
However, it won't work in SQL Server which has SUBSTRING (not SUBSTR).
if you're ready to use different approaches in the different DBs you can also try regular expressions:
Oracle
where not regexp_like(foo, '^A[1-9]{1,3}$')
^ begining of the string
$ end of the string
[1-9] any digit from 1 to 9
{1,3} repeat the previous expression 1,2 or 3 times
Examples of FOOs which match / not match '^A[1-9]{1,3}$'
a123 -- may match / may not (depending on NLS settings regarding case sensitivity)
A123 -- match (the first symbol is 'A', the others are 3 digits)
A123b -- doesn't match (the last symbol should be a digit)
A1234 -- doesn't match (there should be 1,2 or 3 digits an the end)
A12 -- match
A1 -- match
SQL Server
REGEXP_LIKE conversion in SQL Server T-SQL
If your requirement is to include all alphabetic values except 'A' alone, consider using a LIKE expression so that it will work with any ANSI-compliant DBMS:
WHERE FOO <> 'A' AND FOO NOT LIKE '%[^A-Z]%'

SQL statement with local (inline) array

In many languages, one can use inline lists of values, with some form of code similar to this:
for x in [1,7,8,12,14,56,123]:
print x # Or whatever else you fancy doing
Working with SQL for the last year or so, I've found out that even though using such an array in WHERE is not a problem...
select *
from foo
where someColumn in (1,7,8,12,14,56,123) and someThingElse...
...I have not found an equivalent form to GET data from an inline array:
-- This is not working
select *
from (1,7,8,12,14,56,123)
where somethingElse ...
Searching for solutions, I have only found people suggesting a union soup:
select *
from (SELECT 1 UNION SELECT 1 UNION SELECT 7 UNION ...)
where somethingElse ...
...which is arguably, ugly and verbose.
I can quickly generate the UNION soup from the list with a couple of keystrokes in my editor (VIM) and then paste it back to my DB prompt - but I am wondering whether I am missing some other method to accomplish this.
Also, if there's no standard way to do it, I would still be interested in DB-engine-specific solutions (Oracle, PostgreSQL, etc)
Thanks in advance for any pointers.
Row/Table value constructors can sometimes be used as a shortish hand, for example in MSSQL:
select * from (values (1),(7),(8),(12)) as T (f)
The syntax is more complex by necessity than for a simple array-like list passed to in () because it must be able to describe a multi-dimensional set of data:
select * from (values (1, 'a'),(7, 'b'),(8, 'c'),(12, 'd')) as T (f, n)
Of course, when you find the requirement to list literal values its often a good idea to stick them in a table and query for them.

SQL REPLACE function, how to replace single letter

My code is as follows:
REPLACE(REPLACE(cc.contype,'x','y'),'y','z') as ContractType,
This REPLACE's correctly what I would like, but it unfortunatley changes all "z's" to "y's" when I would like
x > y
y > z
Does this make sense? I would not like all of the new Y's to then change again in my second REPLACE function. In Microsoft Access, I would do this with the following
Iif(cc.contype = x, y, iif(cc.contype = y, x))
But I am not sure how to articulate this in SQL, would it be best I do this kind of thing in the client side language?
Many thanks.
EDIT: Have also tried with no luck:
CASE WHEN SUBSTRING(cc.contype, 1, 1) = 'C'
THEN REPLACE(cc.contype, 'C', 'Signed')
CASE WHEN SUBSTRING(cc.contype, 1, 1) = 'E'
THEN REPLACE(cc.contype, 'E', 'Estimate') as ContractType,
Try doing it the other way round if you don't want the new "y"'s to become "z"'s:
REPLACE(REPLACE(cc.contype,'y','z'),'x','y') as ContractType
Not that I'm a big fan of the performance killing process of handling sub-columns, but it appears to me you can do that just by reversing the order:
replace(replace(cc.contype,'y','z'),'x','y') as ContractType,
This will transmute all the y characters to z before transmuting the x characters to y.
If you're after a more general solution, you can do unioned queries like:
select 'Signed: ' || cc.contype as ContractType
wherecc.contype like 'C%' from wherever
union all select 'Estimate: ' || cc.contype as ContractType
where cc.contype like 'E%' from wherever
without having to mess about with substrings at all (at the slight cost of prefixing the string rather than modifying it, and adding any other required conditions as well, of course). This will usually be much more efficient than per-row functions.
Some DBMS' will actually run these sub-queries in parallel for efficiency.
Of course, the ideal solution is to change your schema so that you don't have to handle sub-columns. Separate the contype column into two, storing the first character into contype_first and contype_rest.
Then whenever you want the full contype:
select contype_first || contype_rest ...
For your present query, you could then use a lookup table:
lookup_table:
first char(1) primary key
description varchar(20)
containing:
first description
----- -----------
C Signed:
E Estimate:
and the query:
select lkp.description || cc.contype_rest
from lookup_table lkp, real_table cc
where lkp.first = cc.first ...
Both these queries are likely to be blazingly fast compared to one that does repeated string substitutions on each row.
Even if you can't replace the single column with two independent columns, you can at least create the two new ones and use an insert/update trigger to keep them in sync. This gives you the old way and a new improved way for accessing the contype information.
And while this technically violates 3NF, that's often acceptable for performance reasons, provided you understand and mitigate the risks (with the triggers).
How about
REPLACE(REPLACE(REPLACE(cc.contype,'x','ahhhgh'),'y','z'),'ahhhgh','y') as ContractType,
ahhhgh can be replaced with whatever you like.

SQLite column aliasing

Premise
I recently ran into a bug in a select statement in my code. It was fairly trivial to fix after I realized what was going on, but I'm interested in finding a way to make sure a similar bug doesn't happen again.
Here's an example of an offending query:
select
the,
quick,
brown
fox,
jumped,
over,
the,
lazy,
dog
from table_name;
What I had intended was:
select
the,
quick,
brown,
fox,
jumped,
over,
the,
lazy,
dog
from table_name;
For those who don't see it, a comma is missing after brown in the former. This causes the column to be aliased, because the as keyword is not required. So, what you get in the result is:
the,
quick,
fox,
jumped,
over,
the,
lazy,
dog
...with all the values of brown in a column named fox. This can be noticed pretty easily for a short query like the above (especially when each column has very different values), but where it came up was in a fairly complicated query with mostly integer columns like this:
select
foo,
bar,
baz,
another_table.quux,
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13,
a14,
a15,
a16,
b1,
b2,
b3,
b7,
b8,
b9,
b10,
b11,
b12,
b13,
b14,
b18,
b19,
b20,
b21,
c1,
c2,
c3,
c4,
c5,
c6,
c7,
c8
from table_name
join another_table on table_name.foo_id = another_table.id
where
blah = 'blargh'
-- many other things here
;
Even with better column names, the values are all very similar. If I were to miss a comma after b11 (for example) and then all of the b11 values get called b12, it's pretty unfortunate when we run the data through our processing pipeline (which depends on these column names in the result). Normally, I'd do select * from table_name, but what we needed required us to be a little more selective than that.
Question
What I'm looking for is a strategy to stop this from happening again.
Is there a way to require as when aliasing columns? Or a trick of writing things to make it give an error? (For example, in C-like languages, I started writing 1 == foo instead of foo == 1 to cause a compile error when I accidentally left out an equal sign, making it the invalid 1 = foo instead of foo = 1.)
I use vim normally, so I can use hlsearch to highlight commas just so I can eyeball it. However, I have to write queries in other environments quite often, including a proprietary interface in which I can't do something like this easily.
Thanks for your help!
One thing that I've done before is to move the commas to the beginning of the line. This allows some benefits. First, you can instantly see if there are any commas missing. Second, you can add a new column at the end without having to modify the previously last line.
Missing:
select
the
, quick
, brown
fox
, jumped
, over
, the
, lazy
, dog
from table_name;
Not missing:
select
the
, quick
, brown
, fox
, jumped
, over
, the
, lazy
, dog
from table_name;
You could wrap your SQL calls in a function that would either:
Iterate over the columns in the result set, checking for column names containing a space
or
Accept both the SQL statement and an integer intended number of columns, then check the result set to make sure the number of columns matches what you intended.
I have the same problem that you do. I have used make and the perl script to do a "lint" like check on my code for a long time. It has helped prevent a number of mistakes like this.
In the makefile I have:
lint_code:
perl lint_code.pl <file_1.php
The perl file is:
$st = 0;
$line_no = 0;
while (<>)
{
$line_no++;
$st = 1 if ( /start-sql/ );
$st = 0 if ( /end-sql/ );
$st = 2 if ( $st == 1 && /select/ );
$st = 3 if ( $st == 2 && /from/ );
if ( $st == 2 && /^[ \t]+[a-zA-Z][a-zA-Z0-9]*[ \t*]$/ )
{
if ( ! /select/ )
{
printf ( "Possible Error: Line: $line_no\n" );
}
}
}
I surround my select statements with comments //start-sql and //end-sql. I hope this helps.
I have changed the regular expression to reflect how you formatted your SQL as I have been
using a different format (with the commas in the front).
As a part of my build/test process I run a set of checks over the code. This is a less than
perfect solution but it has helped me.
(I am having a little difficulty with the stackoverflow rich text editor changing my code.
Hopefully I will learn how to properly use it.)
write a comma before the name
first
,short
,medium
,longlonglong
,...
vs
first,
short,
medium,
longlonglong,
...
also makes it really easy to see the list of sql select arguments
works in any IDE :)
If you have columns with similar names, distinguished only by suffix numbers, you've already lost. You have a bad database design.
And most modern developers use SQL generators or ORMs these days, instead of writing this "assembly language" SQL.