How to write SQL script to update conditional data and constraints? - sql

I have just approached SQL and have not had the solution to do this.
I have 2 tables A and B:
A B
ID (char) Year (char)
Zone (char) Code (char)
ZCode (char)
At first, table B will be completely empty. Ex data of table A:
A
01 A 2013/AA
02 A 2018/KK
03 A null
04 B
05 B 2016/HH
I want to update data from table A to table B provided that only ZCode of Zone has the latest year and ZCode will be separated by a "/". This is the result I want:
B
2018 KK
2016 HH
Looking forward to having someone give me the solution to do this.

A very simple solution if your data is consistent. This only works if your data always have a complete year as number ex:2018on left and only 2 characters on right. This is more of hard coding of column length, cant see a reason why u cant use this.
Using Max will select latest year by code
Insert into tableB (Year,Code)
select Max(Left(Columnname,4)) year,
Right (columnname,2) Code from TableA
where Right (columnname,2) is not null or Right (columnname,2)<> ''
group by Right (columnname,2)

Try This
IF OBJECT_ID('dbo.TableA')IS NOT NULL
DROP TABLE TableA
IF OBJECT_ID('dbo.TableB')IS NOT NULL
DROP TABLE TableB
CREATE TABLE TableA (Id INT,Zone VARCHAR(2) ,ZCode VARCHAR(20))
CREATE TABLE TableB ([Year] INT,Code VARCHAR(20))
GO
INSERT INTO TableA(Id,Zone,ZCode)
SELECT 01,'A','2013/AA' UNION ALL
SELECT 02,'B','2016/HH' UNION ALL
SELECT 03,'A','2018/KK'
GO
INSERT INTO TableB
SELECT [Year]
,[Code]
FROM
(
SELECT SUBSTRING(ZCode,0,CHARINDEX('/',ZCode)) As [Year]
,SUBSTRING(ZCode,CHARINDEX('/',ZCode)+1,LEN(ZCode)) AS Code
FROM TableA
)dt
SELECT * FROM TableB ORDER BY [Year] DESC
Result
Year Code
------------
2018 KK
2016 HH
2013 AA

In order to UPDATE in tableB you would required to JOIN the table with tableB on Year / Code columns
WITH CTE AS
(
SELECT
left(a.zcode, 4) year,
substring(a.zcode, charindex('/', a.zcode)+1, len(a.zcode)) code
FROM tableA a
INNER JOIN (
select Zone, max(left(zcode, 4)) year
FROM tableA
GROUP BY Zone
)b ON a.Zone = b.zone and b.year = left(a.zcode, 4)
)
SELECT * FROM CTE

Below code snippet would give your desired output, now based on your requirement you can either do an INSERT into tableB or do an UPDATE
DECLARE #A TABLE(ID CHAR(10), ZONE CHAR(10), ZCODE CHAR(20))
INSERT INTO #A VALUES
('01', 'A', '2013/AA'),
('02', 'B', '2016/HH'),
('03', 'A', '2018/KK')
SELECT Year,Code FROM(
SELECT Year,Code,ROW_NUMBER() OVER (PARTITION BY ZONE ORDER BY Year DESC) rn FROM
(SELECT cast(concat('<x>', REPLACE(ZCODE, '/', '</x><x>'), '</x>') as xml).value('/x[1]','varchar(100)') AS Year,
cast(concat('<x>', REPLACE(ZCODE, '/', '</x><x>'), '</x>') as xml).value('/x[2]','varchar(100)') AS Code,*
FROM #A WHERE ZCODE IS NOT NULL) t1) t2
WHERE rn = 1;

You can use this query to insert data into table B, when it is completely empty
INSERT INTO B ([YEAR], [MONTH])
select
Substring(ZCode,0,charindex('/',ZCode)) BYEAR,
Substring(ZCode,charindex('/',ZCode)+1,LEN(ZCode)-charindex('/',ZCode)) BCode
from A
else use can update this query to update record of table B based on BCode.
Query edited for NOT NULL and GROUP condition
select MAX(v.BYEAR), v.BCode from
(select
Substring(ZCode,0,charindex('/',ZCode)) BYEAR
,Substring(ZCode,charindex('/',ZCode)+1,LEN(ZCode)-charindex('/',ZCode)) BCode
from B ) v
Where v.BCODE IS NOT NULL
Group by v.BCODE

Related

SQL - Select date ranges without overlapping

I have the following table (Oracle database):
ID
valid_from
valid_to
1
01.01.22
28.02.22
1
01.03.22
30.06.22
1
01.07.22
31.12.22
1
01.01.23
null
2
01.01.22
31.03.22
2
01.04.22
null
How do I best extract now all date ranges without overlaps over both IDs? The final result set should look like:
valid_from
valid_to
01.01.22
28.02.22
01.03.22
31.03.22
01.04.22
30.06.22
01.07.23
31.12.22
01.01.23
null
Null stands for max_date (PL / SQL Oracle Max Date).
Moreover, I should only select the values valid for the current year (let's assume we are already in 2022).
Thanks for your help in advance!
You can apply next select statement:
with
-- main table
t1 AS (SELECT w, q1, q2, to_date(q1,'dd.mm.yy') q1d, to_date(q2,'dd.mm.yy') q2d FROM www)
-- custom year in YYYY format
, t0 AS (SELECT '2022' y FROM dual)
-- join and order dates FROM - TO
, t2 AS (SELECT t1.q1, t1.q1d, s2.q2, s2.q2d
FROM t1
LEFT JOIN t1 s2 on t1.q1d <= s2.q2d
ORDER BY t1.q1d, s2.q2d)
-- mark the first each new row-pair by row_number()
, t3 AS (SELECT t2.*,
row_number() OVER (PARTITION BY t2.q1d ORDER BY t2.q1d ) r
FROM t2 )
-- join custom year value and select desired rows based on that value
SELECT q1, q2 FROM t3
JOIN t0 on 1=1
WHERE r = 1
-- for the custom year
AND t0.y <= to_char(q1d, 'yyyy')
ORDER BY q1d;
Demo
In my table-example dates are presented in varchar2 datatype and in dd.mm.yy date format. In case if your table fields have datatype date, then you don't need to implement function to_date() for those 2 fields.
Used table sample:
create table www (w integer, q1 varchar2(30), q2 varchar2(30));
insert into www values (1, '01.01.22', '28.02.22');
insert into www values (1, '01.03.22', '30.06.22');
insert into www values (1, '01.07.22', '31.12.22');
insert into www values (1, '01.01.23', '');
insert into www values (2, '01.01.22', '31.03.22');
insert into www values (2, '01.04.22', '');
If your table sample has more rows which are have null value in the field valid_to and the dates in valid_from are not in any range, let's say:
insert into www values (1, '01.01.24', '');
then previous solution will produce more rows in the end with null value.
In this case you can use that more complex solution:
...
-- join custom year value and select desired rows based on that value
, t4 as (SELECT q1, q2, q1d FROM t3
JOIN t0 on 1=1
WHERE r = 1 AND
-- for the custom year
t0.y <= to_char(q1d, 'yyyy')
ORDER BY q1d)
-- filter non-nullable rows
, t5 as ( SELECT q1, q2 FROM t4 WHERE Q2 IS NOT NULL )
-- max date from rows where Q2 field has null value
, t6 as ( SELECT to_char(MAX(Q1D),'dd.mm.yy') q1, q2
FROM t4
WHERE Q2 IS NULL
GROUP BY q2)
-- append rows with max date
SELECT * FROM t5
UNION ALL
SELECT * FROM t6;
Demo

SQL group three columns into one

I have a table with three columns:
[ID] [name] [link]
1 sample_name_1 sample_link_1
2 sample_name_2 sample_link_2
3 sample_name_3 sample_link_3
I need to somehow group them into one column, so the ideal result is this:
[one_column]
1
sample_name_1
sample_name_1
2
sample_name_2
sample_link_2
3
sample_name_3
sample_link_3
Does anyone have any suggestions on where to look and how to get it done in SQL Server?
You may try to use VALUES table value constructor with CROSS APPLY:
Table:
CREATE TABLE MyTable (
ID int,
name varchar(50),
link varchar(50)
)
INSERT INTO MyTable (ID, name, link)
VALUES
(1, 'sample_name_1', 'sample_link_1'),
(2, 'sample_name_2', 'sample_link_2'),
(3, 'sample_name_3', 'sample_link_3')
Statement:
SELECT v.one_column
FROM MyTable t
CROSS APPLY (VALUES
(1, CONVERT(varchar(50), ID)),
(2, CONVERT(varchar(50), name)),
(3, CONVERT(varchar(50), link))
) v (rn, one_column)
ORDER BY t.ID, v.rn
Result:
one_column
1
sample_name_1
sample_link_1
2
sample_name_2
sample_link_2
3
sample_name_3
sample_link_3
While this is something you should do in your presentation layer (i.e. your app or Website) you can do this in SQL:
select one column
from
(
select cast(id as varchar(10)) as one column, id as sortkey1, 1 as sortkey2 from mytable
union all
select name as one column, id as sortkey1, 2 as sortkey2 from mytable
union all
select link as one column, id as sortkey1, 3 as sortkey2 from mytable
) unioned
order by sortkey1, sortkey2;

Cannot get the values in time period using SQL Server Management Studio

Let me explain the process:
We've got a scanned questionnaries.
The OCR system processes these questionnaries to get data.
Then all recognized data(form_id, question_number, answer etc) goes into database.
For each form there are about 120-150 rows in database:
53453, 1, A, 2016-10-30 23:54:18.590
53453, 2, B, 2016-10-30 23:54:18.690
53453, 3, C, 2016-10-30 23:54:18.790 so on
As you can see, it is difficult enough to find duplicate of a questionnarie form in the database. SQL is not my strong point so I need your help) I need to select ID according to the condition: insertionTime difference of 1 min is not a duplicate. But if the ID exists somwhere else in another Time it would be a dublicate.
P.S. I did my best trying to explain my issue. Excuse me for my english)
Make sure your last column's data type is DATETIME the do:
SELECT tA.*
FROM MyTable tA INNER JOIN MyTable tB ON (tA.ID = tB.ID AND tA.question_number = tB.question_number AND tA.answer = tB.answer)
WHERE DATEDIFF(minute,tA.DateColumn,tB.DateColumn) < 2 -- DATEDIFF returns INT
You check only ID or also question and answer? I wrote my query for only ID and Date because You said If ID exists in other row with different time ( difference is more than minute - it is duplicate) you don't say anything about checking answer / question. In last row I modified time.
DECLARE #TMP TABLE (
ID INT,
VALUE INT,
VALUE2 VARCHAR(5),
DATES DATETIME
)
INSERT INTO #TMP
SELECT 53453, 1, 'A', '2016-10-30 23:54:18.590'
INSERT INTO #TMP
SELECT 53453, 2, 'B', '2016-10-30 23:54:18.690'
INSERT INTO #TMP
SELECT 53453, 3, 'C', '2016-10-30 23:56:20.590'
SELECT ID, MIN(DATES) DATES
INTO #TMP_ID
FROM #TMP
GROUP BY ID
-- MORE THAN MINUTE
SELECT *
FROM #TMP T
WHERE EXISTS (
SELECT NULL
FROM #TMP_ID X
WHERE DATEDIFF(second, x.dates, t.DATES) > 60
and x.id = t.id
)
-- LESS THAN MINUTE
SELECT *
FROM #TMP T
WHERE NOT EXISTS (
SELECT NULL
FROM #TMP_ID X
WHERE DATEDIFF(second, x.dates, t.DATES) > 60
and x.id = t.id
)
DROP TABLE #TMP_ID

Highlight multiple records in a date range

Working with SQL Server 2008.
fromdate todate ID name
--------------------------------
1-Aug-16 7-Aug-16 x jack
3-Aug-16 4-Aug-16 x jack
5-Aug-16 6-Aug-16 x tom
1-Aug-16 2-Aug-16 x john
3-Aug-16 4-Aug-16 x harry
5-Aug-16 6-Aug-16 x mac
Is there a way to script this so that I know if there are multiple names tagged to an ID in the same date range?
For example above, I want to flag that ID x has Name Jack and Tom tagged in the same date range.
ID multiple_flag
------------------------------------------------
x yes
y no
If there is a unique index in your table (in my example it is column i but you could also generate one by means of using ROW_NUMBER()) then you can do the following query based on an INNER JOIN to find overlapping date ranges:
CREATE TABLE #tmp (i int identity primary key,fromdate date,todate date,ID int,name varchar(32));
insert into #tmp (fromdate,todate,ID ,name) values
('1-Aug-16','7-Aug-16',3,'jack'),
('3-Aug-16','4-Aug-16',3,'tom'),
('5-Aug-16','6-Aug-16',3,'jack');
select a.*,b.name bname,b.i i2 from #tmp a
INNER join #tmp b on b.id=a.id AND b.i<>a.i
AND ( b.fromdate between a.fromdate and a.todate
OR b.todate between a.fromdate and a.todate)
(My id column is int). This will give you:
i fromdate todate ID name bname i2
- ---------- ---------- - ---- ----- --
1 2016-08-01 2016-08-07 3 jack tom 2
1 2016-08-01 2016-08-07 3 jack jack 3
Implement further filtering or grouping as required. I left a little demo here.
Please check the below sql, but it might not be the optimal one..
SELECT formdate,todate,id,tab1.name,
case when tab2.#Of >1 then 'yes' else 'no' end as multiple_flag
FROM tab1
inner join (SELECT Name, COUNT(*) as #Of
FROM tab1
GROUP BY Name) as tab2 on tab1.name=tab2.name
order by tab1.id ;
add your where condition, before the order by, if you need to add some date range on your sql.
change formdate to fromdate before run this sql, as I have used formdate in my machine.
The result looks like
One way to do it is using EXISTS CASE:
Please note this part of the query:
-- make sure the records date ranges overlap
AND t1.fromdate <= t2.todate
AND t2.fromdate <= t1.todate
for an explanation on testing for overlapping ranges, read the overlap wiki.
Create and populate sample data (Please save us this step in your future questions)
DECLARE #T as table
(
fromdate date,
todate date,
ID char(1),
name varchar(10)
)
INSERT INTO #T VALUES
('2016-08-01', '2016-08-07', 'x', 'jack'),
('2016-08-03', '2016-08-04', 'x', 'tom'),
('2016-08-05', '2016-08-06', 'x', 'jack'),
('2016-08-01', '2016-08-02', 'y', 'john'),
('2016-08-03', '2016-08-04', 'y', 'harry'),
('2016-08-05', '2016-08-06', 'y', 'mac')
The query:
SELECT DISTINCT id,
CASE WHEN EXISTS
(
SELECT 1
FROM #T t2
WHERE t1.Id = t2.Id
-- make sure it's not the same record
AND t1.fromdate <> t2.fromdate
AND t1.todate <> t2.todate
-- make sure the records date ranges overlap
AND t1.fromdate <= t2.todate
AND t2.fromdate <= t1.todate
)
THEN 'Yes'
ELSE 'No'
END As multiple_flag
FROM #T t1
Results:
id multiple_flag
---- -------------
x Yes
y No

How to update in order and query the updated fields when updating in SQL in a single statement

I need to calculate Dividend Factors in the DB and the basic calculation needed in a general way is row2 field2 = (row2's field1) * (row1's field2) where the field2 is the value I need to both update and query at the same time i.e. when I calculate it for one row, I need the calculated value of the previous row for this row.
Now I have a temp table with has all the values and now I need to calculate the final values, but when I tried this:
UPDATE
#temp
SET
field2 = IsNull(
(SELECT d2.field2 * d.field1 FROM #temp AS d2 WHERE d2.rowNr = d.rowNr - 1)
,d.field1
)
FROM
#temp as d
;
It always saw that the field2 was always NULL and went with the default action, with it should do only for the first row.
Now currently there are only two methods I know for doing this:
Loop through the #temp with a cursor
Use a while statement and loop through the table that way (I opted for this one, because I thought there is no point in using a cursor for a small table of 10-20 rows max)
But I still would like to get this into a single statement, but I have no idea how to do this. I am using MS SQL 2008 R2.
EDIT:
This is the actual data I am working with: (Note, that all field2 values are NULL prior to the calculation and the data type is money)
field1 field2(expected values)
------ ----------------------
1,033 1,033
1,0363 1,0705
1,0558 1,1302
1,0157 1,1479
1,0188 1,1695
1,026 1,1999
1,0286 1,2342
1,0323 1,2741
1,0319 1,3147
Okay if I'm understanding this, you want to find field2 which is based on previous rows of field2 that were just calculated so you need either some form of loop or recursion. Try this recursive solution out:
Setting Up Tables
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp;
DECLARE #yourTable TABLE (ID INT,field1 INT, field2 INT);
INSERT INTO #yourTable(ID,field1,field2)
VALUES (1111,11,11),(2222,22,22),(3333,33,33);
SELECT ROW_NUMBER() OVER (ORDER BY ID) rowNr,
ID,
field1,
field2 INTO #temp
FROM #yourTable;
Calculating values
WITH cte_recursion
AS
(
SELECT TOP 1
rowNR,
ID,
field1,
field2,
field1 AS dividend_factor
FROM #temp A
ORDER BY rowNr
UNION ALL
SELECT B.rowNr,
B.ID,
B.field1,
B.field2,
B.field1 * A.dividend_factor
FROM cte_recursion A
INNER JOIN #temp B
ON A.rowNr = B.rowNr - 1
)
Actual Update
UPDATE #yourTable
SET field2 = B.dividend_factor
FROM #yourTable A
INNER JOIN cte_recursion B
ON A.ID = B.ID
OPTION (MAXRECURSION 0)
SELECT *
FROM #yourTable
Results:
ID field1 field2
----------- ----------- -----------
1111 11 11
2222 22 242
3333 33 7986
Personally I wouldn't use the update because you have to constantly make sure the data is update to date. I'd much rather use the CTE I used to calculate the values and put it in a view so that you know the values are ALWAYS up to date and you don't have to worry about running it. Either that or having a dividend_factor column in your actual table that will be NULL unless the value is updated. Just my two cents
UPDATE d1
SET d1.field2 = IsNull(d2.field2 * d1.field1, d1.field1)
FROM #temp AS d1
left outer join #temp AS d2
on d2.rowNr = d1.rowNr - 1
magic
select d1.field1, EXP(SUM(LOG(d2.field1)))
from #temp AS d1
join #temp AS d2
on d2.rowNr <= d1.rowNr
group by d1.field1
op claims wrong answer
test for youself
drop table #temp;
create table #temp (ID int, val money);
insert into #temp (ID, val) values
(1, 1.033)
, (2, 1.0363)
, (3, 1.0558)
, (4, 1.0157)
, (5, 1.0188)
, (6, 1.026)
, (7, 1.0286)
, (8, 1.0323)
, (9, 1.0319);
SELECT TOP 10 [t1].[ID], EXP(SUM(LOG([t2].[val])))
from #temp AS t1
join #temp AS t2
on t2.[ID] <= t1.[ID]
group by t1.[ID]
order by t1.[ID]