How to display a one to many relationship as one to one - sql

I'm working with a paging system. For some of my co-workers I need to create something of a directory. Each person in the directory has a PAGER_ID and a MESSAGING_ID. The PAGER_ID is unique to a given paging device, while a MESSAGING_ID is unique to a person. A group might work in shifts and pass a single device from shift to shift resulting in several people having an identical PAGER_ID and different MESSAGING_ID. This is fine and by design.
For our directory the users want the following criteria met:
Each PAGER_ID to show up once and only once (I know I could do this by SELECT TOP 1 to the PAGER_ID)
A ten digit MESSAGING_ID is preferred when a PAGER_ID has a MESSAGING_ID that is ten digits and one or more other MESSAGING_ID with a different number of digits.
If there is no 10 digit MESSAGING_ID a MESSAGING_ID with any other number of digits will do.
Succinctly: They want to see only one record for a PAGER_ID / MESSAGING_ID. They don't care if every individual person is listed in the directory. They don't really care which MESSAGING_ID as long as each PAGER_ID shows in the directory with only one MESSAGING_ID and as long as a 10 digit MESSAGING_ID is given preference over those not 10 digits long.
I've tried combinations of TOP and IIF queries and have been unable to get them to all play nice together in the manner needed.
My basic select query is:
SELECT
tbl_Amcom_Prod.NAME,
tbl_Amcom_Prod.PAGER_ID,
tbl_Amcom_Prod.MESSAGING_ID
FROM tbl_Amcom_Prod
WHERE
(((tbl_Amcom_Prod.PAGER_ID) Like "241662"
Or (tbl_Amcom_Prod.PAGER_ID) Like "18888"))
ORDER BY tbl_Amcom_Prod.PAGER_ID;
and results in this:
| NAME | PAGER_ID | MESSAGING_ID |
--------------------------------------------
| TESTER 1 | 18888 | 18888 |
--------------------------------------------
| SMITH, MARK | 18888 | 5735551262 |
--------------------------------------------
| SUPERVISOR | 241662 | 102621 |
--------------------------------------------
| JOHN, JONES | 241662 | 101436 |
--------------------------------------------
| SEEGER, ROBERT | 241662 | 101409 |
--------------------------------------------
They want to see this:
| NAME | PAGER_ID | MESSAGING_ID |
--------------------------------------------
| SMITH, MARK | 18888 | 5735551262 |
--------------------------------------------
| SUPERVISOR | 241662 | 102621 |
--------------------------------------------
Any ideas?

If PAGER_ID and MESSAGING_ID are both text data type, this query returns the results you requested.
SELECT
t.NAME,
t.PAGER_ID,
t.MESSAGING_ID
FROM
(
SELECT
sub1.PAGER_ID,
DLookUp(
"MESSAGING_ID",
"tbl_Amcom_Prod",
"PAGER_ID = '" & sub1.PAGER_ID &
"' AND Len(MESSAGING_ID) = " &
sub1.MESSAGING_ID_max_length
) AS MESSAGING_ID
FROM
(
SELECT
PAGER_ID,
Max(Len(MESSAGING_ID))
AS MESSAGING_ID_max_length
FROM tbl_Amcom_Prod
GROUP BY PAGER_ID
) AS sub1
) AS sub2
INNER JOIN tbl_Amcom_Prod AS t
ON sub2.MESSAGING_ID = t.MESSAGING_ID
WHERE t.PAGER_ID In ("241662","18888")
ORDER BY t.PAGER_ID;
If PAGER_ID is Long Integer instead of text, use ...
DLookUp(
"MESSAGING_ID",
"tbl_Amcom_Prod",
"PAGER_ID = " & sub1.PAGER_ID &
" AND Len(MESSAGING_ID) = " &
sub1.MESSAGING_ID_max_length
) AS MESSAGING_ID
and ...
WHERE t.PAGER_ID In (241662,18888)
If MESSAGING_ID is Long Integer instead of text, change Max(Len(MESSAGING_ID)) to Max(Len(CStr(MESSAGING_ID))) and Len(MESSAGING_ID) to Len(Cstr(MESSAGING_ID))

Perhaps:
SELECT tt.NAME, tt.PAGER_ID, tt.MESSAGING_ID
FROM tt
WHERE tt.PAGER_ID In (
SELECT [PAGER_ID]
FROM tt a
WHERE a.MESSAGING_ID IN (
SELECT TOP 1 MESSAGING_ID
FROM tt
ORDER BY Len(MESSAGING_ID) DESC,PAGER_ID))
Where tt is your table.

Ok lets say your table is MyTable, then all you need is to group:
SELECT FIRST(Name), PAGER_ID, MAX(MESSAGING_ID)
FROM MyTable
GROUP BY PAGER_ID

Related

How to generate a sequence of ID's based on mapping tables and values from the forms in MS-Access (Sql)?

I want to generate ID's based on the form values in MS-Access. And then for each ID generated, create a group of ID's by adding another 4 digits in the end based on a Mapping Table, representing different octets for different time points (12 ID's based on the Initial ID and the mapping Table).
For example, if the ID generated based on form values is 123456, I want to add another four digits and create a group of ID's, say from a mapping table. Like,
123456**1111**
123456**1112**
123456**1113**
and so on.
So far each primary ID, I am slapping on four digits at the end and generating a group of 12 ID's.
I am a beginner in Access and I have tried some code:
UPDATE Table1 SET GenID = UPDATE Table1 SET Table1.GenID = t1 (SELECT Map.V FROM MAP as t1)
However, I get a error that Access does not recognize Map as a valid Field or expression. I am able to break down the problem into this. But could not find a way further and design a query.
Sample Data: (The short_ID and Long_ID tables, uses the mapping tables below each of them as shown.)
Short ID Table:
----------------------------------------------------
ID | Subject_ID | Organ_Type | Category | Short_ID
-----------------------------------------------------
1 | 100 | Kidney | A | 100200300
-----------------------------------------------------
2 | 400 | Heart | B | 400500600
Mapping Tables for Short ID:
Map1 for Table1:
---------------------
Map_from | Map_to |
---------------------
Kidney | 200 |
Heart | 500 |
---------------------
Map2 for Table1:
-----------------------------
Map_cat_from | Map_cat_to |
-----------------------------
A | 300 |
B | 600 |
-----------------------------
Long ID Table:(shown here are just examples for 2 time points rather than 12)
---------------------------------------------------
Subject_ID | Short_ID | Long_ID Timepoint |
---------------------------------------------------
100 | 100200300 | 1002003000001 |
---------------------------------------------------
100 | 100200300 | 1002003000002 |
---------------------------------------------------
400 | 400500600 | 4005006000001 |
---------------------------------------------------
400 | 400500600 | 4005006000002
Timepoint Map for Long ID Table:
------------------------------
Timepoint | Value_to_append |
------------------------------
1 | 0001 |
------------------------------
2 | 0002 |
I need to generate these short and long ID's from the mapping tables directly when input is given in the form. (Category, Organ_Type, Subject_ID)
tldr
generate id from mapping table and form values (id creation)
add four digits at the end and create a group of 12 id's (long id creation) based on a mapping table (which has the 12 four digits that is to be appended in the end)
First, create a query, QShortID:
SELECT
Table1.ID,
Table1.Subject_ID,
Table1.Organ_Type,
Table1.Category,
[Subject_ID] & [Map_to] & [Map_cat_to] AS Short_ID
FROM
(Table1
INNER JOIN
Map1
ON Table1.Organ_Type = Map1.Map_from)
INNER JOIN
Map2
ON Table1.Category = Map2.Map_cat_from;
Output:
Next, create a query, Dozen, that will return 12 rows:
SELECT DISTINCT
Abs([id] Mod 12) AS N
FROM
MSysObjects;
Finally, create a Cartesian (multiplying) query, QLongID:
SELECT Table1.ID, Table1.Subject_ID, Table1.Organ_Type, Table1.Category, [Subject_ID] & [Map_to] & [Map_cat_to] AS Short_ID
FROM (Table1 INNER JOIN Map1 ON Table1.Organ_Type = Map1.Map_from) INNER JOIN Map2 ON Table1.Category = Map2.Map_cat_from;
SELECT
QShortID.Subject_ID,
QShortID.Short_ID,
[Short_ID] & Format([N] + 1, "0000") AS Long_ID
FROM
QShortID,
Dozen
ORDER BY
[Short_ID] & Format([N] + 1, "0000");
Output:
Edit:
To use the timepoint mapping, use:
SELECT
QShortID.Subject_ID,
QShortID.Short_ID,
[Short_ID] & [Value_to_append] AS Long_ID
FROM
QShortID,
TimepointMap
ORDER BY
[Short_ID] & [Value_to_append];
Output:

Check if a string is composed by several substrings from a whitelist Table

Is there any way to select the expected records with Access query or SQL?
Environment
Access 2010
Table "words"
actual number of records: ten thousands-order
id | word |
---|---------------|
1 | green |
2 | light |
3 | greenlight |
4 | redlight |
5 | greenLEDlight |
6 | reddiamond |
Table "whitelist"
actual number of records: thousands-order
listword |
-------- |
green |
light |
Expected result
1) Select the following, with excluding "word" which consists of only "listword" including ones with concatenating them(*)
id | word |
---|---------------|
4 | redlight |
5 | greenLEDlight |
6 | reddiamond |
2) Or, select only "word" which of only "listword" including ones with concatenating them(*)
id | word |
---|---------------|
1 | green |
2 | light |
3 | greenlight |
(*) "green" or "light" or "greenlight" or "lightgreen"
What I tried
SELECT words.id, words.word
FROM words, whitelist
WHERE not exists (
SELECT listword
FROM whitelist
WHERE word Like "*" & [listword] & "*"
)
GROUP BY words.id, words.word;
Result
id | word |
---|---------------|
6 | reddiamond |
Do these two queries return what you look for ?
1)
SELECT id, word
FROM words
WHERE not exists (
SELECT *
FROM whitelist
WHERE listword = word
)
and not exists (
SELECT *
FROM whitelist w1, whitelist w2
WHERE w1.listword & w2.listword = word
)
2)
SELECT id, word
FROM words
WHERE exists (
SELECT *
FROM whitelist
WHERE listword = word
)
or exists (
SELECT *
from whitelist w1, whitelist w2
WHERE w1.listword & w2.listword = word
)
This code will check simple words on the whitelist and the existence of "exact" pairs like "greenlight". But will fail if you also need to check triplets like "greenlightgreen". You can add new subqueries crossing three or more times the whitelist table, but having thousands of records it will be awfully slow.

How to return unique rows having count() of multiple columns = 1 using group by?

So here is my situation:
____________________________________________
| idnumber | name | sectiongroup |
--------------------------------------------
| 123 | Joe | one |
| 123 | Barry | two |
| 1234 | Laura | one |
| 1234 | LauraCopyCat | one |
--------------------------------------------
I am trying to build a query which will return any unique (i.e. - COUNT(idnumber) = 1) id numbers in a given sectiongroup. So if you are in sectiongroup number one and no one else in your sectiongroup has the same ID number as you, then I want your idnumber. If someone in group two happens to have the same idnumer, that is okay, I still want your idnumber.
For example, Barry and Joe have the same id number but they are in separate sectiongroups, so I want to return their idnubers. However, Laura and LauraCopyCat have the SAME sectiongroup, so I do NOT want their idnumbers to be returned. So far I have the following:
SELECT idnumber
FROM namestable
GROUP BY idnumber, sectiongroup
HAVING(COUNT(idnumber) = 1)
Is there a way to add sectiongroup into the COUNT()=1 condition?
Just use COUNT(*) to avoid confusion. This will count the number of records in the particular group. Remember, a group consists of the unique combinations of values in the fields specified in your GROUP BY statement.
SELECT idnumber
FROM namestable
GROUP BY idnumber, sectiongroup
HAVING COUNT(*) = 1
Note that this will result in duplicate idnumbers, if you have records that share an id but have different subgroups. To remove duplicate, just change SELECT to SELECT DISTINCT.
Tested here: http://sqlfiddle.com/#!9/b0a50c/3

How to GROUP BY a new index in a new VIEW

I have 2 tables (load+road) and I want to make a new view with creating new column (flag) that indexate the COUNT of the lines, and then GROUP BY this new index.
I have tried this: (but it doesnt work)
sprintf(my_cmd,
"CREATE VIEW myVIEW(id, Rlength, Llength, flag) AS "
"SELECT road.id, road.length, load.length, COUNT(*) AS flag
FROM road, load "
"WHERE road.id=load.id; "
"SELECT id, Rlength, Llength
FROM myVIEW"
"GROUP BY flag");
ERROR:
Error executing query: ERROR: column "road.id" must appear in the GROUP BY clause or be used in an aggregate function
I am using MY SQL.
*edit:
I dont want that the new column (flag) appears in the last SELECT, but I want to group by it.. dont know if it can be done. if not, the thing I wanna reach, is to use group by on "SELECT id, Rlength, Llength " and to get all the lines in an only one group, but I dont have a Common parameter between thees lines so I have trying to add this "flag"
the full code (sorry for long question):
sprintf(my_cmd,
"CREATE VIEW myVIEW3(id, Rlength, Llength, flag) AS"
" SELECT road.id, road.length, load.length, COUNT(*) AS flag
FROM road, load"
" WHERE road.id=load.id;"
" SELECT id, Rlength, Llength FROM myVIEW3"
" GROUP BY flag"
" HAVING COUNT(*) <= %d"
" ORDER BY (CAST(Llength AS float) / CAST(Rlength AS float)) DESC, id DESC",k);
and what I am trying to do, is to get first k lines after making some ORDER without using LIMIT/TOP (its an assigment). So I have tried using new VIEW with some indecator that I will use for grouping all lines into one group and then use HAVING COUNT(flag) <= k.
road:
.--------.----------------.----------------.
| Id | length | speed |
.--------.----------------.----------------.-
| 9 | 55 | 90 |
| 10 | 44 | 80 |
| 11 | 70 | 100 |
load:
.--------.----------------.----------------.
| Id | length | speed |
.--------.----------------.----------------.-
| 9 | 10 | 20 |
| 10 | 15 | 30 |
| 11 | 30 | 60 |
COMMAND:
loadRanking 2
(k=2, so I want to get first 2 lines after some ORDER, lets not talk about the ORDER in this result)
result:
.--------.----------------.----------------.
| Id | length | speed |
.--------.----------------.----------------.-
| 9 | 10/55 | 20/90 |
| 10 | 15/44 | 30/80 |
Your group by should contain all columns that are being selected that are not part of the aggregate function. So your GROUP BY should look like this:
GROUP BY road.id, road.length, load.length
That being said, I am quite confused by why you have two queries here. I suspect your query should look something like this:
SELECT road.id, road.length, load.length, COUNT(*) AS flag
FROM road, load
WHERE road.id=load.id
GROUP BY road.id, road.length, load.length
HAVING COUNT(*) <= %d
ORDER BY (CAST(load.length AS float) / CAST(road.length AS float)) DESC, road.id DESC
The GROUP BY Statement
Additional note: Try making sure your query works before making it into a view.

SQLite, selecting values having same criteria (throughout all table)

I have an sqlite database table similar to the one given below
Name | Surname | AddrType | Age | Phone
John | Kruger | Home | 23 | 12345
Sarah | Kats | Home | 33 | 12345
Bill | Kruger | Work | 15 | 12345
Lars | Kats | Home | 54 | 12345
Javier | Roux | Work | 45 | 12345
Ryne | Hutt | Home | 36 | 12345
I would like to select Name values matching same "Surname" value for each of the rows in the table.
For example, for the first line the query would be "select Name from myTable where Surname='Kruger'" whereas for the second line the query would be "select Name from myTable where Surname='Kats' and so an....
Is it possible to traverse through the whole table and select all values like that?
PS : I will use these method in a C++ application, the alternative method is to use sqlite3_exec() and process each row one by one. I just want to know if there is any other possible way for the same approach.
I'd do:
sqlite> SELECT group_concat(Name, '|') Names FROM People GROUP BY Surname;
Names
----------
Ryne
Sarah|Lars
John|Bill
Javier
Then split each value of "Names" in C++ using the "|" separator (or any other you choose in group_concat function.
Basically you just want to exclude any records that don't have a buddy.
Something simple like joining the table against itself should work:
SELECT a.Name
FROM tab AS a
JOIN tab AS b
ON a.Surname = b.Surname;
Just returning the full sorted table and doing the duplicate check yourself may be faster if incidence is high (and will always be high for all sets of data). That would be a pretty strong assumption though.
SELECT Name
FROM tab
SORT BY Surname;