Get data from one table using id and date ranges stored in another table - sql

The app I am writing is for telemetry units that get rented out to customers, and I am trying to query logged data for a particular customer, without a customer_id_fk column in the Log table.
I have the following tables:
Customer table:
id | name | ...
Unit table:
id | name | ...
RentOut table:
id | start_date | end_date | unit_id_fk | customer_id_fk
Log table:
id | unit_id_fk | datetime | data1 | data2
The reason a customer_id_fk column is not included in the Log table is so that a mistake in the RentOut table is easily rectified, without the need to alter data in the Log table (though maybe there is a better way to do this?).

It would be more efficient to include the customer in the log table. But, without that, you can get what you want with a bunch of joins:
select l.*, c.*
from log l left outer join
RentOut ro
on l.unit_id_fk = ro.unit_id_rk and
l.datetime between ro.start_date and ro.end_date left outer join
Customer c
on ro.customer_id = c.id
where c.<whatever> = <foobar>
If the dates in the RentOut table are really dates (with no times), and the log records have a datetime, then you might have to do more date arithmetic for the join to work properly. For instance, you might need to say the "start" really is after noon and the "end" is really before noon, for a given log record.

Related

SQL Join using a Junction Table not returning expected results

I'm joining 3 tables in SQL Server for the purpose of mapping users to their managers. The purpose of the junction table is to map their full usernames to their network ID. Here's the query:
select
dbo.Employee_Records.EMPLOYEE_NAME as AD_NAME,
dbo.Employee_Records.NAME as NAME,
dbo.Employee_Records_MANAGER_S_.KEYWORD as SUPERVISOR_FULLNAME,
dbo.employee_records.EMPLOYEE_NAME as SUPERVISOR_ADNAME
from
dbo.Employee_Records
left outer join dbo.Employee_Records_MANAGER_S_ on
dbo.Employee_Records.ID = dbo.Employee_Records_MANAGER_S_.ID
left join dbo.Employee_Records_MANAGER_S_ as ManagersList on
ManagersList.KEYWORD = dbo.employee_records.name
The first 3 columns appear as I'd expect, showing the Network ID, Full User Name and the Manager's Full Name. However, the 4th column is the issue. It's showing the network ID of the user, not the manager.
It appears like this:
AD_NAME | NAME | SUPERVISOR_FULLNAME | SUPERVISOR_ADNAME
USER1 | User, Test | Supervisor, Test | USER1
USER1 | User, Test | Supervisor2, Test | USER1
It should appear like this:
AD_NAME | NAME | SUPERVISOR_FULLNAME | SUPERVISOR_ADNAME
USER1 | User, Test | Supervisor, Test | SUPERVISOR1
USER1 | User, Test | Supervisor2, Test | SUPERVISOR2
The Employee.Records table contains all full usernames and full supervisor names. The Employee.Records_MANAGER_S_ table is used to tie the Supervisors to the users, since each user could have multiple supervisors. All of the mappings for Network Names and Full Names are also in the Employee.Records table, so technically I'm trying to join the Employee.Records table to the Employee_Records_MANAGER_S_ and then do another join of the Employee_Recors_MANAGER_S_ back to the Employee.Records table again, but this time joining on the SUPERVISOR_FULLNAME instead of the Employee's name.
If I understand correctly you need to join your dbo.Employee_Records table on a second time to get the supervisor details.
select
ER.EMPLOYEE_NAME as AD_NAME,
ER.[NAME] as [NAME],
M.KEYWORD as SUPERVISOR_FULLNAME,
MR.EMPLOYEE_NAME as SUPERVISOR_ADNAME
from dbo.Employee_Records as ER
left outer join dbo.Employee_Records_MANAGER_S_ as M on ER.ID = S.ID
left join dbo.Employee_Records as MR on MR.[NAME] = M.KEYWORD;
Note: As shown I highly recommend short table aliases as they make the query much clearer.

How to create a central parameter, like Report-Date?

I would like to create one location on sql server where I store the report-date and all queries and procedures should relate to this one value.
In that way I only have to change the report date on one location and it is valid for all related queries and procedures.
I started with a scalar function that retrieves a value from a table, but this slows down the queries enomoursly.
I tried an inline table valued function, but have no idea how to include this into a query.
I tried with a table that contains the report-date and used a cross join.
But it says:
The multi-part identifier could not be bound
Maybe some of you have an idea what to do here?
One possibility is to create a table, let's say TblReportDate with two columns: id and reportDate.
Then add one row with id 1 like following:
+----+------------+
| id | reportDate |
+----+------------+
| 1 | 04.04.2018 |
+----+------------+
Now join the table with a LEFT JOIN and use the >= operator to compare with the id-column of the main-table:
SELECT * FROM mainTable
LEFT JOIN TblReportDate ON mainTable.id >= TblReportDate.id

Select data from three different tables with null data

I am new in Sql. My question is how to get data from three different tables with null values.
I have tried a query as below:
SELECT *
FROM [USER]
JOIN [Location] ON ([Location].UserId = [USER].Id)
JOIN [ParentChild] ON ([ParentChild].UserId = [USER].Id) WHERE ParentId=7
which I find from this link.
Its working fine but, it not fetches all and each data associated with the ParentId
Something like it only fetches data which are available in all tables, but also omits some data which not available in Location tables but it comes under the given ParentId.
For example:
+----------+-------------+
| UserId | ParentId |
+----------+-------------+
| 1 | 7 |
+----------+-------------+
| 8 | 7 |
+----------+-------------+
For userId 8, there is data available in Location table,so it fetches all data. But there is no data for userId 1 available in Location table, so the query didn't work for this.
But I want all and every data.
If there is no data for userId then it can return only null columns.
Is it possible ??
hope everyone can understand my problem.
If you always want to return a list of users, but some may not have locations then you want to change the type of join from an "Inner Join" (or as you have used the short hand "JOIN") to a "Left Join".
SELECT *
FROM [USER]
INNER JOIN [ParentChild] ON ([ParentChild].UserId = [USER].Id)
LEFT JOIN [Location] ON ([Location].UserId = [USER].Id)
WHERE ParentId=7
This doesn't account for users that do not have a parent. If you still want to return users who do not have a parent then you would then need to change the JOIN type to your ParentChild table to a LEFT join also.

How can I fetch the last N rows, WITHOUT ordering the table

I have tables with multiple million rows and need to fetch the last rows of specific ID's
for example the last row which has device_id = 123 AND the last row which has device_id = 1234
because the tables are so huge and ordering takes so much time, is it possible to select the last 200 without ordering the table and then just order those 200 and fetch the rows I need.
How would I do that?
Thank you in advance for your help!
UPDATE
My PostgreSQL version is 9.2.1
sample data:
time device_id data data ....
"2013-03-23 03:58:00-04" | "001EC60018E36" | 66819.59 | 4.203
"2013-03-23 03:59:00-04" | "001EC60018E37" | 64277.22 | 4.234
"2013-03-23 03:59:00-04" | "001EC60018E23" | 46841.75 | 2.141
"2013-03-23 04:00:00-04" | "001EC60018E21" | 69697.38 | 4.906
"2013-03-23 04:00:00-04" | "001EC600192524"| 69452.69 | 2.844
"2013-03-23 04:01:00-04" | "001EC60018E21" | 69697.47 | 5.156
....
See SQLFiddle of this data
So if device_id = 001EC60018E21
I would want the most recent row with that device_id.
It is a grantee that the last row with that device_id is the row I want, but it may or may not be the last row of the table.
Personally I'd create a composite index on device_id and descending time:
CREATE INDEX table1_deviceid_time ON table1("device_id","time" DESC);
then I'd use a subquery to find the highest time for each device_id and join the subquery results against the main table on device_id and time to find the relevant data, eg:
SELECT t1."device_id", t1."time", t1."data", t1."data1"
FROM Table1 t1
INNER JOIN (
SELECT t1b."device_id", max(t1b."time") FROM Table1 t1b GROUP BY t1b."device_id"
) last_ids("device_id","time")
ON (t1."device_id" = last_ids."device_id"
AND t1."time" = last_ids."time");
See this SQLFiddle.
It might be helpful to maintain a trigger-based materialized view of the highest timestamp for each device ID. However, this will cause concurrency issues if most than one connection can insert data for a given device ID due to the connections fighting for update locks. It's also a pain if you don't know when new device IDs will appear as you have to do an upsert - something that's very inefficient and clumsy. Additionally, the extra write load and autovacuum work created by the summary table may not be worth it; it might be better to just pay the price of the more expensive query.
BTW, time is a terrible name for a column because it's a built-in data type name. Use something more appropriate if you can.
The general way to get the "last" row for each device_id looks like this.
select *
from Table1
inner join (select device_id, max(time) max_time
from Table1
group by device_id) T2
on Table1.device_id = T2.device_id
and Table1.time = T2.max_time;
Getting the "last" 200 device_id numbers without using an ORDER BY isn't really practical, but it's not clear why you might want to do that in the first place. If 200 is an arbitrary number, then you can get better performance by taking a subset of the table that's based on an arbitrary time instead.
select *
from Table1
inner join (select device_id, max(time) max_time
from Table1
where time > '2013-03-23 12:03'
group by device_id) T2
on Table1.device_id = T2.device_id
and Table1.time = T2.max_time;

Tricky SQL statement over 3 tables

I have 3 different transaction tables, which look very similar, but have slight differences. This comes from the fact that there are 3 different transaction types; depending on the transaction types the columns change, so to get them in 3NF I need to have them in separate tables (right?).
As an example:
t1:
date,user,amount
t2:
date,user,who,amount
t3:
date,user,what,amount
Now I need a query who is going to get me all transactions in each table for the same user, something like
select * from t1,t2,t3 where user='me';
(which of course doesn't work).
I am studying JOIN statements but haven't got around the right way to do this. Thanks.
EDIT: Actually I need then all of the columns from every table, not just the ones who are the same.
EDIT #2: Yeah,having transaction_type doesn't break 3NF, of course - so maybe my design is utterly wrong. Here is what really happens (it's an alternative currency system):
- Transactions are between users, like mutual credit. So units get swapped between users.
- Inventarizations are physical stuff brought into the system; a user gets units for this.
- Consumations are physical stuff consumed; a user has to pay units for this.
|--------------------------------------------------------------------------|
| type | transactions | inventarizations | consumations |
|--------------------------------------------------------------------------|
| columns | date | date | date |
| | creditor(FK user) | creditor(FK user) | |
| | debitor(FK user) | | debitor(FK user) |
| | service(FK service)| | |
| | | asset(FK asset) | asset(FK asset) |
| | amount | amount | amount |
| | | | price |
|--------------------------------------------------------------------------|
(Note that 'amount' is in different units;these are the entries and calculations are made on those amounts. Outside the scope to explain why, but these are the fields). So the question changes to "Can/should this be in one table or be multiple tables (as I have it for now)?"
I need the previously described SQL statement to display running balances.
(Should this now become a new question altogether or is that OK to EDIT?).
EDIT #3: As EDIT #2 actually transforms this to a new question, I also decided to post a new question. (I hope this is ok?).
You can supply defaults as constants in the select statements for columns where you have no data;
so
SELECT Date, User, Amount, 'NotApplicable' as Who, 'NotApplicable' as What from t1 where user = 'me'
UNION
SELECT Date, User, Amount, Who, 'NotApplicable' from t2 where user = 'me'
UNION
SELECT Date, User, Amount, 'NotApplicable', What from t3 where user = 'me'
which assumes that Who And What are string type columns. You could use Null as well, but some kind of placeholder is needed.
I think that placing your additional information in a separate table and keeping all transactions in a single table will work better for you though, unless there is some other detail I've missed.
I think the meat of your question is here:
depending on the transaction types the columns change, so to get them in 3NF I need to have them in separate tables (right?).
I'm no 3NF expert, but I would approach your schema a little differently (which might clear up your SQL a bit).
It looks like your data elements are as such: date, user, amount, who, and what. With that in mind, a more normalized schema might look something like this:
User
----
id, user info (username, etc)
Who
---
id, who info
What
----
id, what info
Transaction
-----------
id, date, amount, user_id, who_id, what_id
Your foreign key constraint verbiage will vary based on database implementation, but this is a little clearer (and extendable).
You should consider STI "architecture" (single table inheritance). I.e. put all different columns into one table, and put them all under one index.
In addition you may want to add indexes to other columns you're making selection.
What is the result schema going to look like? - If you only want the minimal columns that are in all 3 tables, then it's easy, you would just UNION the results:
SELECT Date, User, Amount from t1 where user = 'me'
UNION
SELECT Date, User, Amount from t2 where user = 'me'
UNION
SELECT Date, User, Amount from t3 where user = 'me'
Or you could 'SubClass' them
Create Table Transaction
(
TransactionId Integer Primary Key Not Null,
TransactionDateTime dateTime Not Null,
TransactionType Integer Not Null,
-- Othe columns all transactions Share
)
Create Table Type1Transactions
{
TransactionId Integer PrimaryKey Not Null,
// Type 1 specific columns
}
ALTER TABLE Type1Transactions WITH CHECK ADD CONSTRAINT
[FK_Type1Transaction_Transaction] FOREIGN KEY([TransactionId])
REFERENCES [Transaction] ([TransactionId])
Repeat for other types of transactions...
What about simply leaving the unnecessary columns null and adding a TransactionType column? This would result in a simple SELECT statement.
select *
from (
select user from t1
union
select user from t2
union
select user from t3
) u
left outer join t1 on u.user=t1.user
left outer join t2 on u.user=t2.user
left outer join t3 on u.user=t3.user