Implicit outer apply in SQL - sql

thank you in advance for any assistance you can provide.
I am having to work in Oracle SQL and one issue I'm coming across is how to use applies in SQL. Specifically, hoping someone can help me write the code below to work in SQL. It's not a great example but I'm trying to figure out if there is a good way to:
do implicit applies, referencing columns from the "from" clause table, so they can be used in future applies/joins in the same query without having to do multiple temp tables, CTEs, or inner queries.
same question using top 1 in outer applies
Example:
--Drop Table #Users Drop Table #UsersLoginAttempts
Create table #Users(Username varchar(10), Password1 int)
Create Table #UsersLoginAttempts (Username varchar(10), Password1 int, AttemptDate date)
insert into #Users Values(' MAllen',123), ('SEllis ',124), ('MChen ',126)
insert into #UsersLoginAttempts Values
('MAllen ',123, '20221001'), (' SEllis ',124, '20221001')
, (' MChen ',126, '20221001'), ('MAllen ',126, '20221008')
, (' SEllis ',123, '20221008'), (' MChen',128, '20221008')
Select t.*, u.*
from #Users t
outer apply ( select ltrim(rtrim(t.username)) as UserNameTrim) unt
Outer apply ( select top 1 * from #UsersLoginAttempts ula
where ltrim(rtrim(ula.UserName)) = unt.UserNameTrim
order by ula.AttemptDate desc) u

You do not need the first OUTER APPLY (and would need to add FROM DUAL if you did include it as, in Oracle, all queries must include a table name) and TOP is not supported syntax, you can use FETCH FIRST ROW ONLY instead:
Select t.*,
u.*
from Users u
Outer apply (
select *
from UsersLoginAttempts ula
where TRIM(ula.UserName) = TRIM(u.UserName)
order by ula.AttemptDate desc
FETCH FIRST ROW ONLY
) t
Which, for your sample data:
*Note:
Do not use # as the first character of an identifier (otherwise you will need to use quoted identifiers throughout your code).
Your INSERT syntax is not valid for Oracle; use INSERT ALL ... OR INSERT INTO ... SELECT if you want to insert multiple rows.
Use VARCHAR2 and not VARCHAR.
'20221001' is not a date; it is a string literal. If you want to insert a date then either use TO_DATE, with a format model, to explicitly convert it or use a date literal DATE '2022-10-01'.
Create table Users (
Username varchar2(10),
Password1 int
)
Create Table UsersLoginAttempts (
Username varchar2(10),
Password1 int,
AttemptDate date
)
insert into Users (username, password1)
SELECT ' MAllen', 123 FROM DUAL UNION ALL
SELECT 'SEllis ', 124 FROM DUAL UNION ALL
SELECT 'MChen ', 126 FROM DUAL;
insert into UsersLoginAttempts (username, password1, attemptdate)
SELECT 'MAllen ', 123, DATE '2022-10-01' FROM DUAL UNION ALL
SELECT ' SEllis ', 124, DATE '2022-10-01' FROM DUAL UNION ALL
SELECT ' MChen ', 126, DATE '2022-10-01' FROM DUAL UNION ALL
SELECT 'MAllen ', 126, DATE '2022-10-08' FROM DUAL UNION ALL
SELECT ' SEllis ', 123, DATE '2022-10-08' FROM DUAL UNION ALL
SELECT ' MChen', 128, DATE '2022-10-08' FROM DUAL;
Outputs:
USERNAME
PASSWORD1
ATTEMPTDATE
USERNAME
PASSWORD1
MAllen
126
08-OCT-22
 MAllen
123
 SEllis
123
08-OCT-22
SEllis
124
 MChen
128
08-OCT-22
MChen
126
Note: this is not PL/SQL, which is Oracle Procedural Language (similar to T-SQL for SQL Server), it is just Oracle's dialect of SQL.
fiddle

Related

MS SQL sorting a column with Chinese Name

I have a database table which has English and Chinese name in them. I have changed the Chinese name column's collation to Chinese_PRC_CS_AS but it is still not sorting properly.
I would like them to sort according to how the dictionary using (pinyin).
May I know if anybody has done that before?
You can specify Chinese_PRC_CS_AS in your select statement
select * from yourtable order by columnWithChineseName collate Chinese_PRC_CI_AS
I also found the solution at [http://www.blogjava.net/parable-myth/archive/2010/10/12/334525.html][1]. To prevent the link is broken in the future, I will paste the content here. Credit should be given back to the original author.
sql按拼音排序
select * from user order by name collate Chinese_PRC_CS_AS_KS_WS
二.排序规则简介:
什么叫排序规则呢?ms是这样描述的:"在 microsoft sql server 2000 中, 字符串的物理存储由排序规则控制。排序规则指定表示每个字符的位模式以及存 储和比较字符所使用的规则。"
在查询分析器内执行下面语句,可以得到sql server支持的所有排序规则。
select * from ::fn_helpcollations()
排序规则名称由两部份构成,前半部份是指本排序规则所支持的字符集。 如: chinese_prc_cs_ai_ws
前半部份:指unicode字符集,chinese_prc_指针对大陆简体字unicode的排序规则。 排序规则的后半部份即后缀 含义:
_bin 二进制排序
_ci(cs) 是否区分大小写,ci不区分,cs区分
_ai(as) 是否区分重音,ai不区分,as区分   
_ki(ks) 是否区分假名类型,ki不区分,ks区分 
_wi(ws) 是否区分宽度 wi不区分,ws区分 
区分大小写:如果想让比较将大写字母和小写字母视为不等,请选择该选项。
区分重音:如果想让比较将重音和非重音字母视为不等,请选择该选项。如果选择该选项,
比较还将重音不同的字母视为不等。 区分假名:如果想让比较将片假名和平假名日语音节视为不等,请选择该选项。 区分宽度:如果想让比较将半角字符和全角字符视为不等,请选择该选项
三.排序规则的应用: sql server提供了大量的windows和sqlserver专用的排序规则,但它的应用往往
被开发人员所忽略。其实它在实践中大有用处。
例1:让表name列的内容按拼音排序:
create table #t(id int,name varchar(20)) insert #t select 1,中 union
all select 2,国 union all select 3,人 union all select 4,阿
select * from #t order by name collate chinese_prc_cs_as_ks_ws drop
table #t /*结果: id name
----------- -------------------- 4 阿 2 国 3 人 1 中
*/
例2:让表name列的内容按姓氏笔划排序:
create table #t(id int,name varchar(20))
insert #t select 1,三 union all select 2,乙 union all select 3,二 union
all select 4,一 union all select 5,十 select * from #t order by name
collate chinese_prc_stroke_cs_as_ks_ws drop table #t /*结果: id
name
----------- -------------------- 4 一 2 乙 3 二 5 十 1 三
*/
四.在实践中排序规则应用的扩展 sql server汉字排序规则可以按拼音、笔划等排序,那么我们如何利用这种功能
来处理汉字的一些难题呢?我现在举个例子:
用排序规则的特性计算汉字笔划
要计算汉字笔划,我们得先做准备工作,我们知道,windows多国汉字,unicode目前
收录汉字共20902个。简体gbk码汉字unicode值从19968开始。
首先,我们先用sqlserver方法得到所有汉字,不用字典,我们简单利用sql语句就 可以得到:
select top 20902 code=identity(int,19968,1) into #t from syscolumns
a,syscolumns b
再用以下语句,我们就得到所有汉字,它是按unicode值排序的:
select code,nchar(code) as cnword from #t
然后,我们用select语句,让它按笔划排序。
select code,nchar(code) as cnword from #t order by nchar(code)
collate chinese_prc_stroke_cs_as_ks_ws,code
结果: code cnword
本文章出处 http://www.itphome.cn/shujukuyingyong/mssql/2010-01-27/106.html
[1]: http://www.itphome.cn/shujukuyingyong/mssql/2010-01-27/106.html

SELECT * INTO retains ORDER BY in SQL Server 2008 but not 2012

Execute the following SQL in 2008 and 2012. When executed in 2008, the returned result is in its correct sort order. In 2012, the sortorder is not retained.
Is this a known change? Is there a work-around for 2012 to retain the sort order?
CREATE TABLE #MyTable(Name VARCHAR(50), SortOrder INT)
INSERT INTO #MyTable SELECT 'b', 2 UNION ALL SELECT 'c', 3 UNION ALL SELECT 'a', 1 UNION ALL SELECT 'e', 5 UNION ALL SELECT 'd', 4
SELECT * INTO #Result FROM #MyTable ORDER BY SortOrder
SELECT * FROM #Result
DROP TABLE #MyTable
DROP TABLE #Result
How can you tell what the order is inside a table by using select * from #result? There is no guarantee as to the order in a select query.
However, the results are different on SQL Fiddle. If you want to guarantee that the results are the same, then add a primary key. Then the insertion order is guaranteed:
CREATE TABLE MyTable(Name VARCHAR(50), SortOrder INT)
INSERT INTO MyTable SELECT 'b', 2 UNION ALL SELECT 'c', 3 UNION ALL SELECT 'a', 1 UNION ALL SELECT 'e', 5 UNION ALL SELECT 'd', 4
select top 0 * into result from MyTable;
alter table Result add id int identity(1, 1) primary key;
insert into Result(name, sortorder)
SELECT * FROM MyTable
ORDER BY SortOrder;
I still abhor doing select * from Result after this. But yes, it does return them in the correct order in both SQL Server 2008 and 2012. Not only that, but because SQL Server guarantees that primary keys are inserted in the proper order, the records are even guaranteed to be in the correct order in this case.
BUT . . . just because the records are in a particular order on the pages doesn't mean they will be retrieved in that order with no order by clause.
When using ORDER BY with an INSERT, it has never been guaranteed to do anything other than control the order of the identity column if present.
Prior to SQL Server 2012, the optimizer always produced a plan as if an identity column existed and thus appears to order correctly. SQL Server 2012 correctly does not assume an identity column exists, and only orders if the table actually has an identity column.
So you can resolve this issue by adding an Identity column to your temp result table.
However, you really should just add an ORDER BY clause to your SELECT statement? SELECT statements without an ORDER BY have never been guaranteed to return the results in any specific order. Always add the ORDER BY clause to ensure you receive the results the way you expect.
First, thanks sgeddes for the explanation, it helped a lot.
The thing about defining a table variable or creating a temp table is you have to define it, and if you are going to go through the work of defining it, you might as well do the insert the correct way:
INSERT INTO #Result (col1, col2...)
SELECT Col1, Col2... FROM #MyTable....
In my case, the ORDER BY in the INSERT was dynamic so when I called "SELECT * FROM #Result", the ORDER BY was unknown. My solution was to add a ROW_NUMBER column that I could hardcode into the SELECT when I was getting the data.
Yea, I still have to include an ORDER BY, but at least it's static. Here's what I did:
--Insert
SELECT ROW_NUMBER() OVER (ORDER BY T.SortOrder ASC) AS RowNum, T.*
INTO #Result
FROM (SELECT * FROM #MyTable ...) AS T;
--Get data out
SELECT * FROM #Result ORDER BY RowNum;
Hope this helps.
Workaround :
You could add a SET ROWCOUNT before this type of query, then put if back to zero after to reset it, it works. This will force SQL to keep the order in your query.
SET ROWCOUNT 1000000000
CREATE TABLE #MyTable(Name VARCHAR(50), SortOrder INT)
INSERT INTO #MyTable SELECT 'b', 2 UNION ALL SELECT 'c', 3 UNION ALL SELECT 'a', 1 UNION ALL SELECT 'e', 5 UNION ALL SELECT 'd', 4
SELECT * INTO #Result FROM #MyTable ORDER BY SortOrder
SELECT * FROM #Result
SET ROWCOUNT 0
DROP TABLE #MyTable
DROP TABLE #Result
If you have different sorted results when querying each database, your collation is probably different between the two.
Try explicitly setting the collation in your query and see if your results are returned in the same order in both databases, e.g.
SELECT * FROM #Result ORDER BY C1 COLLATE Latin1_General_CS_AS
You must to create ROW_NUMBER() order by column you want to order. Order by directly in the select, is ignored when insert is executed.
CREATE TABLE #MyTable(Name VARCHAR(50), SortOrder INT)
INSERT INTO #MyTable
SELECT 'b', 2
UNION ALL SELECT 'c', 3
UNION ALL SELECT 'a', 1
UNION ALL SELECT 'e', 5
UNION ALL SELECT 'd', 4
SELECT Name,
ROW_NUMBER() OVER (ORDER BY MyTable.SortOrder) AS SortOrder
INTO #Result
FROM #MyTable AS MyTable
ORDER BY SortOrder
SELECT * FROM #Result
DROP TABLE #MyTable
DROP TABLE #Result

Simulate a table with multiple rows just with SELECT statement

If I can do the following select statement to create a table with one value
SELECT 'myname' AS 'Name'
this will return a table with column = Name and one value = myname
how can I work around this to return one column with multiple values from just the select statement
I don't want to do this :
DECLARE #tmp TABLE (Name varchar(50))
INSERT INTO #tmp (Name) VALUES ('myname1'),('myname2')
SELECT * FROM #tmp
Just from a single SELECT statement if possible
Or, you can use the multiple VALUES in the SELECT, like:
SELECT [Name]
FROM (VALUES ('myname1'),('myname2')) AS X([name])
If you're wanting to simulate a table with multiple rows just with SELECT statement, this can typically be done with UNION of rows:
SELECT 'myname1' AS 'Name' UNION
SELECT 'myname2' UNION
SELECT 'myname3'
-- etc
Demo: http://www.sqlfiddle.com/#!3/d41d8/12433
In case you want to simulate sequential data like in your example.You can define a recursive CTE and use it like a table.
Below code will generate 10 records
;With Users as
(
Select 1 as ID, CAST('Username1' AS varchar(25)) as Name
union all
Select ID + 1 , CAST('Username'+CAST(ID+1 AS varchar(5) ) AS varchar(25))
from Users
where ID < 10
)
SELECT * FROM Users
Here is SQL Fiddle http://www.sqlfiddle.com/#!3/d41d8/12452
But CTE cannot be used in multiple statements.If you need to use it in multi statements. Then insert data from CTE to Temp Table or Table Variable.

How can I use the values Selected in a While Exists statement inside the While loop?

I'm new-ish to SQL and am trying to figure out how to use the values from the Select statement in a While Exists conditional loop. The purpose is to combine multiple occurences of an attribute for a Document into a single field, and later pivot and join those results to the Document record.
For example, three tables exist like so:
ATTRIBUTES TABLE
ID, ATTRIBUTE_NAME
---------------------------
1, Created
2, Embedded_Image
...
ATTRIBUTE_VALUES TABLE
ATTRIBUTE_ID, VALUE, DOC_ID
-------------------------------------------
1, 2010/11/01, 1
2, 'Home.png', 1
2, 'Castle.png', 1
2, 'Apartment.jpg', 1
1, 2008/06/23, 2
2, 'Ski Jump.jpg', 2
2, 'Snowboarding.png', 2
...
DOCUMENTS TABLE
ID, TEXT
---------------------------
1, 'Homes of the ...'
2, 'Winter sports ...'
...
So a final Pivot and Join of the tables would look like so:
DOC_ID, TEXT, Created, Embedded_Image
----------------------------------------------------------------------------------------
1, 'Homes of the ...', 2010/11/01, 'Home.png,Castle.png,Apartment.jpg'
2, 'Winter sports ...', 2008/06/23, 'Ski Jump.jpg, Snowboarding.png'
The SQL While Exists condition I've tried to write looks like so:
DECLARE #LOOP_DOC_ID UNIQUEIDENTIFIER
DECLARE #LOOP_ATTRIBUTE_NAME NVARCHAR(MAX)
WHILE EXISTS(
SELECT [dbo].[ATTRIBUTE_VALUES].[DOC_ID], [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME]
FROM ([dbo].[ATTRIBUTE_VALUES] INNER JOIN [dbo].[ATTRIBUTES]
ON [dbo].[ATTRIBUTE_VALUES].[ATTRIBUTE_ID] = [dbo].[ATTRIBUTES].[ID])
)
BEGIN
SET #LOOP_DOC_ID = DOC_ID
SET #LOOP_ATTRIBUTE_NAME = ATTRIBUTE_NAME
SELECT STUFF(
(
SELECT DISTINCT ',' + RTRIM(LTRIM([dbo].[ATTRIBUTE_VALUES].[VALUE]))
FROM
(
[dbo].[ATTRIBUTE_VALUES] INNER JOIN [dbo].[ATTRIBUTES]
ON [dbo].[ATTRIBUTE_VALUES].[ATTRIBUTE_ID] = [dbo].[ATTRIBUTES].[ID]
)
WHERE [dbo].[ATTRIBUTE_VALUES].[DOC_ID] = #LOOP_DOC_ID
AND [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME] = #LOOP_ATTRIBUTE_NAME
ORDER BY ',' + RTRIM(LTRIM([dbo].[ATTRIBUTE_VALUES].[VALUE]))
FOR XML PATH('')
), 1, 2, ''
) AS VALUE, #LOOP_DOC_ID AS DOC_ID, #LOOP_ATTRIBUTE_NAME AS ATTRIBUTE_NAME
END
SQL Server doesn't like the lines where I'm trying to SET the variables to the values from the Select statement in the While Exists condition.
How can I use the [dbo].[ATTRIBUTE_VALUES].[DOC_ID], [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME] values Selected in the While Exists conditional statement between the BEGIN and END statements?
Preferrably I would like to do away with the #LOOP_DOC_ID and #LOOP_ATTRIBUTE_NAME variables and deal directly with the values.
I've looked through forums that have talked about using Cursors to solve similar problems, but each one of them seem to recommend only using Cursors as a last resort due to their lack of speed. I've also seen some people use stored procedures, but I can't use those, since my boss has ruled those as off-limits. Am I in need of a Cursor, or is there a better way to do this?
Have a look at something like this (Full Example)
DECLARE #ATTRIBUTES TABLE(
ID INT,
ATTRIBUTE_NAME VARCHAR(100)
)
INSERT INTO #ATTRIBUTES SELECT 1, 'Created'
INSERT INTO #ATTRIBUTES SELECT 2, 'Embedded_Image'
DECLARE #ATTRIBUTE_VALUES TABLE(
ATTRIBUTE_ID INT,
VALUE VARCHAR(100),
DOC_ID INT
)
INSERT INTO #ATTRIBUTE_VALUES SELECT 1, '2010/11/01', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Home.png', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Castle.png', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Apartment.jpg', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 1, '2008/06/23', 2
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Ski Jump.jpg', 2
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Snowboarding.png', 2
DECLARE #DOCUMENTS TABLE(
ID INT,
[TEXT] VARCHAR(100)
)
INSERT INTO #DOCUMENTS SELECT 1, 'Homes of the ...'
INSERT INTO #DOCUMENTS SELECT 2, 'Winter sports ...'
;WITH Vals AS (
SELECT d.ID DOC_ID,
d.[TEXT] [TEXT],
a.ATTRIBUTE_NAME,
av.VALUE
FROM #DOCUMENTS d INNER JOIN
#ATTRIBUTE_VALUES av ON d.ID = av.DOC_ID INNER JOIN
#ATTRIBUTES a ON av.ATTRIBUTE_ID = a.ID
)
SELECT *
FROM (
SELECT DOC_ID,
[TEXT],
ATTRIBUTE_NAME,
stuff(
(
select ',' + t.VALUE
from Vals t
where t.DOC_ID = v.DOC_ID
AND t.ATTRIBUTE_NAME = v.ATTRIBUTE_NAME
order by t.VALUE
for xml path('')
),1,1,'') Concats
FROM Vals v
GROUP BY DOC_ID,
[TEXT],
ATTRIBUTE_NAME
) s
PIVOT ( MAX(ConCats) FOR ATTRIBUTE_NAME IN ([Created],[Embedded_Image])) pvt
Output
DOC_ID TEXT Created Embedded_Image
1 Homes of the ... 2010/11/01 Apartment.jpg,Castle.png,Home.png
2 Winter sports ... 2008/06/23 Ski Jump.jpg,Snowboarding.png
From your sample, and with support from common sense, I venture the hypothesis that
A document has a single creation date.
A document can have many embedded images.
So pivoting on creation date is straightforward:
SELECT DOC_ID
, VALUE AS Created
FROM ATTRIBUTE_VALUES
WHERE ATTRIBUTE_ID = 1
and joining this subquery to your Documents table gives you the first three columns of your desired output.
Your final column summarizes multiple embedded images for each document. I personally would use some standard reporting tool (e.g. MS Access or Crystal Reports). Alternatively, create a new empty table with your four desired columns, populate the first three columns with a SQL INSERT statement, and then have Perl (or C#, or your favorite declarative language) query for the embedded images of each document, concatenate the results with commas, and insert the concatenation into your fourth column.
But if you want to do it in SQL, the concatenate-multiple-values question has been asked here before, e.g. in How to create a SQL Server function to "join" multiple rows from a subquery into a single delimited field?.

How to write the sql to findout which value is not in the table?

I am having a table Student and i have a set of 20 names.
by using his sql
select name from student st where st.name in (
'abc', 'xyz', . . .
)
i can find out all student names which are in table and in the set.
Now, how can i find out which out of these 20 names are not in Student table.
I'm assuming you want the names themselves.
One option is to create a table with all the available student names, then select from it rows which don't have corresponding rows in the student tables, it will look something like this
select name from student_names
where name not in (select name from students)
CREATE TABLE student(name VARCHAR(255));
INSERT INTO student VALUES('a'), ('abc');
CREATE TABLE temp(x VARCHAR(255));
INSERT INTO temp VALUES('abc'), ('xyz');
SELECT x FROM temp WHERE
NOT EXISTS (SELECT * FROM student st WHERE st.name = x);
Depending on the database you use, there might be an easier way. There is also a way using UNION.
SELECT NOT IN ?
postgresql: http://archives.postgresql.org/pgsql-sql/2002-08/msg00322.php
DECLARE #names table ( name varchar(100) )
INSERT INTO #names VALUES ('abc')
...
INSERT INTO #names VALUES ('xyz')
SELECT name FROM #names WHERE name NOT IN ( SELECT DISTINCT Name FROM Student )
select name from student where name not in (
select name from student st where st.name in (
'abc', 'xyz', . . .
))
EDIT: I might not get what you are looking for. Please run following script and it is giving the results.
declare #student table
(
name varchar(50)
)
insert into #student select 'james'
insert into #student select 'will'
insert into #student select 'bill'
insert into #student select 'adam'
insert into #student select 'jon'
insert into #student select 'white'
insert into #student select 'green'
select name from #student where name in ('james', 'will', 'bill')
select name from #student where name not in (select name from #student where name in ('james', 'will', 'bill'))
Assuming that the tool you are using can generate dynamic sql, try generating an inline view consisting of your set of user names - like so:
select 'abc' check_name union all
select 'xyz' check_name union all
...
(The syntax of the inline view may depend on which version of SQL you are using - some versions of SQL require a from [dummy_table] clause in select statements that are not accessing a table.)
Then construct a query using this inline view with a not exists in student clause, like this:
select check_name from (
select 'abc' check_name union all
select 'xyz' check_name union all
...
) ilv where not exists
(select null from student st where st.name = ilv.check_name)