I have a result set that might look like this:
ID (no column name) anotherID
---- ---------------- ----------
1 super 3
1 super 4
3 duper 6
4 really 7
4 really 8
I have 2 issues:
First: How do I use dapper with a column with no name?
Second: I want to have a parent child relationship such that I get 3 objects each with a list of anotherID's for example:
public class MyObject
{
public int ID
public string Name
public int[] Children
}
Well, un-named columns are not supported by dapper. I never really saw a reason for them.
I guess we could build support for:
class Foo { [ColumnNumber(1)] public string Name {get;set;} }
The trouble is that it introduces a very fragile method of querying which I strongly dislike, passing a directive to Query is just as clunky.
However, if you are happy to change the way you grab the results you could work around this.
var grid = QueryMultiple(#"set nocount on
declare #t table(Id int, Name nvarchar(max), AnotherId int)
insert #t
exec proc
set nocount off
select Id, Name from #t
select Id, AnotherId from #t
");
Then use the technique here to multi map: Multi-Mapper to create object hierarchy
Related
I need to create function in SQL to check data in variables or parameters as below
#Category as varchar(50)='ABC,DEF'
#Value as varchar(50)='1,2'
And compare #Category value with Category in table then return value matching from parameter
JOB TABLE ---
JOB CATEGORY
123 ABC
234 DEF
234 SSS
Select JobNo,FUNCTION(#Category,#Value,CATEGORY) from JOB
FINAL RESULTS
JOB VALUE
123 1
234 2
234 0
If category match then return value from parameter else return 0.
If you can't use a static lookup table as mentioned in the comments (for example, perhaps the mapping needs to be dynamic based on data supplied by the application), then this looks like a job for a table valued parameter.
Right now you have two parameters, but it seems to me that the values in the parameters are related. That is to say, right now you have #category = 'ABC,DEF' and #value = '1,2', but I think you intend each "element" in the comma delimited set of "categories" to associate with the "element" in the comma delimiited set of "values" that is in the same position.
Right now the design is brittle, because what would happen if I use parameters like this: #category = 'ABC,DEF,GHI,JKL', #value = '1'?
So, you can make your code more durable, and use the sort of "join-based" lookup table solution being recommended to you in the comments, by using a function that takes a table valued parameter argument. To do this, you first have to create the table valued parameter as a type in your database. We then accept a parameter of that type, and join onto it. In the solution below I have "guessed" at datatypes for category and value that seem reasonable based on the sample data in your question.
Also, I've kept the "structure" of your solution - ie, the function is written in such a way that it can be "applied" against every row in jobs, individually. We don't have to do it this way. We could instead just do the whole query inside the function (ie, including the join to the job table), but perhaps you want to use the function against other tables that also have a cateogry column? I won't try to second guess the overall design here. But I will switch the function to an inline table valued function (which returns one row with one column) rather than a scalar function, for performance reasons.
-- schema and data to match your question
create table dbo.job (job int, category char(3));
insert dbo.job(job, category) values (123, 'ABC'), (234, 'DEF'), (234, 'SSS');
go
-- solution
create type dbo.CategoryValues as table
(
category char(3) unique,
[value] int
);
go
create or alter function dbo.MapCategory(#category char(3), #map dbo.CategoryValues readonly)
returns table as return
(
select [value] from #map where category = #category
);
go
-- to call the function we need to pass a parameter of type
-- dbo.CategoryValues with the mappings we desire
declare #map dbo.CategoryValues;
insert #map values ('ABC', 1), ('DEF', 2)
-- outer apply the function to each row in the job table.
select j.job, [value] = isnull(v.[value], 0)
from dbo.job j
outer apply dbo.MapCategory(j.category, #map) v
From a table variable #Toc I need to insert content into the table PE_TableOfContents, which has a HIERARCHYID. In PE_TableOfContents there will be a root item. From that root item, I can get the root HIERARCHYID (#rootNode).
But then when I go to insert children, I am unsure of how to create the new HIERARCHYID, as I won't know the ID of that column until after it's inserted.
DECLARE #rootNode HIERARCHYID = (SELECT tocNode from PE_TableOfContents WHERE TocNodeLevel = 0)
INSERT INTO PE_TableOfContents (
TocNode,
... all of the other columns)
SELECT
#rootNode.ToString + ???? + '/',
T... all of the other columns
FROM
#Toc T
I'm looking over all the docs for hierarchyid, and maybe I'm missing something, but I'm not seeing this.
One thing that's not immediately obvious from the documentation on HierarchyID is that you're free to specify whatever numeric data you want as the constituents of the path. I personally like to use the auto-generated ID (whether an identity value or otherwise). I also like to put something like ParentID as a column in the table so that if something goes sideways in keeping the hierarchyid column up to date, you can use a recursive query to re-generate it (which is to say that the hierarchyid column is derived data that is there only to aid in query performance).
To be explicit, let's say I had the following table:
ID ParentID
================
100 NULL
200 100
300 100
400 200
I'd derive the hierarchyid column like this:
ID ParentID h
=========================
100 NULL /100/
200 100 /100/200
300 100 /100/300
400 200 /100/200/400
I can't think of a way to do this w/o first inserting the row with a null value for the hierarchyid if you're using an identity column. If you're using a Sequence to allocate the ID you can get a value from the sequence and use it both for the ID value and to derive the hierarchyid.
Ok, after enough google searches, I found a good explanation.
First, get the root (or parent) node:
DECLARE #rootNode HIERARCHYID = (SELECT tocNode FROM PE_TableOfContents WHERE TocNodeLevel = 0)
Then, get the last known child of that root (or parent) node:
DECLARE #lastNode HIERARCHYID = (SELECT MAX(tocNode) FROM PE_TableOfContents WHERE tocNode.GetAncestor(1) = #rootNode);
Then do the insert as follows:
INSERT INTO PE_TableOfContents (
TocNode,
... all of the other columns)
SELECT
#rootNode.GetDescendant(#lastNode, NULL),
T... all of the other columns
FROM
#Toc T
CREATE TABLE SportsEvent
(ID INT, Name NVARCHAR(20), Results XML);
GO
DECLARE #Results XML=
'<Athletics>
<Event ID="001" Name="100m">
<Gold>John Doe</Gold>
<Silver>Harry Smith</Silver>
<Bronze>Kenneth Brown</Bronze>
</Event>
<Event ID="002" Name="High Jump">
<Gold>Sarah Jones</Gold>
<Silver>Janice Johnson</Silver>
<Bronze>Alicia Armstrong</Bronze>
</Event>
</Athletics>'
INSERT INTO SportsEvent
VALUES(1, 'AthleticsDay', #Results);
SELECT * FROM SportsEvent;
If I want to pull out an element based on the event ID, no problem:
SELECT Results.query('(/Athletics/Event[#ID="001"]/Gold)')
FROM SportsEvent
WHERE ID = 1
I can do the same with a relative reference:
SELECT Results.query('(Athletics/Event)[1]')
FROM SportsEvent
WHERE ID = 1
But what if I want to pull the event Name based on either a relative or absolute ?:
SELECT Results.query('(Athletics/Event[#Name])[#ID="001"]')
FROM SportsEvent
WHERE ID = 1
SELECT Results.query('(Athletics/Event[#Name])[1]')
FROM SportsEvent
WHERE ID = 1
...both bring back ALL the data for that event.
I tried using the value method:
SELECT Results.value('(/Athletics/Event/#Name)[1]','VARCHAR(20)')
FROM SportsEvent
WHERE ID = 1
...but this only works for a relative reference i.e in this case the first set of results in the XML.
What if I want to specify an event ID and return just the event name (either as an XML fragment or as data/value)?
Ok, so for some (many?) this will seem blindingly obvious, but I'll post my asnwer in case it stops someone else spending a frustrating couple of hours going ground and round in circles (as I have just done)...
SELECT Results.value('(/Athletics/Event[#ID="001"]/#Name)[1]','VARCHAR(20)')
FROM SportsEvent
WHERE ID = 1
SELECT Results.value('(/Athletics/Event[#Name="100m"]/#ID)[1]','VARCHAR(20)')
FROM SportsEvent
WHERE ID = 1
SELECT Results.value('(/Athletics/Event[#Name="High Jump"]/#ID)[1]','VARCHAR(20)')
FROM SportsEvent
WHERE ID = 1
SELECT Results.value('(/Athletics/Event[#ID="002"]/#Name)[1]','VARCHAR(20)')
FROM SportsEvent
WHERE ID = 1
From what I can ascertain you can't use the query method to return just the value of an attribute the query would have to be written with the attribute outside the element which is not allowed.
You can however use the query method as follows - the '[1]'(singletons) at the end of the path aren't obligatory (they are with the value method). What you will get is the whole fragment which contains the specified attribute.
SELECT Results.query('(/Athletics/Event[#ID="002"][#Name])[1]')
FROM SportsEvent
WHERE ID = 1
SELECT Results.query('(/Athletics/Event[#Name="100m"][#Name])[1]')
FROM SportsEvent
WHERE ID = 1
Hope this helps someone.
Any comments, corrections or additions are welcomed.
Thanks.
Customer wants to display a list of values returned by my query in a specific order. The issue is ordering simply by asc or desc is not giving me what the customer want. DBA doesn't want me to hard code values. Is there a way to custom sort without hard coding values? Because the values will change every year and would have to maintain/update it every year.
Table Structure:
Column: CMN_CodesID (unique), Name (is what I'd like to display in custom order)
something like this.
order by case when Jack then 1
when Apple then 2
when Orange then 3
...
End
You could use dynamic sql in a stored procedure and pass #MyOrderBy into it as a parameter (Added to this example for illustration).
DECLARE #MyOrderBy VARCHAR(300)
SELECT #MyOrderBy = 'case when myfield = ''Jack'' then 1 when myfield = ''Apple'' then 2 when myfield = ''Orange'' then 3 else 4 End'
DECLARE #sSQL VARCHAR(300)
SELECT #sSQL = 'SELECT * FROM mytable ORDER BY ' + #MyOrderBy
EXEC(#sSQL)
I have a table lets say called FavoriteFruits that has NAME, FRUIT, and GUID for columns. The table is already populated with names and fruits. So lets say:
NAME FRUIT GUID
John Apple NULL
John Orange NULL
John Grapes NULL
Peter Canteloupe NULL
Peter Grapefruit NULL
Ok, now I want to update the GUID column with a new GUID (using NEWID()), but I want to have the same GUID per distinct name. So I want all the John Smiths to have the same GUID, and I want both the Peters to have the same GUID, but that GUID different than the one used for the Johns. So now it would look something like this:
NAME FRUIT GUID
John Apple f6172268-78b7-4c2b-8cd7-7a5ca20f6a01
John Orange f6172268-78b7-4c2b-8cd7-7a5ca20f6a01
John Grapes f6172268-78b7-4c2b-8cd7-7a5ca20f6a01
Peter Canteloupe e3b1851c-1927-491a-803e-6b3bce9bf223
Peter Grapefruit e3b1851c-1927-491a-803e-6b3bce9bf223
Can I do that in an update statement without having to use a cursor? If so can you please give an example?
Thanks guys...
Update a CTE won't work because it'll evaluate per row. A table variable would work:
You should be able to use a table variable as a source from which to update the data. This is untested, but it'll look something like:
DECLARE #n TABLE (Name varchar(10), Guid uniqueidentifier);
INSERT #n
SELECT Name, newid() AS Guid
FROM FavoriteFruits
GROUP BY Name;
UPDATE f
SET f.Guid = n.Guid
FROM #n n
JOIN FavoriteFruits f ON f.Name = n.Name
So that populates a variable with a GUID per name, then joins it back to the original table and updates accordingly.
To clarify comments re a table expression in the USING clause of a MERGE statement.
The following won't work because it'll evaluate per row:
MERGE INTO FavoriteFruits
USING (
SELECT NAME, NEWID() AS GUID
FROM FavoriteFruits
GROUP
BY NAME
) AS source
ON source.NAME = FavoriteFruits.NAME
WHEN MATCHED THEN
UPDATE
SET GUID = source.GUID;
But the following, using a table variable, will work:
DECLARE #n TABLE
(
NAME VARCHAR(10) NOT NULL UNIQUE,
GUID UNIQUEIDENTIFIER NOT NULL UNIQUE
);
INSERT INTO #n (NAME, GUID)
SELECT NAME, NEWID()
FROM FavoriteFruits
GROUP
BY NAME;
MERGE INTO FavoriteFruits
USING #n AS source
ON source.NAME = FavoriteFruits.NAME
WHEN MATCHED THEN
UPDATE
SET GUID = source.GUID;
There's a single-statement solution too, which, however, has some limitations. The idea is to use OPENQUERY(), like this:
UPDATE FavoriteFruits
SET GUID = n.GUID
FROM (
SELECT NAME, GUID
FROM OPENQUERY(
linkedserver,
'SELECT NAME, NEWID() AS GUID FROM database.schema.FavoriteFruits GROUP BY NAME'
)
) n
WHERE FavoriteFruits.NAME = n.NAME
This solution implies that you need to create a self-pointing linked server. Another specificity is that you can't use this method on table variables nor local temporary tables (global ones would do as well as 'normal' tables).