it-swarm.com.de

T-SQL - Was ist der effizienteste Weg, um eine Tabelle zu durchlaufen, bis eine Bedingung erfüllt ist

Ich habe eine Programmieraufgabe im Bereich T-SQL.

Aufgabe:

  1. Die Leute wollen in einen Aufzug, jede Person hat ein bestimmtes Gewicht.
  2. Die Reihenfolge der in der Schlange wartenden Personen wird durch die Spaltenumdrehung bestimmt.
  3. Der Aufzug hat eine maximale Kapazität von <= 1000 lbs.
  4. Geben Sie den Namen der letzten Person zurück, die den Aufzug betreten kann, bevor er zu schwer wird!
  5. Der Rückgabetyp sollte table sein

(enter image description here

Frage: Was ist der effizienteste Weg, um dieses Problem zu lösen? Wenn die Schleife korrekt ist, gibt es Raum für Verbesserungen?

Ich habe eine Schleife und # temporäre Tabellen verwendet, hier meine Lösung:

set rowcount 0
-- THE SOURCE TABLE "LINE" HAS THE SAME SCHEMA AS #RESULT AND #TEMP
use Northwind
go

declare @sum int
declare @curr int
set @sum = 0
declare @id int

IF OBJECT_ID('tempdb..#temp','u') IS NOT NULL
    DROP TABLE #temp

IF OBJECT_ID('tempdb..#result','u') IS NOT NULL
    DROP TABLE #result

create table #result( 
    id int not null,
    [name] varchar(255) not null,
    weight int not null,
    turn int not null
)

create table #temp( 
    id int not null,
    [name] varchar(255) not null,
    weight int not null,
    turn int not null
)

INSERT into #temp SELECT * FROM line order by turn

 WHILE EXISTS (SELECT 1 FROM #temp)
  BEGIN
   -- Get the top record
   SELECT TOP 1 @curr =  r.weight  FROM  #temp r order by turn  
   SELECT TOP 1 @id =  r.id  FROM  #temp r order by turn

    --print @curr
    print @sum

    IF(@sum + @curr <= 1000)
    BEGIN
    print 'entering........ again'
    --print @curr
      set @sum = @sum + @curr
      --print @sum
      INSERT INTO #result SELECT * FROM  #temp where [id] = @id  --id, [name], turn
      DELETE FROM #temp WHERE id = @id
    END
     ELSE
    BEGIN    
    print 'breaaaking.-----'
      BREAK
    END 
  END

   SELECT TOP 1 [name] FROM #result r order by r.turn desc 

Hier das Create-Skript für die Tabelle, die ich mit Northwind zum Testen verwendet habe:

USE [Northwind]
GO

/****** Object:  Table [dbo].[line]    Script Date: 28.05.2018 21:56:18 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[line](
    [id] [int] NOT NULL,
    [name] [varchar](255) NOT NULL,
    [weight] [int] NOT NULL,
    [turn] [int] NOT NULL,
PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
UNIQUE NONCLUSTERED 
(
    [turn] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[line]  WITH CHECK ADD CHECK  (([weight]>(0)))
GO

INSERT INTO [dbo].[line]
    ([id], [name], [weight], [turn])
VALUES
    (5, 'gary', 800, 1),
    (3, 'jo', 350, 2),
    (6, 'thomas', 400, 3),
    (2, 'will', 200, 4),
    (4, 'mark', 175, 5),
    (1, 'james', 100, 6)
;
10
Legends

Sie sollten versuchen, Schleifen generell zu vermeiden. Sie sind normalerweise weniger effizient als satzbasierte Lösungen und weniger lesbar.

Das Folgende sollte ziemlich effizient sein.

Dies gilt umso mehr, wenn die Spalten name und weight im Index INCLUDE- D sein könnten, um die Schlüsselsuche zu vermeiden.

Es kann den eindeutigen Index in der Reihenfolge turn scannen und die laufende Summe der Spalte Weight berechnen. Verwenden Sie dann LEAD mit denselben Ordnungskriterien, um die laufende Summe zu ermitteln Die nächste Reihe wird sein.

Sobald die erste Zeile gefunden wird, in der diese 1000 überschreitet oder NULL ist (was anzeigt, dass keine nächste Zeile vorhanden ist), kann der Scan gestoppt werden.

WITH T1
     AS (SELECT *,
                SUM(Weight) OVER (ORDER BY turn ROWS UNBOUNDED PRECEDING) AS cume_weight
         FROM   [dbo].[line]),
     T2
     AS (SELECT LEAD(cume_weight) OVER (ORDER BY turn) AS next_cume_weight,
                *
         FROM   T1)
SELECT TOP 1 name
FROM   T2
WHERE  next_cume_weight > 1000
        OR next_cume_weight IS NULL
ORDER  BY turn 

Ausführungsplan

(enter image description here

In der Praxis scheint es ein paar Zeilen vor dem unbedingt erforderlichen zu lesen - es sieht so aus, als würde jedes Fenster-Spool/Stream-Aggregat-Paar zwei zusätzliche Zeilen lesen.

Für die Beispieldaten in der Frage müssten im Idealfall nur zwei Zeilen aus dem Index-Scan gelesen werden, in Wirklichkeit werden jedoch 6 Zeilen gelesen. Dies ist jedoch kein wesentliches Effizienzproblem und verschlechtert sich nicht, wenn der Tabelle weitere Zeilen hinzugefügt werden (wie in) diese Demo )

Für diejenigen, die an dieser Ausgabe interessiert sind, wird unten ein Bild mit den von jedem Operator ausgegebenen Zeilen (wie durch das erweiterte Ereignis query_trace_column_values Gezeigt) angezeigt. Die Zeilen werden in der Reihenfolge row_id Ausgegeben (beginnend mit 47 Für die erste Zeile, die vom Index-Scan gelesen wurde und bei 113 Für TOP endet)

Klicken Sie auf das Bild unten, um es zu vergrößern, oder sehen Sie alternativ die animierte Version, um den Ablauf einfacher zu verfolgen .

Anhalten der Animation an dem Punkt, an dem das rechte Stream-Aggregat seine erste Zeile ausgegeben hat (für gary - turn = 1). Es scheint offensichtlich, dass es darauf gewartet hat, seine erste Zeile mit einem anderen WindowCount zu erhalten (für Jo - turn = 2). Und die Fensterspule gibt die erste "Jo" -Zeile erst frei, wenn sie die nächste Zeile mit einem anderen turn gelesen hat (für thomas - turn = 3)

Die Fensterspule und das Stream-Aggregat bewirken also, dass eine zusätzliche Zeile gelesen wird, und es gibt vier davon im Plan - daher 4 zusätzliche Zeilen.

(enter image description here

Es folgt eine Erläuterung der oben gezeigten Spalten (basierend auf info hier )

  • Knotenname: Index-Scan, Knoten-ID: 15, Spaltenname: ID Basistabellenspalte, die vom Index abgedeckt wird
  • Knotenname: Index-Scan, Knoten-ID: 15, Spaltenname: Drehen Sie die Basistabellenspalte , die vom Index abgedeckt wird
  • NodeName: Clustered Index Seek, NodeId: 17, ColumnName: Gewicht Basistabellenspalte, die aus der Suche abgerufen wurde
  • NodeName: Clustered Index Seek, NodeId: 17, ColumnName: Name Basistabellenspalte, die aus der Suche abgerufen wurde
  • NodeName: Segment, NodeId: 13, ColumnName: Segment1010 Gibt 1 am Anfang einer neuen Gruppe zurück oder andernfalls null. Da kein Partition By In SUM vorhanden ist, erhält nur die erste Zeile 1
  • NodeName: Sequenzprojekt, NodeId: 12, ColumnName: RowNumber1009 row_number() innerhalb der durch das Segment1010-Flag angegebenen Gruppe. Da sich alle Zeilen in derselben Gruppe befinden, werden ganze Zahlen von 1 bis 6 aufgestiegen. Wird in Fällen wie rows between 5 preceding and 2 following Zum Filtern von Zeilen mit rechten Rahmen verwendet. (oder wie für LEAD später)
  • NodeName: Segment, NodeId: 11, ColumnName: Segment1011 Gibt 1 am Anfang einer neuen Gruppe zurück oder andernfalls null. Da kein Partition By In SUM vorhanden ist, erhält nur die erste Zeile 1 (wie Segment1010).
  • NodeName: Window Spool, NodeId: 10, ColumnName: WindowCount1012 Attribut, das Zeilen gruppiert, die zu einem Fensterrahmen gehören. Diese Fensterspule verwendet den Fall "Fast Track" für UNBOUNDED PRECEDING. Wo es zwei Zeilen pro Quellzeile ausgibt. Eins mit den kumulativen Werten und eins mit den Detailwerten. Obwohl es keinen sichtbaren Unterschied in den durch query_trace_column_values Offenbarten Zeilen gibt, gehe ich davon aus, dass kumulative Spalten in der Realität vorhanden sind.
  • NodeName: Stream Aggregate, NodeId: 9, ColumnName: Expr1004 Count(*) gruppiert nach WindowCount1012 gemäß Plan, aber tatsächlich eine laufende Anzahl
  • NodeName: Stream Aggregate, NodeId: 9, ColumnName: Expr1005 SUM(weight) gruppiert nach WindowCount1012 gemäß Plan, aber tatsächlich die laufende Gewichtssumme (dh cume_weight)
  • NodeName: Segment, NodeId: 7, ColumnName: Expr1002 CASE WHEN [Expr1004]=(0) THEN NULL ELSE [Expr1005] END - Sehen Sie nicht, wie COUNT(*) kann sei 0, also läuft immer die Summe (cume_weight)
  • Knotenname: Segment, Knoten-ID: 7, Spaltenname: Segment1013 Nein partition by In LEAD, sodass die erste Zeile 1 erhält. Alle verbleibenden werden null
  • NodeName: Sequenzprojekt, NodeId: 6, ColumnName: RowNumber1006 row_number() innerhalb der durch das Segment1013-Flag angegebenen Gruppe. Da sich alle Zeilen in derselben Gruppe befinden, sind dies ganze Zahlen von 1 bis 4
  • NodeName: Segment, NodeId: 4, ColumnName: BottomRowNumber1008 RowNumber1006 + 1, da LEAD die nächste Zeile erfordert
  • NodeName: Segment, NodeId: 4, ColumnName: TopRowNumber1007 RowNumber1006 + 1, da LEAD die nächste Zeile erfordert
  • Knotenname: Segment, Knoten-ID: 4, Spaltenname: Segment1014 Nein partition by In LEAD, sodass die erste Zeile 1 erhält. Alle verbleibenden werden null
  • NodeName: Window Spool, NodeId: 3, ColumnName: WindowCount1015 Attribut, das Zeilen, die zu einem Fensterrahmen gehören, unter Verwendung der vorherigen Zeilennummern gruppiert. Der Fensterrahmen für LEAD hat maximal 2 Zeilen (die aktuelle und die nächste)
  • NodeName: Stream Aggregate, NodeId: 2, ColumnName: Expr1003 LAST_VALUE([Expr1002]) für LEAD(cume_weight)
16
Martin Smith

Ebenso neugierig (da in der Frage T-SQL steht) ist es auch möglich, dieses Problem mit SQLCLR effizient zu lösen.

Die Idee ist, die Zeilen einzeln in der Reihenfolge turn zu lesen, bis weight 1000 überschreitet (oder uns die Zeilen ausgehen), und dann den letzten gelesenen name zurückzugeben.

Der Quellcode lautet:

using Microsoft.SqlServer.Server;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;

public partial class UserDefinedFunctions
{
    [SqlFunction(DataAccess = DataAccessKind.Read,
        SystemDataAccess = SystemDataAccessKind.None,
        IsDeterministic = true, IsPrecise = true)]
    [return: SqlFacet(IsFixedLength = false, IsNullable = true, MaxSize = 255)]
    public static SqlString Elevator()
    {
        const string query =
            @"SELECT L.[name], L.[weight]
            FROM dbo.line AS L
            ORDER BY L.turn;";

        using (var con = new SqlConnection("context connection = true"))
        {
            con.Open();
            using (var cmd = new SqlCommand(query, con))
            {
                var rdr = cmd.ExecuteReader(CommandBehavior.SingleResult);
                var name = SqlString.Null;
                var total = 0;

                while (rdr.Read() && (total += rdr.GetInt32(1)) <= 1000)
                {
                    name = rdr.GetSqlString(0);
                }
                return name;
            }
        }
    }
}

Die kompilierte Assembly- und T-SQL-Funktion:

CREATE Assembly Elevator AUTHORIZATION [dbo]
FROM 0x4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C010300606C0E5B0000000000000000E00022200B013000000C000000060000000000004A2A0000002000000040000000000010002000000002000004000000000000000600000000000000008000000002000000000000030060850000100000100000000010000010000000000000100000000000000000000000F82900004F00000000400000A802000000000000000000000000000000000000006000000C000000C02800001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E74657874000000500A000000200000000C000000020000000000000000000000000000200000602E72737263000000A80200000040000000040000000E0000000000000000000000000000400000402E72656C6F6300000C00000000600000000200000012000000000000000000000000000040000042000000000000000000000000000000002C2A0000000000004800000002000500F0200000D007000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B30030070000000010000117201000070730600000A0A066F0700000A723500007006730800000A0B07176F0900000A0C7E0A00000A0D1613042B0808166F0B00000A0D086F0C00000A2C14110408176F0D00000A5825130420E803000031DC091305DE14072C06076F0E00000ADC062C06066F0E00000ADC11052A011C000002001D003C59000A0000000002000B005863000A000000001E02280F00000A2A42534A4201000100000000000C00000076342E302E33303331390000000005006C000000AC010000237E0000180200002002000023537472696E677300000000380400008801000023555300C0050000100000002347554944000000D00500000002000023426C6F620000000000000002000001471502000900000000FA01330016000001000000110000000200000002000000010000000F000000050000000200000001000000020000000000E70001000000000006008A00A9010600BC00A9010600610096010F00C901000006000202F9000A0075005C010A003E005C010A0038005C010A00AA005C010A00DD00D8010A00120109020A002D0009020A00400109020A00050120010A00770113000A003301200106004D00F900000000000A00000000000100010001001000ED0100001500010001005020000000009600870150000100E820000000008618900106000200000000000000090090010100110090010600190090010A00310090010600490090010600590090011E0071000001060061009001230061004E012A005100F40031006900DA003500810028003B00810001003F0089005900060029009001060020002300860024002B00CE012E000B0055002E0013005E002E001B007D00100044000480000000000000000000000000000000008701000004000000000000000000000047001F000000000004000000000000000000000047001300000000000000000000476574496E743332003C4D6F64756C653E0053797374656D2E44617461006D73636F726C696200526561640053716C436F6D6D616E640053797374656D446174614163636573734B696E640049446973706F7361626C6500446973706F73650044656275676761626C654174747269627574650053716C46756E6374696F6E41747472696275746500436F6D70696C6174696F6E52656C61786174696F6E734174747269627574650053716C46616365744174747269627574650052756E74696D65436F6D7061746962696C6974794174747269627574650047657453716C537472696E6700456C657661746F722E646C6C004E756C6C0053797374656D004F70656E004462436F6E6E656374696F6E0053716C436F6E6E656374696F6E0053797374656D2E446174612E436F6D6D6F6E004462446174615265616465720053716C446174615265616465720045786563757465526561646572004D6963726F736F66742E53716C5365727665722E53657276657200436F6D6D616E644265686176696F7200456C657661746F72002E63746F720053797374656D2E446961676E6F73746963730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300446562756767696E674D6F6465730053797374656D2E446174612E53716C54797065730055736572446566696E656446756E6374696F6E73004F626A6563740053797374656D2E446174612E53716C436C69656E740000003363006F006E007400650078007400200063006F006E006E0065006300740069006F006E0020003D00200074007200750065000081510D000A00200020002000200020002000200020002000200020002000530045004C004500430054000D000A0020002000200020002000200020002000200020002000200020002000200020004C002E005B006E0061006D0065005D002C000D000A0020002000200020002000200020002000200020002000200020002000200020004C002E005B007700650069006700680074005D000D000A00200020002000200020002000200020002000200020002000460052004F004D002000640062006F002E006C0069006E00650020004100530020004C000D000A002000200020002000200020002000200020002000200020004F0052004400450052002000420059000D000A0020002000200020002000200020002000200020002000200020002000200020004C002E007400750072006E003B000D000A00200020002000200020002000200020002000200020002000006C68AC85076C6945BCECB2CE73B9787F000420010108032000010520010111110D0706122D123112351129081129042001010E062002010E122D0620011235113D0306112905200111290803200002042001080802060E08B77A5C561934E08904000011290801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F7773010801000200000000008146010004005455794D6963726F736F66742E53716C5365727665722E5365727665722E446174614163636573734B696E642C2053797374656D2E446174612C2056657273696F6E3D342E302E302E302C2043756C747572653D6E65757472616C2C205075626C69634B6579546F6B656E3D623737613563353631393334653038390A446174614163636573730100000054557F4D6963726F736F66742E53716C5365727665722E5365727665722E53797374656D446174614163636573734B696E642C2053797374656D2E446174612C2056657273696F6E3D342E302E302E302C2043756C747572653D6E65757472616C2C205075626C69634B6579546F6B656E3D623737613563353631393334653038391053797374656D446174614163636573730000000054020F497344657465726D696E69737469630154020949735072656369736501310100030054020D497346697865644C656E6774680054020A49734E756C6C61626C65015408074D617853697A65FF00000000000000606C0E5B00000000020000001C010000DC280000DC0A000052534453F8D0A90B6069F74DB5FBFABECC9D213901000000633A5C75736572735C7061756C775C736F757263655C7265706F735C4461746162617365325C4461746162617365325C6F626A5C52656C656173655C456C657661746F722E7064620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202A000000000000000000003A2A00000020000000000000000000000000000000000000000000002C2A0000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF2500200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001001000000018000080000000000000000000000000000001000100000030000080000000000000000000000000000001000000000048000000584000004C02000000000000000000004C0234000000560053005F00560045005200530049004F004E005F0049004E0046004F0000000000BD04EFFE00000100000000000000000000000000000000003F000000000000000400000002000000000000000000000000000000440000000100560061007200460069006C00650049006E0066006F00000000002400040000005400720061006E0073006C006100740069006F006E00000000000000B004AC010000010053007400720069006E006700460069006C00650049006E0066006F0000008801000001003000300030003000300034006200300000002C0002000100460069006C0065004400650073006300720069007000740069006F006E000000000020000000300008000100460069006C006500560065007200730069006F006E000000000030002E0030002E0030002E00300000003A000D00010049006E007400650072006E0061006C004E0061006D006500000045006C0065007600610074006F0072002E0064006C006C00000000002800020001004C006500670061006C0043006F00700079007200690067006800740000002000000042000D0001004F0072006900670069006E0061006C00460069006C0065006E0061006D006500000045006C0065007600610074006F0072002E0064006C006C0000000000340008000100500072006F006400750063007400560065007200730069006F006E00000030002E0030002E0030002E003000000038000800010041007300730065006D0062006C0079002000560065007200730069006F006E00000030002E0030002E0030002E0030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000C0000004C3A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
WITH PERMISSION_SET = SAFE;
GO
CREATE FUNCTION dbo.Elevator ()
RETURNS nvarchar(255)
AS EXTERNAL NAME Elevator.UserDefinedFunctions.Elevator;

Das Ergebnis erhalten:

SELECT dbo.Elevator();
6
Paul White 9

Leichte Abweichung von Martin Smiths Lösung

SELECT top 1 name
FROM (
    SELECT id, name, weight, turn
         , SUM(weight) OVER (ORDER BY turn) AS cumulative_weight
    FROM line                               
) as T
WHERE cumulative_weight <= 1000
ORDER BY turn DESC 

RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ist der Standardfensterrahmen, daher habe ich das nicht deklariert.

Anstelle des nächsten kumulativen Gewichts wird ein Prädikat für das aktuelle kumulative Gewicht verwendet.

Ich habe keinen Plan überprüft, daher kann ich nicht sagen, ob diesbezüglich ein Unterschied besteht.

1
Lennart

Sie können einen Join gegen sich selbst durchführen:

select 
    a.id, a.turn, a.game, 
    coalesce(sum(b.weight), 0) as cumulative_weight
from
    table a
left join 
    table b
on
    a.turn > b.turn
group by
    a.id, a.turn, a.game ;

Diese Art von Dingen ist nicht sehr effizient, da sie eine Auswahl pro Zeile verursachen. Aber zumindest wird es als eine einzige Aussage ausgedrückt.

Wenn Sie dies nicht vollständig in SQL tun müssen, können Sie einfach alle Zeilen auswählen und sie durchlaufen.

Sie können dasselbe auch in einer gespeicherten Prozedur ohne die temporäre Tabelle tun. Halten Sie einfach die Summe und den Namen der letzten Zeile in einer Variablen.

0
Ewan