ORACLE:- 'SELECT ORDER BY ASC' but 'USA' always first - oracle10g

I have to write a drop down query for countries.
But USA should always be first.
The rest of the countries are in alphabetical order
I tried the following query
SELECT
countries_id
,countries_name
FROM get_countries
WHERE
countries_id = 138
UNION
SELECT
countries_id
,countries_name
FROM get_countries
WHERE
countries_id != 138
ORDER BY 2 ASC

Something like this maybe:
ORDER BY
CASE
WHEN upper(country_name) = 'USA' then '0'
ELSE lower(country_name)
END
Here's a complete example
create TABLE countries (country_name VARCHAR2(50));
INSERT INTO countries VALUES ('USA');
INSERT INTO countries VALUES ('India');
INSERT INTO countries VALUES ('Russia');
INSERT INTO countries VALUES ('China');
COMMIT;
SELECT country_name
FROM countries
ORDER BY
CASE
WHEN upper(country_name) = 'USA' then '0'
ELSE lower(country_name)
END
Returns:
USA
China
India
Russia

It's been a while since I worked with oracle, but you can try ORDER BY countries_name = 'USA', countries_name ASC.
Correction
Sorry that didn't work. I had "countries_name" mis-typed as "country_name", so it may work now.
You could also use ORDER BY decode(countries_name, 'USA', 0, 1), countries_name ASC.

Related

Unpivot Columns with Most Recent Record

Student Records are updated for subject and update date. Student can be enrolled in one or multiple subjects. I would like to get each student record with most subject update date and status.
CREATE TABLE Student
(
StudentID int,
FirstName varchar(100),
LastName varchar(100),
FullAddress varchar(100),
CityState varchar(100),
MathStatus varchar(100),
MUpdateDate datetime2,
ScienceStatus varchar(100),
SUpdateDate datetime2,
EnglishStatus varchar(100),
EUpdateDate datetime2
);
Desired query output, I am using CTE method but trying to find alternative and better way.
SELECT StudentID, FirstName, LastName, FullAddress, CityState, [SubjectStatus], UpdateDate
FROM Student
;WITH orginal AS
(SELECT * FROM Student)
,Math as
(
SELECT DISTINCT StudentID, FirstName, LastName, FullAddress, CityState,
ROW_NUMBER OVER (PARTITION BY StudentID, MathStatus ORDER BY MUpdateDate DESC) as rn
, _o.MathStatus as SubjectStatus, _o.MupdateDate as UpdateDate
FROM original as o
left join orignal as _o on o.StudentID = _o.StudentID
where _o.MathStatus is not null and _o.MUpdateDate is not null
)
,Science AS
(
...--Same as Math
)
,English AS
(
...--Same As Math
)
SELECT * FROM Math WHERE rn = 1
UNION
SELECT * FROM Science WHERE rn = 1
UNION
SELECT * FROM English WHERE rn = 1
First: storing data in a denormalized form is not recommended. Some data model redesign might be in order. There are multiple resources about data normalization available on the web, like this one.
Now then, I made some guesses about how your source table is populated based on the query you wrote. I generated some sample data that could show how the source data is created. Besides that I also reduced the number of columns to reduce my typing efforts. The general approach should still be valid.
Sample data
create table Student
(
StudentId int,
StudentName varchar(15),
MathStat varchar(5),
MathDate date,
ScienceStat varchar(5),
ScienceDate date
);
insert into Student (StudentID, StudentName, MathStat, MathDate, ScienceStat, ScienceDate) values
(1, 'John Smith', 'A', '2020-01-01', 'B', '2020-05-01'),
(1, 'John Smith', 'A', '2020-01-01', 'B+', '2020-06-01'), -- B for Science was updated to B+ month later
(2, 'Peter Parker', 'F', '2020-01-01', 'A', '2020-05-01'),
(2, 'Peter Parker', 'A+', '2020-03-01', 'A', '2020-05-01'), -- Spider-Man would never fail Math, fixed...
(3, 'Tom Holland', null, null, 'A', '2020-05-01'),
(3, 'Tom Holland', 'A-', '2020-07-01', 'A', '2020-05-01'); -- Tom was sick for Math, but got a second chance
Solution
Your question title already contains the word unpivot. That word actually exists in T-SQL as a keyword. You can learn about the unpivot keyword in the documentation. Your own solution already contains common table expression, these constructions should look familiar.
Steps:
cte_unpivot = unpivot all rows, create a Subject column and place the corresponding values (SubjectStat, Date) next to it with a case expression.
cte_recent = number the rows to find the most recent row per student and subject.
Select only those most recent rows.
This gives:
with cte_unpivot as
(
select up.StudentId,
up.StudentName,
case up.[Subject]
when 'MathStat' then 'Math'
when 'ScienceStat' then 'Science'
end as [Subject],
up.SubjectStat,
case up.[Subject]
when 'MathStat' then up.MathDate
when 'ScienceStat' then up.ScienceDate
end as [Date]
from Student s
unpivot ([SubjectStat] for [Subject] in ([MathStat], [ScienceStat])) up
),
cte_recent as
(
select cu.StudentId, cu.StudentName, cu.[Subject], cu.SubjectStat, cu.[Date],
row_number() over (partition by cu.StudentId, cu.[Subject] order by cu.[Date] desc) as [RowNum]
from cte_unpivot cu
)
select cr.StudentId, cr.StudentName, cr.[Subject], cr.SubjectStat, cr.[Date]
from cte_recent cr
where cr.RowNum = 1;
Result
StudentId StudentName Subject SubjectStat Date
----------- --------------- ------- ----------- ----------
1 John Smith Math A 2020-01-01
1 John Smith Science B+ 2020-06-01
2 Peter Parker Math A+ 2020-03-01
2 Peter Parker Science A 2020-05-01
3 Tom Holland Math A- 2020-07-01
3 Tom Holland Science A 2020-05-01

How to combine multiple tables and allow a "like" text search option for filtering, including if the criteria was left blank

I have 3 different tables where I want to be able to combine them and then filter them based off of 4 different inputs OR LESS on a search. I also would like to have it work when some filters are none.. Here is the breakdown what I mean:
So above there are three tables with column names and some data. NOTE: Table 1 has a comma in the location and I will want to separate those as two separate column for searching purposes which I figured out how to do.
Now What the final table I want is:
Table 1
------------------------------------------------
date city state title summary
jan 1 stl MO Book
jan 1 ATL MO Comp
Jan 1 Maine Phone box device
March2 MO Howey weird name
JUL2 cheese
Now My goal is trying to do a text search such as what follows: The 4 criteria of filtering will be State, Title, Date, OR a keyword
I think the solution has involves joining the tables, splitting the comma, using "where true" and having some type of IF statement.
select
date1,
split_part(location1, ',', 1) as city,
split_part(location1, ',', 2) as state,
title1
from
table1
Now I use join or union or something to add other tables?
Note: Null means user didn't have input at all
Example 1: If Date is Jan 1, state is null, title is null, Keyword is null (it should show everything with jan1st)
Example 2: If Date is Jan 1 AND City is stl (show first row), title and keyword is null
Example 3: If Date = Jan 1 and State is MO (Show row 1,2, and 4), title and keyword is null
Last example: if Keyword is Phone and everything else is null it shows row 3.
Is there a way to do this in Postgressql? It also would be convenient if the user didnt spell a state right but still pulled up similar results. Basically I will have a interface where a user will select a date, or title and/or state, or maybe just use a key word to filter the results
Your first task is to build a combined view of the tables involved supplying null for each that does not exit in a particular table. The following uses a CTE consisting of union of the 3 tables to accomplish that.
with combined as
(Select date1 as con_date
, split_part(location1, ',', 1) as city
, split_part(location1, ',', 2) as state
, title1 as title
, null as summary
from table_1
union all
select date2
, state
, null
, title2
, null
from table_2
union all
select date3
, null
, null
, title
, null
from table_3
)
select * from combined;
From there you can build a VIEW using the above then construct an appropriate query for the entered parameters. An alternative is a function that returns a table as below.
create or replace function search_combined(
search_date_in date default null
, search_title_in text default null
, search_state_in text default null
, search_word_in text default null
)
returns table (con_date date
, city text
, state text
, title text
, summary text
)
language sql
as $$
with combined as
(Select date1 as con_date
, split_part(location1, ',', 1) as city
, split_part(location1, ',', 2) as state
, title1 as title
, null as summary
from table_1
union all
select date2
, state
, null
, title2
, null
from table_2
union all
select date3
, null
, null
, title
, null
from table_3
)
select *
from combined
where 1=1
and (con_date = search_date_in or search_date_in is null)
and (state = search_state_in or search_state_in is null)
and (title = search_title_in or search_title_in is null)
and (title = search_word_in or search_word_in is null);
$$;
-- Test
select search_combined(search_date_in => date '2019-01-01');
or
select *
from search_combined(search_date_in => date '2019-01-01');
select *
from search_combned(search_date_in => date '2019-01-01'
,search_state_in => 'Mo'
);
select *
from search_combined( search_word_in => 'Phone');
The above should work as is, but I have not tested it as your sample data is a picture. In the future please provide sample data as text; as that allows copy/past to build it locally.

Using "UNION ALL" and "GROUP BY" to implement "Intersect"

I'v provided following query to find common records in 2 data sets but it's difficult for me to make sure about correctness of my query because of that I have a lot of data records in my DB.
Is it OK to implement Intersect between "Customers" & "Employees" tables using UNION ALL and apply GROUP BY on the result like below?
SELECT D.Country, D.Region, D.City
FROM (SELECT DISTINCT Country, Region, City
FROM Customers
UNION ALL
SELECT DISTINCT Country, Region, City
FROM Employees) AS D
GROUP BY D.Country, D.Region, D.City
HAVING COUNT(*) = 2;
So can we say that any record which exists in the result of this query also exists in the Intersect set between "Customers & Employees" tables AND any record that exists in Intersect set between "Customers & Employees" tables will be in the result of this query too?
So is it right to say any record in result of this query is in
"Intersect" set between "Customers & Employees" "AND" any record that
exist in "Intersect" set between "Customers & Employees" is in result
of this query too?
YES.
... Yes, but it won't be as efficient because you are filtering out duplicates three times instead of once. In your query you're
Using DISTINCT to pull unique records from employees
Using DISTINCT to pull unique records from customers
Combining both queries using UNION ALL
Using GROUP BY in your outer query to to filter the records you retrieved in steps 1,2 and 3.
Using INTERSECT will return identical results but more efficiently. To see for yourself you can create the sample data below and run both queries:
use tempdb
go
if object_id('dbo.customers') is not null drop table dbo.customers;
if object_id('dbo.employees') is not null drop table dbo.employees;
create table dbo.customers
(
customerId int identity,
country varchar(50),
region varchar(50),
city varchar(100)
);
create table dbo.employees
(
employeeId int identity,
country varchar(50),
region varchar(50),
city varchar(100)
);
insert dbo.customers(country, region, city)
values ('us', 'N/E', 'New York'), ('us', 'N/W', 'Seattle'),('us', 'Midwest', 'Chicago');
insert dbo.employees
values ('us', 'S/E', 'Miami'), ('us', 'N/W', 'Portland'),('us', 'Midwest', 'Chicago');
Run these queries:
SELECT D.Country, D.Region, D.City
FROM
(
SELECT DISTINCT Country, Region, City
FROM Customers
UNION ALL
SELECT DISTINCT Country, Region, City
FROM Employees
) AS D
GROUP BY D.Country, D.Region, D.City
HAVING COUNT(*) = 2;
SELECT Country, Region, City
FROM dbo.customers
INTERSECT
SELECT Country, Region, City
FROM dbo.employees;
Results:
Country Region City
----------- ---------- ----------
us Midwest Chicago
Country Region City
----------- ---------- ----------
us Midwest Chicago
If using INTERSECT is not an option OR you want a faster query you could improve the query you posted a couple different ways, such as:
Option 1: let GROUP BY handle ALL the de-duplication like this:
This is the same as what you posted but without the DISTINCTS
SELECT D.Country, D.Region, D.City
FROM
(
SELECT Country, Region, City
FROM Customers
UNION ALL
SELECT Country, Region, City
FROM Employees
) AS D
GROUP BY D.Country, D.Region, D.City
HAVING COUNT(*) = 2;
Option 2: Use ROW_NUMBER
This would be my preference and will likely be most efficient
SELECT Country, Region, City
FROM
(
SELECT
rn = row_number() over (partition by D.Country, D.Region, D.City order by (SELECT null)),
D.Country, D.Region, D.City
FROM
(
SELECT Country, Region, City
FROM Customers
UNION ALL
SELECT Country, Region, City
FROM Employees
) AS D
) uniquify
WHERE rn = 2;

Select non-equal value in SQL grouping

I have a dataset of food eaten:
create table test
(group_id integer,
food varchar,
item_type varchar);
insert into test values
(764, 'apple', 'new_food'),
(123, 'berry', 'new_food'),
(123, 'apple', 'others'),
(123, 'berry', 'others'),
(86, 'carrot', 'others'),
(86, 'carrot', 'new_food'),
(86, 'banana', 'others');
In each group, the new food eaten is of item_type new_food. The previous food that was being eaten is whatever else in the group doesn't equal the new_food's value.
The dataset I would like from this would be:
| group | previous_food | new_food |
------------------------------------
764 null apple
123 apple berry
86 banana carrot
However, I can't get the group selections correct. My attempt is currently:
select
group_id,
max(case when item_type != 'new_food' then food else null end) as previous_food,
max(case when item_type = 'new_food' then food else null end) as new_food
from test
group by group_id
However, we can't rely on the max() function to pick the correct previous food since they are not necessarily alphabetically ordered.
I just need whichever other food in the grouping != the new_food. How can I get this?
Can I avoid using a subquery or is that inevitable? The database says I can't nest aggregate functions and it is frustrating.
Here is my sqlfiddle so far: http://sqlfiddle.com/#!17/a2a46/1
EDIT: I've solved this with a subquery here: http://sqlfiddle.com/#!17/dd8b9/12 but can we do better? Surely there must be a way of doing this comparison easily within the grouping no?

Update table lookup

I have a temp table (#WorkTable) which looks like the following:
InstrID CountryName CreditRating
1 UK AA
2 UK Unclassified
3 South Africa A
4 South Africa A
5 South Africa Unclassified
What I want to be able to do is update this table where column CreditRating is 'Unclassified' with its actual credit rating. So in the example above the UK unclassified would become 'AA' and the South African one would become 'A'.
I'm unsure of the coding for this, any help would be greatly appreciated.
The sql script below will update any unclassified to the 'actual' credit ratings. I hope this helps.
CREATE TABLE #WorkTable
(
InstrID INT,
CountryName VARCHAR(50),
CreditRating VARCHAR(20)
)
INSERT INTO #WorkTable VALUES ( 1, 'UK','AA');
INSERT INTO #WorkTable VALUES ( 2, 'UK','Unclassified');
INSERT INTO #WorkTable VALUES ( 3, 'South Africa','A');
INSERT INTO #WorkTable VALUES ( 4, 'South Africa','A');
INSERT INTO #WorkTable VALUES ( 5, 'South Africa','Unclassified');
WITH cteUnclassified
AS
(
SELECT InstrID,
CountryName,
CeditRating
FROM #WorkTable
WHERE CreditRating != 'Unclassified'
)
UPDATE #WorkTable
SET CreditRating = u.CreditRating
FROM #WorkTable wt
INNER JOIN cteUnclassified u
ON wt.CountryName = u.CountryName
WHERE wt.CreditRating = 'Unclassified'
SELECT *
FROM #WorkTable
Result of the query below:
InstrID CountryName CreditRating
1 UK AA
2 UK AA
3 South Africa A
4 South Africa A
5 South Africa A
I think This example would help you.
String sql = "update table_name set CreditRating = (select unique CreditRating from table_name where CountryName = ? and CreditRating != 'classifie...') where CountryName = ? and CreditRating = 'classifie...'";
Here you can pass the countryName as a paramaeter.
You seem new to SQL. What you need to do is "join" the table to the lookup values. You can find a lot of help on basics of SQL in a lot of places, but if you need a quick solution, try the following:
If you havea seperate table with countries and credit ratings, (I have assumed the name of that table is Rating, and the country names and rating match.)
update #WorkTable
Set w.Creditrating = r.CreditRating
from #WorkTable w join Rating r on w.CountryName=r.CountryName
where w.CreditRating='Unclassified'
On the other hand, if no seperate table exists, and you want to update the unclassified values based on the same temp table, then you need to assume that there is some entry with a CreditRating for that country in the table already (which, in the case you posted, there is.) It also needs for there to be only one CreditRating per country. In this case, the following should work:
update #WorkTable
Set w.Creditrating = w2.CreditRating
from #WorkTable w join
(Select distinct CountryName, CreditRating from #WorkTable where CreditRating<>'Unclassified') w2
on w.CountryName=w2.CountryName
where w.CreditRating='Unclassified'
This uses the table itself to find the values, by looking at the table twice - once as the table to update, where the last line restricts what is updated to only the unclassified items, and once as a source table, where only classified ratings are used. Please leave a comment with more details if this doesn't work for you.