Need to select first address in Access database - sql

I'm using Access in Office 10 in a mixed Windows 7 / Windows XP environment.
I need to be able to select the current address for employees from a list. The problem I have is that the address datefrom could be past, future or null.
Removing future is obviously easy in the criteria, i.e. WHERE datefrom <=date()
The problem I have is that in the initial import of address data, most addresses did not have this information, and so the field is null. An example of the data is below: (date format is dd/mm/yyyy)
ID EmployeeID Postcode DateFrom
1 1 AB12 3CD [null]
2 2 GH12 5RF [null]
3 1 CD34 5EF 10/03/2012
4 3 HA25 3PO [null]
5 3 HA4 7RT 04/06/2012]
6 3 DB43 5YU 12/11/2011]
My desired output would be: (order of employees not important)
ID EmployeeID Postcode DateFrom
2 2 GH12 5RF [null]
3 1 CD34 5EF 10/03/2012
5 3 HA4 7RT 04/06/2012
I've tried sorting by DateFrom DESC which does order the list as below:
ID EmployeeID Postcode DateFrom
3 1 CD34 5EF 10/03/2012
1 1 AB12 3CD [null]
2 2 GH12 5RF [null]
5 3 HA4 7RT 04/06/2012
6 3 DB43 5YU 12/11/2011
4 3 HA25 3PO [null]
So if I could then just take the first result for each employee I'd be fine. However I've tried (and failed) to do SQL including things like DISTINCT, first() and GROUP BY, but don't seem to be able to get anywhere.
I probably just can't see the easy obvious answer, so any help would be very much appreciated.

Use order by like this:
ORDER BY (CASE WHEN [DateFrom] IS NULL THEN 1 ELSE 0 END) DESC,
[DateFrom] DESC
I found the answer from this post. Please make sure to search first.

How about:
SELECT Adr.ID, Adr.EmployeeID, Adr.Postcode, Adr.DateFrom
FROM Adr
WHERE (((Adr.ID) In
(SELECT Top 1 ID
FROM adr b
WHERE b.EmployeeID=Adr.EmployeeID
ORDER BY DateFrom DESC )));
The above is built using the query design wondow, so half the parentheses are unnecessary, however, if you are using the query design window, you may as well leave them.

Could you simply update the null values to be a set date like 1900-01-01?

Related

Why does adding a SUM(column) throw a group by error [SQL]

I found some similar questions, but none of the solutions would work, nor did they explain what was causing the issue.
I have a working query
SELECT pages.pageString pageName, timeSpent
FROM
(SELECT `page_id`, SUM(`time_spent`) as timeSpent
FROM `pageViews`
WHERE `time_spent` > 0
GROUP BY `page_id`) myTable
JOIN pages ON pages.id = page_id
ORDER BY timeSpent DESC
LIMIT 5
This returns results that look like
+------------------------------+-----------+
| pageName | timeSpent |
+------------------------------+-----------+
| page 1 | 394292 |
| page 2 | 66990 |
| page 3 | 53896 |
| page 4 | 37796 |
| page 5 | 14982 |
+------------------------------+-----------+
I'd like to add a column containing the percentage of timeSpent relative to the other pages, to start I added a SUM(timeSpent) to my query but that throws an error
In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'pages.pageString'
Im not sure why this column is effected by adding this new column to the select statement.
Sadly any solution involving changing sql settings won't work due to company policy.
I appreciate any advice
UPDATE
The failing sql statement is
SELECT pages.pageString pageName, timeSpent FROM
(SELECT `page_id`, SUM(`time_spent`) as timeSpent FROM
`pageViews` WHERE `time_spent` > 0 GROUP BY `page_id`) myTable
JOIN pages ON pages.id = page_id ORDER BY timeSpent DESC LIMIT 5
As per the first answer I added a groupBy which solves the error
SELECT pages.pageString pageName, timeSpent, SUM(timeSpent) FROM
(SELECT `page_id`, SUM(`time_spent`) as timeSpent FROM `pageViews` WHERE `time_spent` > 0 GROUP BY `page_id`) myTable
JOIN pages ON pages.id = page_id GROUP BY pageName ORDER BY timeSpent DESC LIMIT 5
This however does not give the proper output
+------------------------------+-----------+----------------+
| pageName | timeSpent | SUM(timeSpent) |
+------------------------------+-----------+----------------+
| page 1. | 390210 | 390210 |
| page 2 | 66972 | 66972 |
| page3 | 52332 | 52332 |
| page4 | 25454 | 25454 |
| page5 | 13552 | 13552 |
+------------------------------+-----------+----------------+
Ideally this SUM(timeSpent) would be 390210+ 66972 + 52332 + 25454 + 13552 so that I may do timeSpent / SUM(timeSpent)
You did not say where you tried to put the sum(timeSpent) but I believe one can try to reconstruct with the error message:
In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'pages.pageString'
It says what the problem is. You added sum(timeSpent) to the projection, but the SQL statement does not have a GROUP BY, in particular it mentions the first item which should be aggregated pages.pageString.
It would mention the other ones too, once you fix this one.
On the other hand, please make sure you post exactly the failing SQL statement instead of trying to describe how to get the error you have. It's better for us who try to help.
Update:
You have two tables/views pages and pageViews. The first one is used to get the page name. I would just focus on the time calculation to make things easier. Figuring out the name afterwards is simple, because it is directly connected to the page_id.
The first information you want is the sum of all times spent so that you can calculate the ratio to this sum.
This is simply an aggregation where you sum the times over all pages.
The second information you want is the sum of the times per page_id. You already know how to do that. You group by the page_id while aggregating the sums of each.
Try to put those two together now. You have the first statement of which the result shall be applied to each row of the second statement so that you get the table form page_id, time_spent_page, time_spent_all.
When you have step 3 then it is easy to add the page_name now, since you have the page_id which is required for a simple join.
I tried no to give away the solution. Maybe you like to try again following the steps above. If you have difficulties, simply leave a comment (maybe showing how far you got).
It might look complex in the beginning, but once you have done that successfully I hope you'll see that it can be simple.
Adding a column containing the percentage of timeSpent relative to the sum of all pages
SELECT pages.pageString pageName, timeSpent,
, timeSpent / sum(timeSpent) over() * 100 p
FROM
(SELECT `page_id`, SUM(`time_spent`) as timeSpent
FROM `pageViews`
WHERE `time_spent` > 0
GROUP BY `page_id`) myTable
JOIN pages ON pages.id = page_id
ORDER BY timeSpent DESC
LIMIT 5

Case statement logic and substring

Say I have the following data:
Passes
ID | Pass_code
-----------------
100 | 2xBronze
101 | 1xGold
102 | 1xSilver
103 | 2xSteel
Passengers
ID | Passengers
-----------------
100 | 2
101 | 5
102 | 1
103 | 3
I want to count then create a ticket in the output of:
ID 100 | 2 pass (bronze)
ID 101 | 5 pass (because it is gold, we count all passengers)
ID 102 | 1 pass (silver)
ID 103 | 2 pass (steel)
I was thinking something like the code below however, I am unsure how to finish my case statement. I want to substring pass_code so that we get show pass numbers e.g '2xBronze' should give me 2. Then for ID 103, we have 2 passes and 3 customers so we should output 2.
Also, is there a way to firstly find '2xbronze' if the pass_code contained lots of other things such as '101001, 1xbronze, FirstClass' - this may change so i don't want to substring, could we search for '2xbronze' and then pull out the 2??
SELECT
CASE
WHEN Passes.pass_code like '%gold%' THEN Passengers.passengers
WHEN Passes.pass_code like '%steel%' THEN SUBSTRING(passes.pass_code, 1,1)
WHEN Passes.pass_code like '%bronze%' THEN SUBSTRING(passes.pass_code, 1,1)
WHEN Passes.pass_code like '%silver%' THEN SUBSTRING(passes.pass_code, 1,1)
else 0 end as no,
Passes.ID,
Passes.Pass_code,
Passengers.Passengers
FROM Passes
JOIN Passengers ON Passes.ID = Passengers.ID
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=db698e8562546ae7658270e0ec26ca54
So assuming you are indeed using Oracle (as your DB fiddle implies).
You can do some string magic with finding position of a splitter character (in your case the x), then substringing based on that. Obviously this has it's problems, and x is a bad character seperator as well.. but based on your current set.
WITH PASSCODESPLIT AS
(
SELECT PASSES.ID,
TO_Number(SUBSTR(PASSES.PASS_CODE, 0, (INSTR(PASSES.PASS_CODE, 'x')) - 1)) AS NrOfPasses,
SUBSTR(PASSES.PASS_CODE, (INSTR(PASSES.PASS_CODE, 'x')) + 1) AS PassType
FROM Passes
)
SELECT
PASSCODESPLIT.ID,
CASE
WHEN PASSCODESPLIT.PassType = 'gold' THEN Passengers.Passengers
ELSE PASSCODESPLIT.NrOfPasses
END AS NrOfPasses,
PASSCODESPLIT.PassType,
Passengers.Passengers
FROM PASSCODESPLIT
INNER JOIN Passengers ON PASSCODESPLIT.ID = Passengers.ID
ORDER BY PASSCODESPLIT.ID ASC
Gives the result of:
ID NROFPASSES PASSTYPE PASSENGERS
100 2 bronze 2
101 5 gold 5
102 1 silver 1
103 2 steel 3
As can also be seen in this fiddle
But I would strongly advise you to fix your table design. Having multiple attributes in the same column leads to troubles like these. And the more variables/variations you start storing, the more 'magic' you need to keep doing.
In this particular example i see no reason why you don't simply have the 3 columns in Passes, also giving you the opportunity to add new columns going forward. I.e. to keep track of First class.
You can extract the numbers using regexp_substr(). So I think this does what you want:
SELECT (CASE WHEN p.pass_code LIKE '%gold%'
THEN TO_NUMBER(REGEXP_SUBSTR(p.pass_code, '^[0-9]+'))
ELSE pp.passengers
END) as num,
p.ID, p.Pass_code, pp.Passengers
FROM Passes p JOIN
Passengers pp
ON p.ID = pp.ID;
Here is a db<>fiddle.
This converts the leading digits in the code to a number. Also note the use of table aliases to simplify the query.

Is there a way to select results after a certain id in an order list?

I'm trying to implement a cursor-based paginating list based off of data from a Postgres database.
As an example, say I have a table with the following columns:
id | firstname | lastname
I want to paginate this data, which would be pretty simple if I only ever wanted to sort it by the id, but in my case, I want the option to sort by last name, and there's guaranteed to be multiple people with the same last name.
If I have a select statement like follows:
SELECT * FROM people
ORDER BY lastname ASC;
In the case, I could make my encoded cursor contain information about the lastname so I could pick up where I left off, but since there will be multiple users with the same last name, this will be buggy. Is there a way in SQL to only get the results after a certain id in an ordered list where it is not the column by which the results are sorted?
Example results from the select statement:
1 | John | Doe
4 | John | Price
2 | Joe | White
6 | Jim | White
3 | Sam | White
5 | Sally | Young
If I wanted a page size of 3, I couldn't add WHERE lastname <= :lastname as I'd have duplicate data on the list since it would return ids 2, 6, and 3 during that call. In my case, it'd be helpful if I could add to my query something similar to AFTER id = 6 where it could skip everything until it finds that id in the ordered list.
Yes. If I understand correctly:
select t.*
from t
where (lastname, id) > (select t2.lastname, t2.id
from t t2
where t2.id = ?
)
order by t.lastname;
I think I would add firstname into the mix, but it is the same idea.
Limit and offset are used for pagination e.g.:
SELECT id, lastname, firstname FROM people
Order by lastname, firstname, id
Offset 0
Limit 10
This will bring you the first to the 10th row, to retrieve the next page you need to specify the offset to 10
Here the documentation:
https://www.postgresql.org/docs/9.6/static/queries-limit.html

SQL Two SELECT vs. JOIN best performance?

I wonder which has better performance in this case. First of all, I want to show to the user his medical information. I have two tables
user
-----
id_user | type_blood | number | ...
1 O 123
2 A+ 442
user_allergies
-----------
id_user | name
1 name1
1 name2
I want to return:
JSON {id_user=1, type_blood=0, allergies=(name1,name2)}
So, Its better do a JOIN for user and user_allergies and iterate, or maybe two SELECT?
But if then I have another table like user_allergies, that the result can be:
user_another_table
-----------
id_user | name
1 namet1
1 namet2
1 namet3
JSON {id_user=1, type_blood=0, allergies=(name1,name2), table=(namet1,namet2,namet3)}
It's better three SELECT or a JOIN, but then I have to iterate on the results and I can't imagine a esay way. A JOIN can give me a result like:
id_user | type_blood | allergy_name | another_table_name
1 O name1 namet1
1 O name1 namet2
1 O name1 namet3
1 O name2 namet1
1 O name2 namet2
1 O name2 namet3
Is there any way to extract:
id_user | type_blood | allergy_name | another_table_name
1 O name1 namet1
1 O name2 namet2
1 O namet3
Thanks community, I'm newbie in SQL
Depending on the data - there is no way to get the 2nd set of results you've shown, if the 1st set of results shows the values. The 2nd one is throwing data away - in this case allergy 'name2' for another_table_name 'namet3'. This is why you get many rows back with repeated data.
You can use the group by clause to restrict this in some cases, but again - it won't let you throw away data like that.
You could try using the COALESCE clause, if your DB supports it.
If not, I think you're going to have to construct your JSON in some business logic, in which case its fine to read the data in a 3-way join. You order by the user id and either create or append the row data to the JSON document depending if a user record is present or not (if you order by user id, you only need to keep track of when the user id value changes).
Alternatively, you can read a list of users and single-item data in one query, and then ht the DB again for the repeating data.

SQL query dynamic row generation with composite key

My question is made of 3 parts.
First part:
Is there a way to generate rows based on a value?
E.g:
I want to give each family a number of vouchers based on their family_members_count.
Each voucher should have a unique id:
Base table:
id name family_members_count
1 fadi 2
2 sami 3
3 ali 1
Result:
family_id name voucher_id
1 fadi 121
1 fadi 122
2 sami 123
2 sami 124
2 sami 125
3 ali 126
Second part:
Can I control the voucher_id composite key? I want the voucher_id to be like this
(location)(cycle)(sequence 5 digits)
If north = 08 and we are in the second cycle it should be:
080200001
080200002
... and so on.
Third part:
I need the solution in both MS Access 2010 SQL and PostgreSQL 9.1 SQL.
Question 1
Use generate_series(). (In the coming version 9.3 look for the key word LATERAL.)
SELECT id AS family_id
,name
,120 + generate_series(1, family_members_count) AS voucher_id
FROM fam;
-> sqlfiddle demo
Question 2
SELECT id AS family_id
,name
,location
|| to_char(cycle, 'FM00')
|| to_char(generate_series(1, family_members_count), 'FM00000')
AS voucher_id
FROM fam2;
-> sqlfiddle demo
Note the use of to_char() to format numbers as text - and in particular the use of the FM pattern modifier to avoid leading white space.
Question 3
Sorry, I got MS Access out of my system 10 years ago and never looked back. Somebody else might fill in. I doubt it will be as simple.