Trouble with Pivot Tables - sql

I know there are a lot of Pivot table examples on the internet, however I'm new to SQL and I'm having a bit of trouble as all the examples seem to be pertaining to aggregate functions.
Table 1:
|Date | Tag |Value |
|06/10 2:00pm | A | 65 |
|06/10 2:00pm | B | 44 |
|06/10 2:00pm | C | 33 |
|06/10 2:02pm | A | 12 |
|06/10 2:02pm | B | 55 |
|06/10 2:02pm | C | 21 |
....
|06/10 1:58am | A | 23 |
What I would like it to look like is (table 2):
|Date | A | B | C |
|06/10 2:00pm| 65 | 44 | 33 |
|06/10 2:02pm| 12 | 55 | 21 |
.....
|06/10 1:58am| 23 | etc. | etc. |
(sorry for the format)
Some problems that encounter (doesn't work with code I have found online)
I'd like to run this as a stored procedure (rather a SQL job), every 2 minutes so that this data from table 1 is constantly being moved to table 2. However I think I would need to alter the date every single time? (thats the syntax I've seen)
The pivot table itself seems simple on its own, but the datetime has been causing me grief.
Any code snipets or links would be greatly appreciated.
Thanks.

The pivot itself seems simple:
select *
from table1
pivot (min (Value) for Tag in ([A], [B], [C])) p
As for stored procedure, I would use last date saved in table2 as a filter for table1, excluding incomplete groups (I'm assuming that there will be, at some point, all three tags present, and that only last date can be incomplete. If not, you will need special processing for last date to update/insert a row).
So, in code:
create proc InsertPivotedTags
as
set NoCount ON
set XACT_ABORT ON
begin transaction
declare #startDate datetime
-- Last date from Table2 or start of time
select #startDate = isnull (max ([Date]), '1753-01-01')
from Table2
insert into Table2
select *
from Table1
pivot (min (Value) for Tag in ([A], [B], [C])) p
where [Date] > #startDate
-- exclude incomplete groups
and a is not null
and b is not null
and c is not null
commit transaction
If groups can be incomplete you should remove exlude filter and add a delete statement that removes last date in case it is incomplete, and adjusts #startDate to three milliseconds earlier to get the same rows again, but now in more filled up state.

Related

PostgreSQL Compare value from row to value in next row (different column)

I have a table of encounters called user_dates that is ordered by 'user' and 'start' like below. I want to create a column indicating whether an encounter was followed up by another encounter within 30 days. So basically I want to go row by row checking if "encounter_stop" is within 30 days of "encounter_start" in the following row (as long as the following row is the same user).
user | encounter_start | encounter_stop
A | 4-16-1989 | 4-20-1989
A | 4-24-1989 | 5-1-1989
A | 6-14-1993 | 6-27-1993
A | 12-24-1999 | 1-2-2000
A | 1-19-2000 | 1-24-2000
B | 2-2-2000 | 2-7-2000
B | 5-27-2001 | 6-4-2001
I want a table like this:
user | encounter_start | encounter_stop | subsequent_encounter_within_30_days
A | 4-16-1989 | 4-20-1989 | 1
A | 4-24-1989 | 5-1-1989 | 0
A | 6-14-1993 | 6-27-1993 | 0
A | 12-24-1999 | 1-2-2000 | 1
A | 1-19-2000 | 1-24-2000 | 0
B | 2-2-2000 | 2-7-2000 | 1
B | 5-27-2001 | 6-4-2001 | 0
You can select..., exists <select ... criteria>, that would return a boolean (always true or false) but if really want 1 or 0 just cast the result to integer: true=>1 and false=>0. See Demo
select ts1.user_id
, ts1.encounter_start
, ts1. encounter_stop
, (exists ( select null
from test_set ts2
where ts1.user_id = ts2.user_id
and ts2.encounter_start
between ts1.encounter_stop
and (ts1.encounter_stop + interval '30 days')::date
)::integer
) subsequent_encounter_within_30_days
from test_set ts1
order by user_id, encounter_start;
Difference: The above (and demo) disagree with your expected result:
B | 2-2-2000 | 2-7-2000| 1
subsequent_encounter (last column) should be 0. This entry starts and ends in Feb 2000, the other B entry starts In May 2001. Please explain how these are within 30 days (other than just a simple typo that is).
Caution: Do not use user as a column name. It is both a Postgres and SQL Standard reserved word. You can sometimes get away with it or double quote it. If you double quote it you MUST always do so. The big problem being it has a predefined meaning (run select user;) and if you forget to double quote is does not necessary produce an error or exception; it is much worse - wrong results.

SELECT dynamic json stored in SQL as part the select query

I know similar questions has been asked multiple times, however my scenario seems to be a bit different.
My database table is like this:
App ID | ID | JSONData | URL | CreatedOn
----------+-----------+-------------------+--------------+-----------------
5b5cd8 | 1 | {"F":"B", "S":"D"}| http://local | Mar 19 2018 13:04
5b5cd8 | 2 | {"F":"C", "S":"K"}| http://remote| Mar 29 2018 09:34
6b9df0 | 3 | {"T":"N", "D":"S"}| http://site | Apr 04 2018 16:12
App ID column can have varying values, however the structure of JSONData is (*supposed to be) same for the same App ID.
Is there anyway I can split the JSONData data and get a result like this?
App ID | ID | F | S | URL | CreatedOn
----------+-----------+-----+-----+--------------+-------------------------
5b5cd8 | 1 | B | D | http://local | Mar 19 2018 13:04
5b5cd8 | 2 | C | K | http://remote| Mar 29 2018 09:34
For the next App ID it is like this
App ID | ID | T | D | URL | CreatedOn
----------+-----------+-----+-----+--------------+-------------------------
6b9df0 | 3 | N | S | http://site | Apr 04 2018 16:12
Note: The data in JSONData field will mostly be one level deep i.e. all the data will be string and no further objects.
The solution I found most of the times like this, was either using static JSON key names to split, or creating temp table that is going to cause performance issues.
You were told already, that the column names of a result set must be known in advance.
The only workaround was dynamic SQL (creation of the statement as string and EXEC() to get its result). But this has some major draw-backs (and some advantages)...
You might go with something along this (needs SQL-Server 2016+):
A mockup-table
DECLARE #tbl TABLE(AppID VARCHAR(100),ID INT,JSONData NVARCHAR(MAX));
INSERT INTO #tbl VALUES
('5b5cd8',1,N'{"F":"B", "S":"D"}')
,('5b5cd8',2,N'{"F":"C", "S":"K"}')
,('6b9df0',3,N'{"T":"N", "D":"S"}');
--This query fetches the values using JSON_VALUE
--You'd need to create one statement for each possible list of columns
--Apply a WHERE to filter for appropriate rows
SELECT t.AppID
,t.ID
,JSON_VALUE(t.JSONData,'$.F') AS F
,JSON_VALUE(t.JSONData,'$.S') AS S
FROM #tbl t
WHERE t.AppID='5b5cd8'
--You might include all possible columns
--This works without a filter, but will return a lot of NULLs
SELECT t.AppID
,t.ID
,JSON_VALUE(t.JSONData,'$.F') AS F
,JSON_VALUE(t.JSONData,'$.S') AS S
,JSON_VALUE(t.JSONData,'$.T') AS T
,JSON_VALUE(t.JSONData,'$.D') AS D
FROM #tbl t
--A bit cleaner / better to read was OPENJSON() in connection with a WITH-clause
SELECT t.AppID
,t.ID
,JsonColumns.*
FROM #tbl t
CROSS APPLY OPENJSON(t.JSONData) WITH(F CHAR(1)
,S CHAR(1)
,T CHAR(1)
,D CHAR(1)) JsonColumns
My suggestion: Create the last one as a VIEW or (probably better) an iTVF and use dedicated statements against this, one per each type of structure.

How to dynamically call date instead of hardcoding in WHERE clause?

In my code using SQL Server, I am comparing data between two months where I have the exact dates identified. I am trying to find if the value in a certain column changes in a bunch of different scenarios. That part works, but what I'd like to do is make it so that I don't have to always go back to change the date each time I wanted to get the results I'm looking for. Is this possible?
My thought was that adding a WITH clause, but it is giving me an aggregation error. Is there anyway I can go about making this date problem simpler? Thanks in advance
EDIT
Ok I'd like to clarify. In my WITH statement, I have:
select distinct
d.Date
from Database d
Which returns:
+------+-------------+
| | Date |
+------+-------------|
| 1 | 01-06-2017 |
| 2 | 01-13-2017 |
| 3 | 01-20-2017 |
| 4 | 01-27-2017 |
| 5 | 02-03-2017 |
| 6 | 02-10-2017 |
| 7 | 02-17-2017 |
| 8 | 02-24-2017 |
| 9 | ........ |
+------+-------------+
If I select this statement and execute, it will return just the dates from my table as shown above. What I'd like to do is be able to have sql that will pull from these date values and compare the last date value from one month to the last date value of the next month. In essence, it should compare the values from date 8 to values from date 4, but it should be dynamic enough that it can do the same for any two dates without much tinkering.
If I didn't misunderstand your request, it seems you need a numbers table, also known as a tally table, or in this case a calendar table.
Recommended post: https://dba.stackexchange.com/questions/11506/why-are-numbers-tables-invaluable
Basically, you create a table and populate it with numbers of year's week o start and end dates. Then join your main query to this table.
+------+-----------+----------+
| week | startDate | endDate |
+------+-----------+----------+
| 1 | 20170101 | 20170107 |
| 2 | 20170108 | 20170114 |
+------+-----------+----------+
Select b.week, max(a.data) from yourTable a
inner join calendarTable b
on a.Date between b.startDate and b.endDate
group by b.week
dynamic dates to filter by BETWEEN
select dateadd(m,-1,dateadd(day,-(datepart(day,cast(getdate() as date))-1),cast(getdate() as date))) -- 1st date of last month
select dateadd(day,-datepart(day,cast(getdate() as date)),cast(getdate() as date)) -- last date of last month
select dateadd(day,-(datepart(day,cast(getdate() as date))-1),cast(getdate() as date)) -- 1st date of current month
select dateadd(day,-datepart(day,dateadd(m,1,cast(getdate() as date))),dateadd(m,1,cast(getdate() as date))) -- last date of the month

Join two tables - One common column with different values

I have been searching around for how to do this for days - unfortunately I don't have much experience with SQL Queries, so it's been a bit of trial and error.
Basically, I have created two tables - both with one DateTime column and a different column with values in.
The DateTime column has different values in each table.
So...
ACOQ1 (Table 1)
===============
| DateTime | ACOQ1_Pump_Running |
|----------+--------------------|
| 7:14:12 | 1 |
| 8:09:03 | 1 |
ACOQ2 (Table 2)
===============
| DateTime | ACOQ2_Pump_Running |
|----------+--------------------|
| 3:54:20 | 1 |
| 7:32:57 | 1 |
I want to combine these two tables to look like this:
| DateTime | ACOQ1_Pump_Running | ACOQ2_Pump_Running |
|----------+--------------------+--------------------|
| 3:54:20 | 0 OR NULL | 1 |
| 7:14:12 | 1 | 0 OR NULL |
| 7:32:57 | 0 OR NULL | 1 |
| 8:09:03 | 1 | 0 OR NULL |
I have achieved this by creating a third table that 'UNION's the DateTime column from both tables and then uses that third table's DateTime column for the new table but was wondering if there was a way to skip this step out.
(Eventually I will be adding more and more columns on from different tables and don't really want to be adding yet more processing time by creating a joint DateTime table that may not be necessary).
My working code at the moment:
CREATE TABLE JointDateTime
(
DateTime CHAR(50)
CONSTRAINT [pk_Key3] PRIMARY KEY (DateTime)
);
INSERT INTO JointDateTime (DateTime)
SELECT ACOQ1.DateTime FROM ACOQ1
UNION
SELECT ACOQ2.DateTime FROM ACOQ2
SELECT JointDateTime.DateTime, ACOQ1.ACOQ1_NO_1_PUMP_RUNNING, ACOQ2.ACOQ2_NO_1_PUMP_RUNNING
FROM (SELECT ACOQ1.DateTime FROM ACOQ1
UNION
SELECT ACOQ2.DateTime FROM ACOQ2) JointDateTime
LEFT OUTER JOIN ACOQ1
ON JointDateTime.DateTime = ACOQ1.DateTime
LEFT OUTER JOIN ACOQ2
ON JointDateTime.DateTime = ACOQ2.DateTime
You need a plain old FULL OUTER JOIN like this.
SELECT COALESCE(A1.DateTime,A2.DateTime) DateTime,ACOQ1_Pump_Running, ACOQ2_Pump_Running
FROM ACOQ1 A1
FULL OUTER JOIN ACOQ2 A2
ON A1.DateTime = A2.DateTime
This will give you NULL for ACOQ1_Pump_Running, ACOQ2_Pump_Running for rows which do not match the date in the corresponding table. If you need 0 just use COALESCE or ISNULL.
Side Note: : In your script, I can see your are using DateTime CHAR(50). Please use appropriate types

Append a zero to value if necessary in SQL statement DB2

I have a complex SQL statement that I need to match up two table based on a join. The the intial part of the complex query has a location number that is stored in a table as a Smallint and the second table has the Store number stored as a CHAR(4). I have been able to cast the smallint to a char(4) like this:
CAST(STR_NBR AS CHAR(4)) AND LOCN_NBR
The issue is that because the Smallint suppresses the leading '0' the join returns null values from the right hand side of the LEFT OUTER JOIN.
Example
Table set A(Smallint) Table Set B (Char(4))
| 96 | | 096 |
| 97 | | 097 |
| 99 | | 099 |
| 100 | <- These return -> | 100 |
| 101 | <- These return -> | 101 |
| 102 | <- These return -> | 102 |
I need to add make it so that they all return, but since it is in a join statement how do you append a zero to the beginning and in certain conditions and not in others?
SELECT RIGHT('0000' || STR_NBR, 4)
FROM TABLE_A
Casting Table B's CHAR to tinyint would work as well:
SELECT ...
FROM TABLE_A A
JOIN TABLE_B B
ON A.num = CAST(B.txt AS TINYINT)
Try LPAD function:
LPAD(col,3,'0' )
I was able to successfully match it out to obtain a 3 digit location number at all times by doing the following:
STR_NBR was originally defined as a SmallINT(2)
LOCN_NO was originally defined as a Char(4)
SELECT ...
FROM TABLE_A AS A
JOIN TABLE_B AS B
ON CAST(SUBSTR(DIGITS(A.STR_NBR),3,3)AS CHAR(4)) = B.LOCN_NO