Related
I have the following table:
id type
1 NULL
2 A
3 NULL
4 NULL
5 A
6 NULL
7 B
8 A
9 B
10 NULL
I want to create a column where each row takes the current status if exist if not take the status from the previous one.
Basically want to get this:
id type new_type
1 NULL NULL -- firs one no previous so it can only be null
2 A A -- current type A so take it
3 NULL A -- no type so take the new_type of previous
4 NULL A
5 A A
6 NULL A
7 B B
8 A A
9 B B
10 NULL B
I know I need window function here but I don't know how a window function can reference a column that is "in progress" basically the window function need to reference both type and new_type but new_type doesn't exist yet.. it's the output.
How can this be done in SQL / Presto?
Presto has comprehensive support for window functions. Here, you can use lag() with the ignore nulls option to replace null values in column type:
select
id,
type,
coalesce(
type,
lag(type ignore nulls) over(order by id)
) new_type
from mytable
Needs a cursor, especially if the id is not guarantee to be sequential and without gaps.
This will run in MS-SQL:
-- stage sample data
drop table if exists oldTable
create table oldTable (id int, old_type nvarchar(1))
go
insert into oldTable values (1, null), (2, 'A'), (3, null), (4, null), (5, 'A'), (6, null), (7, 'B'), (8, 'A'), (9, 'B'), (10, null)
go
-- get new table ready
drop table if exists newTable
create table newTable (
id int,
old_type nvarchar(1),
new_type nvarchar(1)
)
GO
-- prepare for lots of cursing
declare #the_id int
declare #the_type nvarchar(1)
declare #running_type nvarchar(1)
declare mycursor cursor for
select
id, old_type
from
oldTable
-- do a barrel roll
open mycursor
fetch mycursor into #the_id, #the_type
while ##ERROR = 0 and ##FETCH_STATUS = 0
begin
set #running_type = COALESCE(#the_type, #running_type)
insert into newTable(id, old_type, new_type)
values (#the_id, #the_type, #running_type)
fetch mycursor into #the_id, #the_type
end
close mycursor
deallocate mycursor
go
-- verify results
select * from newTable
go
How can this be done in SQL
For example, it can be
SELECT t1.id,
t1.type,
( SELECT t2.type
FROM sourcetable t2
WHERE t2.id <= t1.id
AND t2.type IS NOT NULL
ORDER BY id DESC
LIMIT 1 ) new_type
FROM sourcetable t1
I want to collect a value from the source table of a SELECT statement used in an INSERT statement, that is NOT inserted into the target table
I am using Microsoft SQL Server 2017
I think the following code explains what I'm trying to do: Just cut and paste into SSMS to reproduce the error
DECLARE #CrossRef TABLE (
MyTable_ID INT,
C_C VARCHAR(10)
);
DECLARE #MyData TABLE (
A VARCHAR(10),
B VARCHAR(10),
C VARCHAR(10) );
INSERT INTO #MyData (A, B, C)
VALUES ('A1', 'B1', 'C1'), ('A2', 'B2', 'C2'),('A3', 'B3', 'C3');
DECLARE #MyTable TABLE (
ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
A VARCHAR(10),
B VARCHAR(10) );
INSERT INTO #MyTable (A, B)
OUTPUT INSERTED.Id, MyData.C
INTO #CrossRef (MyTable_ID, C_C)
SELECT A, B
FROM #MyData AS MyData
-- Error: The multi-part identifier "MyData.C" could not be bound.
-- DESIRED OUTPUT
SELECT * FROM #MyTable
/*
ID A B
----------
1 A1 B1
2 A2 B2
3 A3 B3
*/
SELECT * FROM #CrossRef
/*
MyTable_ID C_C
---------------
1 C1
2 C2
3 C3
*/
The OUTPUT clause cannot access anything not in the INSERTED or DELETED internal tables - which is the cause of the error.
However this example Microsoft T-SQL OUTPUT CLAUSE (albeit about DELETED) seems to suggest you can access other tables.
Note - The example has been highly simplified to make the issue as clear as possible
It may seem trivial to get the desired output by other means, but like anything in production the real situation is much more complex
Using the MERGE statement - as Suggested by Tab Alleman here is the solution:
DECLARE #CrossRef TABLE (
MyTable_ID INT,
C_C VARCHAR(10)
);
DECLARE #MyData TABLE (
ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
A VARCHAR(10),
B VARCHAR(10),
C VARCHAR(10) );
INSERT INTO #MyData (A, B, C)
VALUES ('A1', 'B1', 'C1'), ('A2', 'B2', 'C2'),('A3', 'B3', 'C3');
DECLARE #MyTable TABLE (
ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
A VARCHAR(10),
B VARCHAR(10) );
-- MERGE statement does UPDATE where join condition exists and INSERT where it does not
MERGE #MyTable
USING (SELECT A, B, C FROM #MyData) AS [Source]
ON (1=0) -- join never true so everything inserted, nothing updated
WHEN NOT MATCHED THEN
INSERT (A, B)
VALUES ([Source].A, [Source].B)
OUTPUT INSERTED.Id, [Source].C
INTO #CrossRef (MyTable_ID, C_C);
SELECT * FROM #MyData
SELECT * FROM #MyTable
SELECT * FROM #CrossRef
I need to do a JOIN with a 'near match'. The best way to explain this is with an example:
CREATE TABLE Car
(
Vin int,
Make nvarchar(50),
ColorID int,
)
CREATE TABLE Color
(
ColorID int,
ColorCode nvarchar(10)
)
CREATE TABLE ColorName
(
ColorID int,
Languagecode varchar(12),
ColorName nvarchar(50)
)
INSERT INTO Color Values (1, 'RED CODE')
INSERT INTO Color Values (2, 'GREEN CODE')
INSERT INTO Color Values (3, 'BLUE CODE')
INSERT INTO ColorName Values (1, 'en', 'Red')
INSERT INTO ColorName Values (1, 'en-US', 'Red, my friend')
INSERT INTO ColorName Values (1, 'en-GB', 'Red, my dear')
INSERT INTO ColorName Values (1, 'en-AU', 'Red, mate')
INSERT INTO ColorName Values (1, 'fr', 'Rouge')
INSERT INTO ColorName Values (1, 'fr-BE', 'Rouge, mon ami')
INSERT INTO ColorName Values (1, 'fr-CA', 'Rouge, mon chum')
INSERT INTO Car Values (123, 'Honda', 1)
The SPROC would look like this:
DECLARE #LanguageCode varchar(12) = 'en-US'
SELECT * FROM Car A
JOIN Color B ON (A.ColorID = B.ColorID)
LEFT JOIN ColorName C ON (B.ColorID = C.ColorID AND C.LanguageCode = #LanguageCode)
See http://sqlfiddle.com/#!6/ac24d/24 (thanks to Jake!)
Here is the challenge:
When the SPROC parameter #LanguageCode is an exact match, all is well.
I would like for it to also work for partial matches; more specifically: say for example that #LanguageCode would be 'en-NZ' then I would like the SPROC to return the value for language code 'en' (since there is no value for 'en-NZ').
As an extra challenge: if there is no match at all I would like to return the 'en' value; for example if #LanguageCode would be 'es' then the SPROC would return the 'en' value (since there is no value for 'es').
Try left(#LanguageCode, 2) + '%'
http://sqlfiddle.com/#!6/ac24d/26
About second part - you have to query table two times anyway (you can do it in one statement, but if will be like two statements in one). You also can insert data into temporary (or variable) table, check if there's no rows and then make another query
I've made a query with table function
http://sqlfiddle.com/#!6/b7be3/5
So you can write
DECLARE #LanguageCode varchar(12) = 'es'
if not exists (select * from sf_test(#LanguageCode))
select * from sf_test('en')
else
select * from sf_test(#LanguageCode)
you also can write
declare #temp table
(
Vin int,
Make nvarchar(50),
ColorCode nvarchar(10)
)
insert into #temp
select * from sf_test(#LanguageCode)
if not exists (select * from #temp)
select * from sf_test('en')
else
select * from #temp
As #Roman Pekar has said in his comment, this can indeed be done, including your additional request about falling back to en, in one statement with the help of a ranking function. Here's how you could go about it:
WITH FilteredAndRanked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (
PARTITION BY ColorID
ORDER BY CASE LanguageCode
WHEN #LanguageCode THEN 1
WHEN LEFT(#LanguageCode, 2) THEN 2
WHEN 'en' THEN 3
END
)
FROM ColorName
WHERE LanguageCode IN (
#LanguageCode,
LEFT(#LanguageCode, 2),
'en'
)
)
SELECT
...
FROM Car A
INNER JOIN Color B ON (A.ColorID = B.ColorID)
LEFT JOIN FilteredAndRanked C ON (B.ColorID = C.ColorID AND C.rnk = 1)
;
That is, the ColorName table is filtered and ranked before being used in the query, and then only the rows with the rankings of 1 are joined:
The filter for ColorName includes only rows with LanguageCode values of #LanguageCode, LEFT(#LanguageCode, 2) and 'en'.
The ranking values are assigned based on which language code each row contains: rows with LEFT(#LanguageCode, 2) are ranked after those with #LanguageCode but before the 'en' ones.
Here is my scenario:
I have a Person table with following fields.
create table Person(PersonID int primary key identity(1,1),
Age int,
height decimal(4,2),
weight decimal(6,2)
);
insert into Person(Age,height,weight) values (60,6.2,169); -- 1
insert into Person(Age,height,weight) values (15,5.1,100); -- 2
insert into Person(Age,height,weight) values (10,4.5,50); -- 3
What I need to do is,
if the person Age >= 18 and height >= 6 then calculationValue = 20
if the person Age >= 18 and height < 6 then calculationValue = 15
if the person Age < 18 and weight >= 60 then calculationValue = 10
if the person Age < 18 and weight < 60 then calculationValue = 5
based on these condition I need to find the calculationValue and do some math.
I tried to make a flexible model so in future it would be easier to add any more conditions and can easily change the constant values (like 18, 6, 60 etc)
I created couple of tables as below:
create table condTable(condTableID int primary key identity(1,1),
condCol varchar(20),
startValue int,
endValue int
);
insert into condTable(condCol,startValue,endValue) values ('Age',18,999) -- 1
insert into condTable(condCol,startValue,endValue) values ('Height',6,99) -- 2
insert into condTable(condCol,startValue,endValue) values ('Height',0,5.99) -- 3
insert into condTable(condCol,startValue,endValue) values ('Age',0,17) -- 4
insert into condTable(condCol,startValue,endValue) values ('Weight',60,999) -- 5
insert into condTable(condCol,startValue,endValue) values ('Weight',0,59) -- 6
I join two condition to make it one in the following table as given by the requirement.(ie. if age >=18 and height >=6 then calculationValue = 20. etc)
create table CondJoin(CondJoin int,condTableID int,CalculationValue int)
insert into CondJoin values (1,1,20)
insert into CondJoin values (1,2,20)
insert into CondJoin values (2,1,15)
insert into CondJoin values (2,3,15)
insert into CondJoin values (3,4,10)
insert into CondJoin values (3,5,10)
insert into CondJoin values (4,4,5)
insert into CondJoin values (4,6,5)
I think this model will provide the flexibility of adding more conditions in future. But I am having difficulties on implementing it in SQL Server 2005. Anyone can write a sql that process in set basis and compare the value in Person table with CondJoin table and provide the corresponding calculationvalue. For eg. for person ID 1 it should look at CondJoin table and give the calculationValue 20 since his age is greater than 18 and height is greater than 6.
this looks like you are headed towards dynamic sql generation.
i think maybe you would be better off with a row for each column and cutoff values for the ranges, and a value if true ... maybe something like:
age_condition
-----------------
min_age
max_age
value
this is something that you could populate and then query without some dynamic generation.
The following is extremely rough but it should get the point across. It normalizes the data and moves towards a semi-object oriented (attribute/value/attribute value) structure. I'll leave it up to you to reinforce referential integrity, but the following is flexible and will return the results you want:
CREATE TABLE Person (
PersonID INT PRIMARY KEY IDENTITY(1,1)
,Name NVARCHAR(255)
);
GO
CREATE TABLE PersonAttribute (
PersonID INT
,CondAttributeID INT
,Value NVARCHAR(255)
);
GO
CREATE TABLE CondAttribute (
AttributeID INT PRIMARY KEY IDENTITY(1,1)
,Attribute NVARCHAR(255));
GO
CREATE TABLE CondTable (
CondTableID INT PRIMARY KEY IDENTITY(1,1)
,CondAttributeID INT
,StartValue MONEY
,EndValue MONEY
);
GO
CREATE TABLE CalculationValues (
CalculationID INT PRIMARY KEY IDENTITY(1,1)
,CalculationValue INT
);
GO
CREATE TABLE CondCalculation (
CondTableID INT
,CalculationID INT
);
INSERT Person (Name)
VALUES ('Joe')
,('Bob')
,('Tom');
INSERT PersonAttribute (
PersonID
,CondAttributeID
,Value
)
VALUES (1, 1, '60')
,(1, 2, '6.2')
,(1, 3, '169')
,(2, 1, '15')
,(2, 2, '5.1')
,(2, 3, '100')
,(3, 1, '10')
,(3, 2, '4.5')
,(3, 3, '50');
INSERT CondAttribute (Attribute)
VALUES ('Age')
,('height')
,('weight');
INSERT CondTable (
CondAttributeID
,StartValue
,EndValue)
VALUES (1,18,999) --Age
,(2,6,99) --Height
,(2,0,5.99) -- Height
,(1,0,17) -- Age
,(3,60,999) -- Weight
,(3,0,59); -- Weight
INSERT CalculationValues (CalculationValue)
VALUES (5)
,(10)
,(15)
,(20);
INSERT CondCalculation (CondTableID, CalculationID)
VALUES (1,4)
,(2,4)
,(1,3)
,(3,3)
,(4,2)
,(5,2)
,(5,1)
,(6,1);
SELECT *
FROM Person AS p
JOIN PersonAttribute AS pa ON p.PersonID = pa.PersonID
JOIN CondAttribute AS ca ON pa.CondAttributeID = ca.AttributeID
JOIN CondTable AS ct ON ca.AttributeID = ct.CondAttributeID
AND CONVERT(money,pa.Value) BETWEEN ct.StartValue AND ct.EndValue
JOIN CondCalculation AS cc ON cc.CondTableID = ct.CondTableID
JOIN CalculationValues AS c ON cc.CalculationID = c.CalculationID
WHERE p.PersonID = 1
The following solution uses PIVOT (twice) to transform the combination of CondJoin and condTable into a chart, then joins the chart to the Person table to calculate the target value. I believe, a series of CASE expressions could be used instead just as well. Anyway...
All the tables have been turned into table variables, for easier testing. So first, DDL and data preparation:
declare #Person table(PersonID int primary key identity(1,1),
Age int,
height decimal(4,2),
weight decimal(6,2)
);
insert into #Person(Age,height,weight) values (60,6.2,169); -- 1
insert into #Person(Age,height,weight) values (15,5.1,100); -- 2
insert into #Person(Age,height,weight) values (10,4.5,50); -- 3
declare #condTable table(condTableID int primary key identity(1,1),
condCol varchar(20),
startValue int,
endValue int
);
insert into #condTable(condCol,startValue,endValue) values ('Age',18,999) -- 1
insert into #condTable(condCol,startValue,endValue) values ('Height',6,99) -- 2
insert into #condTable(condCol,startValue,endValue) values ('Height',0,5.99) -- 3
insert into #condTable(condCol,startValue,endValue) values ('Age',0,17) -- 4
insert into #condTable(condCol,startValue,endValue) values ('Weight',60,999) -- 5
insert into #condTable(condCol,startValue,endValue) values ('Weight',0,59) -- 6
declare #CondJoin table(CondJoin int,condTableID int,CalculationValue int);
insert into #CondJoin values (1,1,20)
insert into #CondJoin values (1,2,20)
insert into #CondJoin values (2,1,15)
insert into #CondJoin values (2,3,15)
insert into #CondJoin values (3,4,10)
insert into #CondJoin values (3,5,10)
insert into #CondJoin values (4,4,5)
insert into #CondJoin values (4,6,5)
And now the query:
;with startValues as (
select
CondJoin,
Age,
Height,
Weight,
CalculationValue
from (
select
j.CondJoin,
j.CalculationValue,
t.condCol,
t.startValue
from #CondJoin j
inner join #condTable t on j.condTableID = t.condTableID
) j
pivot (
max(startValue) for condCol in (Age, Height, Weight)
) p
),
endValues as (
select
CondJoin,
Age,
Height,
Weight,
CalculationValue
from (
select
j.CondJoin,
j.CalculationValue,
t.condCol,
t.endValue
from #CondJoin j
inner join #condTable t on j.condTableID = t.condTableID
) j
pivot (
max(endValue) for condCol in (Age, Height, Weight)
) p
),
combinedChart as (
select
s.CondJoin,
AgeFrom = s.Age,
AgeTo = e.Age,
HeightFrom = s.Height,
HeightTo = e.Height,
WeightFrom = s.Weight,
WeightTo = e.Weight,
s.CalculationValue
from startValues s
inner join endValues e on s.CondJoin = e.CondJoin
)
select
p.*,
c.CalculationValue
from #Person p
left join combinedChart c
on (c.AgeFrom is null or p.Age between c.AgeFrom and c.AgeTo)
and (c.HeightFrom is null or p.Height between c.HeightFrom and c.HeightTo)
and (c.WeightFrom is null or p.Weight between c.WeightFrom and c.WeightTo)
I have
SELECT * FROM Table1 WHERE Col1 IN(4,2,6)
I want to select and return the records with the specified order which i indicate in the IN clause
(first display record with Col1=4, Col1=2, ...)
I can use
SELECT * FROM Table1 WHERE Col1 = 4
UNION ALL
SELECT * FROM Table1 WHERE Col1 = 6 , .....
but I don't want to use that, cause I want to use it as a stored procedure and not auto generated.
I know it's a bit late but the best way would be
SELECT *
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')
Or
SELECT CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')s_order,
*
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY s_order
You have a couple of options. Simplest may be to put the IN parameters (they are parameters, right) in a separate table in the order you receive them, and ORDER BY that table.
The solution is along this line:
SELECT * FROM Table1
WHERE Col1 IN(4,2,6)
ORDER BY
CASE Col1
WHEN 4 THEN 1
WHEN 2 THEN 2
WHEN 6 THEN 3
END
select top 0 0 'in', 0 'order' into #i
insert into #i values(4,1)
insert into #i values(2,2)
insert into #i values(6,3)
select t.* from Table1 t inner join #i i on t.[in]=t.[col1] order by i.[order]
Replace the IN values with a table, including a column for sort order to used in the query (and be sure to expose the sort order to the calling application):
WITH OtherTable (Col1, sort_seq)
AS
(
SELECT Col1, sort_seq
FROM (
VALUES (4, 1),
(2, 2),
(6, 3)
) AS OtherTable (Col1, sort_seq)
)
SELECT T1.Col1, O1.sort_seq
FROM Table1 AS T1
INNER JOIN OtherTable AS O1
ON T1.Col1 = O1.Col1
ORDER
BY sort_seq;
In your stored proc, rather than a CTE, split the values into table (a scratch base table, temp table, function that returns a table, etc) with the sort column populated as appropriate.
I have found another solution. It's similar to the answer from onedaywhen, but it's a little shorter.
SELECT sort.n, Table1.Col1
FROM (VALUES (4), (2), (6)) AS sort(n)
JOIN Table1
ON Table1.Col1 = sort.n
I am thinking about this problem two different ways because I can't decide if this is a programming problem or a data architecture problem. Check out the code below incorporating "famous" TV animals. Let's say that we are tracking dolphins, horses, bears, dogs and orangutans. We want to return only the horses, bears, and dogs in our query and we want bears to sort ahead of horses to sort ahead of dogs. I have a personal preference to look at this as an architecture problem, but can wrap my head around looking at it as a programming problem. Let me know if you have questions.
CREATE TABLE #AnimalType (
AnimalTypeId INT NOT NULL PRIMARY KEY
, AnimalType VARCHAR(50) NOT NULL
, SortOrder INT NOT NULL)
INSERT INTO #AnimalType VALUES (1,'Dolphin',5)
INSERT INTO #AnimalType VALUES (2,'Horse',2)
INSERT INTO #AnimalType VALUES (3,'Bear',1)
INSERT INTO #AnimalType VALUES (4,'Dog',4)
INSERT INTO #AnimalType VALUES (5,'Orangutan',3)
CREATE TABLE #Actor (
ActorId INT NOT NULL PRIMARY KEY
, ActorName VARCHAR(50) NOT NULL
, AnimalTypeId INT NOT NULL)
INSERT INTO #Actor VALUES (1,'Benji',4)
INSERT INTO #Actor VALUES (2,'Lassie',4)
INSERT INTO #Actor VALUES (3,'Rin Tin Tin',4)
INSERT INTO #Actor VALUES (4,'Gentle Ben',3)
INSERT INTO #Actor VALUES (5,'Trigger',2)
INSERT INTO #Actor VALUES (6,'Flipper',1)
INSERT INTO #Actor VALUES (7,'CJ',5)
INSERT INTO #Actor VALUES (8,'Mr. Ed',2)
INSERT INTO #Actor VALUES (9,'Tiger',4)
/* If you believe this is a programming problem then this code works */
SELECT *
FROM #Actor a
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY case when a.AnimalTypeId = 3 then 1
when a.AnimalTypeId = 2 then 2
when a.AnimalTypeId = 4 then 3 end
/* If you believe that this is a data architecture problem then this code works */
SELECT *
FROM #Actor a
JOIN #AnimalType at ON a.AnimalTypeId = at.AnimalTypeId
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY at.SortOrder
DROP TABLE #Actor
DROP TABLE #AnimalType
ORDER BY CHARINDEX(','+convert(varchar,status)+',' ,
',rejected,active,submitted,approved,')
Just put a comma before and after a string in which you are finding the substring index or you can say that second parameter.
And first parameter of CHARINDEX is also surrounded by , (comma).