Query to run through each instance. MS-Access - sql

I have a MS-Access database with two tables which I would like to query from, the basic table schema is shown below. I am looking to pull out the details for the earliest parish church in each parish – and in the instance that there is no church with ‘parish’ in the name; I would like to pull out the earliest church.
SITEDETAIL:
Site
Reference No. | Civil Parish | Site Name | NGR East | NGR North
1 Assynt Old Parish Church 6137 3172
2 Assynt St. Marys 6097 3870
3 Assynt New Parish Church 6249 3490
4 Bower Grimbister 2095 4067
5 Bower St. Andrews 2304 3194
6 Halkirk Firth Parish Church 7136 3450
7 Holm Strath Parish Church 4586 2045
8 Holm St Nicholas Parish 4132 3146
SITEDATES:
Site
Reference No. | Date
1 1812
2 1300
3 1900
4 1760
5 1750
6 1838
7 1619
8 1774
I have written a query that pulls out all the instances of ‘parish’:
SELECT SITEDETAIL.SITEREFNO, SITEDETAIL.CIVPARBUR_CDE, SITEDETAIL.SITENAME, SITEDETAIL.NGRE, SITEDETAIL.NGRN, SITEDATES.DATE
FROM SITEDETAIL INNER JOIN SITEDATES ON SITEDETAIL.SITEREFNO = SITEDATES.SITEREFNO
WHERE (((SITEDETAIL.SITENAME) Like "par*"));
However, this does not take into account the instances of multiple/no churches with ‘par*’ in the name.
Is it possible to create an SQL query that runs through each civil parish and selects the earliest ‘parish’ or earliest church, or is it necessary to write a perl script to run through them? Is this possible using DBI?
Desired output:
Site
Reference No. | Civil Parish | Site Name | NGR East | NGR North | Date
1 Assynt Old Parish Church 6137 3172 1812
5 Bower St. Andrews 2304 3194 1750
6 Halkirk Firth Parish Church 7136 3450 1838
7 Holm Strath Parish Church 4586 2045 1619
NB:In the case of Assynt, 'Old Parish Church' is selected despite being older because of having 'parish' in the name.

The following query should get you what you need. It's a little long, but it does the trick:
`select LIST.Civil_Parish, SD.Site_name, LIST.MSite_Date
from
(
select Civil_Parish, min(Site_date) as MSite_date
from SiteDetail
where Boolean = 1
group by Civil_Parish
union
select Civil_parish, min(Site_date) as MSite_date
from SiteDetail
where Civil_parish not in
(select Civil_parish
from SiteDetail
where Boolean = 1)
group by Civil_Parish) as LIST
left join sitedetail SD on LIST.Civil_Parish = SD.Civil_Parish and LIST.MSite_Date = SD.Site_Date`
Please note the following:
1) I am using PowerUser's boolean suggestion. If the Boolean column has value 1, then the row is a Parish Church, and 0 if it is not.
2) I combined the tables "SiteDates" and "SiteDetails" for the purpose of this example, as they are 1 to 1.
The core of the query is A) finding the oldest Parish church in a Parish, then B) find Parishes without Parish Churches.
The code for A) is as follows:
'select Civil_Parish, min(Site_date) as MSite_date
from SiteDetail
where Boolean = 1
group by Civil_Parish'
We then union that with the oldest churches in parishes that do not have a parish church:
'select Civil_parish, min(Site_date) as MSite_date
from SiteDetail
where Civil_parish not in
(select Civil_parish
from SiteDetail
where Boolean = 1)
group by Civil_Parish'
We then join the union query (named "LIST" here) with our original "SITEDETAIL" table on Parish and Date to bring in the church name.

Related

Oracle 11g - select only one row for each town in a table

I have a table with 300K records, but only ~100 unique town names. I need sql to return 1 row for each individual town name. Table structure:
UNIQUE_ID
STREET_NUMBER
STREET_NAME
STREET_TYPE
TOWN
ZIP
UID01
11
TROY
STREET
ASHFIELD
2017
UID02
13
ABED
ROAD
ASHFIELD
2017
UID03
2
FRANK
COURT
EMERTON
2021
UID04
8
DENNIS
GROVE
SACKVILLE
2028
UID05
97
MAC
CRESCENT
SACKVILLE
2028
UID06
102
CHARLIE
WALK
SACKVILLE
2028
UID07
70
DEE
BOULEVARD
WINDSOR
2033
UID08
27
POPPY
STREET
WINDSOR
2033
UID09
33
ALLY
WAY
BARGO
2315
UID10
48
ELS
AVENUE
BARGO
2315
I'm trying to get the data returned to be something like:
UNIQUE_ID
STREET_NUMBER
STREET_NAME
STREET_TYPE
TOWN
ZIP
UID01
11
TROY
STREET
ASHFIELD
2017
UID03
2
FRANK
COURT
EMERTON
2021
UID04
8
DENNIS
GROVE
SACKVILLE
2028
UID07
70
DEE
BOULEVARD
WINDSOR
2033
UID09
33
ALLY
WAY
BARGO
2315
Don't care which record is returned for each town name, but need one record for each town.
I've trawled through various similar posts but can't seem to get the syntax correct.
I'm able to select each individual town name using this:
select min(TOWN) keep (dense_rank first order by rownum) TOWN
from ADDRESS_TABLE group by TOWN;
But not sure how to get the other attached data to return as well.
Help please?
If you don't care about which one to take, then take any of them (e.g. first by unique_id):
WITH
temp
AS
(SELECT unique_id,
street_number,
street_name,
street_type,
town,
zip,
ROW_NUMBER () OVER (PARTITION BY town ORDER BY unique_id) rn
FROM address_table)
SELECT unique_id,
street_number,
street_name,
street_type,
town,
zip
FROM temp
WHERE rn = 1

Adding rows in a table from data that is not in a column

I'm trying to create a table to add all Medals won by the participant countries in the Olympics.
I scraped the data from Wikipedia and have something similar to this:
Year
Country_Name
Host_city
Host_Country
Gold
Silver
Bronze
1986
146
Los Angeles
United States
41
32
30
1986
67
Los Angeles
United States
12
12
12
And so on
I double-checked the data for some years, and it seems very accurate. The Country_Name has an ID because I have a Country_ID table that I created and updated the names with the ID:
Country_ID
Country_Name
1986
1
1986
2
So far so good. Now I want to create a new table where I'll have all countries in a specific year and the total medals for that country. I managed to easily do that for countries that participated in an edition, here's an example for the 1896 edition:
INSERT INTO Cumultative_Medals_by_Year(Country_ID, Year, Culmutative_Gold, Culmutative_Silver, Culmutative_Bronze, Total_Medals)
SELECT a.Country_Name, a.Year, SUM(a.Gold) As Cumultative_Gold, SUM(a.Silver) As Cumultative_Silver, SUM(a.Bronze) As Cumultative_Bronze, SUM(a.Gold) + SUM(a.Silver) + SUM(a.Bronze) AS Total_Medals
FROM Country_Medals a
Where a.Year >= 1896 AND Year < 1900
Group By a.Country_Name, a.Year
And I'll have this table:
Country_ID
Year
Cumultative_Gold
Cumultative_Silver
Cumultative_Bronze
Total_Medals
6
1986
2
0
0
5
7
1986
2
1
2
5
35
1986
1
2
3
6
46
1986
5
4
2
11
49
1986
6
5
2
13
51
1986
2
3
2
7
52
1986
10
18
19
47
58
1986
2
1
3
6
85
1986
1
0
1
2
131
1986
1
2
0
3
146
1986
11
7
2
20
To add the other editions I just have to edit the dates, "Where a.Year >= 1900 AND Year < 1904", for example.
INSERT INTO Cumultative_Medals_by_Year(Country_ID, Year, Culmutative_Gold, Culmutative_Silver, Culmutative_Bronze, Total_Medals)
SELECT a.Country_Name, a.Year, SUM(a.Gold) As Cumultative_Gold, SUM(a.Silver) As Cumultative_Silver, SUM(a.Bronze) As Cumultative_Bronze, SUM(a.Gold) + SUM(a.Silver) + SUM(a.Bronze) AS Total_Medals
FROM Country_Medals a
Where a.Year >= 1900 AND Year < 1904
Group By a.Country_Name, a.Year
And the table will grow.
But I'd like to also add all the other countries for the year 1896. This way I'll have a full record of all countries. So for example, you see that Country 1 has no medals in the 1896 Olympic edition, but I'd like to also add it there, even if the sum becomes NULL (where I'll update with a 0).
Why do I want that? I'd like to do an Animated Bar Chart Race, and with the data I have, some counties go "away" from the race. For example, the US didn't participate in the 1980 Olympics, so for a brief moment, the Bar for the US in the chart goes away just to return in 1984 (when it participated again). Another example is the Soviet Union, even though they do not participate anymore, it's the second participant with most medals won (only behind the US), but as the country does not have more participation after 1988, the bar just goes away after that year. By keeping a record of medals for all countries in all editions would prevent that from happening.
I'm pretty sure there are lots of countries that have won metals that were not around in 1896. But if you want a row for every country and every year, then generate the rows you want using cross join. Then join in the available information:
select c.Country_Name, y.Year,
SUM(cm.Gold) As Cumulative_Gold,
SUM(cm.Silver) As Cumulative_Silver,
SUM(cm.Bronze) As Cumulative_Bronze,
COALESCE(SUM(cm.Gold), 0) + COALESCE(SUM(cm.Silver), 0) + COALESCE(SUM(cm.Bronze), 0) AS Total_Medals
from (select distinct year from Country_Medals) y cross join
(select distinct country_name from country_medals) c left join
country_medals cm
on cm.year = y.year and
cm.country_name = c.country_name
group By c.Country_Name, y.Year

Join multiple tables and pick results from most recent table

I have 4 tables. I want all the rows and cols from my first table tbl_2021 and only those data which are not in tbl_2021 but present in the the rest 3 tables, but based on one condition
if there id exist in tbl_2020, tbl_2019 and in tbl_2018 then i need the id and it's details from the most recent table that is tbl_2020.
if an id is across 2019 and 2018 table, then i need the data from 2019 so on like that.If in 2020 and 018 then 020 and so on
if the same is across 2021,2020,2019 and 2018 then the data from 2021 is selected.
And - I'm hail from a shell scripting background, and i've just started with sql. so if any noble mind could tell me the approach or what i should do to get these pieces together would mean more than happiness to me. Thank you
tbl_2021
id
name
addr
location
country
contintent
gdp
123
rob
dware
texas
us
us
8
456
lilly
gwood
london
uk
uk
5
670
rick
utown
newyrok
us
us
8
490
zang
kcity
hk
hongkong
hongkong
6
tbl_020
id
location
name
999
ger
roger
888
bel
leslie
670
us
marie
tbl_019
id
location
name
data
network
999
uk
roger
xx
na
555
rus
vladmir
ux
na
879
us
marie
xx
ua
481
cn
kim
tbl_018
id
location
name
data
network
823
uk
roger
xx
na
555
rus
vladmir
ux
na
879
us
maria
xx
ua
670
us
marie
xy
uy
888
in
raj
xx
jo
output:
id
name
addr
location
country
contintent
gdp
123
rob
dware
texas
us
us
8
456
lilly
gwood
london
uk
uk
5
670
rick
utown
newyrok
us
us
8
490
zang
kcity
hk
hongkong
hongkong
6
999
roger
ger
888
leslie
bel
555
vladmir
rus
879
marie
us
481
kim
cn
823
roger
uk
First, you should fix your data model. It is not a good idea to store such data in separate tables. Instead, you should store in a single table with a year column.
Second, I think you can solve your problem using full join, but it is a little tricky:
select coalesce(t21.id, t20.id, t19.id, t18.id) as id,
coalesce(t21.name, t20.name, t19.name, t18.name) as name,
t21.addr,
. . .
from tbl_2021 t21 full join
tbl_2020 t20
on t21.id = t20.id full join
tbl_2019 t19
on t19.id = coalesce(t21.id, t20.id) full join
tbl_2018 t18
on t18.id = coalesce(t21.id, t20.id, t19.id);
You need to carefully figure out how the columns should be pulled from the different tables.
First you can union all the data from four tables with union all. Then with row_number() we need to serialized rows for each id from higher to lower. Finally select one row for each id with highest year .
with cte as
(
select id,name addr ,location ,country, contintent,data,network, row_number()over (partition by id order by sl ) rn from
(
select id,name ,addr ,location , country, contintent,data,network, 1 sl from tbl_21
union all
select id,name ,'' addr ,location ,'' country,'' contintent, data, network, 2 sl from tbl_20
union all
select id,name ,'' addr ,location ,'' country,'' contintent, data,network, 3 sl from tbl_19
union all
select id,name ,'' addr ,location ,'' country,'' contintent, data,network, 4 sl from tbl_18
)t
)
select id,name ,addr ,location ,country, contintent,data,network from cte where rn=1

How to select data in SQL based on a filter which changes if there is no data in a specific table column?

I have tables similar to the three below. I need to join the first two tables based on id, and then join the third table based on second name. However the last table needs a filter where the city should be equal to London unless age is empty in which case the city should equal Manchester.
I tried the code below using CASE statement but it is not working. I am new to SQL so I was not sure how can I combine a where statement with an if clause where the filter for the selection changes depending on whether there is data in a different column than the one used to filter by. The DBMS I am using Toad for Oracle.
FIRST.NAME.TABLE
ID FIRST_NAME ENTRY_DATE
1 JOHN 09/09/2019
2 NICOLA 09/09/2019
3 PATRICK 05/09/2019
4 JOAN 01/09/2019
5 JAKE 09/09/2019
6 AMELIA 01/09/2019
7 CAMERON 09/09/2019
SECOND.NAME.TABLE
ID SECOND_NAME ENTRY_DATE
1 BROWN 09/09/2019
2 SMITH 09/09/2019
3 COLE 05/09/2019
4 HOUSTON 01/09/2019
5 FARRIS 09/09/2019
6 HATHAWAY 01/09/2019
7 JONES 09/09/2019
CITY.AGE.TABLE
CITY SECOND_NAME AGE
LONDON BROWN 24.00
LONDON SMITH
MANCHESTER COLE 30.00
MANCHESTER HOUSTON 66.00
LONDON FARRIS
LONDON HATHAWAY 32.00
GLASGOW JONES 28.00
MANCHESTER SMITH 32.00
LONDON FARRIS 62.00
SELECT FN.ID,
FN.FIRST_NAME,
SN.SECOND_NAME,
AC.CITY,
AC.AGE
FROM FIRST.NAME.TABLE AS FN
INNER JOIN SECOND.NAME.TABLE SN
ON FN.ID=SN.ID
INNER JOIN CITY.AGE.TABLE AS CA
ON SN.SECOND NAME=AC.SECOND_NAME
WHERE FN.ENTRY_DATE='09-SEP-19'
AND SN.ENTRY_DATE='09-SEP-19'
AND (CASE WHEN AC.CITY='LONDON' AND AC.AGE IS NOT NULL
THEN AC.CITY='LONDON'
ELSE AS.CITY='MANCHESTER' END)
You can express this as boolean logic:
WHERE FN.ENTRY_DATE = DATE '2019-09-09' AND
SN.ENTRY_DATE = DATE '2019-09-09' AND
(AC.AGE IS NOT NULL AND AC.CITY = 'LONDON' OR
AC.AGE IS NULL AND AC.CITY = 'MANCHESTER'
)
This answers your question about how to implement the logic using SQL. However, I'm not sure that is the logic that you really want. I speculate that you really want a LEFT JOIN to the age table.

VBA/SQL recordsets

The project I'm asking about is for sending an email to teachers asking what books they're using for the classes they're teaching next semester, so that the books can be ordered. I have a query that compares the course number of this upcoming semester's classes to the course numbers of historical textbook orders, pulling out only those classes that are being taught this semester. That's where I get lost.
I have a table that contains the following:
Professor
Course Number
Year
Book Title
The data looks like this:
professor year course number title
--------- ---- ------------- -------------------
smith 13 1111 Pride and Prejudice
smith 13 1111 The Fountainhead
smith 13 1222 The Alchemist
smith 12 1111 Pride and Prejudice
smith 11 1222 Infinite Jest
smith 10 1333 The Bible
smith 13 1333 The Bible
smith 12 1222 The Alchemist
smith 10 1111 Moby Dick
johnson 12 1222 The Tipping Point
johnson 11 1333 Anna Kerenina
johnson 10 1333 Everything is Illuminated
johnson 12 1222 The Savage Detectives
johnson 11 1333 In Search of Lost Time
johnson 10 1333 Great Expectations
johnson 9 1222 Proust on the Shore
Here's what I need the code to do "on paper":
Group the records by professor. Determine every unique course number in that group, and group records by course number. For each unique course number, determine the highest year associated. Then spit out every record with that professor+course number+year combination.
With the sample data, the results would be:
professor year course number title
--------- ---- ------------- -------------------
smith 13 1111 Pride and Prejudice
smith 13 1111 The Fountainhead
smith 13 1222 The Alchemist
smith 13 1333 The Bible
johnson 12 1222 The Tipping Point
johnson 11 1333 Anna Kerenina
johnson 12 1222 The Savage Detectives
johnson 11 1333 In Search of Lost Time
I'm thinking I should make a record set for each teacher, and within that, another record set for each course number. Within the course number record set, I need the system to determine what the highest year number is - maybe store that in a variable? Then pull out every associated record so that if the teacher ordered 3 books the last time they taught that class (whether it was in 2013 or 2012 and so on) all three books display. I'm not sure I'm thinking of record sets in the right way, though.
My SQL so far is basic and clearly doesn't work:
SELECT [All].Professor, [All].Course, Max([All].Year)
FROM [All]
GROUP BY [All].Professor, [All].Course;
Use your query as a subquery and INNER JOIN it back to the [ALL] table to filter the rows.
SELECT
a.Professor,
a.Year,
a.Course,
a.title
FROM
[ALL] AS a
INNER JOIN
(
SELECT [All].Professor, [All].Course, Max([All].Year) AS MaxOfYear
FROM [All]
GROUP BY [All].Professor, [All].Course
) AS sub
ON
a.Professor = sub.Professor
AND a.Course = sub.Course
AND a.Year = sub.MaxOfYear;