SQL single query update - sql

I'm having to insert values into a new column in our database but I can't get my head around doing this in a consistent manner. There is a lot of data so doing anything manually is pretty much out of the question. Let me set the stage:
We have a table called Occurrence and a table called OccurenceBuckets where each occurrence is reference the bucket to which it has been assigned. Previously this was a one-way reference but for various reasons we have decided to add a reference back from the OccurrenceBucket to the first Occurrence (first in time, that is). The tables now look like this:
CREATE TABLE Occurrence
OccurrenceID uniqueidentifier,
OccurrenceBucketID uniqueidentifier,
OccurrenceTime datetime,
OccurrenceMessage nvarchar
...other meta data...
CREATE TABLE OccurrenceBucket
OccurrenceBucketID uniqueidentifier,
...other meta data...
FirstOccurrenceID uniqueidentifier,
FirstOccurrenceTime datetime,
FirstOccurrenceMessage nvarchar
I'm looking for a way to determine the first occurrence belonging to a bucket and assigning the FirstOccurrenceID, FirstOccurrenceTime and FirstOccurrenceMessage with values from this first occurrence for all my occurrencebuckets.
Do any of you sql-fu experts out there have the time to help me out, all my attempts seen to produce incorrect or incomplete selection of occurrences.

You can try this
DECLARE #Occurrence TABLE(
OccurrenceID INT,
OccurrenceBucketID INT,
OccurrenceTime DATETIME,
OccurrenceMessage VARCHAR(MAX)
)
INSERT INTO #Occurrence (OccurrenceID,OccurrenceBucketID,OccurrenceTime,OccurrenceMessage)
SELECT 1, 1, '01 Jan 2009', 'A'
INSERT INTO #Occurrence (OccurrenceID,OccurrenceBucketID,OccurrenceTime,OccurrenceMessage)
SELECT 2, 1, '02 Jan 2009', 'B'
INSERT INTO #Occurrence (OccurrenceID,OccurrenceBucketID,OccurrenceTime,OccurrenceMessage)
SELECT 3, 1, '03 Jan 2009', 'C'
INSERT INTO #Occurrence (OccurrenceID,OccurrenceBucketID,OccurrenceTime,OccurrenceMessage)
SELECT 4, 2, '04 Jan 2009', 'D'
INSERT INTO #Occurrence (OccurrenceID,OccurrenceBucketID,OccurrenceTime,OccurrenceMessage)
SELECT 5, 2, '05 Jan 2009', 'E'
INSERT INTO #Occurrence (OccurrenceID,OccurrenceBucketID,OccurrenceTime,OccurrenceMessage)
SELECT 6, 2, '06 Jan 2009', 'F'
SELECT * FROM #Occurrence
DECLARE #OccurrenceBucket TABLE(
OccurrenceBucketID INT,
FirstOccurrenceID INT,
FirstOccurrenceTime DATETIME,
FirstOccurrenceMessage VARCHAR(MAX)
)
INSERT INTO #OccurrenceBucket (OccurrenceBucketID) SELECT 1
INSERT INTO #OccurrenceBucket (OccurrenceBucketID) SELECT 2
SELECT * FROM #OccurrenceBucket
UPDATE #OccurrenceBucket
SET FirstOccurrenceID = OccurrenceID,
FirstOccurrenceTime = OccurrenceTime,
FirstOccurrenceMessage = OccurrenceMessage
FROM #OccurrenceBucket oc INNER JOIN
(
SELECT o.*
FROM #Occurrence o INNER JOIN
(
SELECT OccurrenceBucketID,
MIN(OccurrenceID) FirstOccurrenceID
FROM #Occurrence
GROUP BY OccurrenceBucketID
) Mins ON o.OccurrenceID = mins.FirstOccurrenceID
) Vals ON oc.OccurrenceBucketID = Vals.OccurrenceBucketID
SELECT * FROM #OccurrenceBucket
EDIT:
UPDATE #OccurrenceBucket
SET FirstOccurrenceID = OccurrenceID,
FirstOccurrenceTime = OccurrenceTime,
FirstOccurrenceMessage = OccurrenceMessage
FROM #OccurrenceBucket oc INNER JOIN
(
SELECT o.*
FROM #Occurrence o INNER JOIN
(
SELECT OccurrenceBucketID,
MIN(OccurrenceTime) FirstOccurrenceTime
FROM #Occurrence
GROUP BY OccurrenceBucketID
) Mins ON o.OccurrenceTime = mins.FirstOccurrenceTime
) Vals ON oc.OccurrenceBucketID = Vals.OccurrenceBucketID

Well, to find the first occurrence in a bucket shouldn't the following work?
SELECT TOP 1
OccurranceID, OccurranceTime, OccurranceMessage
FROM Occurance
WHERE
OccurranceBucketID = #OccurranceBucketID
ORDER BY
OccurranceTime ASC
You can assign the returned fields to variables and then update your OccurranceBucket accordingly.
NB: "occurrence" has no "a" in it.

This answer relies on the OccurrenceTime being unique for each Occurrence: -
update OccBuck set
OccBuck.FirstOccurrenceID = Occ.OccurrenceID,
OccBuck.FirstOccurrenceTime = Occ.OccurrenceTime,
OccBuck.FirstOccurrenceMessage = Occ.OccurrenceMessage
from
dbo.OccurrenceBucket as OccBuck
inner join dbo.Occurrence as Occ on OccBuck.OccurrenceBucketID = Occ.OccurrenceBucketID
inner join (select OccurrenceBucketID,
MIN(OccurrenceTime) as 'MinOccurrenceTime'
from dbo.Occurrence
group by OccurrenceBucketID) as minOcc on Occ.OccurrenceBucketID= minOcc.OccurrenceBucketID and
Occ.OccurrenceTime = minOcc.MinOccurrenceTime;

Related

Want to compare 4 different columns with the result of CTE

I have created a CTE (common table Expression) as follows:
DECLARE #N VARCHAR(100)
WITH CAT_NAM AS (
SELECT ID, NAME
FROM TABLE1
WHERE YEAR(DATE) = YEAR(GETDATE())
)
SELECT #N = STUFF((
SELECT ','''+ NAME+''''
FROM CAT_NAM
WHERE ID IN (20,23,25,30,37)
FOR XML PATH ('')
),1,1,'')
The result of above CTE is 'A','B','C','D','F'
Now I need to check 4 different columns CAT_NAM_1,CAT_NAM_2,CAT_NAM_3,CAT_NAM_4 in the result of CTE and form it as one column like follow:
Select
case when CAT_NAM_1 in (#N) then CAT_NAM_1
when CAT_NAM_2 in (#N) then CAT_NAM_2
when CAT_NAM_3 in (#N) then CAT_NAM_3
when CAT_NAM_4 in (#N) then CAT_NAM_4
end as CAT
from table2
When I'm trying to do the above getting error please help me to do.
If my approach is wrong help me with right one.
I am not exactly sure what you are trying to do, but if I understand the following script shows one possible technique. I have created some table variables to mimic the data you presented and then wrote a SELECT statement to do what I think you asked (but I am not sure).
DECLARE #TABLE1 AS TABLE (
ID INT NOT NULL,
[NAME] VARCHAR(10) NOT NULL,
[DATE] DATE NOT NULL
);
INSERT INTO #TABLE1(ID,[NAME],[DATE])
VALUES (20, 'A', '2021-01-01'), (23, 'B', '2021-02-01'),
(25, 'C', '2021-03-01'),(30, 'D', '2021-04-01'),
(37, 'E', '2021-05-01'),(40, 'F', '2021-06-01');
DECLARE #TABLE2 AS TABLE (
ID INT NOT NULL,
CAT_NAM_1 VARCHAR(10) NULL,
CAT_NAM_2 VARCHAR(10) NULL,
CAT_NAM_3 VARCHAR(10) NULL,
CAT_NAM_4 VARCHAR(10) NULL
);
INSERT INTO #TABLE2(ID,CAT_NAM_1,CAT_NAM_2,CAT_NAM_3,CAT_NAM_4)
VALUES (1,'A',NULL,NULL,NULL),(2,NULL,'B',NULL,NULL);
;WITH CAT_NAM AS (
SELECT ID, [NAME]
FROM #TABLE1
WHERE YEAR([DATE]) = YEAR(GETDATE())
AND ID IN (20,23,25,30,37,40)
)
SELECT CASE
WHEN EXISTS(SELECT 1 FROM CAT_NAM WHERE CAT_NAM.[NAME] = CAT_NAM_1) THEN CAT_NAM_1
WHEN EXISTS(SELECT 1 FROM CAT_NAM WHERE CAT_NAM.[NAME] = CAT_NAM_2) THEN CAT_NAM_2
WHEN EXISTS(SELECT 1 FROM CAT_NAM WHERE CAT_NAM.[NAME] = CAT_NAM_3) THEN CAT_NAM_3
WHEN EXISTS(SELECT 1 FROM CAT_NAM WHERE CAT_NAM.[NAME] = CAT_NAM_4) THEN CAT_NAM_4
ELSE '?' -- not sure what you want if there is no match
END AS CAT
FROM #TABLE2;
You can do a bit of set-based logic for this
SELECT
ct.NAME
FROM table2 t2
CROSS APPLY (
SELECT v.NAME
FROM (VALUES
(t2.CAT_NAM_1),
(t2.CAT_NAM_2),
(t2.CAT_NAM_3),
(t2.CAT_NAM_4)
) v(NAME)
INTERSECT
SELECT ct.NAM
FROM CAT_NAM ct
WHERE ct.ID IN (20,23,25,30,37)
) ct;

Select one records from three tables based on latest date

I have a TransactionMaster table in SQL Server 2012 that has unique TransactionID. The same TransactionID wil be availabe in LowTransaction ,MediumTransaction and HighTransaction tables.
For each TransactionID in TransactionMaster, I need to display one StatusMessage. The StatusMessage may come from any of the 3 tables - based on date formulated from CRTDTEC and CRTTIME columns..
What is the best way in SQL Server 2012 to select the StatusMessage corresponding to latest date?
Note: CRTDTEC Format - YYMMDD and CRTTIME Format - HHMMSS
CODE
DECLARE #TransactionMaster TABLE (TransactionID INT)
DECLARE #LowTransaction TABLE (TransactionID INT, StatusMessage VARCHAR(80), CRTDTEC VARCHAR(8), CRTTIME VARCHAR(6))
DECLARE #MediumTransaction TABLE (TransactionID INT, StatusMessage VARCHAR(80), CRTDTEC VARCHAR(8), CRTTIME VARCHAR(6))
DECLARE #HighTransaction TABLE (TransactionID INT, StatusMessage VARCHAR(80), CRTDTEC VARCHAR(8), CRTTIME VARCHAR(6))
INSERT INTO #TransactionMaster VALUES (1)
INSERT INTO #TransactionMaster VALUES (2)
INSERT INTO #TransactionMaster VALUES (3)
INSERT INTO #LowTransaction VALUES (1,'1 Low','20131213','235959')
INSERT INTO #MediumTransaction VALUES (1,'1','20131213','235900')
INSERT INTO #HighTransaction VALUES (1,'1 High','20111213','235959')
INSERT INTO #LowTransaction VALUES (2,'2 Low','20111213','235959')
INSERT INTO #LowTransaction VALUES (3,'3 Low','20111213','235959')
INSERT INTO #MediumTransaction VALUES (3,'3 Medium','20111213','235959')
INSERT INTO #HighTransaction VALUES (3,'3 High','20140101','235959')
Expected Result
(1,'1 Low','20131213','235959')
(2,'2 Low','20111213','235959')
(3,'3 High','20140101','235959')
The simplest way would probably be
WITH TransConsolidated
AS (SELECT *
FROM #LowTransaction
UNION ALL
SELECT *
FROM #MediumTransaction
UNION ALL
SELECT *
FROM #HighTransaction)
SELECT TM.TransactionID,
CA.*
FROM #TransactionMaster TM
CROSS APPLY (SELECT TOP 1 *
FROM TransConsolidated TC
WHERE TC.TransactionID = TM.TransactionID
ORDER BY CRTDTEC DESC,
CRTTIME DESC) CA
Or another possibility (if there is a covering index ordered by TransactionID on all tables) would be to merge join all four tables involved
;WITH CTE
AS (SELECT TM.TransactionID,
MAX(CA.CRTDTEC + CA.CRTTIME + CA.StatusMessage) AS MaxRow
FROM #TransactionMaster TM
LEFT MERGE JOIN #LowTransaction LT
ON LT.TransactionID = TM.TransactionID
LEFT MERGE JOIN #MediumTransaction MT
ON MT.TransactionID = TM.TransactionID
LEFT MERGE JOIN #HighTransaction HT
ON HT.TransactionID = TM.TransactionID
CROSS APPLY (SELECT LT.*
UNION ALL
SELECT MT.*
UNION ALL
SELECT HT.*) CA
GROUP BY TM.TransactionID)
SELECT TransactionID,
SUBSTRING(MaxRow, 1, 8),
SUBSTRING(MaxRow, 9, 6),
SUBSTRING(MaxRow, 16, 80)
FROM CTE
I guess you could do something like this...
SELECT TransactionID, StatusMessage, CRTDTEC, CRTTIME
FROM
(
SELECT *, rn = ROW_NUMBER() OVER (PARTITION BY TransactionID ORDER BY CRTDTEC DESC)
FROM
(
SELECT * FROM #LowTransaction
UNION ALL
SELECT * FROM #MediumTransaction
UNION ALL
SELECT * FROM #HighTransaction
)q
) q2
WHERE rn = 1
Result Set
TransactionID StatusMessage CRTDTEC CRTTIME
1 1 Low 20131213 235959
2 2 Low 20111213 235959
3 3 High 20140101 235959

Returning a set of the most recent rows from a table

I'm trying to retrieve the latest set of rows from a source table containing a foreign key, a date and other fields present. A sample set of data could be:
create table #tmp (primaryId int, foreignKeyId int, startDate datetime,
otherfield varchar(50))
insert into #tmp values (1, 1, '1 jan 2010', 'test 1')
insert into #tmp values (2, 1, '1 jan 2011', 'test 2')
insert into #tmp values (3, 2, '1 jan 2013', 'test 3')
insert into #tmp values (4, 2, '1 jan 2012', 'test 4')
The form of data that I'm hoping to retrieve is:
foreignKeyId maxStartDate otherfield
------------ ----------------------- -------------------------------------------
1 2011-01-01 00:00:00.000 test 2
2 2013-01-01 00:00:00.000 test 3
That is, just one row per foreignKeyId showing the latest start date and associated other fields - the primaryId is irrelevant.
I've managed to come up with:
select t.foreignKeyId, t.startDate, t.otherField from #tmp t
inner join (
select foreignKeyId, max(startDate) as maxStartDate
from #tmp
group by foreignKeyId
) s
on t.foreignKeyId = s.foreignKeyId and s.maxStartDate = t.startDate
but (a) this uses inner queries, which I suspect may lead to performance issues, and (b) it gives repeated rows if two rows in the original table have the same foreignKeyId and startDate.
Is there a query that will return just the first match for each foreign key and start date?
Depending on your sql server version, try the following:
select *
from (
select *, rnum = ROW_NUMBER() over (
partition by #tmp.foreignKeyId
order by #tmp.startDate desc)
from #tmp
) t
where t.rnum = 1
If you wanted to fix your attempt as opposed to re-engineering it then
select t.foreignKeyId, t.startDate, t.otherField from #tmp t
inner join (
select foreignKeyId, max(startDate) as maxStartDate, max(PrimaryId) as Latest
from #tmp
group by foreignKeyId
) s
on t.primaryId = s.latest
would have done the job, assuming PrimaryID increases over time.
Qualms about inner query would have been laid to rest as well assuming some indexes.

Splitting one row into more rows -- table transformation

I have one source table that should be converted to one destination table. The source table contains four columns with sensor values. The destination table should contain four rows with the single sensor value and with one column for number of the sensor -- for each row from the source table. In other words, the destination table will have four times more rows. (I believe this is called normalization. At least, I think it will be more practical in future when more or less or different sensors are to be used.)
More background information to explain. I have already successfully tried an insert trigger that does that for a single line:
CREATE TRIGGER dbo.temperatures_to_sensors
ON dbo.Data
AFTER INSERT
AS
BEGIN
DECLARE #line_no TINYINT;
SET #line_no = 2; -- hardwired for the production line
DECLARE #UTC DATETIME;
DECLARE #value1 FLOAT;
DECLARE #value2 FLOAT;
DECLARE #value3 FLOAT;
DECLARE #value4 FLOAT;
SELECT
#UTC = CAST((CAST(LEFT(inserted.UTC, 16) AS FLOAT) - 2415020.5) AS DATETIME),
#value1 = inserted.temperature_1,
#value2 = inserted.temperature_2,
#value3 = inserted.temperature_3,
#value4 = inserted.temperature_4
FROM inserted;
INSERT INTO dbo.line_sensor_values
(UTC, line_no, sensor_no, sensor_value)
VALUES (#UTC, #line_no, 1, #value1),
(#UTC, #line_no, 2, #value2),
(#UTC, #line_no, 3, #value3),
(#UTC, #line_no, 4, #value4);
END;
GO
Now, I would like to initialize the destination table from the old table once. After that, the trigger will continue to fill the values.
I am not good in SQL. I tried:
CREATE PROCEDURE dbo.init_line_sensor_values
AS
BEGIN
DECLARE #line_no TINYINT;
SET #line_no = 2; -- hardwired for the production line
DECLARE #UTC DATETIME;
DECLARE #value1 FLOAT;
DECLARE #value2 FLOAT;
DECLARE #value3 FLOAT;
DECLARE #value4 FLOAT;
INSERT INTO dbo.line_sensor_values
(UTC, line_no, sensor_no, sensor_value)
VALUES (#UTC, #line_no, 1, #value1),
(#UTC, #line_no, 2, #value2),
(#UTC, #line_no, 3, #value3),
(#UTC, #line_no, 4, #value4)
SELECT
#UTC = CAST((CAST(LEFT(t.UTC, 16) AS FLOAT) - 2415020.5) AS DATETIME),
#value1 = t.temperature_1,
#value2 = t.temperature_2,
#value3 = t.temperature_3,
#value4 = t.temperature_4
FROM dbo.Data AS t;
END;
GO
EXECUTE dbo.init_line_sensor_values
GO
... but it fails with
Cannot insert the value NULL into column 'UTC', table '1000574.dbo.line_sensor_values'; column does not allow nulls. INSERT fails.
It is apparent that the SELECT should be used differently to feed the INSERT. Or do I have to use the loop? (Cursor created and FETCH NEXT... and WHILE...)
UPDATED
The source table can be created this way (simplified):
CREATE TABLE dbo.Data(
UTC varchar(32) NOT NULL,
temperature_1 float NULL,
temperature_2 float NULL,
temperature_3 float NULL,
temperature_4 float NULL
PRIMARY KEY CLUSTERED
(
UTC ASC
)
GO
The destination table was created this way:
CREATE TABLE dbo.line_sensor_values (
UTC DATETIME NOT NULL,
line_no TINYINT NOT NULL, -- line number: 1, 2, 3, etc.
sensor_no TINYINT NOT NULL, -- sensor number: 1, 2, 3, etc.
sensor_value float NULL, -- the measured value
PRIMARY KEY CLUSTERED (
UTC ASC,
line_no ASC,
sensor_no ASC
)
)
GO
Thanks for your help, Petr
If all you need to do is to convert a table with four columns into a single table where each row represents a row number from a source table and a column from a source table, then here is an example:
Here is SQLFiddle
create table fourColumns
(
column1 varchar(50),
column2 varchar(50),
column3 varchar(50),
column4 varchar(50)
)
insert into fourColumns select 'A','B','C','D'
insert into fourColumns select 'E','F','G','H'
;with MyCTE (lineNumber, columnNumber, Result)
as
(
select ROW_NUMBER() OVER(ORDER BY column1 ASC) AS Row, 1, column1
from fourColumns
union all
select ROW_NUMBER() OVER(ORDER BY column2 ASC) AS Row, 2, column2
from fourColumns
union all
select ROW_NUMBER() OVER(ORDER BY column3 ASC) AS Row, 3, column3
from fourColumns
union all
select ROW_NUMBER() OVER(ORDER BY column4 ASC) AS Row, 4, column4
from fourColumns
)
-- add insert here
select lineNumber,
columnNumber,
Result
from MyCTE
order by lineNumber
INSERT INTO dbo.line_sensor_value
(UTC, line_no, sensor_no, sensor_value)
select UTC, line_no, sensor_no, temperature_1 as sensor_value from dbo.Data
union
select UTC, line_no, sensor_no, temperature_2 as sensor_value from dbo.Data
union
select UTC, line_no, sensor_no, temperature_3 as sensor_value from dbo.Data
union
select UTC, line_no, sensor_no, temperature_4 as sensor_value from dbo.Data

Need multiple copies of one resultset in sql without using loop

Following is the sample data. I need to make 3 copies of this data in t sql without using loop and return as one resultset. This is sample data not real.
42 South Yorkshire
43 Lancashire
44 Norfolk
Edit: I need multiple copies and I have no idea in advance that how many copies I need I have to decide this on the basis of dates. Date might be 1st jan to 3rd Jan OR 1st jan to 8th Jan.
Thanks.
Don't know about better but this is definatley more creative! you can use a CROSS JOIN.
EDIT: put some code in to generate a date range, you can change the date range, the rows in the #date are your multiplier.
declare #startdate datetime
, #enddate datetime
create table #data1 ([id] int , [name] nvarchar(100))
create table #dates ([date] datetime)
INSERT #data1 SELECT 42, 'South Yorkshire'
INSERT #data1 SELECT 43, 'Lancashire'
INSERT #data1 SELECT 44, 'Norfolk'
set #startdate = '1Jan2010'
set #enddate = '3Jan2010'
WHILE (#startdate <= #enddate)
BEGIN
INSERT #dates SELECT #startdate
set #startdate=#startdate+1
END
SELECT [id] , [name] from #data1 cross join #dates
drop table #data1
drop table #dates
You could always use a CTE to do the dirty work
Replace the WHERE Counter < 4 with the amount of duplicates you need.
CREATE TABLE City (ID INTEGER PRIMARY KEY, Name VARCHAR(32))
INSERT INTO City VALUES (42, 'South Yorkshire')
INSERT INTO City VALUES (43, 'Lancashire')
INSERT INTO City VALUES (44, 'Norfolk')
/*
The CTE duplicates every row from CTE for the amount
specified by Counter
*/
;WITH CityCTE (ID, Name, Counter) AS
(
SELECT c.ID, c.Name, 0 AS Counter
FROM City c
UNION ALL
SELECT c.ID, c.Name, Counter + 1
FROM City c
INNER JOIN CityCTE cte ON cte.ID = c.ID
WHERE Counter < 4
)
SELECT ID, Name
FROM CityCTE
ORDER BY 1, 2
DROP TABLE City
This may not be the most efficient way of doing it, but it should work.
(select ....)
union all
(select ....)
union all
(select ....)
Assume the table is named CountyPopulation:
SELECT * FROM CountyPopulation
UNION ALL
SELECT * FROM CountyPopulation
UNION ALL
SELECT * FROM CountyPopulation
Share and enjoy.
There is no need to use a cursor. The set-based approach would be to use a Calendar table. So first we make our calendar table which need only be done once and be somewhat permanent:
Create Table dbo.Calendar ( Date datetime not null Primary Key Clustered )
GO
; With Numbers As
(
Select ROW_NUMBER() OVER( ORDER BY S1.object_id ) As [Counter]
From sys.columns As s1
Cross Join sys.columns As s2
)
Insert dbo.Calendar([Date])
Select DateAdd(d, [Counter], '19000101')
From Numbers
Where [Counter] <= 100000
GO
I populated it with a 100K dates which goes into 2300. Obviously you can always expand it. Next we generate our test data:
Create Table dbo.Data(Id int not null, [Name] nvarchar(20) not null)
GO
Insert dbo.Data(Id, [Name]) Values(42,'South Yorkshire')
Insert dbo.Data(Id, [Name]) Values(43, 'Lancashire')
Insert dbo.Data(Id, [Name]) Values(44, 'Norfolk')
GO
Now the problem becomes trivial:
Declare #Start datetime
Declare #End datetime
Set #Start = '2010-01-01'
Set #End = '2010-01-03'
Select Dates.[Date], Id, [Name]
From dbo.Data
Cross Join (
Select [Date]
From dbo.Calendar
Where [Date] >= #Start
And [Date] <= #End
) As Dates
By far the best solution is CROSS JOIN. Most natural.
See my answer here: How to retrieve rows multiple times in SQL Server?
If you have a Numbers table lying around, it's even easier. You can DATEDIFF the dates to give you the filter on the Numbers table