order by case alphabetical ordering - firebird

Question is not too specific but not sure how to explain the question well.
I have table in my db with names in the field.
I would like to order the names in the way if the name starts with certain alphabet, order that first and so on.
What I have now is
SELECT
(T.firstname||' '||T.lastname) as Full_Name
FROM
TABLE T
ORDER BY
CASE
WHEN LPAD(T.firstname, 1) = 'J' THEN T.firstname
WHEN LPAD(T.firstname, 1) = 'B' THEN T.firstname
END DESC,
Full_Name ASC
Now this returns as what I would like to see, name starting with 'J' is ordered first then 'B' then the rest.
However, the result looks like
What I get What I want
Full_Name Full_Name
---------- ----------
Junior MR James A
John Doe Joe Bob
Joe Bob John Doe
James A Junior MR
Brad T B Test
Bob Joe Bb Test
Bb Test Bob Joe
B Test Brad T
A Test A Test
Aa Test Aa Test
AFLKJASDFJ AFLKJASDFJ
Ann Doe Ann Doe
But what I want is that J and B to be sorted alphabetical order as well, right now it is doing reverse alphabetical order.
How can I specify the order inside of case?
I tried having 2 seperate case statement for different cases for starting with 'J' and 'B', it just shows me the same result

Just make one extra column, material using triggers or volatile using expression only executed when select is run, and then use it in sorting.
For secondary sorting use original components of names, not the expression bringing both names together thus destroying the information which was which.
Examples: https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=fbf89b3903d3271ae6c55589fd9cfe23
create table T (
firstname varchar(10),
lastname varchar(10),
fullname computed by (
Coalesce(firstname, '-') || ' ' || Coalesce(T.lastname, '-')
),
sorting_helper computed by (
CASE WHEN firstname starting with 'J' then 100
WHEN firstname starting with 'B' then 50
ELSE 0
END
)
)
Notice the important distinction: my helper expression is "ranking" one. It yields one of several pre-defined ranks, thus putting "James" and "Joe" into the same bin having exactly the same ranking value. Your expression still yields the names themselves, thus erroneously keeping difference between those names. But you do NOT want that difference, you told you want all J-started names to be moved upwards and then sorted among themselves by usual rules. So, just do what you say, make an expression that pulls all J-names together WITHOUT making distinction between those.
insert into T
select
'John', 'Doe'
from rdb$database union all select
'James', 'A'
from rdb$database union all select
'Aa ', 'Test'
from rdb$database union all select
'Ann', 'Doe'
from rdb$database union all select
'Bob', 'Joe'
from rdb$database union all select
'Brad', 'Test'
from rdb$database union all select
NULL, 'Smith'
from rdb$database union all select
'Ken', NULL
from rdb$database
8 rows affected
select * from T
FIRSTNAME | LASTNAME | FULLNAME | SORTING_HELPER
:-------- | :------- | :---------- | -------------:
John | Doe | John Doe | 100
James | A | James A | 100
Aa | Test | Aa Test | 0
Ann | Doe | Ann Doe | 0
Bob | Joe | Bob Joe | 50
Brad | Test | Brad Test | 50
null | Smith | - Smith | 0
Ken | null | Ken - | 0
Select FullName from T order by sorting_helper desc, firstname asc, lastname asc
| FULLNAME |
| :---------- |
| James A |
| John Doe |
| Bob Joe |
| Brad Test |
| - Smith |
| Aa Test |
| Ann Doe |
| Ken - |
Or without computed-by column
Select FullName from T order by (CASE WHEN firstname starting with 'J' then 0
WHEN firstname starting with 'B' then 1
ELSE 2
END) asc, firstname asc, lastname asc
| FULLNAME |
| :---------- |
| James A |
| John Doe |
| Bob Joe |
| Brad Test |
| - Smith |
| Aa Test |
| Ann Doe |
| Ken - |
For extra tuning of the positioning of the rows lacking name or surname you can also use NULLS FIRST or NULLS LAST option as described in Firebird docs at https://firebirdsql.org/file/documentation/reference_manuals/user_manuals/html/nullguide-sorts.html
The problem with this approach however, on big enough tables, would be that you won't be able to use indices built over names and surnames for sorting, instead you would have to resort to un-sorted pulling of data (aka NATURAL SORT when reading QUERY PLAN) and then sorting it into temporary files on disk. Which might turn very slow and volume-consuming on large enough data.
You can try to make it better by creating "index by the expression", using your ranking expression there. And hope that FB optimizer will use it (it is quite tricky with verbose expressions like CASE). Frankly you would probably still be left without it (at least I did not manage to make FB 2.1 utilize index-by-case-expression there).
You can "materialize" the ranking expression into a regular SmallInt Not Null column instead of COMPUTED BY one, and use TRIGGER of BEFORE UPDATE OR INSERT type keep that column populated with proper data. Then you can create a regular index over that regular column. While it will add two bytes to each row, that is not that much a grow.
But even then, the index with very few distinct values does not add much value, it will have "low selectivity". Also, index-by-expression can not be compound one (meaning, including other columns past the expression).
So for large data you'd practically better be with using THREE different queries fused together. Add scaffolding, if you did not do already:
create index i58647579_names on T58647579 ( firstname, lastname )
Then you can do triple-select like this:
WITH S1 as (
select FullName from T58647579
where firstname starting with 'J'
order by firstname asc, lastname asc
), S2 as (
select FullName from T58647579
where firstname starting with 'B'
order by firstname asc, lastname asc
), S3 as (
select FullName from T58647579
where (firstname is null)
or ( (firstname not starting with 'J')
and (firstname not starting with 'B')
)
order by firstname asc, lastname asc
)
SELECT * FROM S1
UNION ALL
SELECT * FROM S2
UNION ALL
SELECT * FROM S3
And while you would traverse the table thrice - you would do it by pre-sorted index:
PLAN (S1 T58647579 ORDER I58647579_NAMES INDEX (I58647579_NAMES))
PLAN (S2 T58647579 ORDER I58647579_NAMES INDEX (I58647579_NAMES))
PLAN (S3 T58647579 ORDER I58647579_NAMES)

Related

SQL WHERE condition that one field's string can be found in another field

Here's some sample data
ID | sys1lname | sys2lname
------------------------------------
1 | JOHNSON | JOHNSON
2 | FULTON | ANDERS-FULTON
3 | SMITH | SMITH-DAVIDS
4 | HARRISON | JONES
The goal is to find records where the last names do NOT match, BUT allow when sys1lname can be found somewhere within sys2lname, which may or may not be a hyphenated name. So from the above data, only record 4 should return.
When I put this (SUBSTRING(sys2lname, CHARINDEX(sys2lname, ccm.NAME_LAST), LEN(sys1lname))) in the SELECT statement it will properly return the part of sys2lname that matches sys1lname.
But when I use that in the WHERE clause
WHERE 1=1
AND sys1lname <> sys2lname
OR sys1lname not in ('%' + (SUBSTRING(sys2lname, CHARINDEX(sys1lname, sys2lname), LEN(sys1lname))))
the records with hyphenated names are in the result set.
And I can't figure out why.
Just use a NOT LIKE:
SELECT ID
FROM dbo.YourTable
WHERE sys2lname NOT LIKE '%' + sys1lname + '%';
If you could have a name like 'Smith' in sys1lname and 'BlackSmith' (or even 'Green-Blacksmith') in sys2lname and don't want them to match, I would use STRING_SPLIT and a NOT EXISTS:
SELECT ID
FROM dbo.YourTable YT
WHERE NOT EXISTS (SELECT 1
FROM STRING_SPLIT(YT.sys2lname,'-') SS
WHERE SS.[value] = YT.sys1lname);

PostgreSQL: Selecting one address from almost but not exactly duplicate rows

I have a big table that I'm trying to join another table to, however the table has entries such as:
--- Name | Address | Priority
----------------------------------------
1 | Jane Doe | 123 Baker St | 1
2 | Jane Doe | 345 Clay Dr | 2
3 | Jeff Boe | 231 Street St| 1
4 | Karen Al | 4232 Elm St | 1
5 | Karen Al | 5632 Pine Ct | 2
What I really want to select is one single address per person. The correct address I want is priority 2. However some of the addresses don't have a priority 2, so I can't join only on priority 2.
I've tried the following test query:
SELECT DISTINCT n.ID, LastName, FirstName, MAX(Address), MAX(Address2), City, State, PostalCode, n.Phone
FROM NormalTable n
JOIN Contracts cn ON n.ID = cn.ID
Which returns the table that I sketched out above, with the same person/sameID but different addresses.
Is there a way to do this in one query? I can think of maybe doing one INSERT statement into my final table where I do all the priority 2 addresses and then ANOTHER INSERT statement for IDs that aren't in the table yet, and use the priority 1 address for those. But I'd much prefer if there's a way to do this all in one go where I end up with only the address I want.
You could choice the address you need joining a subquery for max priority
select m.LastName, m.FirstName, m.Address, m.Address2, m.City, m.State, m.PostalCode, m.Phone
from my_table m
inner join (
select LastName, FirstName, max(priority) max_priority
from my_table
group by LastName, FirstName
) t on t.LastName = m.LastName
AND t.FirstName = m.FirstName
AND t.max_priority = m.priority
I think you want something like this
SELECT DISTINCT (Name), Address, Priority
ORDER BY Priority DESC
How this works is that the DISTINCT (Name) only returns one row per name. The row returned for each Name is the first row. Which will be the one with the highest priority because of the ORDER BY.

postgresql crosstab simple example

I got a key-value based table where each key-value pair is assigned to an entity which is identified by an id:
|_id__|_key_______|_value_|
| 123 | FIRSTNAME | John |
| 123 | LASTNAME | Doe |
And I want to transform it a structre like this:
|_id__|_firstName_|_lastName_|
| 123 | John | Doe |
I suppose one can use postgres build in crosstab function to do it.
Can you show me how to do it and explain why it works?
First of all activate the build in tablefunc-extension:
CREATE EXTENSION tablefunc;
Then create table and add sample data:
CREATE TABLE example (
id int,
key text,
value text
);
INSERT INTO example VALUES
(123, 'firstName', 'John'),
(123, 'lastName', 'Doe');
Now lets prepare the crosstab statment:
SELECT *
FROM example
ORDER BY id ASC, key ASC;
Its important to have the ORDER BY here.
Result:
|_id__|_key_______|_value_|
| 123 | FIRSTNAME | John |
| 123 | LASTNAME | Doe |
Solution
Now crosstab creates the table as we want:
SELECT *
FROM crosstab(
'SELECT *
FROM example
ORDER BY id ASC, key ASC;'
) AS ct(id INT, firstname TEXT, lastname TEXT);
Result:
|_id__|_firstName_|_lastName_|
| 123 | John | Doe |
How it works #1
To however understand how it works I found it easiest to just change the ORDER BY and see what happens:
SELECT *
FROM crosstab(
'SELECT *
FROM example
ORDER BY id ASC, key DESC;'
) AS ct(id INT, firstname TEXT, lastname TEXT);
Result:
|_id__|_firstName_|_lastName_|
| 123 | Doe | John |
As we changed the sorting of the key, the crosstab function sees the keys sorted in the other direction, thus reversing the generated columns.
How it works #2
Another thing that helped me understand how it works: the column definition is all about positions:
SELECT *
FROM crosstab(
'SELECT *
FROM example
ORDER BY id ASC, key ASC;'
) AS ct(blablafirst INT, blablasecond TEXT, blablathird TEXT);
Result
|_blablafirst__|_blablasecond_|_blablathird_|
| 123 | John | Doe |

T SQL Question: How to apply changes to similar rows based on one row's value

In my SQL Server 2008 DB, I have a table with records sort of like this:
ID 1 | Group1 | \ftp\path\group1\file1.txt
ID 2 | Group1 | C:\local\file\path\group1\file1.txt
ID 3 | Group1 | C:\local\file\path\group1\file1.txt
ID 4 | Group1 | C:\local\file\path\group1\file1.txt
ID 5 | Group2 | \ftp\path\group2\file1.txt
ID 6 | Group2 | C:\local\file\path\group2\file1.txt
ID 7 | Group2 | C:\local\file\path\group2\file1.txt
I need to update the table to look like this:
ID 1 | Group1 | \ftp\path\group1\file1.txt
ID 2 | Group1 | \ftp\path\group1\file1.txt
ID 3 | Group1 | \ftp\path\group1\file1.txt
ID 4 | Group1 | \ftp\path\group1\file1.txt
ID 5 | Group2 | \ftp\path\group2\file1.txt
ID 6 | Group2 | \ftp\path\group2\file1.txt
ID 7 | Group2 | \ftp\path\group2\file1.txt
I just don't know how to start this. It's easy for me to find the values in the third column, because they match this wildcard: %:\%.
So, I'm trying to replace the value in those fields that match that wildcard with the correct value in a record that does not match that wildcard. Damn, it's so hard to explain it.
I'm probably doing a poor job of explaining this issue but the right words are eluding me at the moment.
Any ideas? I appreciate the help.
This gets the results you show, but I don't think the rules I applied match the way you described how you got here. You're talking about a wildcard '%:\%' but I see nothing in any of the data that looks anything like that.
DECLARE #foo TABLE
(
ID VARCHAR(32) PRIMARY KEY,
[Group] VARCHAR(32),
Val VARCHAR(32)
);
INSERT #foo SELECT 'ID 1','Group1','Value 1'
UNION ALL SELECT 'ID 2','Group1','Value 2'
UNION ALL SELECT 'ID 3','Group1','Value 3'
UNION ALL SELECT 'ID 4','Group1','Value 4'
UNION ALL SELECT 'ID 5','Group2','A Different Value 1'
UNION ALL SELECT 'ID 6','Group2','A Different Value 2'
UNION ALL SELECT 'ID 7','Group2','A Different Value 3';
SELECT ID, [Group], Val FROM #foo;
WITH x AS
(
SELECT
ID, [Group], Val,
rn = ROW_NUMBER() OVER (PARTITION BY [Group] ORDER BY val)
FROM #foo
)
UPDATE x
SET x.Val = y.Val
FROM x
INNER JOIN x AS y
ON x.[Group] = y.[Group]
WHERE y.rn = 1 AND x.rn > 1;
SELECT ID, [Group], Val FROM #foo;
Something like this maybe?
UPDATE table
SET table.valueColumn = CT.correctValueColumn
FROM table as CT
INNER JOIN table as IT on IT.group = CT.group AND CT.valueColumn LIKE '%:\%'
WHERE IT.valueColumn NOT LIKE '%:\%'
I don't have management studio on this machine so I'm not sure it's syntatically correct.
Hope this helps some.

how to make array_agg() work like group_concat() from mySQL

So I have this table:
create table test (
id integer,
rank integer,
image varchar(30)
);
Then some values:
id | rank | image
---+------+-------
1 | 2 | bbb
1 | 3 | ccc
1 | 1 | aaa
2 | 3 | c
2 | 1 | a
2 | 2 | b
I want to group them by id and concatenate the image name in the order given by rank. In mySQL I can do this:
select id,
group_concat( image order by rank asc separator ',' )
from test
group by id;
And the output would be:
1 aaa,bbb,ccc
2 a,b,c
Is there a way I can have this in postgresql?
If I try to use array_agg() the names will not show in the correct order and apparently I was not able to find a way to sort them. (I was using postgres 8.4 )
In PostgreSQL 8.4 you cannot explicitly order array_agg but you can work around it by ordering the rows passed into to the group/aggregate with a subquery:
SELECT id, array_to_string(array_agg(image), ',')
FROM (SELECT * FROM test ORDER BY id, rank) x
GROUP BY id;
In PostgreSQL 9.0 aggregate expressions can have an ORDER BY clause:
SELECT id, array_to_string(array_agg(image ORDER BY rank), ',')
FROM test
GROUP BY id;